diff --git a/Cargo.toml b/Cargo.toml index 0607945..9694dd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "focus" -version = "0.3.0" +version = "0.4.0" edition = "2021" license = "Apache-2.0" diff --git a/build.rs b/build.rs index 49c5a10..ac9d9fe 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,5 @@ +use std::{path::Path, env, io::{BufWriter, Write}, fs::File}; + use build_data::get_git_dirty; /// Outputs a readable version number such as @@ -15,6 +17,30 @@ fn version() -> String { } } +fn build_cqlmap() { + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("replace_map.rs"); + let mut file = BufWriter::new(File::create(path).unwrap()); + + write!(&mut file, r#" + static REPLACE_MAP: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(|| {{ + let mut map = HashMap::new(); + "#).unwrap(); + + for cqlfile in std::fs::read_dir(Path::new("resources/cql")).unwrap() { + let cqlfile = cqlfile.unwrap(); + let cqlfilename = cqlfile.file_name().to_str().unwrap().to_owned(); + let cqlcontent = std::fs::read_to_string(cqlfile.path()).unwrap(); + write!(&mut file, r####" + map.insert(r###"{cqlfilename}"###, r###"{cqlcontent}"###); + "####).unwrap(); + } + + writeln!(&mut file, " + map + }});" + ).unwrap(); +} + fn main() { build_data::set_GIT_COMMIT_SHORT(); build_data::set_GIT_DIRTY(); @@ -22,4 +48,6 @@ fn main() { build_data::set_BUILD_TIME(); build_data::no_debug_rebuilds(); println!("cargo:rustc-env=SAMPLY_USER_AGENT=Samply.Focus.{}/{}", env!("CARGO_PKG_NAME"), version()); + + build_cqlmap(); } diff --git a/resources/cql/BBMRI_STRAT_AGE_STRATIFIER b/resources/cql/BBMRI_STRAT_AGE_STRATIFIER new file mode 100644 index 0000000..9ae37fc --- /dev/null +++ b/resources/cql/BBMRI_STRAT_AGE_STRATIFIER @@ -0,0 +1,2 @@ +define AgeClass: + (AgeInYears() div 10) * 10 diff --git a/resources/cql/BBMRI_STRAT_CUSTODIAN_STRATIFIER b/resources/cql/BBMRI_STRAT_CUSTODIAN_STRATIFIER new file mode 100644 index 0000000..d7b77f6 --- /dev/null +++ b/resources/cql/BBMRI_STRAT_CUSTODIAN_STRATIFIER @@ -0,0 +1,4 @@ +define Custodian: + First(from Specimen.extension E + where E.url = 'https://fhir.bbmri.de/StructureDefinition/Custodian' + return (E.value as Reference).identifier.value) diff --git a/resources/cql/BBMRI_STRAT_DEF_IN_INITIAL_POPULATION b/resources/cql/BBMRI_STRAT_DEF_IN_INITIAL_POPULATION new file mode 100644 index 0000000..80876f8 --- /dev/null +++ b/resources/cql/BBMRI_STRAT_DEF_IN_INITIAL_POPULATION @@ -0,0 +1 @@ +define InInitialPopulation: diff --git a/resources/cql/BBMRI_STRAT_DEF_SPECIMEN b/resources/cql/BBMRI_STRAT_DEF_SPECIMEN new file mode 100644 index 0000000..2b8bf44 --- /dev/null +++ b/resources/cql/BBMRI_STRAT_DEF_SPECIMEN @@ -0,0 +1 @@ +define Specimen: diff --git a/resources/cql/BBMRI_STRAT_DIAGNOSIS_STRATIFIER b/resources/cql/BBMRI_STRAT_DIAGNOSIS_STRATIFIER new file mode 100644 index 0000000..2c1a690 --- /dev/null +++ b/resources/cql/BBMRI_STRAT_DIAGNOSIS_STRATIFIER @@ -0,0 +1,5 @@ +define Diagnosis: + if InInitialPopulation then [Condition] else {} as List + define function DiagnosisCode(condition FHIR.Condition, specimen FHIR.Specimen): + Coalesce(condition.code.coding.where(system = 'http://hl7.org/fhir/sid/icd-10').code.first(), condition.code.coding.where(system = 'http://fhir.de/CodeSystem/dimdi/icd-10-gm').code.first(), specimen.extension.where(url='https://fhir.bbmri.de/StructureDefinition/SampleDiagnosis').value.coding.code.first(), condition.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first()) + diff --git a/resources/cql/BBMRI_STRAT_GENDER_STRATIFIER b/resources/cql/BBMRI_STRAT_GENDER_STRATIFIER new file mode 100644 index 0000000..7223b2d --- /dev/null +++ b/resources/cql/BBMRI_STRAT_GENDER_STRATIFIER @@ -0,0 +1,2 @@ +define Gender: + if (Patient.gender is null) then 'unknown' else Patient.gender diff --git a/resources/cql/BBMRI_STRAT_SAMPLE_TYPE_STRATIFIER b/resources/cql/BBMRI_STRAT_SAMPLE_TYPE_STRATIFIER new file mode 100644 index 0000000..474d085 --- /dev/null +++ b/resources/cql/BBMRI_STRAT_SAMPLE_TYPE_STRATIFIER @@ -0,0 +1,45 @@ +define function SampleType(specimen FHIR.Specimen): + case FHIRHelpers.ToCode(specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').first()) + when Code 'plasma-edta' from SampleMaterialType then 'blood-plasma' + when Code 'plasma-citrat' from SampleMaterialType then 'blood-plasma' + when Code 'plasma-heparin' from SampleMaterialType then 'blood-plasma' + when Code 'plasma-cell-free' from SampleMaterialType then 'blood-plasma' + when Code 'plasma-other' from SampleMaterialType then 'blood-plasma' + when Code 'plasma' from SampleMaterialType then 'blood-plasma' + when Code 'tissue-formalin' from SampleMaterialType then 'tissue-ffpe' + when Code 'tumor-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' + when Code 'normal-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' + when Code 'other-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' + when Code 'tumor-tissue-frozen' from SampleMaterialType then 'tissue-frozen' + when Code 'normal-tissue-frozen' from SampleMaterialType then 'tissue-frozen' + when Code 'other-tissue-frozen' from SampleMaterialType then 'tissue-frozen' + when Code 'tissue-paxgene-or-else' from SampleMaterialType then 'tissue-other' + when Code 'derivative' from SampleMaterialType then 'derivative-other' + when Code 'liquid' from SampleMaterialType then 'liquid-other' + when Code 'tissue' from SampleMaterialType then 'tissue-other' + when Code 'serum' from SampleMaterialType then 'blood-serum' + when Code 'cf-dna' from SampleMaterialType then 'dna' + when Code 'g-dna' from SampleMaterialType then 'dna' + when Code 'blood-plasma' from SampleMaterialType then 'blood-plasma' + when Code 'tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' + when Code 'tissue-frozen' from SampleMaterialType then 'tissue-frozen' + when Code 'tissue-other' from SampleMaterialType then 'tissue-other' + when Code 'derivative-other' from SampleMaterialType then 'derivative-other' + when Code 'liquid-other' from SampleMaterialType then 'liquid-other' + when Code 'blood-serum' from SampleMaterialType then 'blood-serum' + when Code 'dna' from SampleMaterialType then 'dna' + when Code 'buffy-coat' from SampleMaterialType then 'buffy-coat' + when Code 'urine' from SampleMaterialType then 'urine' + when Code 'ascites' from SampleMaterialType then 'ascites' + when Code 'saliva' from SampleMaterialType then 'saliva' + when Code 'csf-liquor' from SampleMaterialType then 'csf-liquor' + when Code 'bone-marrow' from SampleMaterialType then 'bone-marrow' + when Code 'peripheral-blood-cells-vital' from SampleMaterialType then 'peripheral-blood-cells-vital' + when Code 'stool-faeces' from SampleMaterialType then 'stool-faeces' + when Code 'rna' from SampleMaterialType then 'rna' + when Code 'whole-blood' from SampleMaterialType then 'whole-blood' + when Code 'swab' from SampleMaterialType then 'swab' + when Code 'dried-whole-blood' from SampleMaterialType then 'dried-whole-blood' + when null then 'Unknown' + else 'Unknown' + end diff --git a/resources/cql/DKTK_STRAT_AGE_CLASS_STRATIFIER b/resources/cql/DKTK_STRAT_AGE_CLASS_STRATIFIER new file mode 100644 index 0000000..d673a41 --- /dev/null +++ b/resources/cql/DKTK_STRAT_AGE_CLASS_STRATIFIER @@ -0,0 +1,2 @@ +define AgeClass: +if (PrimaryDiagnosis.onset is null) then 'unknown' else ToString((AgeInYearsAt(FHIRHelpers.ToDateTime(PrimaryDiagnosis.onset)) div 10) * 10) diff --git a/resources/cql/DKTK_STRAT_AGE_STRATIFIER b/resources/cql/DKTK_STRAT_AGE_STRATIFIER new file mode 100644 index 0000000..aab0cb3 --- /dev/null +++ b/resources/cql/DKTK_STRAT_AGE_STRATIFIER @@ -0,0 +1,8 @@ +define PrimaryDiagnosis: +First( +from [Condition] C +where C.extension.where(url='http://hl7.org/fhir/StructureDefinition/condition-related').empty() +sort by date from onset asc) + +define AgeClass: +if (PrimaryDiagnosis.onset is null) then 'unknown' else ToString((AgeInYearsAt(FHIRHelpers.ToDateTime(PrimaryDiagnosis.onset)) div 10) * 10) diff --git a/resources/cql/DKTK_STRAT_DECEASED_STRATIFIER b/resources/cql/DKTK_STRAT_DECEASED_STRATIFIER new file mode 100644 index 0000000..4a4200a --- /dev/null +++ b/resources/cql/DKTK_STRAT_DECEASED_STRATIFIER @@ -0,0 +1,4 @@ +define PatientDeceased: +First (from [Observation: Code '75186-7' from loinc] O return O.value.coding.where(system = 'http://dktk.dkfz.de/fhir/onco/core/CodeSystem/VitalstatusCS').code.first()) +define Deceased: +if (PatientDeceased is null) then 'unbekannt' else PatientDeceased diff --git a/resources/cql/DKTK_STRAT_DEF_IN_INITIAL_POPULATION b/resources/cql/DKTK_STRAT_DEF_IN_INITIAL_POPULATION new file mode 100644 index 0000000..80876f8 --- /dev/null +++ b/resources/cql/DKTK_STRAT_DEF_IN_INITIAL_POPULATION @@ -0,0 +1 @@ +define InInitialPopulation: diff --git a/resources/cql/DKTK_STRAT_DIAGNOSIS_STRATIFIER b/resources/cql/DKTK_STRAT_DIAGNOSIS_STRATIFIER new file mode 100644 index 0000000..1595f84 --- /dev/null +++ b/resources/cql/DKTK_STRAT_DIAGNOSIS_STRATIFIER @@ -0,0 +1,5 @@ +define Diagnosis: +if InInitialPopulation then [Condition] else {} as List + +define function DiagnosisCode(condition FHIR.Condition): +condition.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first() diff --git a/resources/cql/DKTK_STRAT_ENCOUNTER_STRATIFIER b/resources/cql/DKTK_STRAT_ENCOUNTER_STRATIFIER new file mode 100644 index 0000000..ffd36d2 --- /dev/null +++ b/resources/cql/DKTK_STRAT_ENCOUNTER_STRATIFIER @@ -0,0 +1,5 @@ +define Encounter: +if InInitialPopulation then [Encounter] else {} as List + +define function Departments(encounter FHIR.Encounter): +encounter.identifier.where(system = 'http://dktk.dkfz.de/fhir/sid/hki-department').value.first() diff --git a/resources/cql/DKTK_STRAT_GENDER_STRATIFIER b/resources/cql/DKTK_STRAT_GENDER_STRATIFIER new file mode 100644 index 0000000..4145d04 --- /dev/null +++ b/resources/cql/DKTK_STRAT_GENDER_STRATIFIER @@ -0,0 +1,2 @@ +define Gender: +if (Patient.gender is null) then 'unknown' else Patient.gender diff --git a/resources/cql/DKTK_STRAT_HISTOLOGY_STRATIFIER b/resources/cql/DKTK_STRAT_HISTOLOGY_STRATIFIER new file mode 100644 index 0000000..2a56610 --- /dev/null +++ b/resources/cql/DKTK_STRAT_HISTOLOGY_STRATIFIER @@ -0,0 +1,5 @@ +define Histo: +if InInitialPopulation then [Observation] else {} as List + +define function Histlogoy(histo FHIR.Observation): + if histo.code.coding.where(code = '59847-4').code.first() is null then 0 else 1 diff --git a/resources/cql/DKTK_STRAT_MEDICATION_STRATIFIER b/resources/cql/DKTK_STRAT_MEDICATION_STRATIFIER new file mode 100644 index 0000000..f559800 --- /dev/null +++ b/resources/cql/DKTK_STRAT_MEDICATION_STRATIFIER @@ -0,0 +1,2 @@ +define MedicationStatement: +if InInitialPopulation then [MedicationStatement] else {} as List diff --git a/resources/cql/DKTK_STRAT_PRIMARY_DIAGNOSIS_STRATIFIER b/resources/cql/DKTK_STRAT_PRIMARY_DIAGNOSIS_STRATIFIER new file mode 100644 index 0000000..8678d2f --- /dev/null +++ b/resources/cql/DKTK_STRAT_PRIMARY_DIAGNOSIS_STRATIFIER @@ -0,0 +1,6 @@ +define PrimaryDiagnosis: +First( +from [Condition] C +where C.extension.where(url='http://hl7.org/fhir/StructureDefinition/condition-related').empty() +sort by date from onset asc) + diff --git a/resources/cql/DKTK_STRAT_PROCEDURE_STRATIFIER b/resources/cql/DKTK_STRAT_PROCEDURE_STRATIFIER new file mode 100644 index 0000000..49cb341 --- /dev/null +++ b/resources/cql/DKTK_STRAT_PROCEDURE_STRATIFIER @@ -0,0 +1,5 @@ +define Procedure: +if InInitialPopulation then [Procedure] else {} as List + +define function ProcedureType(procedure FHIR.Procedure): +procedure.category.coding.where(system = 'http://dktk.dkfz.de/fhir/onco/core/CodeSystem/SYSTTherapieartCS').code.first() diff --git a/resources/cql/DKTK_STRAT_SPECIMEN_STRATIFIER b/resources/cql/DKTK_STRAT_SPECIMEN_STRATIFIER new file mode 100644 index 0000000..6dd2216 --- /dev/null +++ b/resources/cql/DKTK_STRAT_SPECIMEN_STRATIFIER @@ -0,0 +1,5 @@ +define Specimen: +if InInitialPopulation then [Specimen] else {} as List + +define function SampleType(specimen FHIR.Specimen): +specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first() diff --git a/resources/cql/EXLIQUID_ALIQUOTS_CQL_DIAGNOSIS b/resources/cql/EXLIQUID_ALIQUOTS_CQL_DIAGNOSIS new file mode 100644 index 0000000..1abf1cd --- /dev/null +++ b/resources/cql/EXLIQUID_ALIQUOTS_CQL_DIAGNOSIS @@ -0,0 +1,3 @@ +define retrieveCondition: First(from [Condition] C return C.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first()) +define Diagnosis: if (retrieveCondition is null) then 'unknown' else retrieveCondition + diff --git a/resources/cql/EXLIQUID_ALIQUOTS_CQL_SPECIMEN b/resources/cql/EXLIQUID_ALIQUOTS_CQL_SPECIMEN new file mode 100644 index 0000000..2dcd10a --- /dev/null +++ b/resources/cql/EXLIQUID_ALIQUOTS_CQL_SPECIMEN @@ -0,0 +1,6 @@ +define Specimen: +if InInitialPopulation then [Specimen] else {} as List + define Aliquot: + [Specimen] S + where exists S.collection.quantity.value and exists S.parent.reference and S.container.specimenQuantity.value > 0 define AliquotGroupReferences: flatten Aliquot S return S.parent.reference define AliquotGroupWithAliquot: [Specimen] S where not (S.identifier.system contains 'http://dktk.dkfz.de/fhir/sid/exliquid-specimen') and not exists S.collection.quantity.value and not exists S.container.specimenQuantity.value and AliquotGroupReferences contains 'Specimen/' + S.id define PrimarySampleReferences: flatten AliquotGroupWithAliquot S return S.parent.reference define ExliquidSpecimenWithAliquot: from [Specimen] PrimarySample where PrimarySample.identifier.system contains 'http://dktk.dkfz.de/fhir/sid/exliquid-specimen' and PrimarySampleReferences contains 'Specimen/' + PrimarySample.id + define function SampleType(specimen FHIR.Specimen): specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first() diff --git a/resources/cql/EXLIQUID_CQL_DIAGNOSIS b/resources/cql/EXLIQUID_CQL_DIAGNOSIS new file mode 100644 index 0000000..1abf1cd --- /dev/null +++ b/resources/cql/EXLIQUID_CQL_DIAGNOSIS @@ -0,0 +1,3 @@ +define retrieveCondition: First(from [Condition] C return C.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first()) +define Diagnosis: if (retrieveCondition is null) then 'unknown' else retrieveCondition + diff --git a/resources/cql/EXLIQUID_CQL_SPECIMEN b/resources/cql/EXLIQUID_CQL_SPECIMEN new file mode 100644 index 0000000..c8c3f6a --- /dev/null +++ b/resources/cql/EXLIQUID_CQL_SPECIMEN @@ -0,0 +1,7 @@ +define Specimen: +if InInitialPopulation then [Specimen] else {} as List +define ExliquidSpecimen: + from [Specimen] S + where S.identifier.system contains 'http://dktk.dkfz.de/fhir/sid/exliquid-specimen' +define function SampleType(specimen FHIR.Specimen): + specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first() diff --git a/resources/cql/EXLIQUID_STRAT_DEF_IN_INITIAL_POPULATION b/resources/cql/EXLIQUID_STRAT_DEF_IN_INITIAL_POPULATION new file mode 100644 index 0000000..d83e993 --- /dev/null +++ b/resources/cql/EXLIQUID_STRAT_DEF_IN_INITIAL_POPULATION @@ -0,0 +1,3 @@ +define InInitialPopulation: + exists ExliquidSpecimen and + diff --git a/resources/cql/EXLIQUID_STRAT_W_ALIQUOTS b/resources/cql/EXLIQUID_STRAT_W_ALIQUOTS new file mode 100644 index 0000000..3cfe321 --- /dev/null +++ b/resources/cql/EXLIQUID_STRAT_W_ALIQUOTS @@ -0,0 +1,2 @@ +define InInitialPopulation: exists ExliquidSpecimenWithAliquot and + diff --git a/resources/cql/MTBA_STRAT_GENETIC_VARIANT b/resources/cql/MTBA_STRAT_GENETIC_VARIANT new file mode 100644 index 0000000..308bc3e --- /dev/null +++ b/resources/cql/MTBA_STRAT_GENETIC_VARIANT @@ -0,0 +1,2 @@ +define GeneticVariantCode: +First (from [Observation: Code '69548-6' from loinc] O return O.component.where(code.coding contains Code '48018-6' from loinc).value.coding.code.first()) diff --git a/resources/cql/UCT_STRAT_SPECIMEN_STRATIFIER b/resources/cql/UCT_STRAT_SPECIMEN_STRATIFIER new file mode 100644 index 0000000..0c58d4e --- /dev/null +++ b/resources/cql/UCT_STRAT_SPECIMEN_STRATIFIER @@ -0,0 +1,12 @@ +define Specimen: +if InInitialPopulation then [Specimen] else {} as List + +define function SampleType(specimen FHIR.Specimen): +specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first() + +define function Lagerort(specimen FHIR.Specimen): +specimen.extension.where(url = 'http://uct-locator/specimen/storage').value.coding.code.first() + +define function annotations(specimen FHIR.Specimen): +(if (specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first() is null) then 1 else 0) + +(if (specimen.collection.collected is null) then 1 else 0) diff --git a/resources/test/query_bbmri.cql b/resources/test/query_bbmri.cql new file mode 100644 index 0000000..b080800 --- /dev/null +++ b/resources/test/query_bbmri.cql @@ -0,0 +1,87 @@ +library Retrieve +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +codesystem loinc: 'http://loinc.org' + +codesystem icd10: 'http://hl7.org/fhir/sid/icd-10' + +codesystem SampleMaterialType: 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType' + +context Patient + +define AgeClass: + (AgeInYears() div 10) * 10 + + +define Gender: + if (Patient.gender is null) then 'unknown' else Patient.gender + + +define Custodian: + First(from Specimen.extension E + where E.url = 'https://fhir.bbmri.de/StructureDefinition/Custodian' + return (E.value as Reference).identifier.value) + + +define function SampleType(specimen FHIR.Specimen): + case FHIRHelpers.ToCode(specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').first()) + when Code 'plasma-edta' from SampleMaterialType then 'blood-plasma' + when Code 'plasma-citrat' from SampleMaterialType then 'blood-plasma' + when Code 'plasma-heparin' from SampleMaterialType then 'blood-plasma' + when Code 'plasma-cell-free' from SampleMaterialType then 'blood-plasma' + when Code 'plasma-other' from SampleMaterialType then 'blood-plasma' + when Code 'plasma' from SampleMaterialType then 'blood-plasma' + when Code 'tissue-formalin' from SampleMaterialType then 'tissue-ffpe' + when Code 'tumor-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' + when Code 'normal-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' + when Code 'other-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' + when Code 'tumor-tissue-frozen' from SampleMaterialType then 'tissue-frozen' + when Code 'normal-tissue-frozen' from SampleMaterialType then 'tissue-frozen' + when Code 'other-tissue-frozen' from SampleMaterialType then 'tissue-frozen' + when Code 'tissue-paxgene-or-else' from SampleMaterialType then 'tissue-other' + when Code 'derivative' from SampleMaterialType then 'derivative-other' + when Code 'liquid' from SampleMaterialType then 'liquid-other' + when Code 'tissue' from SampleMaterialType then 'tissue-other' + when Code 'serum' from SampleMaterialType then 'blood-serum' + when Code 'cf-dna' from SampleMaterialType then 'dna' + when Code 'g-dna' from SampleMaterialType then 'dna' + when Code 'blood-plasma' from SampleMaterialType then 'blood-plasma' + when Code 'tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' + when Code 'tissue-frozen' from SampleMaterialType then 'tissue-frozen' + when Code 'tissue-other' from SampleMaterialType then 'tissue-other' + when Code 'derivative-other' from SampleMaterialType then 'derivative-other' + when Code 'liquid-other' from SampleMaterialType then 'liquid-other' + when Code 'blood-serum' from SampleMaterialType then 'blood-serum' + when Code 'dna' from SampleMaterialType then 'dna' + when Code 'buffy-coat' from SampleMaterialType then 'buffy-coat' + when Code 'urine' from SampleMaterialType then 'urine' + when Code 'ascites' from SampleMaterialType then 'ascites' + when Code 'saliva' from SampleMaterialType then 'saliva' + when Code 'csf-liquor' from SampleMaterialType then 'csf-liquor' + when Code 'bone-marrow' from SampleMaterialType then 'bone-marrow' + when Code 'peripheral-blood-cells-vital' from SampleMaterialType then 'peripheral-blood-cells-vital' + when Code 'stool-faeces' from SampleMaterialType then 'stool-faeces' + when Code 'rna' from SampleMaterialType then 'rna' + when Code 'whole-blood' from SampleMaterialType then 'whole-blood' + when Code 'swab' from SampleMaterialType then 'swab' + when Code 'dried-whole-blood' from SampleMaterialType then 'dried-whole-blood' + when null then 'Unknown' + else 'Unknown' + end + + +define Specimen: + + if InInitialPopulation then [Specimen] else {} as List + +define Diagnosis: + if InInitialPopulation then [Condition] else {} as List + define function DiagnosisCode(condition FHIR.Condition, specimen FHIR.Specimen): + Coalesce(condition.code.coding.where(system = 'http://hl7.org/fhir/sid/icd-10').code.first(), condition.code.coding.where(system = 'http://fhir.de/CodeSystem/dimdi/icd-10-gm').code.first(), specimen.extension.where(url='https://fhir.bbmri.de/StructureDefinition/SampleDiagnosis').value.coding.code.first(), condition.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first()) + + + +define InInitialPopulation: + +true \ No newline at end of file diff --git a/resources/test/query_bbmri_placeholders.cql b/resources/test/query_bbmri_placeholders.cql new file mode 100644 index 0000000..f356da6 --- /dev/null +++ b/resources/test/query_bbmri_placeholders.cql @@ -0,0 +1,27 @@ +library Retrieve +using FHIR version '4.0.0' +include FHIRHelpers version '4.0.0' + +codesystem loinc: 'http://loinc.org' + +codesystem icd10: 'http://hl7.org/fhir/sid/icd-10' + +codesystem SampleMaterialType: 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType' + +context Patient + +BBMRI_STRAT_AGE_STRATIFIER + +BBMRI_STRAT_GENDER_STRATIFIER + +BBMRI_STRAT_CUSTODIAN_STRATIFIER + +BBMRI_STRAT_SAMPLE_TYPE_STRATIFIER + +BBMRI_STRAT_DEF_SPECIMEN + if InInitialPopulation then [Specimen] else {} as List + +BBMRI_STRAT_DIAGNOSIS_STRATIFIER + +BBMRI_STRAT_DEF_IN_INITIAL_POPULATION +true \ No newline at end of file diff --git a/src/exporter.rs b/src/exporter.rs index 109b71b..cfa3353 100644 --- a/src/exporter.rs +++ b/src/exporter.rs @@ -36,13 +36,13 @@ pub async fn post_exporter_query(body: &String, execute: bool) -> Result Result, FocusError> { Ok(decoded) } -fn parse_blaze_cql_query(task: &BeamTask) -> Result { +fn parse_blaze_query(task: &BeamTask) -> Result { let decoded = decode_body(task)?; let query: blaze::CqlQuery = @@ -364,7 +365,10 @@ async fn run_cql_query( Ok(result) } -async fn run_intermediate_rep_query(task: &BeamTask, ast: ast::Ast) -> Result { +async fn run_intermediate_rep_query( + task: &BeamTask, + ast: ast::Ast, +) -> Result { let mut err = beam::beam_result::perm_failed( CONFIG.beam_app_id_long.clone(), vec![task.to_owned().from], @@ -383,8 +387,11 @@ async fn run_intermediate_rep_query(task: &BeamTask, ast: ast::Ast) -> Result Result Result>, Ok(io::BufReader::new(file).lines()) } -pub(crate) fn replace_cql(decoded_library: impl Into) -> String { - let replace_map: HashMap<&str, &str> = - [ - ("BBMRI_STRAT_GENDER_STRATIFIER", - "define Gender: - if (Patient.gender is null) then 'unknown' else Patient.gender" - ), - ("BBMRI_STRAT_SAMPLE_TYPE_STRATIFIER", "define function SampleType(specimen FHIR.Specimen):\n case FHIRHelpers.ToCode(specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').first())\n when Code 'plasma-edta' from SampleMaterialType then 'blood-plasma' \n when Code 'plasma-citrat' from SampleMaterialType then 'blood-plasma' \n when Code 'plasma-heparin' from SampleMaterialType then 'blood-plasma' \n when Code 'plasma-cell-free' from SampleMaterialType then 'blood-plasma' \n when Code 'plasma-other' from SampleMaterialType then 'blood-plasma' \n when Code 'plasma' from SampleMaterialType then 'blood-plasma' \n when Code 'tissue-formalin' from SampleMaterialType then 'tissue-ffpe' \n when Code 'tumor-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' \n when Code 'normal-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' \n when Code 'other-tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' \n when Code 'tumor-tissue-frozen' from SampleMaterialType then 'tissue-frozen' \n when Code 'normal-tissue-frozen' from SampleMaterialType then 'tissue-frozen' \n when Code 'other-tissue-frozen' from SampleMaterialType then 'tissue-frozen' \n when Code 'tissue-paxgene-or-else' from SampleMaterialType then 'tissue-other' \n when Code 'derivative' from SampleMaterialType then 'derivative-other' \n when Code 'liquid' from SampleMaterialType then 'liquid-other' \n when Code 'tissue' from SampleMaterialType then 'tissue-other' \n when Code 'serum' from SampleMaterialType then 'blood-serum' \n when Code 'cf-dna' from SampleMaterialType then 'dna' \n when Code 'g-dna' from SampleMaterialType then 'dna' \n when Code 'blood-plasma' from SampleMaterialType then 'blood-plasma' \n when Code 'tissue-ffpe' from SampleMaterialType then 'tissue-ffpe' \n when Code 'tissue-frozen' from SampleMaterialType then 'tissue-frozen' \n when Code 'tissue-other' from SampleMaterialType then 'tissue-other' \n when Code 'derivative-other' from SampleMaterialType then 'derivative-other' \n when Code 'liquid-other' from SampleMaterialType then 'liquid-other' \n when Code 'blood-serum' from SampleMaterialType then 'blood-serum' \n when Code 'dna' from SampleMaterialType then 'dna' \n when Code 'buffy-coat' from SampleMaterialType then 'buffy-coat' \n when Code 'urine' from SampleMaterialType then 'urine' \n when Code 'ascites' from SampleMaterialType then 'ascites' \n when Code 'saliva' from SampleMaterialType then 'saliva' \n when Code 'csf-liquor' from SampleMaterialType then 'csf-liquor' \n when Code 'bone-marrow' from SampleMaterialType then 'bone-marrow' \n when Code 'peripheral-blood-cells-vital' from SampleMaterialType then 'peripheral-blood-cells-vital' \n when Code 'stool-faeces' from SampleMaterialType then 'stool-faeces' \n when Code 'rna' from SampleMaterialType then 'rna' \n when Code 'whole-blood' from SampleMaterialType then 'whole-blood' \n when Code 'swab' from SampleMaterialType then 'swab' \n when Code 'dried-whole-blood' from SampleMaterialType then 'dried-whole-blood' \n when null then 'Unknown'\n else 'Unknown'\n end"), - ("BBMRI_STRAT_CUSTODIAN_STRATIFIER", "define Custodian:\n First(from Specimen.extension E\n where E.url = 'https://fhir.bbmri.de/StructureDefinition/Custodian'\n return (E.value as Reference).identifier.value)"), - ("BBMRI_STRAT_DIAGNOSIS_STRATIFIER", "define Diagnosis:\n if InInitialPopulation then [Condition] else {} as List \n define function DiagnosisCode(condition FHIR.Condition, specimen FHIR.Specimen):\n Coalesce(condition.code.coding.where(system = 'http://hl7.org/fhir/sid/icd-10').code.first(), condition.code.coding.where(system = 'http://fhir.de/CodeSystem/dimdi/icd-10-gm').code.first(), specimen.extension.where(url='https://fhir.bbmri.de/StructureDefinition/SampleDiagnosis').value.coding.code.first(), condition.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first())\n"), - ("BBMRI_STRAT_AGE_STRATIFIER", "define AgeClass:\n (AgeInYears() div 10) * 10"), - ("BBMRI_STRAT_DEF_SPECIMEN", "define Specimen:"), - ("BBMRI_STRAT_DEF_IN_INITIAL_POPULATION", "define InInitialPopulation:"), - ("EXLIQUID_CQL_DIAGNOSIS", "define retrieveCondition: First(from [Condition] C return C.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first())\ndefine Diagnosis: if (retrieveCondition is null) then 'unknown' else retrieveCondition\n"), - ("EXLIQUID_CQL_SPECIMEN", "define Specimen:\nif InInitialPopulation then [Specimen] else {} as List\ndefine ExliquidSpecimen:\n from [Specimen] S\n where S.identifier.system contains 'http://dktk.dkfz.de/fhir/sid/exliquid-specimen'\ndefine function SampleType(specimen FHIR.Specimen):\n specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first()"), - ("EXLIQUID_ALIQUOTS_CQL_DIAGNOSIS", "define retrieveCondition: First(from [Condition] C return C.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first())\ndefine Diagnosis: if (retrieveCondition is null) then 'unknown' else retrieveCondition\n"), - ("EXLIQUID_ALIQUOTS_CQL_SPECIMEN", "define Specimen:\nif InInitialPopulation then [Specimen] else {} as List\n define Aliquot:\n [Specimen] S\n where exists S.collection.quantity.value and exists S.parent.reference and S.container.specimenQuantity.value > 0 define AliquotGroupReferences: flatten Aliquot S return S.parent.reference define AliquotGroupWithAliquot: [Specimen] S where not (S.identifier.system contains 'http://dktk.dkfz.de/fhir/sid/exliquid-specimen') and not exists S.collection.quantity.value and not exists S.container.specimenQuantity.value and AliquotGroupReferences contains 'Specimen/' + S.id define PrimarySampleReferences: flatten AliquotGroupWithAliquot S return S.parent.reference define ExliquidSpecimenWithAliquot: from [Specimen] PrimarySample where PrimarySample.identifier.system contains 'http://dktk.dkfz.de/fhir/sid/exliquid-specimen' and PrimarySampleReferences contains 'Specimen/' + PrimarySample.id \n define function SampleType(specimen FHIR.Specimen): specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first()"), - ("DKTK_STRAT_GENDER_STRATIFIER", "define Gender:\nif (Patient.gender is null) then 'unknown' else Patient.gender"), - ("DKTK_STRAT_AGE_STRATIFIER", "define PrimaryDiagnosis:\nFirst(\nfrom [Condition] C\nwhere C.extension.where(url='http://hl7.org/fhir/StructureDefinition/condition-related').empty()\nsort by date from onset asc)\n\ndefine AgeClass:\nif (PrimaryDiagnosis.onset is null) then 'unknown' else ToString((AgeInYearsAt(FHIRHelpers.ToDateTime(PrimaryDiagnosis.onset)) div 10) * 10)"), - ("DKTK_STRAT_PRIMARY_DIAGNOSIS_STRATIFIER", "define PrimaryDiagnosis:\nFirst(\nfrom [Condition] C\nwhere C.extension.where(url='http://hl7.org/fhir/StructureDefinition/condition-related').empty()\nsort by date from onset asc)\n"), - ("DKTK_STRAT_AGE_CLASS_STRATIFIER", "define AgeClass:\nif (PrimaryDiagnosis.onset is null) then 'unknown' else ToString((AgeInYearsAt(FHIRHelpers.ToDateTime(PrimaryDiagnosis.onset)) div 10) * 10)"), - ("DKTK_STRAT_DECEASED_STRATIFIER", "define PatientDeceased:\nFirst (from [Observation: Code '75186-7' from loinc] O return O.value.coding.where(system = 'http://dktk.dkfz.de/fhir/onco/core/CodeSystem/VitalstatusCS').code.first())\ndefine Deceased:\nif (PatientDeceased is null) then 'unbekannt' else PatientDeceased"), - ("DKTK_STRAT_DIAGNOSIS_STRATIFIER", "define Diagnosis:\nif InInitialPopulation then [Condition] else {} as List\n\ndefine function DiagnosisCode(condition FHIR.Condition):\ncondition.code.coding.where(system = 'http://fhir.de/CodeSystem/bfarm/icd-10-gm').code.first()"), - ("DKTK_STRAT_SPECIMEN_STRATIFIER", "define Specimen:\nif InInitialPopulation then [Specimen] else {} as List\n\ndefine function SampleType(specimen FHIR.Specimen):\nspecimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first()"), - ("UCT_STRAT_SPECIMEN_STRATIFIER", "define Specimen:\nif InInitialPopulation then [Specimen] else {} as List\n\ndefine function SampleType(specimen FHIR.Specimen):\nspecimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first()\n\ndefine function Lagerort(specimen FHIR.Specimen):\nspecimen.extension.where(url = 'http://uct-locator/specimen/storage').value.coding.code.first()\n\ndefine function annotations(specimen FHIR.Specimen):\n(if (specimen.type.coding.where(system = 'https://fhir.bbmri.de/CodeSystem/SampleMaterialType').code.first() is null) then 1 else 0) +\n(if (specimen.collection.collected is null) then 1 else 0)"), - ("DKTK_STRAT_PROCEDURE_STRATIFIER", "define Procedure:\nif InInitialPopulation then [Procedure] else {} as List \n\ndefine function ProcedureType(procedure FHIR.Procedure):\nprocedure.category.coding.where(system = 'http://dktk.dkfz.de/fhir/onco/core/CodeSystem/SYSTTherapieartCS').code.first()"), - ("DKTK_STRAT_MEDICATION_STRATIFIER", "define MedicationStatement:\nif InInitialPopulation then [MedicationStatement] else {} as List "), - ("DKTK_STRAT_ENCOUNTER_STRATIFIER", "define Encounter:\nif InInitialPopulation then [Encounter] else {} as List\n\ndefine function Departments(encounter FHIR.Encounter):\nencounter.identifier.where(system = 'http://dktk.dkfz.de/fhir/sid/hki-department').value.first()"), - ("DKTK_STRAT_HISTOLOGY_STRATIFIER", "define Histo:\nif InInitialPopulation then [Observation] else {} as List \n\ndefine function Histlogoy(histo FHIR.Observation):\n if histo.code.coding.where(code = '59847-4').code.first() is null then 0 else 1"), - ("DKTK_STRAT_DEF_IN_INITIAL_POPULATION", "define InInitialPopulation:"), - ("EXLIQUID_STRAT_DEF_IN_INITIAL_POPULATION", "define InInitialPopulation:\n exists ExliquidSpecimen and\n"), - ("EXLIQUID_STRAT_W_ALIQUOTS", "define InInitialPopulation: exists ExliquidSpecimenWithAliquot and \n"), - ("MTBA_STRAT_GENETIC_VARIANT", "define GeneticVariantCode:\nFirst (from [Observation: Code '69548-6' from loinc] O return O.component.where(code.coding contains Code '48018-6' from loinc).value.coding.code.first())") - ].into(); +// REPLACE_MAP is built in build.rs +include!(concat!(env!("OUT_DIR"), "/replace_map.rs")); +pub(crate) fn replace_cql(decoded_library: impl Into) -> String { let mut decoded_library = decoded_library.into(); - for (key, value) in replace_map { - let replacement_value = value.to_string() + "\n"; - decoded_library = decoded_library.replace(key, &replacement_value[..]); + for (key, value) in REPLACE_MAP.iter() { + decoded_library = decoded_library.replace(key, &value[..]); } decoded_library } @@ -346,6 +314,9 @@ mod test { use super::*; use serde_json::json; + const QUERY_BBMRI_PLACEHOLDERS: &str = + include_str!("../resources/test/query_bbmri_placeholders.cql"); + const QUERY_BBMRI: &str = include_str!("../resources/test/query_bbmri.cql"); const EXAMPLE_MEASURE_REPORT_BBMRI: &str = include_str!("../resources/measure_report_bbmri.json"); const EXAMPLE_MEASURE_REPORT_DKTK: &str = include_str!("../resources/measure_report_dktk.json"); @@ -432,8 +403,15 @@ mod test { #[test] fn test_replace_cql() { + + + let decoded_library = QUERY_BBMRI_PLACEHOLDERS; + let expected_result = QUERY_BBMRI; + assert_eq!(replace_cql(decoded_library), expected_result); + + let decoded_library = "BBMRI_STRAT_GENDER_STRATIFIER"; - let expected_result = "define Gender:\n if (Patient.gender is null) then 'unknown' else Patient.gender\n"; + let expected_result = "define Gender:\n if (Patient.gender is null) then 'unknown' else Patient.gender\n"; assert_eq!(replace_cql(decoded_library), expected_result); let decoded_library = "BBMRI_STRAT_CUSTODIAN_STRATIFIER"; @@ -502,6 +480,7 @@ mod test { let decoded_library = "INVALID_KEY"; let expected_result = "INVALID_KEY"; assert_eq!(replace_cql(decoded_library), expected_result); + } #[test]