diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..dad4757 Binary files /dev/null and b/.DS_Store differ diff --git a/synthea/README.md b/synthea/README.md index 9a1ecc9..896f8de 100644 --- a/synthea/README.md +++ b/synthea/README.md @@ -10,17 +10,18 @@ The [Synthea](https://github.com/synthetichealth/synthea) project can be used to - See the [flexporter documentation](https://github.com/synthetichealth/synthea/wiki/Flexporter) for additional information on the flexporter, mapping file, and limitations. ## Building the Mapping File + Quality Measurement calculation requires data conformant with qicore, an expansive IG, which would require expansive effort to fully map. As such, we can piecemeal address the IG requirements by supporting requirements for individual measures. The recommended process for creating a mapping that addresses a set of measures is: 1. Use [elm-parser-for-ecqms fhir_review branch](https://github.com/projecttacoma/elm-parser-for-ecqms/tree/fhir_review) and get data requirements to build a combined list of mustSupports for all resources across the set of measures. 2. Use [fqm-execution](https://github.com/projecttacoma/fqm-execution) and get data requirements to build a full list of profiles used. 3. For all resource types, ensure resource is exported as a top level resource by Synthea or already supported by the flexporter mapping. If not, use the flexporter `create_resource` action to export the resource based on a logical existing exported resource or based on a logical Synthea module state. Resources that are only used for SDEs may be initially ignored. Make sure all profiles are applied to the correct exported resource. 4. For each must support: - - Check the resource's [qicore 4.1.1](https://hl7.org/fhir/us/qicore/STU4.1.1/) profile Snapshot Table to check if the mustSupport is also required by the profile (minimum cardinality 1). If not, it may be initially ignored. - - If required, check if synthea already exports it by looking in the [FhirR4.java](https://github.com/synthetichealth/synthea/blob/master/src/main/java/org/mitre/synthea/export/FhirR4.java) file. You can look for a string like `Condition()` for an example of how the Fhir object is created and what fields are set on it. - - If Synthea doesn't export it, check if the existing mapping file already adds it through a mapping. - - If not, fill from something logical that synthea does export (if something logical exists). Understanding what values are logical may involve looking at the FHIR specification and/or the measure's CQL for how the must support field is used in the measure logic. - - If a logical field doesn't exist, fill from a chosen pre-set value or choose randomly from a limited set of values. + - Check the resource's [qicore 4.1.1](https://hl7.org/fhir/us/qicore/STU4.1.1/) profile Snapshot Table to check if the mustSupport is also required by the profile (minimum cardinality 1). If not, it may be initially ignored. + - If required, check if synthea already exports it by looking in the [FhirR4.java](https://github.com/synthetichealth/synthea/blob/master/src/main/java/org/mitre/synthea/export/FhirR4.java) file. You can look for a string like `Condition()` for an example of how the Fhir object is created and what fields are set on it. + - If Synthea doesn't export it, check if the existing mapping file already adds it through a mapping. + - If not, fill from something logical that synthea does export (if something logical exists). Understanding what values are logical may involve looking at the FHIR specification and/or the measure's CQL for how the must support field is used in the measure logic. + - If a logical field doesn't exist, fill from a chosen pre-set value or choose randomly from a limited set of values. ## CMS130 Mapping Example @@ -31,31 +32,31 @@ Below is the example list of resources and must support elements collated from e - "clinicalStatus", - "onset", - "abatement" -- Coverage: * exported as a contained resource only -> 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])