From eb42a6c446f1fa67e562b8258ad0f3b9686711bd Mon Sep 17 00:00:00 2001 From: Elsa Perelli Date: Thu, 14 Nov 2024 11:12:24 -0500 Subject: [PATCH] Add QPP profiles to flexporter mapping file (#52) * Add QPP profiles to flexporter mapping file * Clean up code to use Dylan's fancy if syntax * Update synthea/README.md Co-authored-by: lmd59 * Update synthea/README.md Co-authored-by: lmd59 * Add status valuesets, base MedicationAdmin and MedicationDispense on MedicationReq * Use MedicationRequest.medication * Properly add Medication to MedicationDispense * Split medicationdispense status valueset --------- Co-authored-by: lmd59 --- .DS_Store | Bin 0 -> 6148 bytes synthea/README.md | 77 +++++++++++++---- synthea/qicore_130.yaml | 117 -------------------------- synthea/qpp_qicore.yaml | 179 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 242 insertions(+), 131 deletions(-) create mode 100644 .DS_Store delete mode 100644 synthea/qicore_130.yaml create mode 100644 synthea/qpp_qicore.yaml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..dad4757413039352fee9886bc47bd836ada10081 GIT binary patch literal 6148 zcmeHKu};H44E2?&1hI4jG35ssx-x|-d_gxBN)i-`QW8~r6QZ6PUH?6Y?(U^z=G{(UW)Ca=z+cYBmlNq4xn_Wh+8EvTYf+-5>GT~kH{d{#w1nWg2j;&ir8AFn>Y!m^Cxq$m?a z1}{$si|p*-_$#Yq->^z`*~%vONsYipjl3(??QC&Imd<=Tm*>&_(Rl82dFS(X(oH_? zbI3NPjR9l882B*;@Mg0FyMm^T0b{@zSTn%ihY*c1QfvhMrvu8D0Kg&4UeM=Sf@4C( zNU;&b48%z&P(q!S7*4`r57jSHYy>5ooE9HWcXnE#IKMmA54}6NNYJz~U<`B_*plIx z-~W@%^?x_WM#g|K@UIwf!*~`?k&?cxz2x|<4bkRkEbP|^u0zoAPceM?Dc(o(0(;0E VV5Hax!UC}$0Z)S|W8haAcm ignored since Coverage is only used for SDEs +- Coverage: \* exported as a contained resource only -> ignored since Coverage is only used for SDEs - "type", - "period" -- DeviceRequest: * not exported -> created based on existing exported Device resources +- DeviceRequest: \* not exported -> created based on existing exported Device resources - "code", - "authoredOn", - "status", - "intent", - - "modifierExtension", * not exported -> ignored since modifierExtension is only used for doNotPerform + - "modifierExtension", \* not exported -> ignored since modifierExtension is only used for doNotPerform - "modifierExtension.url", - "modifierExtension.value" - Encounter: - "status", - "type", - "period", - - "diagnosis",* - - "diagnosis.condition", * not exported -> set using a js function that finds conditions that reference this encounter and creates a reference to that condition + - "diagnosis",\* + - "diagnosis.condition", \* not exported -> set using a js function that finds conditions that reference this encounter and creates a reference to that condition - "hospitalization" - MedicationRequest: - "medication", - - "doNotPerform", * not exported -> set to false because all resources exported from Synthea are logically performed + - "doNotPerform", \* not exported -> set to false because all resources exported from Synthea are logically performed - "status", - "intent", - "dosageInstruction", - - "dispenseRequest", * not exported -> set from the `authoredOn` field because the measure logic coalesces `dispenseRequest.validityPeriod.start` with `authoredOn` + - "dispenseRequest", \* not exported -> set from the `authoredOn` field because the measure logic coalesces `dispenseRequest.validityPeriod.start` with `authoredOn` --ignore for now - "authoredOn" - Observation: - "code", @@ -67,8 +68,56 @@ Below is the example list of resources and must support elements collated from e - "code", - "performed", - "status" -- ServiceRequest: * exported as a contained resource only -> created based on existing exported Procedure resources +- ServiceRequest: \* exported as a contained resource only -> created based on existing exported Procedure resources - "code", - "authoredOn", - "status", - - "intent" \ No newline at end of file + - "intent" + +Below are the additional resources and must support elements likely required for measures based on the Quality Payment Program (QPP). + +- AdverseEvent: \* not exported -> created for each Patient resource created + - "event" \* not exported -> set to a random code from the http://hl7.org/fhir/ValueSet/adverse-event-type ValueSet +- Communication: \* not exported -> created for each Patient resource created + - "status" \* not exported -> set to `completed` +- CommunicationNotDone: \* not exported -> ignoring for now + - "statusReason" +- ConditionProblemsHealthConcerns: \* not exported -> using qicore 4.1.1 version's Condition instead + - "category", + - "code" +- MedicationAdministration: \* not exported -> created for each Procedure resource created + - "status", \* not exported -> set to `completed` + - "medication", \* not exported -> set to a random code from the `http://hl7.org/fhir/ValueSet/medication-codes` ValueSet + - "effective" \* not exported -> set to the `Procedure.performed` Period +- MedicationAdministrationNotDone: \* not exported -> ignoring for now + - "statusReason", + - "medication" +- MedicationDispense: \* not exported -> created for each Procedure resource created + - "status", \* not exported -> set to `completed` + - "medication" \* not exported -> set to a random code from the `http://hl7.org/fhir/ValueSet/medication-codes` ValueSet +- MedicationNotRequested: \* not exported, ignoring for now + - "status", + - "intent", + - "medication", + - "authoredOn", + - "reasonCode" +- ObservationCanceled: \* not exported -> ignoring for now + - "extension", + - "status", + - "code", + - "issued" +- ProcedureNotDone: \* not exported -> ignoring for now + - "status", + - "code" +- ServiceNotRequested: \* not exported -> ignoring for now + - "status", + - "code", + - "authoredOn" +- SimpleObservation: \* not exported -> ignoring for now + - "status", + - "category", + - "code" +- Task: \* not exported, created for each Procedure resource created where Procedure.performed is of type Period + - "status", \* not exported -> set to `accepted` + - "code", \* not exported -> set to a random code from the `http://hl7.org/fhir/ValueSet/task-code` ValueSet + - "executionPeriod" \* not exported, set to the `Procedure.performed` Period diff --git a/synthea/qicore_130.yaml b/synthea/qicore_130.yaml deleted file mode 100644 index 97e6c60..0000000 --- a/synthea/qicore_130.yaml +++ /dev/null @@ -1,117 +0,0 @@ ---- -name: QI Core - 130 minimal -applicability: true - -actions: - - name: Apply Profiles - profiles: - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient - applicability: Patient - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter - applicability: Encounter - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-condition - applicability: Condition - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-observation - applicability: Observation - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-procedure - applicability: Procedure - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest - applicability: MedicationRequest - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-immunization - applicability: Immunization - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-careplan - applicability: CarePlan - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-imagingstudy - applicability: ImagingStudy - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-device - applicability: Device - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-practitioner - applicability: Practitioner - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-allergyintolerance - applicability: AllergyIntolerance - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-claim - applicability: Claim - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-coverage - applicability: Coverage - - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-servicerequest - applicability: ServiceRequest - - - - name: Set Missing Values - set_values: - - applicability: MedicationRequest - fields: - - location: MedicationRequest.doNotPerform - value: "false" - - location: MedicationRequest.dispenseRequest.validityPeriod.start - value: $getField([MedicationRequest.authoredOn]) - - name: Apply Script​ - execute_script: - - apply_to: resource - function_name: addDiagnosis - resource_type: Encounter - function: | - function addDiagnosis(resource, bundle) { - const conditionEntry = bundle.entry.find(e => e.resource?.resourceType === 'Condition' && e.resource?.encounter?.reference === `urn:uuid:${resource.id}`); - if (conditionEntry){ - resource.diagnosis = [{ - condition:{ - reference: `Condition/${conditionEntry.resource.id}` - } - }]; - } - } - - name: Create Resources - create_resource: - - resourceType: ServiceRequest - based_on: - resource: Procedure.performed.ofType(Period) # handle value setting for period choice type - fields: - - location: ServiceRequest.intent - value: order - - location: ServiceRequest.encounter.reference - value: $getField([Procedure.encounter.reference]) - - location: ServiceRequest.subject.reference - value: $findRef([Patient]) - - location: ServiceRequest.status - value: completed # all procedures are exported as completed - - location: ServiceRequest.authoredOn - value: $getField([Procedure.performed.start]) # period choice type - - location: ServiceRequest.code - value: $getField([Procedure.code]) - writeback: - - location: Procedure.basedOn.reference - value: $setRef([ServiceRequest]) - - resourceType: ServiceRequest - based_on: - resource: Procedure.performed.ofType(dateTime) # handle value setting for datetime choice type - fields: - - location: ServiceRequest.intent - value: order - - location: ServiceRequest.encounter.reference - value: $getField([Procedure.encounter.reference]) - - location: ServiceRequest.subject.reference - value: $findRef([Patient]) - - location: ServiceRequest.status - value: completed # all procedures are exported as completed - - location: ServiceRequest.authoredOn - value: $getField([Procedure.performed]) # datetime choice type - - location: ServiceRequest.code - value: $getField([Procedure.code]) - writeback: - - location: Procedure.basedOn.reference - value: $setRef([ServiceRequest]) - - resourceType: DeviceRequest - based_on: - resource: Device - profiles: - - http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-devicerequest - fields: - - location: DeviceRequest.code.reference - value: $findRef([Device]) - - location: DeviceRequest.authoredOn - value: $getField([Device.manufactureDate]) # manufacture time is set 3 weeks before device model's start, so this is close enough - - location: DeviceRequest.status - value: completed - - location: DeviceRequest.intent - value: order \ No newline at end of file diff --git a/synthea/qpp_qicore.yaml b/synthea/qpp_qicore.yaml new file mode 100644 index 0000000..0c6e9e4 --- /dev/null +++ b/synthea/qpp_qicore.yaml @@ -0,0 +1,179 @@ +--- +name: QI Core - 130 minimal and QPP +applicability: true + +customValueSets: + - url: http://example.com/medicationdispense-status-positive + compose: + include: + system: http://hl7.org/fhir/ValueSet/medicationdispense-status + concept: + - code: in-progress + display: In Progress + - code: completed + display: Completed + - code: preparation + display: Preparation + - code: on-hold + display: On Hold + - url: http://example.com/medicationdispense-status-negative + compose: + include: + system: http://hl7.org/fhir/ValueSet/medicationdispense-status + concept: + - code: cancelled + display: Cancelled + - code: entered-in-error + display: Entered in Error + - code: stopped + display: Stopped + - code: declined + display: Declined + +actions: + - name: Apply Profiles + profiles: + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient + applicability: Patient + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter + applicability: Encounter + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-condition + applicability: Condition + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-observation + applicability: Observation + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-procedure + applicability: Procedure + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest + applicability: MedicationRequest + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-immunization + applicability: Immunization + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-careplan + applicability: CarePlan + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-imagingstudy + applicability: ImagingStudy + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-device + applicability: Device + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-practitioner + applicability: Practitioner + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-allergyintolerance + applicability: AllergyIntolerance + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-claim + applicability: Claim + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-coverage + applicability: Coverage + - profile: http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-servicerequest + applicability: ServiceRequest + + - name: Set Missing Values + set_values: + - applicability: MedicationRequest + fields: + - location: MedicationRequest.doNotPerform + value: 'false' + - location: MedicationRequest.dispenseRequest.validityPeriod.start + value: $getField([MedicationRequest.authoredOn]) + - name: Apply Script​ + execute_script: + - apply_to: resource + function_name: addDiagnosis + resource_type: Encounter + function: | + function addDiagnosis(resource, bundle) { + const conditionEntry = bundle.entry.find(e => e.resource?.resourceType === 'Condition' && e.resource?.encounter?.reference === `urn:uuid:${resource.id}`); + if (conditionEntry){ + resource.diagnosis = [{ + condition:{ + reference: `Condition/${conditionEntry.resource.id}` + } + }]; + } + } + - name: Create Resources + create_resource: + - resourceType: ServiceRequest + based_on: + resource: Procedure + fields: + - location: ServiceRequest.intent + value: order + - location: ServiceRequest.encounter.reference + value: $getField([Procedure.encounter.reference]) + - location: ServiceRequest.subject.reference + value: $findRef([Patient]) + - location: ServiceRequest.status + value: completed # all procedures are exported as completed + - if: Procedure.performed.ofType(Period) # handle value setting for period choice type + location: ServiceRequest.authoredOn + value: $getField([Procedure.performed.start]) # period choice type + - if: Procedure.performed.ofType(dateTime) # handle value setting for datetime choice type + location: ServiceRequest.authoredOn + value: $getField([Procedure.performed]) #datetime + - location: ServiceRequest.code + value: $getField([Procedure.code]) + writeback: + - location: Procedure.basedOn.reference + value: $setRef([ServiceRequest]) + - resourceType: DeviceRequest + based_on: + resource: Device + profiles: + - http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-devicerequest + fields: + - location: DeviceRequest.code.reference + value: $findRef([Device]) + - location: DeviceRequest.authoredOn + value: $getField([Device.manufactureDate]) # manufacture time is set 3 weeks before device model's start, so this is close enough + - location: DeviceRequest.status + value: completed + - location: DeviceRequest.intent + value: order + # Creating one AdverseEvent per patient + - resourceType: AdverseEvent + based_on: + resource: Patient + profiles: + - http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-adverseevent + fields: + - location: AdverseEvent.event.coding + value: $randomCode([http://hl7.org/fhir/ValueSet/adverse-event-type]) # may change in future to use a smaller subset of the ValueSet + # Creating one Communication per patient + - resourceType: Communication + based_on: + resource: Patient + profiles: + - http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-communication + fields: + - location: Communication.status + value: $randomCode([http://hl7.org/fhir/ValueSet/event-status,code]) + # Creating one MedicationDispense for each MedicationRequest resource + - resourceType: MedicationDispense + based_on: + resource: MedicationRequest + profiles: + - http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationdispense + fields: + - if: MedicationRequest.status = 'stopped' + location: MedicationDispense.status + value: $randomCode([http://example.com/medicationdispense-status-negative,code]) + - if: MedicationRequest.status = 'active' + location: MedicationDispense.status + value: $randomCode([http://example.com/medicationdispense-status-positive,code]) + - if: MedicationRequest.medication.ofType(CodeableConcept) + location: MedicationDispense.medicationCodeableConcept.coding + value: $getField([MedicationRequest.medication.coding]) + - if: MedicationRequest.medication.ofType(reference) + location: MedicationDispense.medicationReference.reference + value: $getField([MedicationRequest.medication.reference]) + # Creating one Task per Procedure where performed is of type period + - resourceType: Task + based_on: + resource: Procedure.performed.ofType(Period) # handle value setting for period choice type + profiles: + - http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-task + fields: + - location: Task.status + value: $randomCode([http://hl7.org/fhir/ValueSet/task-status,code]) + - location: Task.code.coding + value: $randomCode([http://hl7.org/fhir/ValueSet/task-code]) # randomize based on values available + - location: Task.executionPeriod + value: $getField([Procedure.performed])