Kerndatensatz Senologie
0.9.0 - ci-build

Kerndatensatz Senologie - Local Development build (v0.9.0) built by the FHIR (HL7® FHIR® Standard) Build Tools. See the Directory of published versions

StructureMap: SenologieToOncoBoxBrustVerlauf

Official URL: https://www.senologie.org/fhir/StructureMap/SenologieToOncoBoxBrustVerlauf Version: 0.9.0
Draft as of 2026-05-04 Computable Name: SenologieToOncoBoxBrustVerlauf

title: Senologie Bundle to OncoBox Brust Verlauf (inkl. OncoBox 2.0 FM-Felder J03-J05) status: draft

map "https://www.senologie.org/fhir/StructureMap/SenologieToOncoBoxBrustVerlauf" = "SenologieToOncoBoxBrustVerlauf"

// title: Senologie Bundle to OncoBox Brust Verlauf (inkl. OncoBox 2.0 FM-Felder J03-J05)
// status: draft

uses "http://hl7.org/fhir/StructureDefinition/Bundle" alias Bundle as source
uses "http://hl7.org/fhir/StructureDefinition/Condition" alias Condition as source
uses "http://hl7.org/fhir/StructureDefinition/Observation" alias Observation as source
uses "http://hl7.org/fhir/StructureDefinition/Procedure" alias Procedure as source

// Import-only map: no target `uses` declaration — the calling map
// (SenologieToOncoBoxBrustPrimaerfall) provides the correct BackboneElement
// context (primaerfall for MapVerlaufFromBundle, primaerfall.verlauf for sub-groups).
// Omitting the root-level target type avoids SM_TARGET_PATH false positives
// where the validator would resolve property names against oncobox-brust-meldung root.
// ============================================================================
// MapVerlaufFromBundle: Bundle -> OncoBox Primaerfall.verlauf (repeating)
// Extrahiert Verlaufsereignisse aus dem Bundle und mappt auf den OncoBox-
// Verlauf-Block. Drei Quellen fuer Verlaufseintraege:
// 1. Senologie_FollowUp Observations (SCT 396432002) -> M01-M10 + Gesamtbeurteilung
// Bildet die vollstaendige Verlaufsmeldung ab inkl. Nachsorge-Art, Vitalstatus,
// Tumorstatus lokal/LK/FM und Zweittumor. Gesamtbeurteilung (D27) wird auf
// verlauf.ereignis gemappt: P->6 (Progress), Y->1 (Lokalrezidiv Default).
// 2. Condition mit clinicalStatus=recurrence -> Rezidiv / Fernmetastase
// 3. Observation LOINC 21907-1 (Distant metastases.clinical) -> Fernmetastase
// Fuer Fernmetastasen-Ereignisse (ereignis=3) werden zusaetzlich die
// OncoBox 2.0 Felder J03-J05 befuellt:
// J03: FM_OP_Datum       -> aus Procedure (Operation) mit FM-reasonReference
// J04: FM_Therapien      -> Existenz-Check fuer OP/Syst/ST/Endo im Bundle
// J05: FM_Residualstatus -> aus Procedure.outcome (Operation)
// ============================================================================
group MapVerlaufFromBundle(source src : Bundle, target tgt : BackboneElement) {
  // --- Senologie_FollowUp Observations -> Verlauf M01-M10 + Gesamtbeurteilung ---
  // SCT 396432002 = "Status of regression of tumor"
  src.entry as fuEntry where resource.is(Observation) and resource.code.coding.exists(code = '396432002') then {
    fuEntry.resource as fuObs -> tgt.verlauf as verl then MapVerlaufFollowUp(fuObs, verl) "CallMapVerlaufFU";
  } "EntryVerlaufFU";
  // --- Fernmetastasen-Observations -> Verlauf mit ereignis=3 ---
  // LOINC 21907-1 = "Distant metastases.clinical Cancer"
  src.entry as fmEntry where resource.is(Observation) and resource.code.coding.exists(code = '21907-1') then {
    fmEntry.resource as fmObs -> tgt.verlauf as verl then MapVerlaufFernmetastase(fmObs, verl) "CallMapVerlaufFM";
    // === OncoBox 2.0: J03 — FM_OP_Datum (moved from MapVerlaufFernmetastase) ===
    src.entry as opEntry where resource.is(Procedure) and resource.meta.profile.exists($this.contains('senologie-operation') or $this.contains('senologie-brustop')) then {
      opEntry.resource as proc then {
        proc.extension as ext where url.contains('Intention') then {
          ext.value as val then {
            val.coding as c where code = 'P' then {
              proc.performed as perf where $this.is(dateTime) ->  verl.fmOperation as fmOp,  fmOp.opDatum = perf "SetFMOpDatum";
            } "CheckIntentionPalliativ";
          } "MapIntentionValue";
        } "MapIntentionExt";
      } "MapFMOpProc";
    } "EntryFMOp";
    // === OncoBox 2.0: J04 — FM_Therapien (moved from MapVerlaufFernmetastase) ===
    fmEntry.resource as fmObsCtx -> verl.fmTherapie as fmTh then MapFMTherapien(src, fmTh) "CallMapFMTherapien";
    // === OncoBox 2.0: J05 — FM_Residualstatus (moved from MapVerlaufFernmetastase) ===
    src.entry as opEntry2 where resource.is(Procedure) and resource.meta.profile.exists($this.contains('senologie-operation') or $this.contains('senologie-brustop')) then {
      opEntry2.resource as proc then {
        proc.extension as ext where url.contains('Intention') then {
          ext.value as val then {
            val.coding as c where code = 'P' then {
              proc.outcome as oc then {
                oc.coding as rc where system.contains('residualstatus') ->  verl.fmOperation as fmOp,  fmOp.residualstatus = rc "SetFMResidualCode";
              } "MapFMResidualCoding";
            } "CheckIntentionPalliativR";
          } "MapIntentionValueR";
        } "MapIntentionExtR";
      } "MapFMResidualProc";
    } "EntryFMResidual";
  } "EntryVerlaufFM";
  // --- Vitalstatus aus Patient.deceased (Fallback fuer Verlauf ohne FollowUp) ---
  src.entry as patEntry where resource.is(Patient) then {
    patEntry.resource as patient then {
      patient where deceased.exists() ->  tgt.verlauf as verl,  verl.vitalstatus = 'verstorben' "SetVSVerstorben";
      patient where deceased.exists().not() ->  tgt.verlauf as verl,  verl.vitalstatus = 'lebend' "SetVSLebend";
    } "PatientCtx";
  } "EntryPatVS";
  // --- Conditions mit recurrence -> Verlauf (Lokalrezidiv, Regionaerrezidiv, etc.) ---
  src.entry as recEntry where resource.is(Condition) and resource.clinicalStatus.coding.exists(code = 'recurrence') then {
    recEntry.resource as recCond -> tgt.verlauf as verl then MapVerlaufRezidiv(recCond, verl) "CallMapVerlaufRez";
  } "EntryVerlaufRez";
}

// ============================================================================
// MapVerlaufFollowUp: Observation (Senologie_FollowUp) -> Verlauf M01-M10
// Bildet eine Senologie_FollowUp Observation (SCT 396432002) auf den
// vollstaendigen OncoBox-Verlauf-Block ab:
// M01: Meldedatum       <- effectiveDateTime
// M02: Melder           <- performer.display
// M03: Nachsorge-Art    <- component[nachsorge-art]
// M04: Vitalstatus      <- component[vitalstatus]
// M05: Tumorstatus lok. <- component[Tumor_Verlauf] (SCT 445200009)
// M06: Tumorstatus LK   <- component[Lymphknoten_Verlauf] (SCT 399656008)
// M07: Tumorstatus FM   <- component[Fernmetastasen_Verlauf] (SCT 399608002)
// M08: Zweittumor       <- component[zweittumor]
// M09: Zweittumor ICD   <- component[zweittumor-icd]
// M10: Zweittumor Datum <- component[zweittumor-datum]
// Ereignis              <- Gesamtbeurteilung (valueCodeableConcept):
// P->6 (Progress), Y->1 (Lokalrezidiv Default)
// ============================================================================
group MapVerlaufFollowUp(source src : Observation, target tgt : BackboneElement) {
  // M01: Meldedatum
  src.effective as eff where $this.is(dateTime) -> tgt.meldedatum = eff "SetM01Datum";
  // M02: Melder (Performer-Name als String)
  src.performer as perf then {
    perf.display as d -> tgt.melder = d "SetM02Melder";
  } "MapM02Perf";
  // M03: Art der Nachsorge (aus Component mit Code nachsorge-art)
  src.component as comp where code.coding.exists(code = 'nachsorge-art') then {
    comp.value as val then {
      val.coding as c then {
        c.code as cd where $this = 'aktiv' -> tgt.nachsorgeArt = 'aktiv' "SetM03Aktiv";
        c.code as cd where $this = 'passiv' -> tgt.nachsorgeArt = 'passiv' "SetM03Passiv";
      } "MapM03Coding";
    } "MapM03Val";
  } "MapM03";
  // M04: Vitalstatus (SCT 438949009=lebend, 419099009=verstorben, 261665006=unbekannt)
  src.component as comp where code.coding.exists(code = 'vitalstatus') then {
    comp.value as val then {
      val.coding as c then {
        c.code as cd where $this = '438949009' -> tgt.vitalstatus = 'lebend' "SetM04Lebend";
        c.code as cd where $this = '419099009' -> tgt.vitalstatus = 'verstorben' "SetM04Verstorben";
        c.code as cd where $this = '261665006' -> tgt.vitalstatus = 'unbekannt' "SetM04Unbekannt";
      } "MapM04Coding";
    } "MapM04Val";
  } "MapM04";
  // M05: Lokaler Tumorstatus (SCT 445200009 = Status of residual neoplasm)
  src.component as comp where code.coding.exists(code = '445200009') then {
    comp.value as val then {
      val.coding as c where system.contains('verlauf-primaertumor') then {
        c.code as cd -> tgt.tumorstatusLokal = cd "SetM05";
      } "MapM05Coding";
    } "MapM05Val";
  } "MapM05";
  // M06: Lymphknoten-Tumorstatus (SCT 399656008)
  src.component as comp where code.coding.exists(code = '399656008') then {
    comp.value as val then {
      val.coding as c where system.contains('verlauf-lymphknoten') then {
        c.code as cd -> tgt.tumorstatusLK = cd "SetM06";
      } "MapM06Coding";
    } "MapM06Val";
  } "MapM06";
  // M07: Fernmetastasen-Tumorstatus (SCT 399608002)
  src.component as comp where code.coding.exists(code = '399608002') then {
    comp.value as val then {
      val.coding as c where system.contains('verlauf-fernmetastasen') then {
        c.code as cd -> tgt.tumorstatusFM = cd "SetM07";
      } "MapM07Coding";
    } "MapM07Val";
  } "MapM07";
  // M08: Zweittumor (SCT 373066001=ja, 373067005=nein, 261665006=unbekannt)
  src.component as comp where code.coding.exists(code = 'zweittumor') then {
    comp.value as val then {
      val.coding as c then {
        c.code as cd where $this = '373066001' -> tgt.zweittumor = 'ja' "SetM08Ja";
        c.code as cd where $this = '373067005' -> tgt.zweittumor = 'nein' "SetM08Nein";
        c.code as cd where $this = '261665006' -> tgt.zweittumor = 'unbekannt' "SetM08Unbekannt";
      } "MapM08Coding";
    } "MapM08Val";
  } "MapM08";
  // M09: Zweittumor ICD-10-GM
  src.component as comp where code.coding.exists(code = 'zweittumor-icd') then {
    comp.value as val then {
      val.coding as c where system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm' then {
        c.code as cd -> tgt.zweitttumorIcd = cd "SetM09";
      } "MapM09Coding";
    } "MapM09Val";
  } "MapM09";
  // M10: Zweittumor Diagnosedatum
  src.component as comp where code.coding.exists(code = 'zweittumor-datum') then {
    comp.value as val where $this.is(dateTime) -> tgt.zweitttumorDatum = val "SetM10";
  } "MapM10";
  // Gesamtbeurteilung -> Verlauf_Ereignis
  // ConceptMap: V/T/K/B/R -> kein Ereignis (stabil); P -> 6; Y -> 1
  src.value as val then {
    val.coding as c where system.contains('verlauf-gesamtbeurteilung') then {
      // P = Progression -> 6 (Progress)
      c.code as cd where $this = 'P' -> tgt.ereignis = '6' "SetEreignisProgress";
      // Y = Rezidiv -> 1 (Lokalrezidiv als Default)
      c.code as cd where $this = 'Y' -> tgt.ereignis = '1' "SetEreignisRezidiv";
    } "MapGesamtCoding";
  } "MapGesamt";
}

// ============================================================================
// MapVerlaufFernmetastase: Observation (FM) -> Verlauf + OncoBox 2.0 J03-J05
// Bildet eine Fernmetastasen-Observation auf den OncoBox-Verlauf-Block ab.
// Zusaetzlich werden FM-spezifische Therapie-Procedures aus dem Bundle
// extrahiert fuer die OncoBox 2.0 Felder.
// FM-spezifische Procedures werden identifiziert ueber:
// - meta.profile enthält das jeweilige Senologie-Profil
// - extension:Intention mit code 'P' (palliativ) als FM-Kontextindikator
// ============================================================================
group MapVerlaufFernmetastase(source src : Observation, target tgt : BackboneElement) {
  // Verlauf_Datum: aus Observation.effectiveDateTime
  src.effective as eff where $this.is(dateTime) -> tgt.meldedatum = eff "SetVerlaufDatumFM";
  // Verlauf_Ereignis: 3 = Fernmetastase
  src -> tgt.ereignis = '3' "SetEreignisFM";
}

// ============================================================================
// MapFMTherapien: Bundle -> FM_Therapien (J04)
// Prueft fuer jede Therapieart ob eine Procedure mit palliativer Intention
// im Bundle vorhanden ist und setzt entsprechend 0/1.
// ============================================================================
group MapFMTherapien(source src : Bundle, target tgt : BackboneElement) {
  // Operation bei FM
  src.entry as entry where resource.is(Procedure) and resource.meta.profile.exists($this.contains('senologie-operation') or $this.contains('senologie-brustop')) then {
    entry.resource as proc then {
      proc.extension as ext where url.contains('Intention') then {
        ext.value as val then {
          val.coding as c where code = 'P' -> tgt.operation = '1' "SetFMThOP";
        } "CheckIntentionPalliativOP";
      } "MapIntentionExtOP";
    } "MapFMThOPProc";
  } "EntryFMThOP";
  // Systemtherapie bei FM
  src.entry as entry where resource.is(Procedure) and resource.meta.profile.exists($this.contains('senologie-systemtherapie')) then {
    entry.resource as proc then {
      proc.extension as ext where url.contains('Intention') then {
        ext.value as val then {
          val.coding as c where code = 'P' -> tgt.systemtherapie = '1' "SetFMThSyst";
        } "CheckIntentionPalliativSyst";
      } "MapIntentionExtSyst";
    } "MapFMThSystProc";
  } "EntryFMThSyst";
  // Strahlentherapie bei FM
  src.entry as entry where resource.is(Procedure) and resource.meta.profile.exists($this.contains('senologie-strahlentherapie')) then {
    entry.resource as proc then {
      proc.extension as ext where url.contains('Intention') then {
        ext.value as val then {
          val.coding as c where code = 'P' -> tgt.strahlentherapie = '1' "SetFMThST";
        } "CheckIntentionPalliativST";
      } "MapIntentionExtST";
    } "MapFMThSTProc";
  } "EntryFMThST";
  // Endokrine Therapie bei FM (Senologie Systemtherapie mit endocrine type)
  // Endokrine Therapie wird als Systemtherapie mit spezifischem Code erfasst
  src.entry as entry where resource.is(Procedure) and resource.meta.profile.exists($this.contains('senologie-systemtherapie')) then {
    entry.resource as proc then {
      proc.code as code then {
        code.coding as c where code = '169413002' then {
          proc.extension as ext where url.contains('Intention') then {
            ext.value as val then {
              val.coding as ic where code = 'P' -> tgt.endokrineTherapie = '1' "SetFMThEndo";
            } "CheckIntentionPalliativEndo";
          } "MapIntentionExtEndo";
        } "CheckEndocrineCode";
      } "MapEndocrineCodeCtx";
    } "MapFMThEndoProc";
  } "EntryFMThEndo";
}

// ============================================================================
// MapVerlaufRezidiv: Condition (Rezidiv) -> Verlauf (Nicht-FM-Ereignis)
// Mappt Rezidiv-Conditions auf den OncoBox-Verlauf-Block fuer Lokalrezidive,
// Regionaerrezidive und andere Verlaufsereignisse.
// FM-spezifische Felder J03-J05 werden hier NICHT befuellt.
// ============================================================================
group MapVerlaufRezidiv(source src : Condition, target tgt : BackboneElement) {
  // Verlauf_Datum: aus Condition.recordedDate oder extension Feststellungsdatum
  src.extension as ext where url = 'https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-ex-onko-feststellungsdatum' then {
    ext.value as val where $this.is(dateTime) -> tgt.datum = val "SetVerlaufDatumExt";
  } "MapVerlaufDatumExt";
  // Fallback: recordedDate
  src.recordedDate as rd -> tgt.datum = rd "SetVerlaufDatumFallback";
  // Verlauf_Ereignis: aus Condition.code oder stage
  // Mapping: Lokalrezidiv=1, Regionaerrezidiv=2, kontralateral=4, Zweitmalignom=5, Progress=6
  // (Fernmetastase=3 wird ueber MapVerlaufFernmetastase abgedeckt)
  src.code as code then {
    code.coding as c where system = 'http://snomed.info/sct' then {
      // 255226008 = Local recurrence of tumor
      c.code as cd where $this = '255226008' -> tgt.ereignis = '1' "SetEreignisLokal";
      // 315266007 = Regional recurrence
      c.code as cd where $this = '315266007' -> tgt.ereignis = '2' "SetEreignisRegional";
      // 94222008 = Contralateral breast cancer (Secondary malignant neoplasm of contralateral breast)
      c.code as cd where $this = '94222008' -> tgt.ereignis = '4' "SetEreignisKontralateral";
    } "MapEreignisSCT";
  } "MapEreignis";
}