Skip to content

Commit

Permalink
Merge pull request #210 from cqframework/feature-203-search-parameters
Browse files Browse the repository at this point in the history
#203: Add support for search parameters in the model info
  • Loading branch information
JPercival authored Mar 30, 2021
2 parents beccb1e + 26bace6 commit e6d3cce
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -12,6 +14,7 @@ public class ModelInfoSettings {
public String patientClassName;
public String patientBirthDatePropertyName;
public String targetQualifier;
//public Map<String, String> primarySearchPath = new HashMap<String, String>();

public Collection<ConversionInfo> conversionInfos;

Expand Down
150 changes: 150 additions & 0 deletions src/main/java/org/opencds/cqf/tooling/modelinfo/SearchInfoBuilder.java
Original file line number Diff line number Diff line change
@@ -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<String, TypeInfo> typeInfos;
protected ModelInfoSettings settings;

public SearchInfoBuilder(ModelInfoSettings settings, Atlas atlas, Map<String, TypeInfo> 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:
// <type>.<property> (| <type>.<property>)*
List<String> typePaths = new ArrayList<String>();
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<TypeSpecifier> targets = new ArrayList<TypeSpecifier>();
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<SearchInfo> buildSearchInfo(SearchParameter sp) {
// TODO: Composite search parameter support?
if (sp.getType() == Enumerations.SearchParamType.COMPOSITE) {
return null;
}

List<SearchInfo> sis = new ArrayList<SearchInfo>();
for (CodeType resourceType : sp.getBase()) {
SearchInfo si = buildSearchInfo(sp, resourceType.getCode());
if (si != null) {
sis.add(si);
}
}

return sis;
}

/*
private String primarySearchPath(Iterable<SearchInfo> 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<SearchInfo> 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()));
}
}
*/
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, TypeInfo> 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);
}

Expand Down Expand Up @@ -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" +
Expand Down Expand Up @@ -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<Element>: external\n" +
"define function extension(resource Resource, url String) returns List<Element>: external\n" +
"define function hasValue(element Element) returns Boolean: external\n" +
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -37,6 +38,72 @@ public FHIRModelInfoSettings(String version) {
.withFunctionName("FHIRHelpers.ToRatio"));
}
};

/*
this.primarySearchPath = new HashMap<String, String>() {
{
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");
}
};
*/
}
}

0 comments on commit e6d3cce

Please sign in to comment.