| name | fhir-structuredefinition |
| description | Guía para trabajar con StructureDefinitions y ElementDefinitions en FHIR. Usar cuando se necesite entender o manipular definiciones de estructura. |
| allowed-tools | Read, Bash, Glob, Grep, WebFetch |
| user-invocable | true |
Skill: fhir-structuredefinition
Guía para trabajar con StructureDefinitions y ElementDefinitions en el validador FHIR.
Cuándo Usar
- Al implementar validación basada en StructureDefinitions
- Al resolver tipos de elementos
- Al trabajar con paths FHIR
- Al debuggear problemas de validación
Anatomía de un StructureDefinition
{
"resourceType": "StructureDefinition",
"url": "http://hl7.org/fhir/StructureDefinition/Patient",
"name": "Patient",
"kind": "resource",
"abstract": false,
"type": "Patient",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/DomainResource",
"derivation": "specialization",
"snapshot": {
"element": [
]
},
"differential": {
"element": [
]
}
}
Jerarquía de Kinds
primitive-type (string, boolean, date...)
↓ base: Element
complex-type (HumanName, Identifier, Coding...)
↓ base: Element
resource (Patient, Observation...)
↓ base: DomainResource ← Resource
logical
↓ base: Element o Base
Anatomía de un ElementDefinition
{
"id": "Patient.name",
"path": "Patient.name",
"sliceName": "official",
"min": 0,
"max": "*",
"type": [
{
"code": "HumanName",
"profile": ["http://..."],
"targetProfile": ["http://..."]
}
],
"binding": {
"strength": "required",
"valueSet": "http://..."
},
"constraint": [
{
"key": "pat-1",
"severity": "error",
"human": "...",
"expression": "..."
}
],
"fixedString": "...",
"patternCoding": {...},
"slicing": {
"discriminator": [
{ "type": "value", "path": "system" }
],
"rules": "open"
}
}
Paths FHIR
Formato de Paths
| Tipo | Ejemplo | Descripción |
|---|
| Simple | Patient.name | Elemento directo |
| Array | Patient.name.given | Dentro de array (sin índice en SD) |
| Choice | Observation.value[x] | Tipo polimórfico en SD |
| Choice resuelto | Observation.valueQuantity | En instancia |
| Slice | Patient.identifier:ssn | Slice específico |
Resolución de Choice Types
SD path: Observation.value[x]
↓
Instance: valueQuantity, valueString, valueCodeableConcept, etc.
↓
Para validar: usar el tipo del sufijo (Quantity, String, etc.)
Tipos permitidos: ElementDefinition.type[].code
Path en Instancia vs Path en SD
sdPath := "Patient.name"
sdPathChoice := "Observation.value[x]"
instancePath := "Patient.name[0]"
instancePath := "Patient.name[0].given[1]"
instancePath := "Observation.valueQuantity"
Resolver Tipo de un Elemento
ed := registry.GetElementDefinition("Patient.name")
for _, t := range ed.Type {
typeName := *t.Code
typeSD := registry.GetStructureDefinition(
"http://hl7.org/fhir/StructureDefinition/" + typeName,
)
}
Tipos con Profile
if len(t.Profile) > 0 {
profileSD := registry.GetStructureDefinition(t.Profile[0])
}
Trabajar con Cardinalidad
func checkCardinality(ed *ElementDefinition, count int) error {
if ed.Min != nil && count < int(*ed.Min) {
return fmt.Errorf("minimum %d not met, got %d", *ed.Min, count)
}
if ed.Max != nil && *ed.Max != "*" {
maxInt, _ := strconv.Atoi(*ed.Max)
if count > maxInt {
return fmt.Errorf("maximum %d exceeded, got %d", maxInt, count)
}
}
return nil
}
Trabajar con Bindings
func checkBinding(ed *ElementDefinition, code string) (bool, Severity) {
if ed.Binding == nil {
return true, ""
}
strength := *ed.Binding.Strength
valueSetURL := *ed.Binding.ValueSet
inValueSet := terminology.ValidateCode(valueSetURL, code)
switch strength {
case "required":
if !inValueSet {
return false, SeverityError
}
case "extensible":
if !inValueSet {
return false, SeverityWarning
}
case "preferred", "example":
}
return true, ""
}
Trabajar con Constraints
func evaluateConstraints(ed *ElementDefinition, resourceJSON []byte, contextPath string) []Issue {
var issues []Issue
for _, c := range ed.Constraint {
if c.Expression == nil {
continue
}
result, err := fhirpath.EvaluateToBoolean(resourceJSON, *c.Expression)
if err != nil {
issues = append(issues, Issue{
Severity: SeverityWarning,
Code: IssueTypeInvariant,
Message: fmt.Sprintf("Constraint %s failed to evaluate: %v", *c.Key, err),
})
continue
}
if !result {
severity := SeverityWarning
if c.Severity != nil && *c.Severity == "error" {
severity = SeverityError
}
issues = append(issues, Issue{
Severity: severity,
Code: IssueTypeInvariant,
Expression: []string{contextPath},
Message: fmt.Sprintf("Constraint %s: %s", *c.Key, safeString(c.Human)),
})
}
}
return issues
}
Trabajar con Slicing
Detectar Slicing
if ed.Slicing != nil {
}
if ed.SliceName != nil {
}
Resolver Discriminador
func matchSlice(element *Element, discriminator Discriminator, sliceED *ElementDefinition) bool {
switch discriminator.Type {
case "value":
actualValue := element.GetValueAt(discriminator.Path)
expectedValue := sliceED.GetFixedOrPatternAt(discriminator.Path)
return actualValue == expectedValue
case "pattern":
actualValue := element.GetValueAt(discriminator.Path)
patternValue := sliceED.GetPatternAt(discriminator.Path)
return matchesPattern(actualValue, patternValue)
case "type":
actualType := element.GetTypeAt(discriminator.Path)
expectedTypes := sliceED.GetTypesAt(discriminator.Path)
return contains(expectedTypes, actualType)
case "profile":
profileURL := sliceED.GetProfileAt(discriminator.Path)
return validateAgainstProfile(element, profileURL)
}
return false
}
Snapshot vs Differential
Cuándo Usar Cada Uno
| Situación | Usar |
|---|
| Validación normal | Snapshot (tiene todo) |
| Crear profile | Differential (solo cambios) |
| Snapshot no existe | Generar desde differential + base |
Generar Snapshot
func generateSnapshot(sd *StructureDefinition) error {
if sd.Snapshot != nil {
return nil
}
baseSD := registry.GetStructureDefinition(*sd.BaseDefinition)
snapshot := cloneElements(baseSD.Snapshot.Element)
for _, diffElem := range sd.Differential.Element {
applyDifferential(snapshot, diffElem)
}
sd.Snapshot = &StructureDefinitionSnapshot{Element: snapshot}
return nil
}
Comandos Útiles para Explorar SDs
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq .
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq '.snapshot.element[] | {path, min, max, type: [.type[]?.code]}'
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq '.snapshot.element[] | select(.min > 0) | {path, min}'
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq '.snapshot.element[] | select(.constraint) | {path, constraints: [.constraint[].key]}'
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq '.snapshot.element[] | select(.binding) | {path, strength: .binding.strength, valueSet: .binding.valueSet}'
Referencias