diff --git a/.github/workflows/r4_firely_terminal.yaml b/.github/workflows/r4_firely_terminal.yaml index 88adae20..089a151f 100644 --- a/.github/workflows/r4_firely_terminal.yaml +++ b/.github/workflows/r4_firely_terminal.yaml @@ -30,7 +30,7 @@ jobs: java-version: '21' - name: Firely.Terminal (GitHub Actions) - uses: FirelyTeam/firely-terminal-pipeline@v0.7.32-alpha2 + uses: FirelyTeam/firely-terminal-pipeline@v0.8.1 with: PATH_TO_CONFORMANCE_RESOURCES: fsh-generated/resources PATH_TO_EXAMPLES: fsh-generated/resources diff --git a/.github/workflows/r4_firely_terminal_nts.yaml b/.github/workflows/r4_firely_terminal_nts.yaml index 83c897e3..057fdbd6 100644 --- a/.github/workflows/r4_firely_terminal_nts.yaml +++ b/.github/workflows/r4_firely_terminal_nts.yaml @@ -65,7 +65,7 @@ jobs: echo "JAVA_TOOL_OPTIONS=-Dhttp.proxyHost=localhost -Dhttp.proxyPort=8080 -Dhttps.proxyHost=localhost -Dhttps.proxyPort=8080 -Dhttp.nonProxyHosts=" >> $GITHUB_ENV - name: Firely.Terminal (GitHub Actions) - uses: FirelyTeam/firely-terminal-pipeline@v0.7.32-alpha2 + uses: FirelyTeam/firely-terminal-pipeline@v0.8.1 with: PATH_TO_CONFORMANCE_RESOURCES: fsh-generated/resources PATH_TO_EXAMPLES: fsh-generated/resources diff --git a/input/fsh/Alias.fsh b/input/fsh/Alias.fsh index b1089561..39b41995 100644 --- a/input/fsh/Alias.fsh +++ b/input/fsh/Alias.fsh @@ -1,5 +1,7 @@ Alias: $snomed = http://snomed.info/sct Alias: $v3-NullFlavor = http://terminology.hl7.org/CodeSystem/v3-NullFlavor +Alias: $DHD-CBV = urn:oid:2.16.840.1.113883.2.4.3.120.5.3 +Alias: $NZa = urn:oid:2.16.840.1.113883.2.4.3.27.15.5 Alias: $DataAbsentReason = http://terminology.hl7.org/CodeSystem/data-absent-reason Alias: $v2-0203 = http://terminology.hl7.org/CodeSystem/v2-0203 Alias: $v3-RoleCode = http://terminology.hl7.org/CodeSystem/v3-RoleCode diff --git a/input/fsh/CapabilityStatement.fsh b/input/fsh/CapabilityStatement.fsh index e0c85b72..b742239d 100644 --- a/input/fsh/CapabilityStatement.fsh +++ b/input/fsh/CapabilityStatement.fsh @@ -139,7 +139,11 @@ Usage: #definition * valueCode = #SHOULD * type = #Observation // Supported profiles for Observation resource are set to SHOULD because not all Observation have to be implemented. - * supportedProfile[0] = "https://api.iknl.nl/docs/pzp/r4/StructureDefinition/ACP-OrganDonationChoiceRegistration" + * supportedProfile[0] = "https://api.iknl.nl/docs/pzp/r4/StructureDefinition/ACP-LegallyCapable" + * extension + * url = $CapExpectation + * valueCode = #SHOULD + * supportedProfile[+] = "https://api.iknl.nl/docs/pzp/r4/StructureDefinition/ACP-OrganDonationChoiceRegistration" * extension * url = $CapExpectation * valueCode = #SHOULD @@ -483,7 +487,11 @@ Usage: #definition * valueCode = #SHALL * type = #Observation // Supported profiles for Observation resource are set to SHOULD because not all Observation have to be implemented. - * supportedProfile[0] = "https://api.iknl.nl/docs/pzp/r4/StructureDefinition/ACP-OrganDonationChoiceRegistration" + * supportedProfile[0] = "https://api.iknl.nl/docs/pzp/r4/StructureDefinition/ACP-LegallyCapable" + * extension + * url = $CapExpectation + * valueCode = #SHOULD + * supportedProfile[+] = "https://api.iknl.nl/docs/pzp/r4/StructureDefinition/ACP-OrganDonationChoiceRegistration" * extension * url = $CapExpectation * valueCode = #SHOULD @@ -567,7 +575,7 @@ Usage: #definition * extension * url = $CapExpectation * valueCode = #SHALL - * type = #Communication + * type = #CommunicationRequest * supportedProfile[0] = "https://api.iknl.nl/docs/pzp/r4/StructureDefinition/ACP-InformRelativesRequest" * extension * url = $CapExpectation diff --git a/input/fsh/CommunicationRequest.fsh b/input/fsh/CommunicationRequest.fsh index 2113c5c4..6a6479cb 100644 --- a/input/fsh/CommunicationRequest.fsh +++ b/input/fsh/CommunicationRequest.fsh @@ -7,13 +7,17 @@ Description: "A CommunicationRequest representing the advice or instruction give * category 1..* * category = $snomed#223449006 * category ^comment = "The `category.text` element may be used to provide additional context for human readers next to the pattern category coding, for example: 'Request for patient to inform relatives about treatment agreements'." +* subject 1..1 * subject only Reference(ACPPatient) * encounter only Reference(ACPEncounter) +* requester 1..1 * requester only Reference(ACPHealthProfessionalPractitionerRole or ACPHealthProfessionalPractitioner) +* sender 1..1 * sender only Reference(ACPPatient) * recipient only Reference(ACPContactPerson) * reasonCode 1..* * reasonCode = $snomed#713603004 // "advance care planning" +* obeys cr-date-required * insert ObligationRules(category) // already 1..1 so may not be needed place under obligation but added for consistency * insert ObligationRules(subject) @@ -25,11 +29,17 @@ Description: "A CommunicationRequest representing the advice or instruction give * insert ObligationRules(reasonCode) // already 1..1 so may not be needed place under obligation but added for consistency +Invariant: cr-date-required +Description: "The date of the CommunicationRequest is expected to be captured either in the resource itself or in the Encounter in which the CommunicationRequest originated." +Severity: #error +Expression: "authoredOn.exists() or encounter.exists()" + + Mapping: MapACPInformRelativesRequest -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPInformRelativesRequest -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "734" "Heeft u patient geïnformeerd over eigen verantwoordelijkheid om deze behandelafspraken met naasten te bespreken?" diff --git a/input/fsh/Consent.fsh b/input/fsh/Consent.fsh index 93072775..cecda6f8 100644 --- a/input/fsh/Consent.fsh +++ b/input/fsh/Consent.fsh @@ -20,10 +20,10 @@ Description: "A verbal or written description of the patient’s wishes with reg Mapping: MapACPAdvanceDirective -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPAdvanceDirective -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "690" "Euthanasieverklaring (Wilsverklaring)" * -> "700" "Keuze orgaandonatie vastgelegd (Wilsverklaring)" * -> "721" "Eerder vastgelegde behandelafspraken (Wilsverklaring)" @@ -123,10 +123,10 @@ Description: "A joint decision between a health professional (for example a gene Mapping: MapACPTreatmentDirective -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPTreatmentDirective -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "602" "Behandelgrens (BehandelAanwijzing)" * -> "637" "Afspraak uitzetten ICD (BehandelAanwijzing)" * modifierExtension[specificationOther] -> "605" "SpecificatieAnders" diff --git a/input/fsh/Device.fsh b/input/fsh/Device.fsh index 9314de95..2d83d6d2 100644 --- a/input/fsh/Device.fsh +++ b/input/fsh/Device.fsh @@ -15,10 +15,10 @@ Description: "The medical device (internally or externally). In the context of A Mapping: MapACPMedicalDeviceProductICD -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPMedicalDeviceProductICD -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "621" "Product" * identifier[gs1ProductID] -> "622" "ProductID" * identifier[hibcProductID] -> "622" "ProductID" diff --git a/input/fsh/DeviceUseStatement.fsh b/input/fsh/DeviceUseStatement.fsh index 1a56a5ec..dabf531c 100644 --- a/input/fsh/DeviceUseStatement.fsh +++ b/input/fsh/DeviceUseStatement.fsh @@ -19,10 +19,10 @@ Description: "Any internally implanted and external devices and/or aids used by * insert ObligationRules(note.text) Mapping: MapACPMedicalDevice -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPMedicalDevice -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "619" "Heeft de patient een ICD?" * -> "620" "ICD (MedischHulpmiddel)" * extension[healthProfessional] -> "635" "Zorgverlener" diff --git a/input/fsh/Encounter.fsh b/input/fsh/Encounter.fsh index c1470f61..41e03acf 100644 --- a/input/fsh/Encounter.fsh +++ b/input/fsh/Encounter.fsh @@ -25,10 +25,10 @@ Description: "Any interaction, regardless of the situation, between a patient an Mapping: MapACPEncounter -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPEncounter -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "808" "Contact" * class -> "809" "ContactType" * subject -> "514" "Gesprek gevoerd in bijzijn van (Patient)" // In R5 patient is added to .participant.individual. For now, if present at .subject, we assume the patient was present. Also clear from the definition of the subject element: "The patient or group present at the encounter" diff --git a/input/fsh/Extension.fsh b/input/fsh/Extension.fsh deleted file mode 100644 index 2a7de771..00000000 --- a/input/fsh/Extension.fsh +++ /dev/null @@ -1,22 +0,0 @@ -Extension: ExtPatientLegallyCapableMedicalTreatmentDecisions -Id: ext-Patient.LegallyCapableMedicalTreatmentDecisions -Title: "ext Patient.LegallyCapableMedicalTreatmentDecisions" -Description: "An extension to indicate the patient's legal capability regarding medical treatment decisions, and to provide a comment on this capability." -Context: Patient -* ^purpose = "This extension is based on the [extension FreedomRestrictingIntervention.LegallyCapable](http://nictiz.nl/fhir/StructureDefinition/ext-FreedomRestrictingIntervention.LegallyCapable), but is adapted for the ACP context by allowing its use on the Patient resource and specifying its application to treatment decisions." -* insert MetaRules -* extension ^slicing.discriminator.type = #value -* extension ^slicing.discriminator.path = "url" -* extension ^slicing.rules = #open -* extension ^min = 0 -* extension contains - legallyCapable 0..1 and - legallyCapableComment 0..1 -* extension[legallyCapable].value[x] only boolean -* extension[legallyCapable].value[x] ^short = "LegallyCapable" -* extension[legallyCapable].value[x] ^definition = "Indicates the patient's legal capacity (LegallyCapable) regarding medical treatment decisions." -* extension[legallyCapable].value[x] ^alias = "Wilsbekwaam" -* extension[legallyCapableComment].value[x] only string -* extension[legallyCapableComment].value[x] ^short = "LegallyCapableComment" -* extension[legallyCapableComment].value[x] ^definition = "A comment regarding the patient's legal capacity regarding medical treatment decisions." -* extension[legallyCapableComment].value[x] ^alias = "WilsbekwaamToelichting" \ No newline at end of file diff --git a/input/fsh/Goal.fsh b/input/fsh/Goal.fsh index b24018ed..deb24316 100644 --- a/input/fsh/Goal.fsh +++ b/input/fsh/Goal.fsh @@ -16,10 +16,10 @@ Description: "The primary, agreed-upon goal of a patient's medical treatment pol * insert ObligationRules(note.text) Mapping: MapACPMedicalPolicyGoal -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPMedicalPolicyGoal -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "590" "Belangrijkste doel van behandeling ([Meting])" * -> "591" "Belangrijkste doel van behandeling ([MetingNaam])" * description -> "592" "Doel ([MetingWaarde])" diff --git a/input/fsh/Observation.fsh b/input/fsh/Observation.fsh index ba4bf2f1..49abac6a 100644 --- a/input/fsh/Observation.fsh +++ b/input/fsh/Observation.fsh @@ -25,10 +25,10 @@ Description: "The patient's wishes and expectations concerning their treatment, * insert ObligationRules(performer) Mapping: MapACPSpecificCareWishes -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPSpecificCareWishes -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "654" "Specifieke wensen ([Meting])" * code -> "655" "Wens en verwachting patient ([MetingNaam])" * valueString -> "656" "Wens en verwachting patient ([MetingWaarde])" @@ -98,10 +98,10 @@ Description: "The preferred place of death. This is the place where the patient * insert ObligationRules(performer) Mapping: MapACPSPreferredPlaceOfDeath -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPPreferredPlaceOfDeath -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "666" "Gewenste plek van overlijden ([Meting]))" * code -> "667" "Gewenste plek van overlijden ([Meting])" * valueCodeableConcept -> "668" "Voorkeursplek ([MetingWaarde])" @@ -121,7 +121,7 @@ Usage: #example * subject = Reference(ACP-Patient-HendrikHartman-Pat1) "Patient, Hendrik Hartman" * performer = Reference(ACP-HealthProfessional-PractitionerRole-DrVanHuissen-Pat1) "Healthcare professional (role), van Huissen" * status = #final -* code = $snomed#395091006 "Preferred place of death" +* code = $snomed#395091006 "gewenste plek van overlijden" // * valueCodeableConcept = $v3-NullFlavor#UNK -- Cannot have a value[x] if you have data absent reason * dataAbsentReason = $DataAbsentReason#asked-unknown * effectiveDateTime = "2020-10-01" @@ -139,7 +139,7 @@ Usage: #example * subject = Reference(ACP-Patient-SamiraVanDerSluijs-Pat2) "Patient, Samira van der Sluijs" * performer = Reference(ACP-HealthProfessional-PractitionerRole-DesireeWolters-Pat2) "Healthcare professional (role), Desiree Wolters" * status = #final -* code = $snomed#395091006 "Preferred place of death" +* code = $snomed#395091006 "gewenste plek van overlijden" * effectiveDateTime = "2025-08-07" * valueCodeableConcept = $snomed#264362003 "thuis" * note.text = "Het liefst rustig thuis" @@ -170,10 +170,10 @@ Description: "The patient's position regarding euthanasia. Based on Observation Mapping: MapACPPositionRegardingEuthanasia -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPPositionRegardingEuthanasia -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "678" "Euthanasie standpunt ([Meting])" * code -> "679" "Euthanasie standpunt ([MetingNaam])" * valueCodeableConcept -> "680" "Euthanasie standpunt ([MetingWaarde])" @@ -240,10 +240,10 @@ Description: "Observation capturing whether the patient's organ donation choice * insert ObligationRules(performer) Mapping: MapACPOrganDonationChoiceRegistration -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPOrganDonationChoiceRegistration -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "746" "Keuze orgaandonatie vastgelegd in donorregister? ([Meting])" * code -> "747" "Keuze orgaandonatie vastgelegd in donorregister? ([MetingNaam])" * valueCodeableConcept -> "748" "Keuze orgaandonatie in donorregister ([MetingWaarde])" @@ -307,10 +307,10 @@ Description: "Observation capturing the patient's sense of purpose and other imp Mapping: MapACPSenseOfPurpose -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPSenseOfPurpose -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "709" "Wat verder nog belangrijk is ([Meting])" * code -> "710" "Wat verder nog belangrijk is ([MetingNaam])" * valueString -> "711" "Wat verder nog belangrijk is ([MetingWaarde])" @@ -347,4 +347,68 @@ Usage: #example * status = #final * code = $snomed#247751003 "gevoel van zingeving" * valueString = "Mevrouw is gek op haar kleinzoon, dus brengt graag veel tijd met hem door." -* effectiveDateTime = "2025-08-07" \ No newline at end of file +* effectiveDateTime = "2025-08-07" + + +Profile: ACPLegallyCapable +Parent: Observation +Id: ACP-LegallyCapable +Title: "ACP Legally Capable" +Description: "Indicates whether the patient is currently assessed as having the capacity to understand and oversee the consequences of medical treatment decisions. If the patient is not legally capable, there should be a legal representative captured in a RelatedPerson resource. Based on Observation resource." +* insert MetaRules +* encounter only Reference(ACPEncounter) +* subject only Reference(ACPPatient) +* code = $snomed#665671000146101 +* value[x] only boolean + +* insert ObligationRules(encounter) +* insert ObligationRules(subject) +* insert ObligationRules(code) +* insert ObligationRules(valueBoolean) +* insert ObligationRules(dataAbsentReason) +* insert ObligationRules(effective[x]) +* insert ObligationRules(note.text) +* insert ObligationRules(performer) + +Mapping: MapACPLegallyCapable +Id: pall-izppz-zib2020 +Title: "ACP dataset" +Source: ACPLegallyCapable +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +* -> "761" "Wilsbekwaamheid m.b.t. medische behandelbeslissingen" +* valueBoolean -> "762" "Wilsbekwaamheid m.b.t. medische behandelbeslissingen" +* dataAbsentReason -> "762" "Wilsbekwaamheid m.b.t. medische behandelbeslissingen" +* note.text -> "763" "[Toelichting]" + + +Instance: ACP-LegallyCapable-Pat1 +InstanceOf: ACPLegallyCapable +Title: "ACP Legally Capable - Pat 1" +Usage: #example +* identifier.type = $v2-0203#RI "Resource identifier" +* identifier.system = "https://acme.com/fhir/NamingSystem/resource-business-identifier" +* identifier.value = "928e493b-3265-46ad-a155-b46d3d31b821" +* encounter = Reference(ACP-Encounter-Pat1) "Encounter, 2020-10-01" +* subject = Reference(ACP-Patient-HendrikHartman-Pat1) "Patient, Hendrik Hartman" +* performer = Reference(ACP-HealthProfessional-PractitionerRole-DrVanHuissen-Pat1) "Healthcare professional (role), van Huissen" +* status = #final +* code = $snomed#665671000146101 "juridisch in staat om beslissingen te nemen over medische behandelingen" +* valueBoolean = true +* effectiveDateTime = "2020-10-01" + + +Instance: ACP-LegallyCapable-Pat2 +InstanceOf: ACPLegallyCapable +Title: "ACP Legally Capable - Pat 2" +Usage: #example +* identifier.type = $v2-0203#RI "Resource identifier" +* identifier.system = "https://acme.com/fhir/NamingSystem/resource-business-identifier" +* identifier.value = "337d9e4a-08a3-486f-9f24-d6c60c6f342a" +* encounter = Reference(ACP-Encounter-1-Pat2) "Encounter, 2025-08-07" +* subject = Reference(ACP-Patient-SamiraVanDerSluijs-Pat2) "Patient, Samira van der Sluijs" +* performer = Reference(ACP-HealthProfessional-PractitionerRole-DesireeWolters-Pat2) "Healthcare professional (role), Desiree Wolters" +* status = #final +* code = $snomed#665671000146101 "juridisch in staat om beslissingen te nemen over medische behandelingen" +* valueBoolean = true +* effectiveDateTime = "2025-08-07" +* note.text = "Patiënt is wilsbekwaam. Bij verandering van de situatie wordt haar partner haar wettelijk vertegenwoordiger." \ No newline at end of file diff --git a/input/fsh/Patient.fsh b/input/fsh/Patient.fsh index a661f874..29ba6a0f 100644 --- a/input/fsh/Patient.fsh +++ b/input/fsh/Patient.fsh @@ -4,18 +4,11 @@ Id: ACP-Patient Title: "ACP Patient" Description: "A person who receives medical, psychological, paramedical, or nursing care. Based on nl-core-Patient and HCIM Patient." * insert MetaRules -* obeys ACP-Patient-1 -* extension contains - ExtPatientLegallyCapableMedicalTreatmentDecisions named legallyCapableMedicalTreatmentDecisions 0..1 -* extension[legallyCapableMedicalTreatmentDecisions] ^condition = "ACP-Patient-1" * name 1..* -* contact ^condition = "ACP-Patient-1" -* contact.extension[relatedPerson] ^condition = "ACP-Patient-1" * contact.extension[relatedPerson] ^comment = "All information regarding the patient's contact persons should preferably be stored in the RelatedPerson resource, and optionally in `Patient.contact`. The http://hl7.org/fhir/StructureDefinition/patient-relatedPerson extension is used to link the contact person to the Patient and to emphasize that the related person is also a contact person of the patient." * contact.extension[relatedPerson].valueReference only Reference(ACPContactPerson) -* contact.relationship ^condition = "ACP-Patient-1" -* insert ObligationRules(extension[legallyCapableMedicalTreatmentDecisions]) + * insert ObligationRules(contact.extension[relatedPerson]) * insert ObligationRules(identifier) * insert ObligationRules(name[nameInformation].given) @@ -48,24 +41,14 @@ Description: "A person who receives medical, psychological, paramedical, or nurs * insert ObligationRules(address.use) * insert ObligationRules(address.type) - -Invariant: ACP-Patient-1 -Description: "If the patient is not legally capable, there should be a legal representative." -* severity = #warning -* expression = "extension.where(url='https://api.iknl.nl/docs/pzp/r4/StructureDefinition/ext-LegallyCapable-MedicalTreatmentDecisions').extension.where(url='legallyCapable').value = false implies (contact.where(relationship.coding.code = '24').exists() or contact.extension.where(url='http://hl7.org/fhir/StructureDefinition/patient-relatedPerson').exists())" - - Mapping: MapACPPatient -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPPatient -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "351" "Patient" * -> "613" "Patient" * -> "648" "Patient" -* extension[legallyCapableMedicalTreatmentDecisions] -> "761" "Wilsbekwaamheid m.b.t. medische behandelbeslissingen" -* extension[legallyCapableMedicalTreatmentDecisions].extension[legallyCapable] -> "762" "Wilsbekwaamheid m.b.t. medische behandelbeslissingen" -* extension[legallyCapableMedicalTreatmentDecisions].extension[legallyCapableComment] -> "763" "Toelichting" * identifier -> "385" "Identificatienummer" * name -> "352" "Naamgegevens" * name[nameInformation].given -> "353" "Voornamen" @@ -123,7 +106,6 @@ Instance: ACP-Patient-HendrikHartman-Pat1 InstanceOf: ACPPatient Title: "ACP Patient - Hendrik Hartman - Pat 1" Usage: #example -* extension[legallyCapableMedicalTreatmentDecisions].extension[legallyCapable].valueBoolean = true * identifier.system = "http://fhir.nl/fhir/NamingSystem/bsn" * identifier.value = "999911120" * name[nameInformation].extension.url = "http://hl7.org/fhir/StructureDefinition/humanname-assembly-order" @@ -162,8 +144,6 @@ Instance: ACP-Patient-SamiraVanDerSluijs-Pat2 InstanceOf: ACPPatient Title: "ACP Patient - Samira van der Sluijs - Pat 2" Usage: #example -* extension[legallyCapableMedicalTreatmentDecisions].extension[legallyCapable].valueBoolean = true -* extension[legallyCapableMedicalTreatmentDecisions].extension[legallyCapableComment].valueString = "Patiënt is wilsbekwaam. Bij verandering van de situatie wordt haar partner haar wettelijk vertegenwoordiger." * identifier.system = "http://fhir.nl/fhir/NamingSystem/bsn" * identifier.value = "999998298" * name[0].use = #official diff --git a/input/fsh/Practitioner.fsh b/input/fsh/Practitioner.fsh index 5f36db98..b09a71c5 100644 --- a/input/fsh/Practitioner.fsh +++ b/input/fsh/Practitioner.fsh @@ -12,10 +12,10 @@ Description: "A person who is authorized to perform actions in the field of indi Mapping: MapACPHealthProfessionalPractitioner -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPHealthProfessionalPractitioner -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "391" "Gesprek gevoerd door (Zorgverlener)" * identifier -> "392" "ZorgverlenerIdentificatienummer" * name -> "393" "Naamgegevens" diff --git a/input/fsh/PractitionerRole.fsh b/input/fsh/PractitionerRole.fsh index 33c950ad..24b8cb51 100644 --- a/input/fsh/PractitionerRole.fsh +++ b/input/fsh/PractitionerRole.fsh @@ -10,10 +10,10 @@ Description: "The specialty of a person who is authorized to perform actions in * insert ObligationRules(specialty[specialty]) Mapping: MapACPHealthProfessionalPractitionerRole -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPHealthProfessionalPractitionerRole -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "391" "Gesprek gevoerd door (Zorgverlener)" * -> "617" "Zorgverlener" * -> "636" "Zorgverlener" diff --git a/input/fsh/Procedure.fsh b/input/fsh/Procedure.fsh index f8b78222..f37af33d 100644 --- a/input/fsh/Procedure.fsh +++ b/input/fsh/Procedure.fsh @@ -6,18 +6,18 @@ Description: "Advance Care Planning procedure. Based on nl-core-Procedure-event * insert MetaRules * subject only Reference(ACPPatient) * encounter only Reference(ACPEncounter) +* code from ACPProcedureTypeVS (required) * code 1..1 -* code = $snomed#713603004 * insert ObligationRules(subject) * insert ObligationRules(encounter) * insert ObligationRules(code) Mapping: MapACPProcedure -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPProcedure -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "820" "Verrichting" * code -> "827" "PZP gesprek (VerrichtingType)" @@ -60,7 +60,7 @@ Usage: #example * performer[=].actor.type = "Patient" * performedPeriod.start = "2025-08-07" * performedPeriod.end = "2025-08-07" -* code = $snomed#713603004 "advance care planning" +* code = $DHD-CBV#411600B "PROACTIEVE ZORGPLANNING-OPSTEL. INDIV.ZORGPL.PALLIAT.FASE" Instance: ACP-Procedure-2-Pat2 InstanceOf: ACPProcedure @@ -79,4 +79,4 @@ Usage: #example * performer[=].actor.type = "Patient" * performedPeriod.start = "2024-07-28" * performedPeriod.end = "2024-07-28" -* code = $snomed#713603004 "advance care planning" +* code = $NZa#190099 "Proactieve zorgplanning" diff --git a/input/fsh/RelatedPerson.fsh b/input/fsh/RelatedPerson.fsh index 3908c414..4d210379 100644 --- a/input/fsh/RelatedPerson.fsh +++ b/input/fsh/RelatedPerson.fsh @@ -49,10 +49,10 @@ For the ACP use case, additional codes beyond those in the existing ContactPerso Mapping: MapACPContactPerson -Id: pall-izppz-zib2020v2026-02-24 +Id: pall-izppz-zib2020 Title: "ACP dataset" Source: ACPContactPerson -Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-02-24T09:29:59" +Target: "https://decor.nictiz.nl/exist/apps/api/dataset/2.16.840.1.113883.2.4.3.11.60.117.1.1/2020-07-29T10%3A37%3A48/$view?language=nl-NL&ui=nl-NL&format=html&hidecolumns=3456gh&release=2026-05-12T07%3A58%3A08" * -> "441" "Wettelijk vertegenwoordiger (Contactpersoon)" * -> "478" "Eerste contactpersoon (Contactpersoon)" * -> "615" "Contactpersoon" diff --git a/input/fsh/RuleSet.fsh b/input/fsh/RuleSet.fsh index d1f2a2f2..ecdbb5b4 100644 --- a/input/fsh/RuleSet.fsh +++ b/input/fsh/RuleSet.fsh @@ -1,5 +1,5 @@ RuleSet: MetaRules -* ^version = "1.0.0-rc2" +* ^version = "1.0.0-rc3" * ^status = #draft * ^experimental = false * ^publisher = "IKNL" @@ -10,7 +10,7 @@ RuleSet: MetaRules * ^copyright = "Copyright and related rights waived via CC0, https://creativecommons.org/publicdomain/zero/1.0/. This does not apply to information from third parties, for example a medical terminology system. The implementer alone is responsible for identifying and obtaining any necessary licenses or authorizations to utilize third party IP in connection with the specification or otherwise." RuleSet: MetaRulesDefinitionalArtifact -* version = "1.0.0-rc2" +* version = "1.0.0-rc3" * date = "2026-03-03" * status = #active * experimental = false diff --git a/input/fsh/ValueSet.fsh b/input/fsh/ValueSet.fsh index 3faba5dc..6542703a 100644 --- a/input/fsh/ValueSet.fsh +++ b/input/fsh/ValueSet.fsh @@ -1,6 +1,6 @@ ValueSet: ACPPreferredPlaceOfDeathVS -Id: ACP-PreferredPlaceOfDeath -Title: "ACP Preferred Place of Death" +Id: ACP-PreferredPlaceOfDeathVS +Title: "ACP Preferred Place of Death Codes" Description: "ValueSet for Preferred Place of Death, representing the place where the patient prefers to die, if possible." * insert MetaRules * ^copyright = "This artefact includes content from SNOMED Clinical Terms® (SNOMED CT®) which is copyright of the International Health Terminology Standards Development Organisation (IHTSDO). Implementers of these artefacts must have the appropriate SNOMED CT Affiliate license - for more information contact http://www.snomed.org/snomed-ct/getsnomed-ct or info@snomed.org." @@ -94,8 +94,8 @@ Description: "ValueSet for Preferred Place of Death, representing the place wher ValueSet: ACPPositionRegardingEuthanasiaVS -Id: ACP-PositionRegardingEuthanasia -Title: "ACP Position Regarding Euthanasia" +Id: ACP-PositionRegardingEuthanasiaVS +Title: "ACP Position Regarding Euthanasia Codes" Description: "ValueSet for Position Regarding Euthanasia, representing the the patient's position regarding euthanasia and information on the presence of a euthanasia statement." * insert MetaRules * ^copyright = "This artefact includes content from SNOMED Clinical Terms® (SNOMED CT®) which is copyright of the International Health Terminology Standards Development Organisation (IHTSDO). Implementers of these artefacts must have the appropriate SNOMED CT Affiliate license - for more information contact http://www.snomed.org/snomed-ct/getsnomed-ct or info@snomed.org." @@ -109,8 +109,8 @@ Description: "ValueSet for Position Regarding Euthanasia, representing the the p ValueSet: ACPMedicalPolicyGoalVS -Id: ACP-MedicalPolicyGoal -Title: "ACP Primary Agreed-upon Goal of Medical Policy" +Id: ACP-MedicalPolicyGoalVS +Title: "ACP Primary Agreed-upon Goal of Medical Policy Codes" Description: "ValueSet for Medical Policy Goal, representing the primary agreed-upon goal of a patient's medical treatment policy." * insert MetaRules * ^copyright = "This artefact includes content from SNOMED Clinical Terms® (SNOMED CT®) which is copyright of the International Health Terminology Standards Development Organisation (IHTSDO). Implementers of these artefacts must have the appropriate SNOMED CT Affiliate license - for more information contact http://www.snomed.org/snomed-ct/getsnomed-ct or info@snomed.org." @@ -121,7 +121,7 @@ Description: "ValueSet for Medical Policy Goal, representing the primary agreed- ValueSet: ACPMedicalDeviceProductTypeICDVS -Id: ACP-MedicalDeviceProductType-ICD +Id: ACP-MedicalDeviceProductType-ICDVS Title: "ACP MedicalDevice ProductType ICD" Description: "ICD product code for MedicalDevice ProductType. This ValueSet is conceptually based on SNOMED CT codes that are descendants of `72506001` (implanteerbare cardioverter-defibrillator), i.e. an `is-a` filter. However, the codes are explicitly enumerated rather than using an intensional `is-a` filter to make the ValueSet easier to understand and implement for consumers." * insert MetaRules @@ -134,7 +134,7 @@ Description: "ICD product code for MedicalDevice ProductType. This ValueSet is c * $snomed#1236894001 "subcutane implanteerbare cardioverter-defibrillator" ValueSet: ACPContactPersonRoleVS -Id: ACP-ContactPersonRole +Id: ACP-ContactPersonRoleVS Title: "ACP ContactPerson Role zib2024 backport" Description: "ValueSet containing additional codes to the ContactPerson's [RolCodelijst](http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.3.1.2--20200901000000). These codes are applied from the zib2024 release onwards. Currently, the ValueSet contains only SNOMED CT code `310141000146103` (Schriftelijk gemachtigde zorg en behandeling / Holder of medical power of attorney) from the zib2024 release. The ValueSet is bound to a slice in `RelatedPerson.relationship." * insert MetaRules @@ -144,7 +144,7 @@ Description: "ValueSet containing additional codes to the ContactPerson's [RolCo ValueSet: ACPYesNoUnknownVS Id: ACP-YesNoUnknownVS -Title: "ACP Yes, No, Unknown valueSet" +Title: "ACP Yes, No, Unknown Codes" Description: "ValueSet representing 'Yes, No, Unknown' answers." * insert MetaRules * ^copyright = "This artefact includes content from SNOMED Clinical Terms® (SNOMED CT®) which is copyright of the International Health Terminology Standards Development Organisation (IHTSDO). Implementers of these artefacts must have the appropriate SNOMED CT Affiliate license - for more information contact http://www.snomed.org/snomed-ct/getsnomed-ct or info@snomed.org." @@ -217,4 +217,37 @@ Description: "ValueSet representing 'Yes, No, Unknown' answers." * $snomed#373067005 ^designation[=].value = "ontkenning" * $snomed#373067005 ^designation[+].language = #nl-NL * $snomed#373067005 ^designation[=].use = $snomed#900000000000013009 "Synonym" -* $snomed#373067005 ^designation[=].value = "neen" \ No newline at end of file +* $snomed#373067005 ^designation[=].value = "neen" + + +ValueSet: ACPProcedureTypeVS +Id: ACP-ProcedureTypeVS +Title: "ACP ProcedureType" +Description: "ValueSet for ProcedureType, representing allowed codes for the ACP conversation. The DHD Verrichtingenthesaurus code `0000106562` (proactieve zorgplanning in palliatieve fase) is not included in this ValueSet, as this set is not meant to be used for exchange (see [ZIB-1233](https://nictiz.atlassian.net/browse/ZIB-1233)). The included SNOMED code is part of the referentieset of the DHD Verrichtingenthesaurus." +* insert MetaRules +* ^copyright = "This artefact includes content from SNOMED Clinical Terms® (SNOMED CT®) which is copyright of the International Health Terminology Standards Development Organisation (IHTSDO). Implementers of these artefacts must have the appropriate SNOMED CT Affiliate license - for more information contact http://www.snomed.org/snomed-ct/getsnomed-ct or info@snomed.org." +* $snomed#713603004 "advance care planning" +* $snomed#713603004 ^designation[0].language = #en-US +* $snomed#713603004 ^designation[=].use.system = "http://snomed.info/sct" +* $snomed#713603004 ^designation[=].use = $snomed#900000000000013009 "Synonym" +* $snomed#713603004 ^designation[=].use.display = "Synonym" +* $snomed#713603004 ^designation[=].value = "Advance care planning (procedure)" +* $snomed#713603004 ^designation[+].language = #en-US +* $snomed#713603004 ^designation[=].use.system = "http://snomed.info/sct" +* $snomed#713603004 ^designation[=].use = $snomed#900000000000013009 "Synonym" +* $snomed#713603004 ^designation[=].use.display = "Synonym" +* $snomed#713603004 ^designation[=].value = "Advance care planning" +* $snomed#713603004 ^designation[+].language = #nl-NL +* $snomed#713603004 ^designation[=].use = $snomed#900000000000013009 "Synonym" +* $snomed#713603004 ^designation[=].use.display = "Synonym" +* $snomed#713603004 ^designation[=].value = "advance care planning (verrichting)" +* $snomed#713603004 ^designation[+].language = #nl-NL +* $snomed#713603004 ^designation[=].use = $snomed#900000000000013009 "Synonym" +* $snomed#713603004 ^designation[=].use.display = "Synonym" +* $snomed#713603004 ^designation[=].value = "bespreken van wensen en behoeften voor toekomstige zorg" +* $snomed#713603004 ^designation[+].language = #nl-NL +* $snomed#713603004 ^designation[=].use = $snomed#900000000000013009 "Synonym" +* $snomed#713603004 ^designation[=].use.display = "Synonym" +* $snomed#713603004 ^designation[=].value = "advance care planning" +* $DHD-CBV#411600B "PROACTIEVE ZORGPLANNING-OPSTEL. INDIV.ZORGPL.PALLIAT.FASE" +* $NZa#190099 "Proactieve zorgplanning" diff --git a/input/ignoreWarnings.txt b/input/ignoreWarnings.txt index 76431926..6aae3bc8 100644 --- a/input/ignoreWarnings.txt +++ b/input/ignoreWarnings.txt @@ -18,8 +18,11 @@ # Manual check in latest version of SNOMED does find the code '1351964001'. The error might be due to a older version of SNOMED available in tx.fhir.org/r4 %Unknown code '1351964001' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/11000146104/version/20240930'% -%Geen van de gevonden codings bestaan in waardelijst 'ACP Primary Agreed-upon Goal of Medical Policy' (https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-MedicalPolicyGoal|1.0.0-rc2) en een coding uit deze waardelijst is verplicht (codes = http://snomed.info/sct#1351964001)% -%None of the codings provided are in the value set 'ACP Primary Agreed-upon Goal of Medical Policy' (https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-MedicalPolicyGoal|1.0.0-rc2), and a coding from this value set is required) (codes = http://snomed.info/sct#1351964001)% +%Geen van de gevonden codings bestaan in waardelijst 'ACP Primary Agreed-upon Goal of Medical Policy Codes' (https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-MedicalPolicyGoalVS|1.0.0-rc3) en een coding uit deze waardelijst is verplicht (codes = http://snomed.info/sct#1351964001)% +%None of the codings provided are in the value set 'ACP Primary Agreed-upon Goal of Medical Policy Codes' (https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-MedicalPolicyGoalVS|1.0.0-rc3), and a coding from this value set is required) (codes = http://snomed.info/sct#1351964001)% + +# Manual check in latest version of SNOMED does find the code '665671000146101'. The error might be due to a older version of SNOMED available in tx.fhir.org/r4 +%Unknown code '665671000146101' in the CodeSystem 'http://snomed.info/sct' version 'http://snomed.info/sct/11000146104/version/20240930' (Netherlands Edition)% # This is a proposed fixed value in the Nictiz Consent profiles as there is no better alternative. %URL-waarde 'https://wetten.overheid.nl/' komt nergens uit% diff --git a/input/includes/fhir-data-exchange-individual-resources-mermaid-diagram.md b/input/includes/fhir-data-exchange-individual-resources-mermaid-diagram.md index e8dc3272..f419603d 100644 --- a/input/includes/fhir-data-exchange-individual-resources-mermaid-diagram.md +++ b/input/includes/fhir-data-exchange-individual-resources-mermaid-diagram.md @@ -11,7 +11,7 @@ sequenceDiagram par %% 1. Procedures - C->>S: GET /Procedure?patient=Patient/[id]
&code=sct|713603004
&_include=Procedure:encounter + C->>S: GET /Procedure?patient=Patient/[id]
&code=http://snomed.info/sct|713603004,
urn:oid:2.16.840.1.113883.2.4.3.120.5.3|411600B,
urn:oid:2.16.840.1.113883.2.4.3.27.15.5|190099
&_include=Procedure:encounter activate S S-->>C: 200 OK: Bundle (Procedure + Encounter) deactivate S @@ -35,19 +35,19 @@ sequenceDiagram deactivate S and %% 5. Observations - C->>S: GET /Observation?patient=Patient/[id]
&code=http://snomed.info/sct|153851000146100,395091006,340171000146104,247751003 + C->>S: GET /Observation?patient=Patient/[id]
&code=http://snomed.info/sct|665671000146101,http://snomed.info/sct|153851000146100,http://snomed.info/sct|395091006,http://snomed.info/sct|340171000146104,http://snomed.info/sct|247751003 activate S S-->>C: 200 OK: Bundle (Observation) deactivate S and %% 6. Devices - C->>S: GET /DeviceUseStatement?patient=Patient/[id]
&device.type=http://snomed.info/sct|72506001,465460004,468542000,704707009,1263462004,1236894001
&_include=DeviceUseStatement:device + C->>S: GET /DeviceUseStatement?patient=Patient/[id]
&device.type=http://snomed.info/sct|72506001,http://snomed.info/sct|465460004,http://snomed.info/sct|468542000,http://snomed.info/sct|704707009,http://snomed.info/sct|1263462004,http://snomed.info/sct|1236894001
&_include=DeviceUseStatement:device activate S S-->>C: 200 OK: Bundle (DeviceUseStatement + Device) deactivate S and %% 7. CommunicationRequests - C->>S: GET /CommunicationRequest?patient=Patient/[id]
&category=http://snomed.info/sct|713603004 + C->>S: GET /CommunicationRequest?patient=Patient/[id]
&category=http://snomed.info/sct|223449006 activate S S-->>C: 200 OK: Bundle (CommunicationRequest) deactivate S diff --git a/input/includes/fhir-data-model-mermaid-diagram.md b/input/includes/fhir-data-model-mermaid-diagram.md index d7072c78..1c3478ad 100644 --- a/input/includes/fhir-data-model-mermaid-diagram.md +++ b/input/includes/fhir-data-model-mermaid-diagram.md @@ -56,6 +56,7 @@ flowchart TB end subgraph "Observation" + ACPLegallyCapable ACPOrganDonationChoiceRegistration ACPPositionRegardingEuthanasia ACPPreferredPlaceOfDeath @@ -76,6 +77,7 @@ flowchart TB class ACPPositionRegardingEuthanasia C0 class ACPOrganDonationChoiceRegistration C0 class ACPSenseOfPurpose C0 + class ACPLegallyCapable C0 class ACPPatient C1 class ACPHealthProfessionalPractitioner C1 class ACPHealthProfessionalPractitionerRole C1 diff --git a/input/includes/mappings.md b/input/includes/mappings.md index 694738c2..cf10e57a 100644 --- a/input/includes/mappings.md +++ b/input/includes/mappings.md @@ -62,9 +62,10 @@ This table provides an overview of all dataset elements that are mapped to FHIR | 399 |    Voorvoegsels | Practitioner (ACPHealthProfessionalPractitioner) | `name[nameInformation].family.extension[prefix]` | | 400 |    Achternaam | Practitioner (ACPHealthProfessionalPractitioner) | `name[nameInformation].family.extension[lastName]` | | 405 |  Functie (Specialisme) | PractitionerRole (ACPHealthProfessionalPractitionerRole) | `specialty[specialty]` | -| 761 | Wilsbekwaamheid m.b.t. medische behandelbeslissingen | Patient (ACPPatient) | `extension[legallyCapableMedicalTreatmentDecisions]` | -| 762 |  Wilsbekwaamheid m.b.t. medische behandelbeslissingen | Patient (ACPPatient) | `extension[legallyCapableMedicalTreatmentDecisions].extension[legallyCapable]` | -| 763 |  Toelichting | Patient (ACPPatient) | `extension[legallyCapableMedicalTreatmentDecisions].extension[legallyCapableComment]` | +| 761 | Wilsbekwaamheid m.b.t. medische behandelbeslissingen | Observation (ACPLegallyCapable) | `` | +| 762 |  Wilsbekwaamheid m.b.t. medische behandelbeslissingen | Observation (ACPLegallyCapable) | `valueBoolean` | +| 762 |  Wilsbekwaamheid m.b.t. medische behandelbeslissingen | Observation (ACPLegallyCapable) | `dataAbsentReason` | +| 763 |  Toelichting | Observation (ACPLegallyCapable) | `note.text` | | 441 | Wettelijk vertegenwoordiger (Contactpersoon) | RelatedPerson (ACPContactPerson) | `` | | 442 |  Naamgegevens | RelatedPerson (ACPContactPerson) | `name` | | 443 |   Voornamen | RelatedPerson (ACPContactPerson) | `name[nameInformation].given` | diff --git a/input/pagecontent/StructureDefinition-ACP-ContactPerson-intro.md b/input/pagecontent/StructureDefinition-ACP-ContactPerson-intro.md index 7ed1d5df..1d55d427 100644 --- a/input/pagecontent/StructureDefinition-ACP-ContactPerson-intro.md +++ b/input/pagecontent/StructureDefinition-ACP-ContactPerson-intro.md @@ -2,4 +2,4 @@ This profile adds ACP-specific mappings to the ART-DECOR dataset and obligation extensions for Provider and Consulter actors. Profile references are constrained to ACP profiles where available. The following change affects implementation beyond the base nl-core profile: -* `.relationship:role` is mandatory. \ No newline at end of file +* An additional slice `.relationship:roleAdditional` has been added, bound to [ACPContactPersonRoleVS](ValueSet-ACP-ContactPersonRoleVS.html). This slice accommodates role codes required by the ACP dataset that are not included in the `RolCodelijst` bound to `.relationship:role` (e.g. SNOMED CT code `310141000146103` _Schriftelijk gemachtigde zorg en behandeling_, pre-adopted from the [RolCodelijst in zib Contactpersoon v4.1 (2024)](https://zibs.nl/wiki/Contactpersoon-v4.1(2024NL)#RolCodelijst)). \ No newline at end of file diff --git a/input/pagecontent/changelog.md b/input/pagecontent/changelog.md index db4eaf95..1c386e9c 100644 --- a/input/pagecontent/changelog.md +++ b/input/pagecontent/changelog.md @@ -1,3 +1,22 @@ +### 1.0.0-rc3 + +| Issue | Short Description | +|-------|-------------------| +| [#137](https://github.com/IKNL/PZP-FHIR-R4/issues/137) | Replaced legally capable extension on Patient with an Observation to better support representation of legal capacity. | +| [#132](https://github.com/IKNL/PZP-FHIR-R4/issues/132) | Updated `Procedure.code` to support multiple ACP procedure codes and ensure compliance with zib/nl-core terminology requirements. | +| [#157](https://github.com/IKNL/PZP-FHIR-R4/issues/157) | Updated CommunicationRequest cardinalities by requiring `subject`, `requester` and `sender`, and adding an invariant to ensure either `authoredOn` or a reference to Encounter. | +| [#151](https://github.com/IKNL/PZP-FHIR-R4/issues/151) | Corrected search URLs by fixing code parameter syntax to ensure proper OR behavior. | +| [#136](https://github.com/IKNL/PZP-FHIR-R4/issues/136) | Improved search query documentation by clarifying how to retrieve specific data items such as ContactPerson (e.g. legal representative). | +| [#135](https://github.com/IKNL/PZP-FHIR-R4/issues/135) | Updated questionnaire answer options to use `answerValueSet` references where possible and improve alignment with terminology, resolving validation issues. | +| [#138](https://github.com/IKNL/PZP-FHIR-R4/issues/138) | Updated a couple questionnaire items from `choice` to `boolean` to resolve validation issues and align with the dataset. | +| [#126](https://github.com/IKNL/PZP-FHIR-R4/issues/126) | Updated questionnaire to align with latest dataset changes, including support for 'other' treatment directives and addition of missing 'MeetMethode' elements. | +| [#131](https://github.com/IKNL/PZP-FHIR-R4/issues/131) | Replaced fixed questionnaire answer options for 'Functie (specialisme)' with AGB and UZI value sets. | +| [#138](https://github.com/IKNL/PZP-FHIR-R4/issues/138) | Updated yes/no questions in the questionnaire to use `boolean` instead of `choice`. | +| [#133](https://github.com/IKNL/PZP-FHIR-R4/issues/133) | Aligned API documentation by correcting CommunicationRequest category code in sequence diagram and updating CapabilityStatement to use CommunicationRequest. | +| [#129](https://github.com/IKNL/PZP-FHIR-R4/issues/129) | Clarified ContactPerson artifact documentation for `.relationship:roleAdditional` slice. | +| [#127](https://github.com/IKNL/PZP-FHIR-R4/issues/127) | Added missing `subject` references to QuestionnaireResponse examples and improved consistency of author/source formatting. | +| [#156](https://github.com/IKNL/PZP-FHIR-R4/issues/156) | Applied QA fixes including ValueSet renaming to avoid duplicate titles. | + ### 1.0.0-rc2 | Issue | Short Description | diff --git a/input/pagecontent/data-exchange.md b/input/pagecontent/data-exchange.md index 708d2b1b..70ed3dd6 100644 --- a/input/pagecontent/data-exchange.md +++ b/input/pagecontent/data-exchange.md @@ -36,9 +36,9 @@ This approach is useful for applications that need to query specific parts of a The below listed search requests show how all the ACP agreements, procedural information and relevant clinical context can be retrieved. Information on individuals involved in the ACP process are referenced from these resources and can be retrieved using the `_include` statement as defined below, or by resolving the references. Standard FHIR rules apply on the search syntax. The Provider CapabilityStatement and Consulter CapabilityStatement resources may provide a more structured overview of the below requirements. ``` -1a GET [base]/Procedure?patient=Patient/[id]&code=http://snomed.info/sct|713603004&_include=Procedure:encounter +1a GET [base]/Procedure?patient=Patient/[id]&code=http://snomed.info/sct|713603004,urn:oid:2.16.840.1.113883.2.4.3.120.5.3|411600B,urn:oid:2.16.840.1.113883.2.4.3.27.15.5|190099&_include=Procedure:encounter -1b GET [base]/Encounter?patient=Patient/[id]&reason-reference:Procedure.code=http://snomed.info/sct|713603004&_include=Encounter:reason-reference +1b GET [base]/Encounter?patient=Patient/[id]&reason-reference:Procedure.code=http://snomed.info/sct|713603004,urn:oid:2.16.840.1.113883.2.4.3.120.5.3|411600B,urn:oid:2.16.840.1.113883.2.4.3.27.15.5|190099&_include=Encounter:reason-reference 2 GET [base]/Consent?patient=Patient/[id]&scope=http://terminology.hl7.org/CodeSystem/consentscope|treatment&category=http://snomed.info/sct|129125009&_include=Consent:actor @@ -46,9 +46,9 @@ The below listed search requests show how all the ACP agreements, procedural inf 4 GET [base]/Goal?patient=Patient/[id]&category=http://snomed.info/sct|713603004 -5 GET [base]/Observation?patient=Patient/[id]&code=http://snomed.info/sct|153851000146100,395091006,340171000146104,247751003,570801000146104 +5 GET [base]/Observation?patient=Patient/[id]&code=http://snomed.info/sct|665671000146101,http://snomed.info/sct|153851000146100,http://snomed.info/sct|395091006,http://snomed.info/sct|340171000146104,http://snomed.info/sct|247751003,http://snomed.info/sct|570801000146104 -6 GET [base]/DeviceUseStatement?patient=Patient/[id]&device.type=http://snomed.info/sct|72506001,465460004,468542000,704707009,1263462004,1236894001&_include=DeviceUseStatement:device +6 GET [base]/DeviceUseStatement?patient=Patient/[id]&device.type=http://snomed.info/sct|72506001,http://snomed.info/sct|465460004,http://snomed.info/sct|468542000,http://snomed.info/sct|704707009,http://snomed.info/sct|1263462004,http://snomed.info/sct|1236894001&_include=DeviceUseStatement:device 7 GET [base]/CommunicationRequest?patient=[id]&category=http://snomed.info/sct|223449006 ``` @@ -59,10 +59,12 @@ The below listed search requests show how all the ACP agreements, procedural inf 2. Retrieves `Consent` resources for Treatment Directives and includes the agreement parties (Patient, ContactPersons, and HealthProfessionals). 3. Retrieves `Consent` resources for Advance Directives and includes the representatives (ContactPersons). 4. Retrieves `Goal` resources related to advance care planning. -5. Retrieves `Observation` resources related to specific wishes and plans, as defined by the profiles in the Implementation Guide. +5. Retrieves `Observation` resources related to specific wishes, plans and whether the patient is legally capable, as defined by the profiles in the Implementation Guide. 6. Retrieves `DeviceUseStatement` resources for devices representing an ICD, and includes the corresponding `Device` resource. 7. Retrieves `CommunicationRequest` resources representing all communication requests related to the ACP procedure. +For `RelatedPerson` and `Practitioner` there is no specific query as according to the model there are references made to these resources. If there is a legal representative we expect that to be present in `Patient.contact`. For related persons attending the encounter a reference is expected to be made in `Encounter.participant`. + #### Advanced Search Parameters Supported The queries above use several search parameter types and modifiers: * `_include`: Returns referenced resources in the same `Bundle`, reducing the need for additional API calls. diff --git a/input/pagecontent/downloads.md b/input/pagecontent/downloads.md index 5e173924..284ae2d7 100644 --- a/input/pagecontent/downloads.md +++ b/input/pagecontent/downloads.md @@ -1,23 +1,23 @@ ### Full IG -Download the entire implementation guide [here](full-ig.zip). +Download the entire implementation guide [here](../full-ig.zip). ### NPM Package and Definitions The following file contains all the value sets, profiles, extensions, list of pages and urls in the IG, etc. defined as part of this Implementation Guide: -- [NPM Package](package.tgz) +- [NPM Package](../package.tgz) In addition there are format specific definition files: -- [XML](definitions.xml.zip) -- [JSON](definitions.json.zip) -- [TTL](definitions.ttl.zip) +- [XML](../definitions.xml.zip) +- [JSON](../definitions.json.zip) +- [TTL](../definitions.ttl.zip) ### Examples All of the examples that are used in this Implementation Guide are available for download: -- [XML](examples.xml.zip) -- [JSON](examples.json.zip) -- [TTL](examples.ttl.zip) \ No newline at end of file +- [XML](../examples.xml.zip) +- [JSON](../examples.json.zip) +- [TTL](../examples.ttl.zip) \ No newline at end of file diff --git a/input/resources/Questionnaire-ACP-zib2020.json b/input/resources/Questionnaire-ACP-zib2020.json index 17be0c76..6f8bc7fe 100644 --- a/input/resources/Questionnaire-ACP-zib2020.json +++ b/input/resources/Questionnaire-ACP-zib2020.json @@ -2,15 +2,15 @@ "resourceType": "Questionnaire", "id": "ACP-zib2020", "url": "https://api.iknl.nl/docs/pzp/r4/Questionnaire/ACP-zib2020", - "version": "1.0.0-rc2", + "version": "1.0.0-rc3", "name": "ACPzib2020", - "title": "Uniform vastleggen proactieve zorgpanning (PZP) o.b.v. zibs2020 - ReleaseCandidate2 03-03-2026", + "title": "Uniform vastleggen proactieve zorgpanning (PZP) o.b.v. zibs2020 - ReleaseCandidate3 02-06-2026", "status": "draft", "experimental": false, "publisher": "Published by PZNL & executed by IKNL", - "copyright": "This form is subject to copyright, user rights and a disclaimer, as specified for all IKNL information standards. For details, see the paragraph on Gebruikersrechten en disclaimer at https://iknl.nl/onderzoek/eenheid-van-taal.", + "description": "This form was developed to clearly document agreements resulting from the advance care planning (ACP) process. It is NOT a checklist. It can only be completed by a healthcare provider after a professional and nuanced conversation. For advice on conducting these conversations, please refer to the guideline for proactive care planning in the palliative phase and Palliaweb, see https://palliaweb.nl/zorgpraktijk/proactieve-zorgplanning. \nEnter 'unknown' if a topic is not discussed or if the patient does not (yet) have an opinion.When transferring to a long-term care setting, consider adding conversation records about advance care planning (ACP) to the transfer documents.", "purpose": "This form was developed to clearly document agreements resulting from the advance care planning (ACP) process.", - "description": "This form was developed to clearly document agreements resulting from the advance care planning (ACP) process. It is NOT a checklist. It can only be completed by a healthcare provider after a professional and nuanced conversation. For advice on conducting these conversations, please refer to the guideline for advance care planning in the palliative phase and Palliaweb, see https://palliaweb.nl/zorgpraktijk/proactieve-zorgplanning. \nEnter 'unknown' if a topic is not discussed or if the patient does not (yet) have an opinion.When transferring to a long-term care setting, consider adding conversation records about advance care planning (ACP) to the transfer documents.", + "copyright": "This form is subject to copyright, user rights and a disclaimer, as specified for all IKNL information standards. For details, see the paragraph on Gebruikersrechten en disclaimer at https://iknl.nl/onderzoek/eenheid-van-taal.", "item": [ { "linkId": "963", @@ -96,50 +96,7 @@ "type": "choice", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", - "code": "01.000", - "display": "Arts" - } - }, - { - "valueCoding": { - "system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", - "code": "01.015", - "display": "Huisarts" - } - }, - { - "valueCoding": { - "system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", - "code": "01.047", - "display": "Specialist ouderengeneeskunde" - } - }, - { - "valueCoding": { - "system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", - "code": "30.000", - "display": "Verpleegkundige" - } - }, - { - "valueCoding": { - "system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", - "code": "81.000", - "display": "Physician assistant" - } - }, - { - "valueCoding": { - "system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", - "code": "99.000", - "display": "Zorgverlener andere zorg" - } - } - ] + "answerValueSet": "http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.121.11.22--20200901000000" } ] }, @@ -151,25 +108,11 @@ "repeats": false, "item": [ { - "linkId": "1406", + "linkId": "1651", "text": "Is de patiënt op dit moment wilsbekwaam m.b.t. medische behandelbeslissingen?", - "type": "choice", + "type": "boolean", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "code": "1", - "display": "Ja" - } - }, - { - "valueCoding": { - "code": "0", - "display": "Nee" - } - } - ], "item": [ { "linkId": "1407", @@ -531,23 +474,9 @@ "linkId": "984", "prefix": "d)", "text": "Is de wettelijk vertegenwoordiger ook de eerste contactpersoon?", - "type": "choice", + "type": "boolean", "required": false, - "repeats": false, - "answerOption": [ - { - "valueCoding": { - "code": "1", - "display": "Ja" - } - }, - { - "valueCoding": { - "code": "0", - "display": "Nee" - } - } - ] + "repeats": false }, { "linkId": "53", @@ -558,10 +487,7 @@ { "question": "984", "operator": "=", - "answerCoding": { - "code": "0", - "display": "Nee" - } + "answerBoolean": false } ], "enableBehavior": "any", @@ -599,10 +525,7 @@ { "question": "984", "operator": "=", - "answerCoding": { - "code": "0", - "display": "Nee" - } + "answerBoolean": false } ], "enableBehavior": "any", @@ -645,15 +568,15 @@ { "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-AddressUse", - "code": "MC", - "display": "Mobiel telefoonnummer" + "code": "PG", + "display": "Pieper" } }, { "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-AddressUse", - "code": "PG", - "display": "Pieper" + "code": "MC", + "display": "Mobiel telefoonnummer" } } ] @@ -745,10 +668,7 @@ { "question": "984", "operator": "=", - "answerCoding": { - "code": "0", - "display": "Nee" - } + "answerBoolean": false } ], "enableBehavior": "any", @@ -775,10 +695,7 @@ { "question": "984", "operator": "=", - "answerCoding": { - "code": "0", - "display": "Nee" - } + "answerBoolean": false } ], "enableBehavior": "any", @@ -905,148 +822,168 @@ ] }, { - "linkId": "997", - "text": "Rol", - "type": "choice", - "required": false, - "repeats": true, - "answerOption": [ - { - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", - "code": "03", - "display": "Curator (juridisch)" - } - }, - { - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", - "code": "15", - "display": "Mentor" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "310141000146103", - "display": "Schriftelijk gemachtigde" - } - }, - { - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", - "code": "09", - "display": "Anders" - } - } - ], + "linkId": "1652", + "text": "Contactperso(o)n(en)", + "type": "group", + "repeats": false, "item": [ { - "linkId": "414", - "text": "Anders, namelijk:", - "type": "string", + "linkId": "997", + "text": "Rol", + "type": "choice", "required": false, - "repeats": false - } - ] - }, - { - "linkId": "998", - "text": "Relatie tot patiënt", - "type": "choice", - "required": false, - "repeats": true, - "answerOption": [ - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "DOMPART", - "display": "Partner" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "HUSB", - "display": "Echtgenoot" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "WIFE", - "display": "Echtgenote" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "FTH", - "display": "Vader" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "MTH", - "display": "Moeder" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "SONC", - "display": "Zoon" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "DAUC", - "display": "Dochter" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "BRO", - "display": "Broer" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "SIS", - "display": "Zuster" - } + "repeats": true, + "answerOption": [ + { + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", + "code": "03", + "display": "Curator (juridisch)" + } + }, + { + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", + "code": "15", + "display": "Mentor" + } + }, + { + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "310141000146103", + "display": "Schriftelijk gemachtigde" + } + }, + { + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", + "code": "09", + "display": "Anders" + } + } + ], + "item": [ + { + "linkId": "414", + "text": "Anders, namelijk:", + "type": "string", + "enableWhen": [ + { + "question": "997", + "operator": "=", + "answerCoding": { + "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", + "code": "09", + "display": "Anders" + } + } + ], + "enableBehavior": "any", + "required": false, + "repeats": false + } + ] }, { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "OTH", - "display": "Anders" - } - } - ], - "item": [ - { - "linkId": "413", - "text": "Anders, namelijk:", - "type": "string", - "enableWhen": [ + "linkId": "998", + "text": "Relatie tot patiënt", + "type": "choice", + "required": false, + "repeats": true, + "answerOption": [ + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "DOMPART", + "display": "Partner" + } + }, + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "HUSB", + "display": "Echtgenoot" + } + }, + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "WIFE", + "display": "Echtgenote" + } + }, + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "FTH", + "display": "Vader" + } + }, + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "MTH", + "display": "Moeder" + } + }, + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "SONC", + "display": "Zoon" + } + }, + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "DAUC", + "display": "Dochter" + } + }, + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "BRO", + "display": "Broer" + } + }, + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "SIS", + "display": "Zuster" + } + }, { - "question": "998", - "operator": "=", - "answerCoding": { + "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", "code": "OTH", "display": "Anders" } } ], - "enableBehavior": "any", - "required": false, - "repeats": false + "item": [ + { + "linkId": "413", + "text": "Anders, namelijk:", + "type": "string", + "enableWhen": [ + { + "question": "998", + "operator": "=", + "answerCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", + "code": "OTH", + "display": "Anders" + } + } + ], + "enableBehavior": "any", + "required": false, + "repeats": false + } + ] } ] } @@ -1083,36 +1020,7 @@ "type": "choice", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "385987000", - "display": "Curatief / actief ziektebeleid" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "1351964001", - "display": "Palliatief met als doel levensverlenging én symptoomverlichting" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "225353007", - "display": "Palliatief met als doel symptoomverlichting, waarbij levensverlenging niet gewenst is" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "UNK", - "display": "Nog onbekend" - } - } - ] + "answerValueSet": "https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-MedicalPolicyGoal" } ] }, @@ -1694,10 +1602,19 @@ "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", "code": "OTH", - "display": "Other" + "display": "Anders" }, "initialSelected": true } + ], + "item": [ + { + "linkId": "1645", + "text": "Anders, namelijk:", + "type": "string", + "required": false, + "repeats": false + } ] }, { @@ -1748,29 +1665,7 @@ "type": "choice", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "373066001", - "display": "Ja" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "373067005", - "display": "Nee" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "UNK", - "display": "Nog onbekend" - } - } - ], + "answerValueSet": "https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-YesNoUnknownVS", "item": [ { "linkId": "1008", @@ -1783,57 +1678,14 @@ "answerCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], "enableBehavior": "any", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "72506001", - "display": "implanteerbare cardioverter-defibrillator" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "465460004", - "display": "univentriculaire implanteerbare cardioverter-defibrillator" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "468542000", - "display": "implanteerbare tweekamercardioverter-defibrillator" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "704707009", - "display": "implanteerbare biventriculaire cardioverter-defibrillator" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "1263462004", - "display": "pulsgenerator van defibrillator voor cardiale resynchronisatietherapie" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "1236894001", - "display": "subcutane implanteerbare cardioverter-defibrillator" - } - } - ] + "answerValueSet": "https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-MedicalDeviceProductType-ICD" }, { "linkId": "1009", @@ -1846,7 +1698,7 @@ "answerCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], @@ -1868,7 +1720,7 @@ "answerCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], @@ -1904,7 +1756,7 @@ "answerCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], @@ -1986,6 +1838,24 @@ "type": "string", "required": false, "repeats": false + }, + { + "linkId": "1648", + "text": "Vaststellen wens en verwachting patiënt ([MeetMethode])", + "type": "choice", + "required": false, + "repeats": false, + "readOnly": true, + "answerOption": [ + { + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "370819000", + "display": "Vaststellen van persoonlijke waarden en wensen met betrekking tot zorg" + }, + "initialSelected": true + } + ] } ] }, @@ -1997,7 +1867,7 @@ "repeats": false, "item": [ { - "linkId": "1430", + "linkId": "1649", "text": "Gewenste plek van overlijden ([MetingNaam])", "type": "choice", "required": false, @@ -2008,7 +1878,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "395091006", - "display": "Gewenste plek van overlijden" + "display": "Voorkeur voor plaats van overlijden (waarneembare entiteit)" }, "initialSelected": true } @@ -2020,50 +1890,7 @@ "type": "choice", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "264362003", - "display": "Thuis" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "22232009", - "display": "Ziekenhuis" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "108344006", - "display": "Verpleeghuis" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "284546000", - "display": "Hospice" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "OTH", - "display": "Anders" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "UNK", - "display": "Nog onbekend" - } - } - ], + "answerValueSet": "https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-PreferredPlaceOfDeath", "item": [ { "linkId": "1192", @@ -2107,36 +1934,7 @@ "type": "choice", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "340181000146102", - "display": "Heeft euthanasieverklaring" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "340201000146103", - "display": "Wenst geen euthanasie" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "340191000146100", - "display": "Geen euthanasieverklaring, zou wel verzoek kunnen doen in bepaalde situaties" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "UNK", - "display": "Nog onbekend" - } - } - ], + "answerValueSet": "https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-PositionRegardingEuthanasia", "item": [ { "linkId": "1194", @@ -2180,27 +1978,23 @@ "type": "choice", "required": false, "repeats": false, + "answerValueSet": "https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-YesNoUnknownVS" + }, + { + "linkId": "1650", + "text": "Keuze orgaandonatie vastgelegd in donorregister? ([MeetMethode])", + "type": "choice", + "required": false, + "repeats": false, + "readOnly": true, "answerOption": [ { "valueCoding": { "system": "http://snomed.info/sct", - "code": "373066001", - "display": "Ja" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "373067005", - "display": "Nee" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "UNK", - "display": "Nog onbekend" - } + "code": "1156040003", + "display": "Self reported (qualifier value)" + }, + "initialSelected": true } ] } @@ -2256,29 +2050,7 @@ "type": "choice", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "373066001", - "display": "Ja" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "373067005", - "display": "Nee" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "UNK", - "display": "Nog onbekend" - } - } - ], + "answerValueSet": "https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-YesNoUnknownVS", "item": [ { "linkId": "1198", @@ -2301,36 +2073,14 @@ "answerCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], "enableBehavior": "any", "required": false, "repeats": false, - "answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "373066001", - "display": "Ja" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "373067005", - "display": "Nee" - } - }, - { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", - "code": "UNK", - "display": "Nog onbekend" - } - } - ] + "answerValueSet": "https://api.iknl.nl/docs/pzp/r4/ValueSet/ACP-YesNoUnknownVS" } ] }, @@ -2345,23 +2095,9 @@ "linkId": "1200", "prefix": "a)", "text": "Heeft u patient geïnformeerd over eigen verantwoordelijkheid om deze behandelafspraken met naasten te bespreken?", - "type": "choice", + "type": "boolean", "required": false, - "repeats": false, - "answerOption": [ - { - "valueCoding": { - "code": "1", - "display": "Ja" - } - }, - { - "valueCoding": { - "code": "0", - "display": "Nee" - } - } - ] + "repeats": false }, { "linkId": "1201", @@ -2390,4 +2126,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/input/resources/QuestionnaireResponse-HendrikHartman-20201001.json b/input/resources/QuestionnaireResponse-HendrikHartman-20201001.json index bf7847ca..c623588a 100644 --- a/input/resources/QuestionnaireResponse-HendrikHartman-20201001.json +++ b/input/resources/QuestionnaireResponse-HendrikHartman-20201001.json @@ -4,13 +4,17 @@ "meta": { "tag": [ { - "code": "lformsVersion: 38.2.0" + "code": "lformsVersion: 42.2.0" } ] }, "status": "completed", "authored": "2025-08-25T19:14:50.150Z", - "questionnaire": "https://api.iknl.nl/docs/pzp/r4/Questionnaire/ACP-zib2020|1.0.0-rc2", + "questionnaire": "https://api.iknl.nl/docs/pzp/r4/Questionnaire/ACP-zib2020|1.0.0-rc3", + "subject": { + "reference": "Patient/ACP-Patient-HendrikHartman-Pat1", + "display": "Patient, Hendrik Hartman" + }, "author": { "reference": "PractitionerRole/ACP-HealthProfessional-PractitionerRole-DrVanHuissen-Pat1", @@ -114,13 +118,10 @@ { "answer": [ { - "valueCoding": { - "code": "1", - "display": "Ja" - } + "valueBoolean": true } ], - "linkId": "1406", + "linkId": "1651", "text": "Is de patiënt op dit moment wilsbekwaam m.b.t. medische behandelbeslissingen?" }, { @@ -185,6 +186,19 @@ } ] }, + { + "linkId": "982", + "text": "Relatie tot patiënt (1)", + "answer": [ + { + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", + "code": "24", + "display": "Wettelijke vertegenwoordiger" + } + } + ] + }, { "answer": [ { @@ -201,10 +215,7 @@ { "answer": [ { - "valueCoding": { - "code": "1", - "display": "Ja" - } + "valueBoolean": true } ], "linkId": "984", @@ -231,15 +242,21 @@ "text": "Patiënt" }, { - "linkId": "998", - "text": "Relatie tot patiënt", - "answer": [ + "linkId": "1652", + "text": "Contactperso(o)n(en)", + "item": [ { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "BRO", - "display": "Broer" - } + "linkId": "998", + "text": "Relatie tot patiënt", + "answer": [ + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "BRO", + "display": "Broer" + } + } + ] } ] } @@ -268,7 +285,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "1351964001", - "display": "Palliatief met als doel levensverlenging én symptoomverlichting" + "display": "levensverlengende behandeling" } } ], @@ -483,7 +500,7 @@ "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", "code": "OTH", - "display": "Other" + "display": "Anders" } } ], @@ -522,7 +539,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" }, "item": [ { @@ -627,6 +644,19 @@ ], "linkId": "1190", "text": "Wens en verwachting patient ([MetingWaarde])" + }, + { + "answer": [ + { + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "370819000", + "display": "Vaststellen van persoonlijke waarden en wensen met betrekking tot zorg" + } + } + ], + "linkId": "1648", + "text": "Vaststellen wens en verwachting patiënt ([MeetMethode])" } ] }, @@ -640,11 +670,11 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "395091006", - "display": "Gewenste plek van overlijden" + "display": "Voorkeur voor plaats van overlijden (waarneembare entiteit)" } } ], - "linkId": "1430", + "linkId": "1649", "text": "Gewenste plek van overlijden ([MetingNaam])" }, { @@ -696,7 +726,7 @@ "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", "code": "UNK", - "display": "Nog onbekend" + "display": "nog onbekend" }, "item": [ { @@ -739,12 +769,25 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], "linkId": "1435", "text": "Keuze orgaandonatie in donorregister ([MetingWaarde])" + }, + { + "answer": [ + { + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "1156040003", + "display": "Self reported (qualifier value)" + } + } + ], + "linkId": "1650", + "text": "Keuze orgaandonatie vastgelegd in donorregister? ([MeetMethode])" } ] } @@ -788,7 +831,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373067005", - "display": "Nee" + "display": "nee" } } ], @@ -804,10 +847,7 @@ { "answer": [ { - "valueCoding": { - "code": "1", - "display": "Ja" - } + "valueBoolean": true } ], "linkId": "1200", diff --git a/input/resources/QuestionnaireResponse-HendrikHartman-20221108.json b/input/resources/QuestionnaireResponse-HendrikHartman-20221108.json index 2c231a6b..c6f9a12d 100644 --- a/input/resources/QuestionnaireResponse-HendrikHartman-20221108.json +++ b/input/resources/QuestionnaireResponse-HendrikHartman-20221108.json @@ -4,13 +4,17 @@ "meta": { "tag": [ { - "code": "lformsVersion: 38.2.0" + "code": "lformsVersion: 42.2.0" } ] }, "status": "completed", "authored": "2025-08-25T19:18:32.253Z", - "questionnaire": "https://api.iknl.nl/docs/pzp/r4/Questionnaire/ACP-zib2020|1.0.0-rc2", + "questionnaire": "https://api.iknl.nl/docs/pzp/r4/Questionnaire/ACP-zib2020|1.0.0-rc3", + "subject": { + "reference": "Patient/ACP-Patient-HendrikHartman-Pat1", + "display": "Patient, Hendrik Hartman" + }, "author": { "reference": "PractitionerRole/ACP-HealthProfessional-PractitionerRole-DrVanHuissen-Pat1", @@ -114,13 +118,10 @@ { "answer": [ { - "valueCoding": { - "code": "1", - "display": "Ja" - } + "valueBoolean": true } ], - "linkId": "1406", + "linkId": "1651", "text": "Is de patiënt op dit moment wilsbekwaam m.b.t. medische behandelbeslissingen?" }, { @@ -185,6 +186,19 @@ } ] }, + { + "linkId": "982", + "text": "Relatie tot patiënt (1)", + "answer": [ + { + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.2.4.3.11.22.472", + "code": "24", + "display": "Wettelijke vertegenwoordiger" + } + } + ] + }, { "answer": [ { @@ -201,10 +215,7 @@ { "answer": [ { - "valueCoding": { - "code": "1", - "display": "Ja" - } + "valueBoolean": true } ], "linkId": "984", @@ -231,15 +242,21 @@ "text": "Patiënt" }, { - "linkId": "998", - "text": "Relatie tot patiënt", - "answer": [ + "linkId": "1652", + "text": "Contactperso(o)n(en)", + "item": [ { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "BRO", - "display": "Broer" - } + "linkId": "998", + "text": "Relatie tot patiënt", + "answer": [ + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "BRO", + "display": "Broer" + } + } + ] } ] } @@ -267,8 +284,8 @@ { "valueCoding": { "system": "http://snomed.info/sct", - "code": "225353007", - "display": "Palliatief met als doel symptoomverlichting, waarbij levensverlenging niet gewenst is" + "code": "713148004", + "display": "voorkomen en behandelen van symptomen" } } ], @@ -516,7 +533,7 @@ "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", "code": "OTH", - "display": "Other" + "display": "Anders" } } ], @@ -544,7 +561,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" }, "item": [ { @@ -638,6 +655,19 @@ ], "linkId": "1190", "text": "Wens en verwachting patient ([MetingWaarde])" + }, + { + "answer": [ + { + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "370819000", + "display": "Vaststellen van persoonlijke waarden en wensen met betrekking tot zorg" + } + } + ], + "linkId": "1648", + "text": "Vaststellen wens en verwachting patiënt ([MeetMethode])" } ] }, @@ -651,11 +681,11 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "395091006", - "display": "Gewenste plek van overlijden" + "display": "Voorkeur voor plaats van overlijden (waarneembare entiteit)" } } ], - "linkId": "1430", + "linkId": "1649", "text": "Gewenste plek van overlijden ([MetingNaam])" }, { @@ -664,7 +694,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "264362003", - "display": "Thuis" + "display": "thuis" }, "item": [ { @@ -707,7 +737,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "340201000146103", - "display": "Wenst geen euthanasie" + "display": "wil geen euthanasie" } } ], @@ -739,12 +769,25 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], "linkId": "1435", "text": "Keuze orgaandonatie in donorregister ([MetingWaarde])" + }, + { + "answer": [ + { + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "1156040003", + "display": "Self reported (qualifier value)" + } + } + ], + "linkId": "1650", + "text": "Keuze orgaandonatie vastgelegd in donorregister? ([MeetMethode])" } ] } @@ -788,7 +831,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" }, "item": [ { @@ -812,7 +855,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], @@ -828,10 +871,7 @@ { "answer": [ { - "valueCoding": { - "code": "1", - "display": "Ja" - } + "valueBoolean": true } ], "linkId": "1200", diff --git a/input/resources/QuestionnaireResponse-SamiraVanDerSluijs-20251117.json b/input/resources/QuestionnaireResponse-SamiraVanDerSluijs-20251117.json index 1526d234..82099cee 100644 --- a/input/resources/QuestionnaireResponse-SamiraVanDerSluijs-20251117.json +++ b/input/resources/QuestionnaireResponse-SamiraVanDerSluijs-20251117.json @@ -10,15 +10,17 @@ }, "status": "completed", "authored": "2025-11-27T16:15:00.733Z", - "questionnaire": "https://api.iknl.nl/docs/pzp/r4/Questionnaire/ACP-zib2020|1.0.0-rc2", - "author": - { - "reference": "PractitionerRole/ACP-HealthProfessional-PractitionerRole-DesireeWolters-Pat2", - "display": "Healthcare professional (role), Desiree Wolters" - } - , + "questionnaire": "https://api.iknl.nl/docs/pzp/r4/Questionnaire/ACP-zib2020|1.0.0-rc3", + "subject": { + "reference": "Patient/ACP-Patient-SamiraVanDerSluijs-Pat2", + "display": "Patient, Samira van der Sluijs" + }, + "author": { + "reference": "PractitionerRole/ACP-HealthProfessional-PractitionerRole-DesireeWolters-Pat2", + "display": "Healthcare professional (role), Desiree Wolters" + }, "source": { - "reference": "Patient/ACP-Patient-SamiraVanDerSluijs-Pat2", + "reference": "Patient/ACP-Patient-SamiraVanDerSluijs-Pat2", "display": "Patient, Samira van der Sluijs" }, "item": [ @@ -123,15 +125,12 @@ { "answer": [ { - "valueCoding": { - "code": "1", - "display": "Ja" - }, + "valueBoolean": true, "item": [ { "answer": [ { - "valueString": "Patiënt is wilsbekwaam. Bij verandering van de situatie wordt haar partner haar wettelijk vertegenwoordiger." + "valueString": "Patiënt is wilsbekwaam. Bij verandering van de situatie wordt haar partner haar wettelijk vertegenwoordiger." } ], "linkId": "1407", @@ -140,7 +139,7 @@ ] } ], - "linkId": "1406", + "linkId": "1651", "text": "Is de patiënt op dit moment wilsbekwaam m.b.t. medische behandelbeslissingen?" }, { @@ -284,10 +283,7 @@ { "answer": [ { - "valueCoding": { - "code": "0", - "display": "Nee" - } + "valueBoolean": false } ], "linkId": "984", @@ -448,15 +444,21 @@ "text": "Patiënt" }, { - "linkId": "998", - "text": "Relatie tot patiënt", - "answer": [ + "linkId": "1652", + "text": "Contactperso(o)n(en)", + "item": [ { - "valueCoding": { - "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", - "code": "SIS", - "display": "Zuster" - } + "linkId": "998", + "text": "Relatie tot patiënt", + "answer": [ + { + "valueCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", + "code": "SIS", + "display": "Zuster" + } + } + ] } ] } @@ -484,8 +486,8 @@ { "valueCoding": { "system": "http://snomed.info/sct", - "code": "225353007", - "display": "Palliatief met als doel symptoomverlichting, waarbij levensverlenging niet gewenst is" + "code": "713148004", + "display": "voorkomen en behandelen van symptomen" } } ], @@ -645,7 +647,7 @@ { "answer": [ { - "valueString": "Alleen als de patiënt een kans heeft om weer uit het ziekenhuis te komen, anders hoeft het niet meer voor mevrouw" + "valueString": "Alleen als de patiënt een kans heeft om weer uit het ziekenhuis te komen, anders hoeft het niet meer voor mevrouw" } ], "linkId": "1394", @@ -733,7 +735,7 @@ "valueCoding": { "system": "http://terminology.hl7.org/CodeSystem/v3-NullFlavor", "code": "OTH", - "display": "Other" + "display": "Anders" } } ], @@ -772,7 +774,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" }, "item": [ { @@ -872,11 +874,24 @@ { "answer": [ { - "valueString": "De kleinzoon van mevrouw van der Sluijs is geboren en mevrouw is dolgelukkig dat ze hem heeft kunnen zien. Ze merkt dat ze fysiek erg achteruit gaat. Mevrouw heeft daar nu vrede mee, in tegenstelling tot eerdere gesprekken." + "valueString": "De kleinzoon van mevrouw van der Sluijs is geboren en mevrouw is dolgelukkig dat ze hem heeft kunnen zien. Ze merkt dat ze fysiek erg achteruit gaat. Mevrouw heeft daar nu vrede mee, in tegenstelling tot eerdere gesprekken." } ], "linkId": "1190", "text": "Wens en verwachting patient ([MetingWaarde])" + }, + { + "answer": [ + { + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "370819000", + "display": "Vaststellen van persoonlijke waarden en wensen met betrekking tot zorg" + } + } + ], + "linkId": "1648", + "text": "Vaststellen wens en verwachting patiënt ([MeetMethode])" } ] }, @@ -890,11 +905,11 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "395091006", - "display": "Gewenste plek van overlijden" + "display": "Voorkeur voor plaats van overlijden (waarneembare entiteit)" } } ], - "linkId": "1430", + "linkId": "1649", "text": "Gewenste plek van overlijden ([MetingNaam])" }, { @@ -903,7 +918,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "264362003", - "display": "Thuis" + "display": "thuis" }, "item": [ { @@ -946,7 +961,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "340201000146103", - "display": "Wenst geen euthanasie" + "display": "wil geen euthanasie" } } ], @@ -978,12 +993,25 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], "linkId": "1435", "text": "Keuze orgaandonatie in donorregister ([MetingWaarde])" + }, + { + "answer": [ + { + "valueCoding": { + "system": "http://snomed.info/sct", + "code": "1156040003", + "display": "Self reported (qualifier value)" + } + } + ], + "linkId": "1650", + "text": "Keuze orgaandonatie vastgelegd in donorregister? ([MeetMethode])" } ] } @@ -1009,7 +1037,7 @@ { "answer": [ { - "valueString": "Mevrouw is gek op haar kleinzoon, dus brengt graag veel tijd met hem door. Verder is ze gek op muziek en luistert ze dat graag als ze alleen is." + "valueString": "Mevrouw is gek op haar kleinzoon, dus brengt graag veel tijd met hem door. Verder is ze gek op muziek en luistert ze dat graag als ze alleen is." } ], "linkId": "1196", @@ -1027,7 +1055,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" }, "item": [ { @@ -1051,7 +1079,7 @@ "valueCoding": { "system": "http://snomed.info/sct", "code": "373066001", - "display": "Ja" + "display": "ja" } } ], @@ -1067,10 +1095,7 @@ { "answer": [ { - "valueCoding": { - "code": "1", - "display": "Ja" - } + "valueBoolean": true } ], "linkId": "1200", @@ -1092,4 +1117,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/input/resources/README.md b/input/resources/README.md index c680afbb..606085bd 100644 --- a/input/resources/README.md +++ b/input/resources/README.md @@ -65,83 +65,28 @@ Transform the exported metadata to follow IG standards. Use English for all meta - **`publisher`**: Use simplified form `"PZNL & IKNL"` +### Step: 2a +Question for 967 - Geboortedatum patient needs to be a date instead of dateTime. + ### Step 3: Save to Repository Save the adjusted Questionnaire as `Questionnaire-[id].json` in `input/resources/` -### Step 4: Fix Conditional Expressions - -Use [NLM Form Builder](https://formbuilder.nlm.nih.gov/) to correct invalid FHIRPath expressions: - -1. Select **"Start with existing form"** → **"Import from local file"** -2. Import your adjusted Questionnaire JSON -3. Review warnings for invalid FHIRPath conditions -4. Fix conditional displays (especially boolean comparisons) - - **Example fix needed:** - - Item: "Naam eerste contactpersoon" should display when: - - Question `984` ("Is de wettelijk vertegenwoordiger ook de eerste contactpersoon?") = `Nee (0)` - -### Step 5: Set Read-Only Treatment/Measurement Codes +### Step 4: Replace all anwserOption with a answerValueSet reference +To ensure better maintainability and consistency, replace all `answerOption` arrays in the questionnaire items with a reference to an `answerValueSet`. Use a diff to identify all `answerValueSet` references and replace the corresponding `answerOption` arrays with the appropriate `ValueSet` reference. -For sections **4. Behandelgrenzen** and **5. Behandelwensen**, configure treatment codes and measurement names as: -- **Read only**: Yes -- **Value method**: Pick initial value - -This simplifies QuestionnaireResponse creation. Example result: - -```json -"item": [ - { - "type": "choice", - "linkId": "1408", - "text": "Belangrijkste doel van behandeling ([MetingNaam])", - "required": false, - "repeats": false, - "readOnly": true, - "answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "180771000146100", - "display": "Focus van behandeling (waarneembare entiteit)" - }, - "initialSelected": true - } - ] - }, -] -``` - -### Step 6: Export and Replace +### Step 5: Export and Replace 1. In Form Builder, select top-right menu → **"Export"** → **"Export to file in FHIR R4 format"** 2. Save and replace the file in `input/resources/` -### Step 7: Expand ICD valueset -In the question concerning the 'ProductType van IC' (linkID 1008), replace the ICD code and display by the valueset values, including the system, the code and the display. Illustrated for first two answer options: -```json -"answerOption": [ - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "72506001", - "display": "implanteerbare cardioverter-defibrillator" - } - }, - { - "valueCoding": { - "system": "http://snomed.info/sct", - "code": "465460004", - "display": "univentriculaire implanteerbare cardioverter-defibrillator" - } - }, -] -``` -### Step 8: Remove 'code' keys from questionnaire items with Python script +### Step 6: Populate item prefix with Python script +Run the Questionnaire Item Prefix Populator script (`/util\questionnaire_item_prefix_populator.py/`) that populates the `prefix` field for all questionnaire items based on their `linkId` values, following the pattern "Q[linkId]". This ensures consistent and clear identification of questionnaire items in the IG. + +### Step 7: Remove 'code' keys from questionnaire items with Python script Run the Questionnaire Item Code Remover script (`/util\questionnaire_item_code_remover.py/`) that removes 'code' keys from all questionnaire items, including incorrect 'code' properties. -### Step 9: Register in Configuration for better presentation in IG +### Step 8: Register in Configuration for better presentation in IG Add the Questionnaire to `sushi-config.yaml`: @@ -166,8 +111,11 @@ groups: ## QuestionnaireResponse Creation Process +### Step 0: Prepare Questionnaire +Run `util\questionnaire_item_anwserOption_expander.py` to expand answer options with proper display values, ensuring that the questionnaire is fully functional for data entry. + ### Step 1: Load Questionnaire -Use [LHC Forms](https://lhcforms.nlm.nih.gov/lhcforms/) to create example responses: +Use [LHC Forms](https://lhcforms.nlm.nih.gov/lhcforms/) to create example responses - use the expanded version of the questionnaire (output step 0): 1. Select **"Load From File"** 2. Choose the adjusted Questionnaire from `input/resources/` diff --git a/local-template/package/package.json b/local-template/package/package.json index 64cf87db..244cfe82 100644 --- a/local-template/package/package.json +++ b/local-template/package/package.json @@ -6,8 +6,8 @@ "description": "Sample Template - not intended for real use", "author": "http://hl7.org/fhir", "canonical": "http://github.com/HL7/ig-template-sample", - "base": "fhir.base.template", + "base": "fhir2.base.template", "dependencies": { - "fhir.base.template": "current" + "fhir2.base.template": "current" } } \ No newline at end of file diff --git a/publication-request.json b/publication-request.json index f3ce21f3..4431e136 100644 --- a/publication-request.json +++ b/publication-request.json @@ -1,12 +1,12 @@ { "package-id" : "iknl.fhir.r4.pzp", - "version" : "1.0.0-rc2", - "path" : "https://api.iknl.nl/docs/pzp/r4/1.0.0-rc2", + "version" : "1.0.0-rc3", + "path" : "https://api.iknl.nl/docs/pzp/r4/1.0.0-rc3", "mode" : "working", "status" : "trial-use", "sequence" : "Release 1", "desc" : "1.0.0 release candidate of the IKNL PZP information standard.", - "first" : true, + "first" : false, "title" : "Advance Care Planning (PZP)", "ci-build" : "https://build.fhir.org/ig/IKNL/PZP-FHIR-R4/branches/develop/", "category" : "Care Management", diff --git a/sushi-config.yaml b/sushi-config.yaml index eb583e13..06c4be8a 100644 --- a/sushi-config.yaml +++ b/sushi-config.yaml @@ -9,7 +9,7 @@ name: PZP title: Advance Care Planning (PZP) description: Advance Care Planning (PZP) IG for FHIR R4 based on zib release 2020 status: active # draft | active | retired | unknown -version: 1.0.0-rc2 +version: 1.0.0-rc3 fhirVersion: 4.0.1 # https://www.hl7.org/fhir/valueset-FHIR-version.html copyrightYear: 2026+ releaseLabel: trial-use @@ -207,6 +207,7 @@ groups: name: "Structures: Resource Profiles" description: These are ACP profiles based on FHIR core resources. resources: + - StructureDefinition/ACP-LegallyCapable - StructureDefinition/ACP-InformRelativesRequest - StructureDefinition/ACP-MedicalPolicyGoal - StructureDefinition/ACP-OrganDonationChoiceRegistration diff --git a/util/patient_bundle_generator.py b/util/patient_bundle_generator.py index 7242d778..730f7c30 100644 --- a/util/patient_bundle_generator.py +++ b/util/patient_bundle_generator.py @@ -9,13 +9,18 @@ Workflow: 1. Scans the fsh-generated/resources folder for FHIR JSON files. - 2. Groups instance resources by patient using the `-PatN` suffix that - appears in both resource IDs and Patient references. - 3. Creates one Bundle (type "transaction") per patient group containing + 2. Also scans the extra-resources-dir (default: input/resources) for + additional instance resources, e.g. QuestionnaireResponses. + 3. Groups instance resources by patient using Patient references found + anywhere in each resource. + 4. Creates one Bundle (type "transaction") per patient group containing all associated resources, with PUT requests for each entry. + 5. Creates one shared Bundle for resources not linked to any patient + (e.g. Practitioner, PractitionerRole, Organization). Usage: python util/patient_bundle_generator.py [--resources-dir DIR] + [--extra-resources-dir DIR] [--output-dir DIR] Examples: @@ -25,6 +30,7 @@ # Custom paths python util/patient_bundle_generator.py \\ --resources-dir fsh-generated/resources \\ + --extra-resources-dir input/resources \\ --output-dir util/patient-bundles """ @@ -42,11 +48,15 @@ # Directory containing the compiled FHIR JSON resources (output of SUSHI). DEFAULT_RESOURCES_DIR = "fsh-generated/resources" -# Output directory where the generated patient Bundle files are written. +# Additional directory scanned for instance resources (e.g. QuestionnaireResponses +# authored outside the FSH build, stored as plain JSON). +DEFAULT_EXTRA_RESOURCES_DIR = "input/resources" + +# Output directory where the generated Bundle files are written. DEFAULT_OUTPUT_DIR = "util/patient-bundles" # FHIR resource types that should be skipped (definition / infrastructure -# resources that are not relevant for patient bundles). +# resources that are not relevant for patient or shared bundles). SKIP_RESOURCE_TYPES = { 'StructureDefinition', 'ValueSet', @@ -55,6 +65,7 @@ 'ActorDefinition', 'SearchParameter', 'CapabilityStatement', + 'Questionnaire', } # Base URL used for Bundle identifiers and tags. @@ -179,9 +190,13 @@ def create_patient_bundle(patient_id, patient_resource, associated_resources): } -def discover_patients_and_resources(resources_dir): +def discover_patients_and_resources(resources_dir, extra_resources_dir=None): """Load all FHIR instance resources and discover Patient resources. + Scans *resources_dir* first, then optionally *extra_resources_dir* for + additional instance resources (e.g. QuestionnaireResponses stored as plain + JSON outside the FSH build). + Returns: patients: dict mapping Patient resource ID → Patient resource dict instances: list of (filename, resource) tuples for non-Patient, @@ -197,12 +212,24 @@ def discover_patients_and_resources(resources_dir): skipped_count = 0 error_count = 0 - json_files = sorted(resources_path.glob("*.json")) - print(f"Scanning {len(json_files)} files...\n") + # Collect all JSON files: primary dir first, then extra dir (if provided). + all_json_files = sorted(resources_path.glob("*.json")) + if extra_resources_dir: + extra_path = Path(extra_resources_dir) + if extra_path.exists(): + extra_files = sorted(extra_path.glob("*.json")) + print(f"Scanning {len(all_json_files)} file(s) in '{resources_dir}' " + f"+ {len(extra_files)} file(s) in '{extra_resources_dir}'...\n") + all_json_files = all_json_files + extra_files + else: + print(f"Warning: Extra resources directory not found: {extra_resources_dir}") + print(f"Scanning {len(all_json_files)} file(s) in '{resources_dir}'...\n") + else: + print(f"Scanning {len(all_json_files)} files...\n") # --- Pass 1: discover all Patient resources --- - print("Pass 1 — discovering Patient resources...") - for json_file in json_files: + print("Pass 1 - discovering Patient resources...") + for json_file in all_json_files: resource = load_fhir_resource(json_file) if not resource: error_count += 1 @@ -231,6 +258,57 @@ def discover_patients_and_resources(resources_dir): return patients, instances +def create_shared_bundle(ungrouped_resources): + """Create a Bundle resource containing all non-patient-linked resources. + + These are resources such as Practitioner, PractitionerRole, and + Organization that are referenced by patient-linked resources but do not + themselves carry a direct Patient reference. + """ + bundle = { + "resourceType": "Bundle", + "id": "SharedResourcesBundle", + "meta": { + "profile": [ + "http://hl7.org/fhir/StructureDefinition/Bundle" + ], + "lastUpdated": datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + "tag": [ + { + "system": f"{BUNDLE_SYSTEM_BASE}/CodeSystem/bundle-type", + "code": "shared-data", + "display": "Shared Resources Bundle" + } + ] + }, + "identifier": { + "system": f"{BUNDLE_SYSTEM_BASE}/NamingSystem/shared-bundle", + "value": "SharedResourcesBundle" + }, + "type": "transaction", + "timestamp": datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + "entry": [] + } + + resource_types = {} + sorted_resources = sorted( + ungrouped_resources, + key=lambda r: (r.get('resourceType', ''), r.get('id', '')) + ) + + for resource in sorted_resources: + entry = create_bundle_entry(resource) + if entry: + bundle["entry"].append(entry) + res_type = resource.get('resourceType', 'Unknown') + resource_types[res_type] = resource_types.get(res_type, 0) + 1 + + return bundle, { + 'total_resources': len(sorted_resources), + 'resource_types': resource_types + } + + def group_resources_by_patient(patients, instances): """Group instance resources by the Patient(s) they reference. @@ -246,7 +324,7 @@ def group_resources_by_patient(patients, instances): groups = {pid: [] for pid in known_patient_ids} ungrouped = [] - print("\nPass 2 — grouping resources by Patient reference...") + print("\nPass 2 - grouping resources by Patient reference...") for filename, resource in instances: referenced_ids = find_patient_references(resource) & known_patient_ids @@ -254,25 +332,27 @@ def group_resources_by_patient(patients, instances): for pid in referenced_ids: groups[pid].append(resource) ref_list = ', '.join(sorted(referenced_ids)) - print(f" {filename} → {ref_list}") + print(f" {filename} -> {ref_list}") else: ungrouped.append((filename, resource)) - print(f" {filename} → (no patient reference)") + print(f" {filename} -> (no patient reference)") return groups, ungrouped -def generate_patient_bundles(resources_dir, output_dir): - """Generate Bundle resources for each patient.""" +def generate_patient_bundles(resources_dir, output_dir, extra_resources_dir=None): + """Generate Bundle resources for each patient and one shared resources bundle.""" print("IKNL PZP FHIR R4 Patient Bundle Generator") print("=" * 50) - print(f"Resources directory: {resources_dir}") - print(f"Output directory: {output_dir}") + print(f"Resources directory: {resources_dir}") + if extra_resources_dir: + print(f"Extra resources directory: {extra_resources_dir}") + print(f"Output directory: {output_dir}") print() # Discover patients and load all instance resources - patients, instances = discover_patients_and_resources(resources_dir) + patients, instances = discover_patients_and_resources(resources_dir, extra_resources_dir) if not patients: print("\nNo Patient resources found — nothing to bundle.") @@ -282,7 +362,8 @@ def generate_patient_bundles(resources_dir, output_dir): groups, ungrouped = group_resources_by_patient(patients, instances) if ungrouped: - print(f"\n{len(ungrouped)} resource(s) not associated with any patient:") + print(f"\n{len(ungrouped)} resource(s) not associated with any patient " + f"(will go into shared bundle):") for filename, resource in ungrouped: print(f" {filename} ({resource.get('resourceType', 'Unknown')})") @@ -320,7 +401,27 @@ def generate_patient_bundles(resources_dir, output_dir): else: print(f" Failed to create bundle for {patient_id}") - print(f"\n{bundles_created} patient bundle(s) created successfully!") + # Generate shared resources bundle for ungrouped resources + if ungrouped: + ungrouped_resources = [resource for _, resource in ungrouped] + print(f"\nGenerating shared resources bundle ({len(ungrouped_resources)} resource(s))...") + bundle, metadata = create_shared_bundle(ungrouped_resources) + bundle_filename = "SharedResourcesBundle.json" + bundle_path = output_path / bundle_filename + + try: + with open(bundle_path, 'w', encoding='utf-8') as f: + json.dump(bundle, f, indent=2, ensure_ascii=False) + + print(f" Created: {bundle_filename}") + print(f" Total resources in bundle: {metadata['total_resources']}") + print(f" Resource types: {', '.join(f'{k}({v})' for k, v in sorted(metadata['resource_types'].items()))}") + bundles_created += 1 + + except IOError as e: + print(f" Error writing shared bundle: {e}") + + print(f"\n{bundles_created} bundle(s) created successfully!") return bundles_created > 0 @@ -334,9 +435,14 @@ def main(): '--resources-dir', default=DEFAULT_RESOURCES_DIR, help=f"Directory containing compiled FHIR JSON resources.\n(default: '{DEFAULT_RESOURCES_DIR}')" ) + parser.add_argument( + '--extra-resources-dir', default=DEFAULT_EXTRA_RESOURCES_DIR, + help=f"Additional directory with instance resources (e.g. QuestionnaireResponses).\n" + f"Pass an empty string to disable.\n(default: '{DEFAULT_EXTRA_RESOURCES_DIR}')" + ) parser.add_argument( '--output-dir', default=DEFAULT_OUTPUT_DIR, - help=f"Output directory for the generated patient Bundle files.\n(default: '{DEFAULT_OUTPUT_DIR}')" + help=f"Output directory for the generated Bundle files.\n(default: '{DEFAULT_OUTPUT_DIR}')" ) args = parser.parse_args() @@ -345,8 +451,9 @@ def main(): project_root = script_dir.parent resources_dir = project_root / args.resources_dir output_dir = project_root / args.output_dir + extra_resources_dir = (project_root / args.extra_resources_dir) if args.extra_resources_dir else None - success = generate_patient_bundles(resources_dir, output_dir) + success = generate_patient_bundles(resources_dir, output_dir, extra_resources_dir) return 0 if success else 1 diff --git a/util/questionnaire_item_anwserOption_expander.py b/util/questionnaire_item_anwserOption_expander.py new file mode 100644 index 00000000..f12c90e3 --- /dev/null +++ b/util/questionnaire_item_anwserOption_expander.py @@ -0,0 +1,543 @@ +#!/usr/bin/env python3 +"""Expand Questionnaire ``answerValueSet`` references into inline ``answerOption`` lists. + +Why this exists +--------------- +The NLM LHC-Forms tool (https://lhcforms.nlm.nih.gov/) cannot resolve ``answerValueSet`` +references against a terminology server. To preview/use the ACP Questionnaire there, every +question that points at a ValueSet needs the matching codes materialised as ``answerOption``. + +What the script does +-------------------- +For every ``Questionnaire`` resource found in ``input/resources`` it walks the item tree and, +for each item that has an ``answerValueSet``, it: + + 1. Resolves the ValueSet, looking first in the local ``fsh-generated/resources`` folder and + then in the FHIR package cache (``~/.fhir/packages``). The packages to scan are taken + from the ``dependencies:`` block of ``sushi-config.yaml``; if that is missing, the + resolved dependency list in ``fhirpkg.lock.json`` is used instead. As a last resort the + whole package cache is searched. + 2. Expands it: + * a pre-computed ``expansion.contains`` is used as-is when present; + * otherwise ``compose.include`` is processed - explicit ``concept`` lists are used + directly, ``valueSet`` imports are resolved recursively, and a bare ``system`` + (whole code system) is enumerated from the resolved CodeSystem; + * ``compose.exclude`` entries are removed; + * the CodeSystem is consulted to fill in a missing ``display``. + 3. Replaces the item's ``answerValueSet`` with the resulting ``answerOption`` array. + +Because the form is Dutch, the Dutch (``nl-NL``) designation is preferred for the display, +falling back to the concept's base ``display`` and finally the code. + +Some ValueSets reference a CodeSystem we have no access to (e.g. the UZI based +``SpecialismeCodelijst``). For those a fixed answer list is supplied via ``HARDCODED_ANSWER_OPTIONS``. + +The original files are left untouched; a copy suffixed with ``-expanded`` is written next to +each source Questionnaire. + +Usage +----- + python util/questionnaire_item_anwserOption_expander.py + python util/questionnaire_item_anwserOption_expander.py --input-dir input/resources +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path + +# -------------------------------------------------------------------------------------- +# Configuration +# -------------------------------------------------------------------------------------- + +# Repository root (this file lives in /util/). +ROOT = Path(__file__).resolve().parent.parent + +# Where the source Questionnaires live and where expanded copies are written. +DEFAULT_INPUT_DIR = ROOT / "input" / "resources" + +# Locally generated conformance resources (ValueSets / CodeSystems produced by SUSHI). +FSH_GENERATED_DIR = ROOT / "fsh-generated" / "resources" + +# Resolved dependency list (authoritative); fall back to sushi-config.yaml if absent. +LOCK_FILE = ROOT / "fhirpkg.lock.json" +SUSHI_CONFIG = ROOT / "sushi-config.yaml" + +# FHIR package cache. Honour the standard override, otherwise ~/.fhir/packages. +import os + +FHIR_PACKAGE_CACHE = Path( + os.environ.get("FHIR_PACKAGE_CACHE", Path.home() / ".fhir" / "packages") +) + +# Suffix added to the expanded copy (before the .json extension). +OUTPUT_SUFFIX = "-expanded" + +# Preferred display language (the form is Dutch). +PREFERRED_LANGUAGE = "nl-NL" + +# SNOMED CT system URL. SNOMED designations often carry a trailing semantic tag in +# parentheses (e.g. "ja (kwalificatiewaarde)"); that tag is stripped from the display. +SNOMED_SYSTEM = "http://snomed.info/sct" + +# ValueSets that cannot be resolved from the packages (their CodeSystem is not available). +# Map the ValueSet canonical URL to a fixed list of FHIR answerOption entries. +HARDCODED_ANSWER_OPTIONS = { + # Functie (Specialisme) - SpecialismeCodelijst. Imports UZI based code lists that are + # not distributed in the package cache, so a representative subset is hardcoded. + "http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.121.11.22--20200901000000": [ + {"valueCoding": {"system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", "code": "01.000", "display": "Arts"}}, + {"valueCoding": {"system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", "code": "01.015", "display": "Huisarts"}}, + {"valueCoding": {"system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", "code": "01.047", "display": "Specialist ouderengeneeskunde"}}, + {"valueCoding": {"system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", "code": "30.000", "display": "Verpleegkundige"}}, + {"valueCoding": {"system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", "code": "81.000", "display": "Physician assistant"}}, + {"valueCoding": {"system": "http://fhir.nl/fhir/NamingSystem/uzi-rolcode", "code": "99.000", "display": "Zorgverlener andere zorg"}}, + ], +} + + +# -------------------------------------------------------------------------------------- +# Resource resolver: maps canonical URL -> file path for ValueSets and CodeSystems. +# -------------------------------------------------------------------------------------- + + +class ResourceResolver: + """Locate ValueSet / CodeSystem resources locally and in the FHIR package cache.""" + + def __init__(self) -> None: + # url -> Path. Local (fsh-generated) entries take precedence over package entries. + self._valuesets: dict[str, Path] = {} + self._codesystems: dict[str, Path] = {} + # Cache of loaded JSON resources keyed by path. + self._loaded: dict[Path, dict] = {} + self._scanned_full_cache = False + + self._index_local() + self._index_declared_packages() + + # -- index building ----------------------------------------------------------------- + + def _register(self, url: str | None, resource_type: str | None, path: Path) -> None: + if not url or not resource_type: + return + target = self._valuesets if resource_type == "ValueSet" else ( + self._codesystems if resource_type == "CodeSystem" else None + ) + if target is None: + return + # First registration wins (local before packages, declared deps before fallback). + target.setdefault(url, path) + + def _index_local(self) -> None: + """Index ValueSets / CodeSystems generated locally by SUSHI.""" + if not FSH_GENERATED_DIR.is_dir(): + return + for path in FSH_GENERATED_DIR.glob("*.json"): + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + continue + if isinstance(data, dict): + self._register(data.get("url"), data.get("resourceType"), path) + # Cache it - we already paid the read cost. + self._loaded[path] = data + + def _package_dirs_from_dependencies(self) -> list[Path]: + """Determine which package directories to index from the project dependencies.""" + deps: dict[str, str] = {} + + # Primary source: the dependencies declared in sushi-config.yaml. + if SUSHI_CONFIG.is_file(): + deps.update(_parse_sushi_dependencies(SUSHI_CONFIG)) + + # Fallback: the resolved dependency list in fhirpkg.lock.json. + if not deps and LOCK_FILE.is_file(): + try: + lock = json.loads(LOCK_FILE.read_text(encoding="utf-8")) + deps.update(lock.get("dependencies", {})) + except (json.JSONDecodeError, OSError): + pass + + # hl7.fhir.r4.core is always needed for base FHIR code systems. + deps.setdefault("hl7.fhir.r4.core", "4.0.1") + + dirs: list[Path] = [] + for pkg_id, version in deps.items(): + candidate = FHIR_PACKAGE_CACHE / f"{pkg_id}#{version}" + if candidate.is_dir(): + dirs.append(candidate) + else: + print(f" ! dependency package not found in cache: {pkg_id}#{version}", + file=sys.stderr) + return dirs + + def _index_package_dir(self, pkg_dir: Path) -> None: + index_file = pkg_dir / "package" / ".index.json" + if not index_file.is_file(): + return + try: + index = json.loads(index_file.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + return + for entry in index.get("files", []): + rtype = entry.get("resourceType") + if rtype not in ("ValueSet", "CodeSystem"): + continue + filename = entry.get("filename") + if not filename: + continue + self._register(entry.get("url"), rtype, pkg_dir / "package" / filename) + + def _index_declared_packages(self) -> None: + for pkg_dir in self._package_dirs_from_dependencies(): + self._index_package_dir(pkg_dir) + + def _index_full_cache(self) -> None: + """Fallback: index every package in the cache (used only when a URL is missing).""" + if self._scanned_full_cache or not FHIR_PACKAGE_CACHE.is_dir(): + return + self._scanned_full_cache = True + for pkg_dir in FHIR_PACKAGE_CACHE.iterdir(): + if pkg_dir.is_dir(): + self._index_package_dir(pkg_dir) + + # -- loading ------------------------------------------------------------------------ + + def _load(self, path: Path) -> dict | None: + if path in self._loaded: + return self._loaded[path] + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + return None + self._loaded[path] = data + return data + + def get_valueset(self, url: str) -> dict | None: + url = _strip_version(url) + if url not in self._valuesets: + self._index_full_cache() + path = self._valuesets.get(url) + return self._load(path) if path else None + + def get_codesystem(self, url: str) -> dict | None: + url = _strip_version(url) + if url not in self._codesystems: + self._index_full_cache() + path = self._codesystems.get(url) + return self._load(path) if path else None + + +def _strip_version(url: str) -> str: + """Drop a trailing ``|version`` from a canonical reference.""" + return url.split("|", 1)[0] if url else url + + +def _parse_sushi_dependencies(config_path: Path) -> dict[str, str]: + """Minimal parser for the ``dependencies:`` block of sushi-config.yaml. + + Avoids a hard dependency on PyYAML. Only handles the simple ``id: version`` form, + which is what this project uses. + """ + deps: dict[str, str] = {} + try: + lines = config_path.read_text(encoding="utf-8").splitlines() + except OSError: + return deps + + in_block = False + dep_re = re.compile(r"^(\s*)([A-Za-z0-9._-]+)\s*:\s*([^\s#]+)") + for line in lines: + stripped = line.strip() + if not in_block: + if stripped.startswith("dependencies:"): + in_block = True + continue + # Stop when a new top-level (non-indented, non-comment) key starts. + if stripped and not line[0].isspace() and not stripped.startswith("#"): + break + if not stripped or stripped.startswith("#"): + continue + m = dep_re.match(line) + if m: + deps[m.group(2)] = m.group(3) + return deps + + +# -------------------------------------------------------------------------------------- +# ValueSet expansion +# -------------------------------------------------------------------------------------- + + +_SNOMED_SEMANTIC_TAG = re.compile(r"\s*\([^()]*\)\s*$") + + +def _clean_display(display: str | None, system: str | None) -> str | None: + """Strip the trailing SNOMED semantic tag (e.g. " (kwalificatiewaarde)") from a display.""" + if display and system == SNOMED_SYSTEM: + stripped = _SNOMED_SEMANTIC_TAG.sub("", display).strip() + if stripped: + return stripped + return display + + +def _dutch_display(concept: dict, system: str | None = None) -> str | None: + """Return the preferred display for a concept, favouring the Dutch designation. + + For SNOMED concepts a parenthesis-free designation is preferred, and any remaining + trailing semantic tag is stripped (so "ja (kwalificatiewaarde)" becomes "ja"). + """ + nl_values = [ + d["value"] for d in concept.get("designation", []) + if d.get("language") == PREFERRED_LANGUAGE and d.get("value") + ] + display = None + if nl_values: + # Prefer a Dutch designation without a parenthetical part, if one exists. + display = next((v for v in nl_values if "(" not in v), nl_values[0]) + else: + display = concept.get("display") + return _clean_display(display, system) + + +def _codesystem_display_map(codesystem: dict, system: str | None = None) -> dict[str, str]: + """Flatten a (possibly hierarchical) CodeSystem into code -> display.""" + result: dict[str, str] = {} + + def walk(concepts: list) -> None: + for concept in concepts: + code = concept.get("code") + if code is not None: + result[code] = _dutch_display(concept, system) or concept.get("display") or code + if concept.get("concept"): + walk(concept["concept"]) + + walk(codesystem.get("concept", [])) + return result + + +class ValueSetExpander: + def __init__(self, resolver: ResourceResolver) -> None: + self.resolver = resolver + + def expand(self, url: str) -> list[dict]: + """Expand a ValueSet URL into a list of FHIR ``answerOption`` entries.""" + if url in HARDCODED_ANSWER_OPTIONS: + return [dict(opt) for opt in HARDCODED_ANSWER_OPTIONS[url]] + + codings = self._expand_to_codings(url, seen=set()) + return [{"valueCoding": coding} for coding in codings] + + def _expand_to_codings(self, url: str, seen: set[str]) -> list[dict]: + url = _strip_version(url) + if url in seen: + return [] # guard against circular imports + seen.add(url) + + valueset = self.resolver.get_valueset(url) + if valueset is None: + raise LookupError(f"ValueSet not found: {url}") + + codings: list[dict] = [] + + # 1. Pre-computed expansion wins. + expansion = valueset.get("expansion", {}) + for contains in expansion.get("contains", []): + self._collect_contains(contains, codings) + if codings: + return _dedupe(codings) + + compose = valueset.get("compose", {}) + + # 2. Includes. + for include in compose.get("include", []): + codings.extend(self._expand_include(include, seen)) + + # 3. Excludes - drop matching (system, code) pairs. + excluded = set() + for exclude in compose.get("exclude", []): + for coding in self._expand_include(exclude, seen): + excluded.add((coding.get("system"), coding.get("code"))) + if excluded: + codings = [c for c in codings if (c.get("system"), c.get("code")) not in excluded] + + return _dedupe(codings) + + def _collect_contains(self, contains: dict, out: list[dict]) -> None: + if not contains.get("abstract") and contains.get("code") is not None: + coding = {} + if contains.get("system"): + coding["system"] = contains["system"] + coding["code"] = contains["code"] + display = _dutch_display(contains, contains.get("system")) + if display: + coding["display"] = display + out.append(coding) + for child in contains.get("contains", []): + self._collect_contains(child, out) + + def _expand_include(self, include: dict, seen: set[str]) -> list[dict]: + codings: list[dict] = [] + + # Imported ValueSets. + for imported_url in include.get("valueSet", []): + try: + codings.extend(self._expand_to_codings(imported_url, seen)) + except LookupError as exc: + print(f" ! could not resolve imported ValueSet: {exc}", file=sys.stderr) + + system = include.get("system") + + if "concept" in include and system: + # Explicit concept list; fill in missing displays from the CodeSystem. + cs_map: dict[str, str] | None = None + for concept in include["concept"]: + display = _dutch_display(concept, system) + if not display: + if cs_map is None: + codesystem = self.resolver.get_codesystem(system) + cs_map = _codesystem_display_map(codesystem, system) if codesystem else {} + display = cs_map.get(concept.get("code")) + coding = {"system": system, "code": concept.get("code")} + if display: + coding["display"] = display + codings.append(coding) + elif system and "filter" not in include: + # Whole code system - enumerate it from the resolved CodeSystem. + codesystem = self.resolver.get_codesystem(system) + if codesystem is None: + print(f" ! cannot enumerate system (CodeSystem not found): {system}", + file=sys.stderr) + else: + for code, display in _codesystem_display_map(codesystem, system).items(): + coding = {"system": system, "code": code} + if display: + coding["display"] = display + codings.append(coding) + elif system and "filter" in include: + print(f" ! filter-based include not supported for system: {system}", + file=sys.stderr) + + return codings + + +def _dedupe(codings: list[dict]) -> list[dict]: + """Remove duplicate (system, code) codings, preserving order.""" + seen: set[tuple] = set() + result: list[dict] = [] + for coding in codings: + key = (coding.get("system"), coding.get("code")) + if key not in seen: + seen.add(key) + result.append(coding) + return result + + +# -------------------------------------------------------------------------------------- +# Questionnaire processing +# -------------------------------------------------------------------------------------- + + +def process_items(items: list[dict], expander: ValueSetExpander, stats: dict) -> None: + """Recursively expand answerValueSet references on a list of Questionnaire items.""" + for item in items: + value_set_url = item.get("answerValueSet") + if value_set_url: + stats["found"] += 1 + try: + options = expander.expand(value_set_url) + except LookupError as exc: + stats["failed"] += 1 + print(f" ! {exc} (linkId={item.get('linkId')})", file=sys.stderr) + options = None + if options: + # Merge with any existing answerOption, then drop the reference so LHC + # uses the inline options instead of trying to resolve the ValueSet. + existing = item.get("answerOption", []) + item["answerOption"] = _merge_options(existing, options) + del item["answerValueSet"] + stats["expanded"] += 1 + print(f" + linkId={item.get('linkId')}: {len(options)} options " + f"from {value_set_url}") + elif options is not None: + print(f" ! linkId={item.get('linkId')}: 0 options from {value_set_url}", + file=sys.stderr) + + if item.get("item"): + process_items(item["item"], expander, stats) + + +def _merge_options(existing: list[dict], expanded: list[dict]) -> list[dict]: + """Append expanded options to any pre-existing ones, de-duplicating by coding.""" + seen: set[tuple] = set() + merged: list[dict] = [] + for opt in list(existing) + list(expanded): + coding = opt.get("valueCoding", {}) + key = (coding.get("system"), coding.get("code")) + if key not in seen: + seen.add(key) + merged.append(opt) + return merged + + +def process_questionnaire(path: Path, expander: ValueSetExpander) -> bool: + """Expand one Questionnaire file. Returns True if an expanded copy was written.""" + try: + data = json.loads(path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError) as exc: + print(f"! skipping {path.name}: {exc}", file=sys.stderr) + return False + + if not isinstance(data, dict) or data.get("resourceType") != "Questionnaire": + return False + + print(f"\nProcessing {path.name} ...") + stats = {"found": 0, "expanded": 0, "failed": 0} + process_items(data.get("item", []), expander, stats) + + if stats["found"] == 0: + print(" (no answerValueSet references found)") + return False + + out_path = path.with_name(f"{path.stem}{OUTPUT_SUFFIX}{path.suffix}") + out_path.write_text( + json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8" + ) + print(f" -> wrote {out_path.relative_to(ROOT)} " + f"(found {stats['found']}, expanded {stats['expanded']}, failed {stats['failed']})") + return True + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--input-dir", type=Path, default=DEFAULT_INPUT_DIR, + help=f"directory to scan for Questionnaires (default: {DEFAULT_INPUT_DIR})") + args = parser.parse_args() + + input_dir: Path = args.input_dir + if not input_dir.is_dir(): + print(f"Input directory does not exist: {input_dir}", file=sys.stderr) + return 1 + + print(f"FHIR package cache: {FHIR_PACKAGE_CACHE}") + resolver = ResourceResolver() + expander = ValueSetExpander(resolver) + + written = 0 + for path in sorted(input_dir.glob("*.json")): + # Skip the expanded copies themselves. + if path.stem.endswith(OUTPUT_SUFFIX): + continue + if process_questionnaire(path, expander): + written += 1 + + print(f"\nDone. Expanded copies written: {written}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/util/questionnaire_item_prefix_populator.py b/util/questionnaire_item_prefix_populator.py index 3d0e188d..9bf6e49c 100644 --- a/util/questionnaire_item_prefix_populator.py +++ b/util/questionnaire_item_prefix_populator.py @@ -65,7 +65,9 @@ def extract_prefix_from_text(text: str) -> Tuple[Optional[str], str]: return match.group(1), match.group(2) # Pattern for number followed by . - e.g., "1.", "2.", "3." - number_dot_pattern = re.compile(r'^(\d+\.)\s*(.*)$') + # The trailing dot is dropped from the prefix (e.g. "1." -> "1"), while + # other separators like ")" are preserved by the patterns below. + number_dot_pattern = re.compile(r'^(\d+)\.\s*(.*)$') match = number_dot_pattern.match(text) if match: return match.group(1), match.group(2)