fhir-structuredefinition

star 2

Guía para trabajar con StructureDefinitions y ElementDefinitions en FHIR. Usar cuando se necesite entender o manipular definiciones de estructura.

gofhir By gofhir schedule Updated 1/29/2026

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",              // resource | complex-type | primitive-type | logical
  "abstract": false,
  "type": "Patient",               // Tipo que define
  "baseDefinition": "http://hl7.org/fhir/StructureDefinition/DomainResource",
  "derivation": "specialization",  // specialization | constraint

  "snapshot": {
    "element": [
      // ElementDefinitions completos (heredados + propios)
    ]
  },

  "differential": {
    "element": [
      // Solo los ElementDefinitions modificados respecto al base
    ]
  }
}

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",              // Path completo
  "sliceName": "official",             // Nombre del slice (si aplica)

  "min": 0,                            // Cardinalidad mínima
  "max": "*",                          // Cardinalidad máxima

  "type": [                            // Tipos permitidos
    {
      "code": "HumanName",
      "profile": ["http://..."],       // Profile específico (opcional)
      "targetProfile": ["http://..."]  // Para Reference, target permitido
    }
  ],

  "binding": {                         // Para coded elements
    "strength": "required",            // required | extensible | preferred | example
    "valueSet": "http://..."
  },

  "constraint": [                      // Invariantes FHIRPath
    {
      "key": "pat-1",
      "severity": "error",
      "human": "...",
      "expression": "..."
    }
  ],

  "fixedString": "...",                // Valor fijo (fixed[x])
  "patternCoding": {...},              // Patrón (pattern[x])

  "slicing": {                         // Definición de slicing
    "discriminator": [
      { "type": "value", "path": "system" }
    ],
    "rules": "open"                    // open | closed | openAtEnd
  }
}

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

// En StructureDefinition
sdPath := "Patient.name"           // Sin índices
sdPathChoice := "Observation.value[x]"

// En instancia (para FHIRPath en issues)
instancePath := "Patient.name[0]"           // Con índice
instancePath := "Patient.name[0].given[1]"  // Arrays anidados
instancePath := "Observation.valueQuantity" // Choice resuelto

Resolver Tipo de un Elemento

// 1. Obtener ElementDefinition por path
ed := registry.GetElementDefinition("Patient.name")

// 2. Obtener tipos permitidos
for _, t := range ed.Type {
    typeName := *t.Code  // "HumanName", "string", etc.

    // 3. Cargar StructureDefinition del tipo
    typeSD := registry.GetStructureDefinition(
        "http://hl7.org/fhir/StructureDefinition/" + typeName,
    )

    // 4. Validar elemento contra typeSD
}

Tipos con Profile

// ElementDefinition.type puede tener profile específico
if len(t.Profile) > 0 {
    // Usar profile en lugar de tipo base
    profileSD := registry.GetStructureDefinition(t.Profile[0])
}

Trabajar con Cardinalidad

func checkCardinality(ed *ElementDefinition, count int) error {
    // Min es uint32
    if ed.Min != nil && count < int(*ed.Min) {
        return fmt.Errorf("minimum %d not met, got %d", *ed.Min, count)
    }

    // Max es string ("*" = unbounded)
    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, ""  // No binding = todo OK
    }

    strength := *ed.Binding.Strength
    valueSetURL := *ed.Binding.ValueSet

    inValueSet := terminology.ValidateCode(valueSetURL, code)

    switch strength {
    case "required":
        if !inValueSet {
            return false, SeverityError  // Must be in ValueSet
        }
    case "extensible":
        if !inValueSet {
            return false, SeverityWarning  // Should be in ValueSet
        }
    case "preferred", "example":
        // No validation needed
    }

    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
        }

        // Evaluar usando fhirpath
        result, err := fhirpath.EvaluateToBoolean(resourceJSON, *c.Expression)

        if err != nil {
            // Error de evaluación = warning
            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

// Un elemento tiene slicing si ElementDefinition.slicing != nil
if ed.Slicing != nil {
    // Este elemento es el "slicing entry"
    // Los slices subsecuentes tienen sliceName
}

// Un slice específico tiene sliceName
if ed.SliceName != nil {
    // Este es un slice llamado *ed.SliceName
}

Resolver Discriminador

func matchSlice(element *Element, discriminator Discriminator, sliceED *ElementDefinition) bool {
    switch discriminator.Type {
    case "value":
        // El valor en discriminator.Path debe coincidir exactamente
        actualValue := element.GetValueAt(discriminator.Path)
        expectedValue := sliceED.GetFixedOrPatternAt(discriminator.Path)
        return actualValue == expectedValue

    case "pattern":
        // El valor debe contener el patrón
        actualValue := element.GetValueAt(discriminator.Path)
        patternValue := sliceED.GetPatternAt(discriminator.Path)
        return matchesPattern(actualValue, patternValue)

    case "type":
        // El tipo del elemento debe coincidir
        actualType := element.GetTypeAt(discriminator.Path)
        expectedTypes := sliceED.GetTypesAt(discriminator.Path)
        return contains(expectedTypes, actualType)

    case "profile":
        // El elemento debe validar contra el 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  // Ya tiene snapshot
    }

    // 1. Cargar base definition
    baseSD := registry.GetStructureDefinition(*sd.BaseDefinition)

    // 2. Clonar elementos del base
    snapshot := cloneElements(baseSD.Snapshot.Element)

    // 3. Aplicar differential
    for _, diffElem := range sd.Differential.Element {
        applyDifferential(snapshot, diffElem)
    }

    sd.Snapshot = &StructureDefinitionSnapshot{Element: snapshot}
    return nil
}

Comandos Útiles para Explorar SDs

# Ver StructureDefinition de Patient
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq .

# Ver solo ElementDefinitions de Patient
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq '.snapshot.element[] | {path, min, max, type: [.type[]?.code]}'

# Buscar elementos requeridos (min > 0)
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq '.snapshot.element[] | select(.min > 0) | {path, min}'

# Ver constraints de un recurso
cat ~/.fhir/packages/hl7.fhir.r4.core#4.0.1/package/StructureDefinition-Patient.json | jq '.snapshot.element[] | select(.constraint) | {path, constraints: [.constraint[].key]}'

# Ver bindings
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

Install via CLI
npx skills add https://github.com/gofhir/validator --skill fhir-structuredefinition
Repository Details
star Stars 2
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator