diff --git a/src/main/java/org/opencds/cqf/tooling/modelinfo/ClassInfoBuilder.java b/src/main/java/org/opencds/cqf/tooling/modelinfo/ClassInfoBuilder.java index 2073be775..33ff99a76 100644 --- a/src/main/java/org/opencds/cqf/tooling/modelinfo/ClassInfoBuilder.java +++ b/src/main/java/org/opencds/cqf/tooling/modelinfo/ClassInfoBuilder.java @@ -1457,7 +1457,7 @@ private Boolean isContentReferenceTypeSpecifier(TypeSpecifier typeSpecifier) { } else if (typeSpecifier instanceof ListTypeSpecifier) { ListTypeSpecifier lts = (ListTypeSpecifier) typeSpecifier; - if (lts.getElementType().startsWith("#")) { + if (lts.getElementType() != null && lts.getElementType().startsWith("#")) { return true; } else if (lts.getElementTypeSpecifier() != null) { @@ -1477,7 +1477,7 @@ private String getContentReference(TypeSpecifier typeSpecifier) { } else if (typeSpecifier instanceof ListTypeSpecifier) { ListTypeSpecifier lts = (ListTypeSpecifier) typeSpecifier; - if (lts.getElementType().startsWith("#")) { + if (lts.getElementType() != null && lts.getElementType().startsWith("#")) { return lts.getElementType(); } else if (lts.getElementTypeSpecifier() != null) { diff --git a/src/main/java/org/opencds/cqf/tooling/modelinfo/ModelInfoSettings.java b/src/main/java/org/opencds/cqf/tooling/modelinfo/ModelInfoSettings.java index b6032c55f..dfcf4db3e 100644 --- a/src/main/java/org/opencds/cqf/tooling/modelinfo/ModelInfoSettings.java +++ b/src/main/java/org/opencds/cqf/tooling/modelinfo/ModelInfoSettings.java @@ -1,6 +1,8 @@ package org.opencds.cqf.tooling.modelinfo; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import org.hl7.elm_modelinfo.r1.ConversionInfo; @@ -12,6 +14,7 @@ public class ModelInfoSettings { public String patientClassName; public String patientBirthDatePropertyName; public String targetQualifier; + //public Map primarySearchPath = new HashMap(); public Collection conversionInfos; diff --git a/src/main/java/org/opencds/cqf/tooling/modelinfo/SearchInfoBuilder.java b/src/main/java/org/opencds/cqf/tooling/modelinfo/SearchInfoBuilder.java new file mode 100644 index 000000000..24e7a0f29 --- /dev/null +++ b/src/main/java/org/opencds/cqf/tooling/modelinfo/SearchInfoBuilder.java @@ -0,0 +1,150 @@ +package org.opencds.cqf.tooling.modelinfo; + +import org.hl7.elm_modelinfo.r1.*; +import org.hl7.fhir.r4.model.*; + +import java.util.*; + +public class SearchInfoBuilder { + protected Atlas atlas; + protected Map typeInfos; + protected ModelInfoSettings settings; + + public SearchInfoBuilder(ModelInfoSettings settings, Atlas atlas, Map typeInfos) { + this.settings = settings; + this.atlas = atlas; + this.typeInfos = typeInfos; + } + + public void build() { + this.innerBuild(); + } + + protected SearchInfo buildSearchInfo(SearchParameter sp, String resourceType) { + // Do not add search infos for types that cannot be resolved + String typeName = this.settings.name + "." + resourceType; + TypeInfo baseType = typeInfos.get(typeName); + if (baseType == null) { + return null; + } + + // Do not add search infos for non-class types (Should never be a thing) + if (!(baseType instanceof ClassInfo)) { + return null; + } + + // TODO: Consider supporting non-computable search parameters? + // Do not add search infos for search parameters without an expression, not computable... + if (!sp.hasExpression()) { + return null; + } + + ClassInfo ci = (ClassInfo)baseType; + + SearchInfo si = new SearchInfo(); + si.setName(sp.getCode()); + + // Path syntax expectations: + // . (| .)* + List typePaths = new ArrayList(); + for (String path : sp.getExpression().split("\\|")) { + path = path.trim(); + if (path.startsWith(ci.getName() + ".")) { + typePaths.add(path.substring(path.indexOf(".") + 1)); + } + } + + // Do not add search infos for search parameters without paths matching the base type. + if (typePaths.size() == 0) { + return null; + } + + si.setPath(String.join("|", typePaths)); + + switch (sp.getType()) { + case NUMBER: si.setType("System.Decimal"); break; + case DATE: si.setType("System.DateTime"); break; + case STRING: si.setType("System.String"); break; + case TOKEN: si.setType("System.Code"); break; + case REFERENCE: { + if (sp.getTarget() != null) { + List targets = new ArrayList(); + for (CodeType code : sp.getTarget()) { + TypeInfo ti = typeInfos.get(String.format("%s.%s", settings.name, code.getCode())); + if (ti != null && ti instanceof ClassInfo) { + targets.add(new NamedTypeSpecifier().withNamespace(((ClassInfo)ti).getNamespace()).withName(((ClassInfo)ti).getName())); + } + } + if (targets.size() > 1) { + si.setTypeSpecifier(new ChoiceTypeSpecifier().withChoice(targets)); + } + else if (targets.size() == 1) { + NamedTypeSpecifier ts = (NamedTypeSpecifier)targets.get(0); + si.setType(String.format("%s.%s", ts.getNamespace(), ts.getName())); + } + } + + if (si.getTypeSpecifier() == null && si.getType() == null) { + si.setType("FHIR.Reference"); + } + } + break; + case QUANTITY: si.setType("System.Quantity"); break; + case URI: si.setType("System.String"); break; + case SPECIAL: si.setType("System.Any"); break; + } + + ci.getSearch().add(si); + + return si; + } + + protected Iterable buildSearchInfo(SearchParameter sp) { + // TODO: Composite search parameter support? + if (sp.getType() == Enumerations.SearchParamType.COMPOSITE) { + return null; + } + + List sis = new ArrayList(); + for (CodeType resourceType : sp.getBase()) { + SearchInfo si = buildSearchInfo(sp, resourceType.getCode()); + if (si != null) { + sis.add(si); + } + } + + return sis; + } + +/* + private String primarySearchPath(Iterable searches, String typeName) { + if (this.settings.primarySearchPath.containsKey(typeName)) { + return this.settings.primarySearchPath.get(typeName); + } + else if (searches != null) { + for (SearchInfo s : searches) { + if (s.getName().toLowerCase().equals("code")) { + return s.getName(); + } + } + } + + return null; + } + */ + + protected void innerBuild() { + for (SearchParameter sp : atlas.getSearchParameters().values()) { + Iterable sis = buildSearchInfo(sp); + // NOTE: There is nothing to add here, search infos are added to the classinfos they are part of + } +/* + for (TypeInfo ti : typeInfos.values()) { + if (ti instanceof ClassInfo) { + ClassInfo ci = (ClassInfo)ti; + ci.setPrimarySearchPath(primarySearchPath(ci.getSearch(), ci.getName())); + } + } +*/ + } +} diff --git a/src/main/java/org/opencds/cqf/tooling/modelinfo/StructureDefinitionToModelInfo.java b/src/main/java/org/opencds/cqf/tooling/modelinfo/StructureDefinitionToModelInfo.java index 819961807..20c584372 100644 --- a/src/main/java/org/opencds/cqf/tooling/modelinfo/StructureDefinitionToModelInfo.java +++ b/src/main/java/org/opencds/cqf/tooling/modelinfo/StructureDefinitionToModelInfo.java @@ -55,10 +55,10 @@ public class StructureDefinitionToModelInfo extends Operation { Examples: // Build the base pure-FHIR model - mvn exec:java -Dexec.args"-GenerateMIs -ip=C:\Users\Bryn\Documents\Src\HL7\FHIR-Spec -rp=4.0.1 -mn=FHIR -mv=4.0.1" -im=true + mvn exec:java -Dexec.args"-GenerateMIs -ip=C:\Users\Bryn\Documents\Src\HL7\FHIR-Spec -rp=4.0.1 -mn=FHIR -mv=4.0.1 -im=true" // Build the base simple-FHIR model - mvn exec:java -Dexec.args"-GenerateMIs -ip=C:\Users\Bryn\Documents\Src\HL7\FHIR-Spec -rp=4.0.1 -mn=FHIR -mv=4.0.1-S" -ucp=true + mvn exec:java -Dexec.args"-GenerateMIs -ip=C:\Users\Bryn\Documents\Src\HL7\FHIR-Spec -rp=4.0.1 -mn=FHIR -mv=4.0.1-S -ucp=true" inputPath: Path to the folder containing spec directories If not specified, defaults to a peer directory named FHIR-Spec diff --git a/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRClassInfoSettings.java b/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRClassInfoSettings.java index d675b6c6a..9696eba33 100644 --- a/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRClassInfoSettings.java +++ b/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRClassInfoSettings.java @@ -135,13 +135,11 @@ public FHIRClassInfoSettings() { put("OperationOutcome", "issue.code"); put("PractitionerRole", "code"); put("Procedure", "code"); - put("ProcedureRequest", "code"); put("Questionnaire", "name"); - put("ReferralRequest", "type"); put("RelatedPerson", "relationship"); put("RiskAssessment", "code"); put("SearchParameter", "target"); - put("Sequence", "type"); + put("ServiceRequest", "code"); put("Specimen", "type"); put("Substance", "code"); put("SupplyDelivery", "type"); diff --git a/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRModelInfoBuilder.java b/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRModelInfoBuilder.java index ddaab9829..f34d746e1 100644 --- a/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRModelInfoBuilder.java +++ b/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRModelInfoBuilder.java @@ -14,15 +14,18 @@ import org.opencds.cqf.tooling.modelinfo.Atlas; import org.opencds.cqf.tooling.modelinfo.ContextInfoBuilder; import org.opencds.cqf.tooling.modelinfo.ModelInfoBuilder; +import org.opencds.cqf.tooling.modelinfo.SearchInfoBuilder; public class FHIRModelInfoBuilder extends ModelInfoBuilder { private String fhirHelpersPath; + private SearchInfoBuilder searchInfoBuilder; private ContextInfoBuilder contextInfoBuilder; public FHIRModelInfoBuilder(String version, Map typeInfos, Atlas atlas, String fhirHelpersPath) { super(typeInfos.values()); this.fhirHelpersPath = fhirHelpersPath; this.settings = new FHIRModelInfoSettings(version); + this.searchInfoBuilder = new SearchInfoBuilder(settings, atlas, typeInfos); this.contextInfoBuilder = new ContextInfoBuilder(settings, atlas, typeInfos); } @@ -57,7 +60,16 @@ protected void beforeBuild() { // TODO: File naming? try { PrintWriter pw = new PrintWriter(this.fhirHelpersPath); - pw.println(String.format("library FHIRHelpers version '%s'\n", this.settings.version) + + pw.println( + "/*\n" + + "@author: Bryn Rhodes\n" + + "@description: This library defines functions to convert between FHIR \n" + + " data types and CQL system-defined types, as well as functions to support\n" + + " FHIRPath implementation. For more information, the FHIRHelpers wiki page:\n" + + " https://github.com/cqframework/clinical_quality_language/wiki/FHIRHelpers\n" + + "@allowFluent: true\n" + + "*/\n" + + String.format("library FHIRHelpers version '%s'\n", this.settings.version) + "\n" + String.format("using FHIR version '%s'\n", this.settings.version) + "\n" + @@ -166,8 +178,15 @@ protected void beforeBuild() { " display: concept.text.value\n" + " }\n" + "\n" + + "define function reference(reference String):\n" + + " if reference is null then\n" + + " null\n" + + " else\n" + + " Reference { reference: string { value: reference } }\n" + + "\n" + + "define function resolve(reference String) returns Resource: external\n" + "define function resolve(reference Reference) returns Resource: external\n" + - "define function references(reference Reference, resource Resource) returns Boolean: external\n" + + "define function reference(resource Resource) returns Reference: external\n" + "define function extension(element Element, url String) returns List: external\n" + "define function extension(resource Resource, url String) returns List: external\n" + "define function hasValue(element Element) returns Boolean: external\n" + @@ -200,6 +219,8 @@ protected void beforeBuild() { @Override protected ModelInfo afterBuild(ModelInfo mi) { + // Build search infos (attaches SearchInfo to the existing TypeInfos) + this.searchInfoBuilder.build(); mi.withContextInfo(this.contextInfoBuilder.build().values()); // Apply fixups return mi; diff --git a/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRModelInfoSettings.java b/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRModelInfoSettings.java index 865511a1f..b6fe7c34f 100644 --- a/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRModelInfoSettings.java +++ b/src/main/java/org/opencds/cqf/tooling/modelinfo/fhir/FHIRModelInfoSettings.java @@ -1,6 +1,7 @@ package org.opencds.cqf.tooling.modelinfo.fhir; import java.util.ArrayList; +import java.util.HashMap; import org.hl7.elm_modelinfo.r1.ConversionInfo; import org.opencds.cqf.tooling.modelinfo.ModelInfoSettings; @@ -37,6 +38,72 @@ public FHIRModelInfoSettings(String version) { .withFunctionName("FHIRHelpers.ToRatio")); } }; - +/* + this.primarySearchPath = new HashMap() { + { + put("Account", "type"); + put("ActivityDefinition", "context"); + put("AdverseEvent", "event"); + put("AllergyIntolerance", "code"); + put("Appointment", "service-type"); + put("Basic", "code"); + put("BodyStructure", "location"); + put("CarePlan", "category"); + put("CareTeam", "category"); + put("ChargeItemDefinition", "context"); + put("Claim", "use"); + put("ClinicalImpression", "status"); // TODO: Should request a search parameter on clinical impression by code + put("CodeSystem", "context"); + put("Communication", "category"); + put("CommunicationRequest", "category"); + put("CompartmentDefinition", "context"); + put("Composition", "type"); + put("Condition", "code"); + put("Consent", "category"); + put("Coverage", "type"); + put("DetectedIssue", "code"); + put("Device", "type"); + put("DeviceMetric", "type"); + put("DeviceRequest", "code"); + put("DeviceUseStatement", "device"); + put("DiagnosticReport", "code"); + put("Encounter", "type"); + put("EpisodeOfCare", "type"); + put("ExplanationOfBenefit", "status"); // TODO: Should request a search parameter on explanation of benefit by type + put("Flag", "identifier"); // TODO: Should request a search parameter on Flag by code + put("Goal", "category"); + put("GuidanceResponse", "identifier"); // TODO: Should request a search parameter on GuidanceResponse by module + put("HealthcareService", "service-type"); + put("Immunization", "vaccine-code"); + put("Library", "context"); + put("Location", "type"); + put("Measure", "context"); + put("MeasureReport", "measure"); + put("Medication", "code"); + put("MedicationAdministration", "medication"); + put("MedicationDispense", "medication"); + put("MedicationRequest", "medication"); + put("MedicationStatement", "medication"); + put("MessageDefinition", "context"); + put("Observation", "code"); + //put("OperationOutcome", "issue.code"); + put("PlanDefinition", "context"); + put("PractitionerRole", "role"); + put("Procedure", "code"); + put("Questionnaire", "context"); + put("RelatedPerson", "relationship"); + put("RiskAssessment", "method"); // TODO: Request a search parameter by code + put("SearchParameter", "context"); + put("ServiceRequest", "code"); + put("Specimen", "type"); + put("StructureDefinition", "context"); + put("Substance", "code"); + put("SupplyDelivery", "status"); // TODO: Request a searchparameter by type + put("SupplyRequest", "category"); + put("Task", "code"); + put("ValueSet", "context"); + } + }; +*/ } } \ No newline at end of file