@@ -86,6 +86,7 @@ class GtsEntity:
8686 selected_entity_field : Optional [str ] = None
8787 selected_schema_id_field : Optional [str ] = None
8888 description : str = ""
89+ raw_id : Optional [str ] = None # Stores raw ID value (may be non-GTS)
8990 schemaRefs : List [Dict [str , str ]] = field (default_factory = list )
9091
9192 def __init__ (
@@ -122,6 +123,7 @@ def __init__(
122123 # Calculate IDs if config provided
123124 if cfg is not None :
124125 idv = self ._calc_json_entity_id (cfg )
126+ self .raw_id = idv # Store raw ID even if non-GTS
125127 self .schemaId = self ._calc_json_schema_id (cfg )
126128 # If no valid GTS ID found in entity fields, use schema ID as fallback
127129 if not (idv and GtsID .is_valid (idv )):
@@ -289,13 +291,11 @@ def _get_field_value(self, field: str) -> Optional[str]:
289291 return None
290292
291293 def _first_non_empty_field (self , fields : List [str ]) -> Optional [Tuple [str , str ]]:
292- """Find first non-empty field, preferring valid GTS IDs."""
293- # First pass: look for valid GTS IDs
294- for f in fields :
295- v = self ._get_field_value (f )
296- if v and GtsID .is_valid (v ):
297- return f , v
298- # Second pass: any non-empty string
294+ """Find first non-empty field value in order.
295+
296+ Returns the first non-empty string value without preferring GTS IDs.
297+ This ensures UUID and non-GTS values are returned when they appear first.
298+ """
299299 for f in fields :
300300 v = self ._get_field_value (f )
301301 if v :
@@ -311,22 +311,74 @@ def _calc_json_entity_id(self, cfg: GtsConfig) -> str:
311311 return f"{ self .file .path } #{ self .list_sequence } "
312312 return self .file .path if self .file else ""
313313
314- def _calc_json_schema_id (self , cfg : GtsConfig ) -> str :
314+ def _calc_json_schema_id (self , cfg : GtsConfig ) -> Optional [str ]:
315+ """Calculate schema_id based on entity type and content.
316+
317+ Rules:
318+ - For schemas: extract parent from $id chain, or fallback to $schema
319+ - For instances: look for type/schema fields in schema_id_fields
320+ - Return None if no schema reference found for instances
321+ """
322+ # For schemas, derive from the entity ID (parent of chain)
323+ if self .is_schema :
324+ # Get entity ID (the $id field for schemas)
325+ idv = self ._get_field_value ("$id" )
326+ if idv and GtsID .is_valid (idv ):
327+ # Check if it's a chained ID (derived schema)
328+ last_tilde = idv .rfind ("~" )
329+ if last_tilde > 0 :
330+ # Find the previous segment (parent)
331+ parent_end = last_tilde
332+ # Check if there's another segment before this one
333+ prefix = idv [:parent_end ]
334+ prev_tilde = prefix .rfind ("~" )
335+ if prev_tilde > 0 :
336+ # Has a parent chain - return first segment (base type)
337+ self .selected_schema_id_field = "$id"
338+ return prefix [:prev_tilde + 1 ]
339+ else :
340+ # Single segment schema - base type, return $schema
341+ schema_val = self ._get_field_value ("$schema" )
342+ if schema_val :
343+ self .selected_schema_id_field = "$schema"
344+ return schema_val
345+ # Fallback to $schema for schemas
346+ schema_val = self ._get_field_value ("$schema" )
347+ if schema_val :
348+ self .selected_schema_id_field = "$schema"
349+ return schema_val
350+ return None
351+
352+ # For instances, look in schema_id_fields
315353 cand = self ._first_non_empty_field (cfg .schema_id_fields )
316354 if cand :
317355 self .selected_schema_id_field = cand [0 ]
318- return cand [1 ]
319- idv = self ._calc_json_entity_id (cfg )
320- if idv and isinstance (idv , str ) and GtsID .is_valid (idv ):
321- if idv .endswith ("~" ):
322- return idv
323- last = idv .rfind ("~" )
324- if last > 0 :
325- self .selected_schema_id_field = self .selected_entity_field
326- return idv [: last + 1 ]
327- if self .file and self .list_sequence is not None :
328- return f"{ self .file .path } #{ self .list_sequence } "
329- return self .file .path if self .file else ""
356+ schema_id = cand [1 ]
357+ # If schema_id is a chained GTS ID, extract parent (base type)
358+ if GtsID .is_valid (schema_id ):
359+ last_tilde = schema_id .rfind ("~" )
360+ if last_tilde > 0 and not schema_id .endswith ("~" ):
361+ # It's an instance ID in type field - extract schema part
362+ return schema_id [:last_tilde + 1 ]
363+ return schema_id
364+
365+ # For instances with chained GTS ID in entity_id field, derive schema_id
366+ # BUT only if the ID is a proper chained instance ID (not a single schema segment)
367+ if self .selected_entity_field and self .selected_entity_field != "$id" :
368+ # Only derive from fields like "id", not from "$id" (which is for schemas)
369+ idv = self ._get_field_value (self .selected_entity_field )
370+ if idv and GtsID .is_valid (idv ):
371+ # Check if it's a chained ID (instance ID) vs single segment (schema ID)
372+ if not idv .endswith ("~" ):
373+ # Instance ID: extract schema part (everything up to and including last ~)
374+ last_tilde = idv .rfind ("~" )
375+ if last_tilde > 0 :
376+ self .selected_schema_id_field = self .selected_entity_field
377+ return idv [:last_tilde + 1 ]
378+
379+ # No schema reference found for instance
380+ # Note: Single-segment schema IDs in $id don't count as schema_id for instances
381+ return None
330382
331383 def _extract_uuid_from_content (self ) -> Optional [str ]:
332384 """Extract a UUID value from content to use as instance identifier."""
0 commit comments