Refactor prefetch generation out of discovery service. Add support for CRMI extensions.
This commit is contained in:
parent
90201095c4
commit
09eb56e2db
|
@ -24,6 +24,11 @@ public class CdsCrConstants {
|
|||
|
||||
public static final String CDS_CR_MODULE_ID = "CR";
|
||||
|
||||
public static final String CRMI_EFFECTIVE_DATA_REQUIREMENTS =
|
||||
"http://hl7.org/fhir/uv/crmi/StructureDefinition/crmi-effectiveDataRequirements";
|
||||
|
||||
public static final String CQF_FHIR_QUERY_PATTERN = "http://hl7.org/fhir/StructureDefinition/cqf-fhirQueryPattern";
|
||||
|
||||
// CDS Hook field names
|
||||
public static final String CDS_PARAMETER_USER_ID = "userId";
|
||||
public static final String CDS_PARAMETER_PATIENT_ID = "patientId";
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - CDS Hooks
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||
|
||||
import org.opencds.cqf.fhir.api.Repository;
|
||||
|
||||
public abstract class BasePrefetchTemplateBuilder {
|
||||
protected final String PATIENT_ID_CONTEXT = "{{context.patientId}}";
|
||||
protected final int DEFAULT_MAX_URI_LENGTH = 8000;
|
||||
protected int myMaxUriLength;
|
||||
|
||||
protected final Repository myRepository;
|
||||
|
||||
public BasePrefetchTemplateBuilder(Repository theRepository) {
|
||||
myRepository = theRepository;
|
||||
myMaxUriLength = DEFAULT_MAX_URI_LENGTH;
|
||||
}
|
||||
}
|
|
@ -23,32 +23,24 @@ import ca.uhn.fhir.context.FhirVersionEnum;
|
|||
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
||||
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrUtils;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.DataRequirement;
|
||||
import org.hl7.fhir.dstu3.model.Library;
|
||||
import org.hl7.fhir.dstu3.model.PlanDefinition;
|
||||
import org.hl7.fhir.dstu3.model.StringType;
|
||||
import org.hl7.fhir.dstu3.model.ValueSet;
|
||||
import org.hl7.fhir.dstu3.model.TriggerDefinition;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.opencds.cqf.fhir.api.Repository;
|
||||
import org.opencds.cqf.fhir.utility.dstu3.SearchHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CrDiscoveryServiceDstu3 implements ICrDiscoveryService {
|
||||
|
||||
protected final String PATIENT_ID_CONTEXT = "{{context.patientId}}";
|
||||
protected final int DEFAULT_MAX_URI_LENGTH = 8000;
|
||||
protected int myMaxUriLength;
|
||||
|
||||
protected Repository myRepository;
|
||||
protected final Repository myRepository;
|
||||
protected final IIdType myPlanDefinitionId;
|
||||
protected final PrefetchTemplateBuilderDstu3 myPrefetchTemplateBuilder;
|
||||
|
||||
public CrDiscoveryServiceDstu3(IIdType thePlanDefinitionId, Repository theRepository) {
|
||||
myPlanDefinitionId = thePlanDefinitionId;
|
||||
myRepository = theRepository;
|
||||
myMaxUriLength = DEFAULT_MAX_URI_LENGTH;
|
||||
myPrefetchTemplateBuilder = new PrefetchTemplateBuilderDstu3(myRepository);
|
||||
}
|
||||
|
||||
public CdsServiceJson resolveService() {
|
||||
|
@ -59,12 +51,36 @@ public class CrDiscoveryServiceDstu3 implements ICrDiscoveryService {
|
|||
protected CdsServiceJson resolveService(IBaseResource thePlanDefinition) {
|
||||
if (thePlanDefinition instanceof PlanDefinition) {
|
||||
PlanDefinition planDef = (PlanDefinition) thePlanDefinition;
|
||||
return new CrDiscoveryElementDstu3(planDef, getPrefetchUrlList(planDef)).getCdsServiceJson();
|
||||
String triggerEvent = getTriggerEvent(planDef);
|
||||
if (triggerEvent != null) {
|
||||
PrefetchUrlList prefetchUrlList =
|
||||
isEca(planDef) ? myPrefetchTemplateBuilder.getPrefetchUrlList(planDef) : new PrefetchUrlList();
|
||||
return new CrDiscoveryElementDstu3(planDef, prefetchUrlList).getCdsServiceJson();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isEca(PlanDefinition thePlanDefinition) {
|
||||
protected String getTriggerEvent(PlanDefinition thePlanDefinition) {
|
||||
if (thePlanDefinition == null
|
||||
|| !thePlanDefinition.hasAction()
|
||||
|| thePlanDefinition.getAction().stream().noneMatch(a -> a.hasTriggerDefinition())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var triggerDefs = thePlanDefinition.getAction().stream()
|
||||
.filter(a -> a.hasTriggerDefinition())
|
||||
.flatMap(a -> a.getTriggerDefinition().stream())
|
||||
.filter(t -> t.getType().equals(TriggerDefinition.TriggerType.NAMEDEVENT))
|
||||
.collect(Collectors.toList());
|
||||
if (triggerDefs == null || triggerDefs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return triggerDefs.get(0).getEventName();
|
||||
}
|
||||
|
||||
protected boolean isEca(PlanDefinition thePlanDefinition) {
|
||||
if (thePlanDefinition.hasType() && thePlanDefinition.getType().hasCoding()) {
|
||||
for (Coding coding : thePlanDefinition.getType().getCoding()) {
|
||||
if (coding.getCode().equals("eca-rule")) {
|
||||
|
@ -74,372 +90,4 @@ public class CrDiscoveryServiceDstu3 implements ICrDiscoveryService {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||
// Assuming 1 library
|
||||
// TODO: enhance to handle multiple libraries - need a way to identify primary
|
||||
// library
|
||||
Library library = null;
|
||||
if (thePlanDefinition.hasLibrary()
|
||||
&& thePlanDefinition.getLibraryFirstRep().hasReference()) {
|
||||
library = myRepository.read(
|
||||
Library.class, thePlanDefinition.getLibraryFirstRep().getReferenceElement());
|
||||
}
|
||||
return library;
|
||||
}
|
||||
|
||||
public List<String> resolveValueCodingCodes(List<Coding> theValueCodings) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
StringBuilder codes = new StringBuilder();
|
||||
for (Coding coding : theValueCodings) {
|
||||
if (coding.hasCode()) {
|
||||
String system = coding.getSystem();
|
||||
String code = coding.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> resolveValueSetCodes(StringType theValueSetId) {
|
||||
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, theValueSetId);
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder codes = new StringBuilder();
|
||||
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||
valueSet.getExpansion().getContains()) {
|
||||
String system = contains.getSystem();
|
||||
String code = contains.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||
String system = concepts.getSystem();
|
||||
if (concepts.hasConcept()) {
|
||||
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||
String code = concept.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected StringBuilder getCodesStringBuilder(
|
||||
List<String> theList, StringBuilder theCodes, String theSystem, String theCode) {
|
||||
String codeToken = theSystem + "|" + theCode;
|
||||
int postAppendLength = theCodes.length() + codeToken.length();
|
||||
|
||||
if (theCodes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||
theCodes.append(",");
|
||||
} else if (postAppendLength > myMaxUriLength) {
|
||||
theList.add(theCodes.toString());
|
||||
theCodes = new StringBuilder();
|
||||
}
|
||||
theCodes.append(codeToken);
|
||||
return theCodes;
|
||||
}
|
||||
|
||||
public List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||
if (!isPatientCompartment(theDataRequirement.getType())) return null;
|
||||
String patientRelatedResource = theDataRequirement.getType() + "?"
|
||||
+ getPatientSearchParam(theDataRequirement.getType())
|
||||
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||
List<String> ret = new ArrayList<>();
|
||||
if (theDataRequirement.hasCodeFilter()) {
|
||||
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||
theDataRequirement.getCodeFilter()) {
|
||||
if (!codeFilterComponent.hasPath()) continue;
|
||||
String path = mapCodePathToSearchParam(theDataRequirement.getType(), codeFilterComponent.getPath());
|
||||
|
||||
StringType codeFilterComponentString = null;
|
||||
if (codeFilterComponent.hasValueSetStringType()) {
|
||||
codeFilterComponentString = codeFilterComponent.getValueSetStringType();
|
||||
} else if (codeFilterComponent.hasValueSetReference()) {
|
||||
codeFilterComponentString = new StringType(
|
||||
codeFilterComponent.getValueSetReference().getReference());
|
||||
} else if (codeFilterComponent.hasValueCoding()) {
|
||||
List<Coding> codeFilterValueCodings = codeFilterComponent.getValueCoding();
|
||||
boolean isFirstCodingInFilter = true;
|
||||
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||
if (isFirstCodingInFilter) {
|
||||
ret.add(patientRelatedResource + "&" + path + "=" + code);
|
||||
} else {
|
||||
ret.add("," + code);
|
||||
}
|
||||
|
||||
isFirstCodingInFilter = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (codeFilterComponentString != null) {
|
||||
for (String codes : resolveValueSetCodes(codeFilterComponentString)) {
|
||||
ret.add(patientRelatedResource + "&" + path + "=" + codes);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
ret.add(patientRelatedResource);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||
if (thePlanDefinition == null) return null;
|
||||
if (!isEca(thePlanDefinition)) return null;
|
||||
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||
// TODO: resolve data requirements
|
||||
if (!library.hasDataRequirement()) return null;
|
||||
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||
if (requestUrls != null) {
|
||||
prefetchList.addAll(requestUrls);
|
||||
}
|
||||
}
|
||||
|
||||
return prefetchList;
|
||||
}
|
||||
|
||||
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||
switch (theDataType) {
|
||||
case "MedicationAdministration":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationDispense":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationRequest":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationStatement":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "ProcedureRequest":
|
||||
if (thePath.equals("bodySite")) return "body-site";
|
||||
break;
|
||||
default:
|
||||
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||
break;
|
||||
}
|
||||
return thePath.replace('.', '-').toLowerCase();
|
||||
}
|
||||
|
||||
public static boolean isPatientCompartment(String theDataType) {
|
||||
if (theDataType == null) {
|
||||
return false;
|
||||
}
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
case "AdverseEvent":
|
||||
case "AllergyIntolerance":
|
||||
case "Appointment":
|
||||
case "AppointmentResponse":
|
||||
case "AuditEvent":
|
||||
case "Basic":
|
||||
case "BodySite":
|
||||
case "CarePlan":
|
||||
case "CareTeam":
|
||||
case "ChargeItem":
|
||||
case "Claim":
|
||||
case "ClaimResponse":
|
||||
case "ClinicalImpression":
|
||||
case "Communication":
|
||||
case "CommunicationRequest":
|
||||
case "Composition":
|
||||
case "Condition":
|
||||
case "Consent":
|
||||
case "Coverage":
|
||||
case "DetectedIssue":
|
||||
case "DeviceRequest":
|
||||
case "DeviceUseStatement":
|
||||
case "DiagnosticReport":
|
||||
case "DocumentManifest":
|
||||
case "EligibilityRequest":
|
||||
case "Encounter":
|
||||
case "EnrollmentRequest":
|
||||
case "EpisodeOfCare":
|
||||
case "ExplanationOfBenefit":
|
||||
case "FamilyMemberHistory":
|
||||
case "Flag":
|
||||
case "Goal":
|
||||
case "Group":
|
||||
case "ImagingManifest":
|
||||
case "ImagingStudy":
|
||||
case "Immunization":
|
||||
case "ImmunizationRecommendation":
|
||||
case "List":
|
||||
case "MeasureReport":
|
||||
case "Media":
|
||||
case "MedicationAdministration":
|
||||
case "MedicationDispense":
|
||||
case "MedicationRequest":
|
||||
case "MedicationStatement":
|
||||
case "NutritionOrder":
|
||||
case "Observation":
|
||||
case "Patient":
|
||||
case "Person":
|
||||
case "Procedure":
|
||||
case "ProcedureRequest":
|
||||
case "Provenance":
|
||||
case "QuestionnaireResponse":
|
||||
case "ReferralRequest":
|
||||
case "RelatedPerson":
|
||||
case "RequestGroup":
|
||||
case "ResearchSubject":
|
||||
case "RiskAssessment":
|
||||
case "Schedule":
|
||||
case "Specimen":
|
||||
case "SupplyDelivery":
|
||||
case "SupplyRequest":
|
||||
case "VisionPrescription":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPatientSearchParam(String theDataType) {
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
return "subject";
|
||||
case "AdverseEvent":
|
||||
return "subject";
|
||||
case "AllergyIntolerance":
|
||||
return "patient";
|
||||
case "Appointment":
|
||||
return "actor";
|
||||
case "AppointmentResponse":
|
||||
return "actor";
|
||||
case "AuditEvent":
|
||||
return "patient";
|
||||
case "Basic":
|
||||
return "patient";
|
||||
case "BodySite":
|
||||
return "patient";
|
||||
case "CarePlan":
|
||||
return "patient";
|
||||
case "CareTeam":
|
||||
return "patient";
|
||||
case "ChargeItem":
|
||||
return "subject";
|
||||
case "Claim":
|
||||
return "patient";
|
||||
case "ClaimResponse":
|
||||
return "patient";
|
||||
case "ClinicalImpression":
|
||||
return "subject";
|
||||
case "Communication":
|
||||
return "subject";
|
||||
case "CommunicationRequest":
|
||||
return "subject";
|
||||
case "Composition":
|
||||
return "subject";
|
||||
case "Condition":
|
||||
return "patient";
|
||||
case "Consent":
|
||||
return "patient";
|
||||
case "Coverage":
|
||||
return "patient";
|
||||
case "DetectedIssue":
|
||||
return "patient";
|
||||
case "DeviceRequest":
|
||||
return "subject";
|
||||
case "DeviceUseStatement":
|
||||
return "subject";
|
||||
case "DiagnosticReport":
|
||||
return "subject";
|
||||
case "DocumentManifest":
|
||||
return "subject";
|
||||
case "DocumentReference":
|
||||
return "subject";
|
||||
case "EligibilityRequest":
|
||||
return "patient";
|
||||
case "Encounter":
|
||||
return "patient";
|
||||
case "EnrollmentRequest":
|
||||
return "subject";
|
||||
case "EpisodeOfCare":
|
||||
return "patient";
|
||||
case "ExplanationOfBenefit":
|
||||
return "patient";
|
||||
case "FamilyMemberHistory":
|
||||
return "patient";
|
||||
case "Flag":
|
||||
return "patient";
|
||||
case "Goal":
|
||||
return "patient";
|
||||
case "Group":
|
||||
return "member";
|
||||
case "ImagingManifest":
|
||||
return "patient";
|
||||
case "ImagingStudy":
|
||||
return "patient";
|
||||
case "Immunization":
|
||||
return "patient";
|
||||
case "ImmunizationRecommendation":
|
||||
return "patient";
|
||||
case "List":
|
||||
return "subject";
|
||||
case "MeasureReport":
|
||||
return "patient";
|
||||
case "Media":
|
||||
return "subject";
|
||||
case "MedicationAdministration":
|
||||
return "patient";
|
||||
case "MedicationDispense":
|
||||
return "patient";
|
||||
case "MedicationRequest":
|
||||
return "subject";
|
||||
case "MedicationStatement":
|
||||
return "subject";
|
||||
case "NutritionOrder":
|
||||
return "patient";
|
||||
case "Observation":
|
||||
return "subject";
|
||||
case "Patient":
|
||||
return "_id";
|
||||
case "Person":
|
||||
return "patient";
|
||||
case "Procedure":
|
||||
return "patient";
|
||||
case "ProcedureRequest":
|
||||
return "patient";
|
||||
case "Provenance":
|
||||
return "patient";
|
||||
case "QuestionnaireResponse":
|
||||
return "subject";
|
||||
case "ReferralRequest":
|
||||
return "patient";
|
||||
case "RelatedPerson":
|
||||
return "patient";
|
||||
case "RequestGroup":
|
||||
return "subject";
|
||||
case "ResearchSubject":
|
||||
return "individual";
|
||||
case "RiskAssessment":
|
||||
return "subject";
|
||||
case "Schedule":
|
||||
return "actor";
|
||||
case "Specimen":
|
||||
return "subject";
|
||||
case "SupplyDelivery":
|
||||
return "patient";
|
||||
case "SupplyRequest":
|
||||
return "subject";
|
||||
case "VisionPrescription":
|
||||
return "patient";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,31 +24,23 @@ import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
|||
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.CanonicalType;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.r4.model.DataRequirement;
|
||||
import org.hl7.fhir.r4.model.Library;
|
||||
import org.hl7.fhir.r4.model.PlanDefinition;
|
||||
import org.hl7.fhir.r4.model.ValueSet;
|
||||
import org.hl7.fhir.r4.model.TriggerDefinition;
|
||||
import org.opencds.cqf.fhir.api.Repository;
|
||||
import org.opencds.cqf.fhir.utility.r4.SearchHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CrDiscoveryServiceR4 implements ICrDiscoveryService {
|
||||
|
||||
protected final String PATIENT_ID_CONTEXT = "{{context.patientId}}";
|
||||
protected final int DEFAULT_MAX_URI_LENGTH = 8000;
|
||||
protected int myMaxUriLength;
|
||||
|
||||
protected final Repository myRepository;
|
||||
protected final IIdType myPlanDefinitionId;
|
||||
protected final PrefetchTemplateBuilderR4 myPrefetchTemplateBuilder;
|
||||
|
||||
public CrDiscoveryServiceR4(IIdType thePlanDefinitionId, Repository theRepository) {
|
||||
myPlanDefinitionId = thePlanDefinitionId;
|
||||
myRepository = theRepository;
|
||||
myMaxUriLength = DEFAULT_MAX_URI_LENGTH;
|
||||
myPrefetchTemplateBuilder = new PrefetchTemplateBuilderR4(myRepository);
|
||||
}
|
||||
|
||||
public CdsServiceJson resolveService() {
|
||||
|
@ -59,12 +51,36 @@ public class CrDiscoveryServiceR4 implements ICrDiscoveryService {
|
|||
protected CdsServiceJson resolveService(IBaseResource thePlanDefinition) {
|
||||
if (thePlanDefinition instanceof PlanDefinition) {
|
||||
PlanDefinition planDef = (PlanDefinition) thePlanDefinition;
|
||||
return new CrDiscoveryElementR4(planDef, getPrefetchUrlList(planDef)).getCdsServiceJson();
|
||||
String triggerEvent = getTriggerEvent(planDef);
|
||||
if (triggerEvent != null) {
|
||||
PrefetchUrlList prefetchUrlList =
|
||||
isEca(planDef) ? myPrefetchTemplateBuilder.getPrefetchUrlList(planDef) : new PrefetchUrlList();
|
||||
return new CrDiscoveryElementR4(planDef, prefetchUrlList).getCdsServiceJson();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isEca(PlanDefinition planDefinition) {
|
||||
protected String getTriggerEvent(PlanDefinition thePlanDefinition) {
|
||||
if (thePlanDefinition == null
|
||||
|| !thePlanDefinition.hasAction()
|
||||
|| thePlanDefinition.getAction().stream().noneMatch(a -> a.hasTrigger())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var triggerDefs = thePlanDefinition.getAction().stream()
|
||||
.filter(a -> a.hasTrigger())
|
||||
.flatMap(a -> a.getTrigger().stream())
|
||||
.filter(t -> t.getType().equals(TriggerDefinition.TriggerType.NAMEDEVENT))
|
||||
.collect(Collectors.toList());
|
||||
if (triggerDefs == null || triggerDefs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return triggerDefs.get(0).getName();
|
||||
}
|
||||
|
||||
protected boolean isEca(PlanDefinition planDefinition) {
|
||||
if (planDefinition.hasType() && planDefinition.getType().hasCoding()) {
|
||||
for (Coding coding : planDefinition.getType().getCoding()) {
|
||||
if (coding.getCode().equals("eca-rule")) {
|
||||
|
@ -74,356 +90,4 @@ public class CrDiscoveryServiceR4 implements ICrDiscoveryService {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||
// The CPGComputablePlanDefinition profile limits the cardinality of library to 1
|
||||
Library library = null;
|
||||
if (thePlanDefinition.hasLibrary() && !thePlanDefinition.getLibrary().isEmpty()) {
|
||||
library = (Library) SearchHelper.searchRepositoryByCanonical(
|
||||
myRepository, thePlanDefinition.getLibrary().get(0));
|
||||
}
|
||||
return library;
|
||||
}
|
||||
|
||||
public List<String> resolveValueCodingCodes(List<Coding> valueCodings) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
StringBuilder codes = new StringBuilder();
|
||||
for (Coding coding : valueCodings) {
|
||||
if (coding.hasCode()) {
|
||||
String system = coding.getSystem();
|
||||
String code = coding.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> resolveValueSetCodes(CanonicalType valueSetId) {
|
||||
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, valueSetId);
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder codes = new StringBuilder();
|
||||
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||
valueSet.getExpansion().getContains()) {
|
||||
String system = contains.getSystem();
|
||||
String code = contains.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||
String system = concepts.getSystem();
|
||||
if (concepts.hasConcept()) {
|
||||
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||
String code = concept.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected StringBuilder getCodesStringBuilder(List<String> ret, StringBuilder codes, String system, String code) {
|
||||
String codeToken = system + "|" + code;
|
||||
int postAppendLength = codes.length() + codeToken.length();
|
||||
|
||||
if (codes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||
codes.append(",");
|
||||
} else if (postAppendLength > myMaxUriLength) {
|
||||
ret.add(codes.toString());
|
||||
codes = new StringBuilder();
|
||||
}
|
||||
codes.append(codeToken);
|
||||
return codes;
|
||||
}
|
||||
|
||||
public List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||
if (!isPatientCompartment(theDataRequirement.getType())) return null;
|
||||
String patientRelatedResource = theDataRequirement.getType() + "?"
|
||||
+ getPatientSearchParam(theDataRequirement.getType())
|
||||
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||
List<String> ret = new ArrayList<>();
|
||||
if (theDataRequirement.hasCodeFilter()) {
|
||||
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||
theDataRequirement.getCodeFilter()) {
|
||||
if (!codeFilterComponent.hasPath()) continue;
|
||||
String path = mapCodePathToSearchParam(theDataRequirement.getType(), codeFilterComponent.getPath());
|
||||
if (codeFilterComponent.hasValueSetElement()) {
|
||||
for (String codes : resolveValueSetCodes(codeFilterComponent.getValueSetElement())) {
|
||||
ret.add(patientRelatedResource + "&" + path + "=" + codes);
|
||||
}
|
||||
} else if (codeFilterComponent.hasCode()) {
|
||||
List<Coding> codeFilterValueCodings = codeFilterComponent.getCode();
|
||||
boolean isFirstCodingInFilter = true;
|
||||
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||
if (isFirstCodingInFilter) {
|
||||
ret.add(patientRelatedResource + "&" + path + "=" + code);
|
||||
} else {
|
||||
ret.add("," + code);
|
||||
}
|
||||
|
||||
isFirstCodingInFilter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
ret.add(patientRelatedResource);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||
if (thePlanDefinition == null) return null;
|
||||
if (!isEca(thePlanDefinition)) return null;
|
||||
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||
// TODO: resolve data requirements
|
||||
if (library == null || !library.hasDataRequirement()) return null;
|
||||
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||
if (requestUrls != null) {
|
||||
prefetchList.addAll(requestUrls);
|
||||
}
|
||||
}
|
||||
return prefetchList;
|
||||
}
|
||||
|
||||
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||
switch (theDataType) {
|
||||
case "MedicationAdministration":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationDispense":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationRequest":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationStatement":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
default:
|
||||
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||
break;
|
||||
}
|
||||
return thePath.replace('.', '-').toLowerCase();
|
||||
}
|
||||
|
||||
public static boolean isPatientCompartment(String theDataType) {
|
||||
if (theDataType == null) {
|
||||
return false;
|
||||
}
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
case "AdverseEvent":
|
||||
case "AllergyIntolerance":
|
||||
case "Appointment":
|
||||
case "AppointmentResponse":
|
||||
case "AuditEvent":
|
||||
case "Basic":
|
||||
case "BodyStructure":
|
||||
case "CarePlan":
|
||||
case "CareTeam":
|
||||
case "ChargeItem":
|
||||
case "Claim":
|
||||
case "ClaimResponse":
|
||||
case "ClinicalImpression":
|
||||
case "Communication":
|
||||
case "CommunicationRequest":
|
||||
case "Composition":
|
||||
case "Condition":
|
||||
case "Consent":
|
||||
case "Coverage":
|
||||
case "CoverageEligibilityRequest":
|
||||
case "CoverageEligibilityResponse":
|
||||
case "DetectedIssue":
|
||||
case "DeviceRequest":
|
||||
case "DeviceUseStatement":
|
||||
case "DiagnosticReport":
|
||||
case "DocumentManifest":
|
||||
case "DocumentReference":
|
||||
case "Encounter":
|
||||
case "EnrollmentRequest":
|
||||
case "EpisodeOfCare":
|
||||
case "ExplanationOfBenefit":
|
||||
case "FamilyMemberHistory":
|
||||
case "Flag":
|
||||
case "Goal":
|
||||
case "Group":
|
||||
case "ImagingStudy":
|
||||
case "Immunization":
|
||||
case "ImmunizationEvaluation":
|
||||
case "ImmunizationRecommendation":
|
||||
case "Invoice":
|
||||
case "List":
|
||||
case "MeasureReport":
|
||||
case "Media":
|
||||
case "MedicationAdministration":
|
||||
case "MedicationDispense":
|
||||
case "MedicationRequest":
|
||||
case "MedicationStatement":
|
||||
case "MolecularSequence":
|
||||
case "NutritionOrder":
|
||||
case "Observation":
|
||||
case "Patient":
|
||||
case "Person":
|
||||
case "Procedure":
|
||||
case "Provenance":
|
||||
case "QuestionnaireResponse":
|
||||
case "RelatedPerson":
|
||||
case "RequestGroup":
|
||||
case "ResearchSubject":
|
||||
case "RiskAssessment":
|
||||
case "Schedule":
|
||||
case "ServiceRequest":
|
||||
case "Specimen":
|
||||
case "SupplyDelivery":
|
||||
case "SupplyRequest":
|
||||
case "VisionPrescription":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPatientSearchParam(String theDataType) {
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
return "subject";
|
||||
case "AdverseEvent":
|
||||
return "subject";
|
||||
case "AllergyIntolerance":
|
||||
return "patient";
|
||||
case "Appointment":
|
||||
return "actor";
|
||||
case "AppointmentResponse":
|
||||
return "actor";
|
||||
case "AuditEvent":
|
||||
return "patient";
|
||||
case "Basic":
|
||||
return "patient";
|
||||
case "BodyStructure":
|
||||
return "patient";
|
||||
case "CarePlan":
|
||||
return "patient";
|
||||
case "CareTeam":
|
||||
return "patient";
|
||||
case "ChargeItem":
|
||||
return "subject";
|
||||
case "Claim":
|
||||
return "patient";
|
||||
case "ClaimResponse":
|
||||
return "patient";
|
||||
case "ClinicalImpression":
|
||||
return "subject";
|
||||
case "Communication":
|
||||
return "subject";
|
||||
case "CommunicationRequest":
|
||||
return "subject";
|
||||
case "Composition":
|
||||
return "subject";
|
||||
case "Condition":
|
||||
return "patient";
|
||||
case "Consent":
|
||||
return "patient";
|
||||
case "Coverage":
|
||||
return "policy-holder";
|
||||
case "DetectedIssue":
|
||||
return "patient";
|
||||
case "DeviceRequest":
|
||||
return "subject";
|
||||
case "DeviceUseStatement":
|
||||
return "subject";
|
||||
case "DiagnosticReport":
|
||||
return "subject";
|
||||
case "DocumentManifest":
|
||||
return "subject";
|
||||
case "DocumentReference":
|
||||
return "subject";
|
||||
case "Encounter":
|
||||
return "patient";
|
||||
case "EnrollmentRequest":
|
||||
return "subject";
|
||||
case "EpisodeOfCare":
|
||||
return "patient";
|
||||
case "ExplanationOfBenefit":
|
||||
return "patient";
|
||||
case "FamilyMemberHistory":
|
||||
return "patient";
|
||||
case "Flag":
|
||||
return "patient";
|
||||
case "Goal":
|
||||
return "patient";
|
||||
case "Group":
|
||||
return "member";
|
||||
case "ImagingStudy":
|
||||
return "patient";
|
||||
case "Immunization":
|
||||
return "patient";
|
||||
case "ImmunizationRecommendation":
|
||||
return "patient";
|
||||
case "Invoice":
|
||||
return "subject";
|
||||
case "List":
|
||||
return "subject";
|
||||
case "MeasureReport":
|
||||
return "patient";
|
||||
case "Media":
|
||||
return "subject";
|
||||
case "MedicationAdministration":
|
||||
return "patient";
|
||||
case "MedicationDispense":
|
||||
return "patient";
|
||||
case "MedicationRequest":
|
||||
return "subject";
|
||||
case "MedicationStatement":
|
||||
return "subject";
|
||||
case "MolecularSequence":
|
||||
return "patient";
|
||||
case "NutritionOrder":
|
||||
return "patient";
|
||||
case "Observation":
|
||||
return "subject";
|
||||
case "Patient":
|
||||
return "_id";
|
||||
case "Person":
|
||||
return "patient";
|
||||
case "Procedure":
|
||||
return "patient";
|
||||
case "Provenance":
|
||||
return "patient";
|
||||
case "QuestionnaireResponse":
|
||||
return "subject";
|
||||
case "RelatedPerson":
|
||||
return "patient";
|
||||
case "RequestGroup":
|
||||
return "subject";
|
||||
case "ResearchSubject":
|
||||
return "individual";
|
||||
case "RiskAssessment":
|
||||
return "subject";
|
||||
case "Schedule":
|
||||
return "actor";
|
||||
case "ServiceRequest":
|
||||
return "patient";
|
||||
case "Specimen":
|
||||
return "subject";
|
||||
case "SupplyDelivery":
|
||||
return "patient";
|
||||
case "SupplyRequest":
|
||||
return "subject";
|
||||
case "VisionPrescription":
|
||||
return "patient";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,31 +24,23 @@ import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceJson;
|
|||
import ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r5.model.CanonicalType;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.DataRequirement;
|
||||
import org.hl7.fhir.r5.model.Library;
|
||||
import org.hl7.fhir.r5.model.PlanDefinition;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.model.TriggerDefinition;
|
||||
import org.opencds.cqf.fhir.api.Repository;
|
||||
import org.opencds.cqf.fhir.utility.r5.SearchHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CrDiscoveryServiceR5 implements ICrDiscoveryService {
|
||||
|
||||
protected final String PATIENT_ID_CONTEXT = "{{context.patientId}}";
|
||||
protected final int DEFAULT_MAX_URI_LENGTH = 8000;
|
||||
protected int myMaxUriLength;
|
||||
|
||||
protected final Repository myRepository;
|
||||
protected final IIdType myPlanDefinitionId;
|
||||
protected final PrefetchTemplateBuilderR5 myPrefetchTemplateBuilder;
|
||||
|
||||
public CrDiscoveryServiceR5(IIdType thePlanDefinitionId, Repository theRepository) {
|
||||
myPlanDefinitionId = thePlanDefinitionId;
|
||||
myRepository = theRepository;
|
||||
myMaxUriLength = DEFAULT_MAX_URI_LENGTH;
|
||||
myPrefetchTemplateBuilder = new PrefetchTemplateBuilderR5(myRepository);
|
||||
}
|
||||
|
||||
public CdsServiceJson resolveService() {
|
||||
|
@ -59,12 +51,36 @@ public class CrDiscoveryServiceR5 implements ICrDiscoveryService {
|
|||
protected CdsServiceJson resolveService(IBaseResource thePlanDefinition) {
|
||||
if (thePlanDefinition instanceof PlanDefinition) {
|
||||
PlanDefinition planDef = (PlanDefinition) thePlanDefinition;
|
||||
return new CrDiscoveryElementR5(planDef, getPrefetchUrlList(planDef)).getCdsServiceJson();
|
||||
String triggerEvent = getTriggerEvent(planDef);
|
||||
if (triggerEvent != null) {
|
||||
PrefetchUrlList prefetchUrlList =
|
||||
isEca(planDef) ? myPrefetchTemplateBuilder.getPrefetchUrlList(planDef) : new PrefetchUrlList();
|
||||
return new CrDiscoveryElementR5(planDef, prefetchUrlList).getCdsServiceJson();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isEca(PlanDefinition thePlanDefinition) {
|
||||
protected String getTriggerEvent(PlanDefinition thePlanDefinition) {
|
||||
if (thePlanDefinition == null
|
||||
|| !thePlanDefinition.hasAction()
|
||||
|| thePlanDefinition.getAction().stream().noneMatch(a -> a.hasTrigger())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var triggerDefs = thePlanDefinition.getAction().stream()
|
||||
.filter(a -> a.hasTrigger())
|
||||
.flatMap(a -> a.getTrigger().stream())
|
||||
.filter(t -> t.getType().equals(TriggerDefinition.TriggerType.NAMEDEVENT))
|
||||
.collect(Collectors.toList());
|
||||
if (triggerDefs == null || triggerDefs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return triggerDefs.get(0).getName();
|
||||
}
|
||||
|
||||
protected boolean isEca(PlanDefinition thePlanDefinition) {
|
||||
if (thePlanDefinition.hasType() && thePlanDefinition.getType().hasCoding()) {
|
||||
for (Coding coding : thePlanDefinition.getType().getCoding()) {
|
||||
if (coding.getCode().equals("eca-rule")) {
|
||||
|
@ -74,358 +90,4 @@ public class CrDiscoveryServiceR5 implements ICrDiscoveryService {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||
// The CPGComputablePlanDefinition profile limits the cardinality of library to 1
|
||||
Library library = null;
|
||||
if (thePlanDefinition.hasLibrary() && !thePlanDefinition.getLibrary().isEmpty()) {
|
||||
library = (Library) SearchHelper.searchRepositoryByCanonical(
|
||||
myRepository, thePlanDefinition.getLibrary().get(0));
|
||||
}
|
||||
return library;
|
||||
}
|
||||
|
||||
public List<String> resolveValueCodingCodes(List<Coding> theValueCodings) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
StringBuilder codes = new StringBuilder();
|
||||
for (Coding coding : theValueCodings) {
|
||||
if (coding.hasCode()) {
|
||||
String system = coding.getSystem();
|
||||
String code = coding.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> resolveValueSetCodes(CanonicalType theValueSetId) {
|
||||
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, theValueSetId);
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder codes = new StringBuilder();
|
||||
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||
valueSet.getExpansion().getContains()) {
|
||||
String system = contains.getSystem();
|
||||
String code = contains.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||
String system = concepts.getSystem();
|
||||
if (concepts.hasConcept()) {
|
||||
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||
String code = concept.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected StringBuilder getCodesStringBuilder(
|
||||
List<String> theList, StringBuilder theCodes, String theSystem, String theCode) {
|
||||
String codeToken = theSystem + "|" + theCode;
|
||||
int postAppendLength = theCodes.length() + codeToken.length();
|
||||
|
||||
if (theCodes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||
theCodes.append(",");
|
||||
} else if (postAppendLength > myMaxUriLength) {
|
||||
theList.add(theCodes.toString());
|
||||
theCodes = new StringBuilder();
|
||||
}
|
||||
theCodes.append(codeToken);
|
||||
return theCodes;
|
||||
}
|
||||
|
||||
public List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||
if (!isPatientCompartment(theDataRequirement.getType().toCode())) return null;
|
||||
String patientRelatedResource = theDataRequirement.getType() + "?"
|
||||
+ getPatientSearchParam(theDataRequirement.getType().toCode())
|
||||
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||
List<String> ret = new ArrayList<>();
|
||||
if (theDataRequirement.hasCodeFilter()) {
|
||||
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||
theDataRequirement.getCodeFilter()) {
|
||||
if (!codeFilterComponent.hasPath()) continue;
|
||||
String path =
|
||||
mapCodePathToSearchParam(theDataRequirement.getType().toCode(), codeFilterComponent.getPath());
|
||||
if (codeFilterComponent.hasValueSetElement()) {
|
||||
for (String codes : resolveValueSetCodes(codeFilterComponent.getValueSetElement())) {
|
||||
ret.add(patientRelatedResource + "&" + path + "=" + codes);
|
||||
}
|
||||
} else if (codeFilterComponent.hasCode()) {
|
||||
List<Coding> codeFilterValueCodings = codeFilterComponent.getCode();
|
||||
boolean isFirstCodingInFilter = true;
|
||||
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||
if (isFirstCodingInFilter) {
|
||||
ret.add(patientRelatedResource + "&" + path + "=" + code);
|
||||
} else {
|
||||
ret.add("," + code);
|
||||
}
|
||||
|
||||
isFirstCodingInFilter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
ret.add(patientRelatedResource);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||
if (thePlanDefinition == null) return null;
|
||||
if (!isEca(thePlanDefinition)) return null;
|
||||
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||
// TODO: resolve data requirements
|
||||
if (library == null || !library.hasDataRequirement()) return null;
|
||||
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||
if (requestUrls != null) {
|
||||
prefetchList.addAll(requestUrls);
|
||||
}
|
||||
}
|
||||
return prefetchList;
|
||||
}
|
||||
|
||||
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||
switch (theDataType) {
|
||||
case "MedicationAdministration":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationDispense":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationRequest":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationStatement":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
default:
|
||||
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||
break;
|
||||
}
|
||||
return thePath.replace('.', '-').toLowerCase();
|
||||
}
|
||||
|
||||
public static boolean isPatientCompartment(String theDataType) {
|
||||
if (theDataType == null) {
|
||||
return false;
|
||||
}
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
case "AdverseEvent":
|
||||
case "AllergyIntolerance":
|
||||
case "Appointment":
|
||||
case "AppointmentResponse":
|
||||
case "AuditEvent":
|
||||
case "Basic":
|
||||
case "BodyStructure":
|
||||
case "CarePlan":
|
||||
case "CareTeam":
|
||||
case "ChargeItem":
|
||||
case "Claim":
|
||||
case "ClaimResponse":
|
||||
case "ClinicalImpression":
|
||||
case "Communication":
|
||||
case "CommunicationRequest":
|
||||
case "Composition":
|
||||
case "Condition":
|
||||
case "Consent":
|
||||
case "Coverage":
|
||||
case "CoverageEligibilityRequest":
|
||||
case "CoverageEligibilityResponse":
|
||||
case "DetectedIssue":
|
||||
case "DeviceRequest":
|
||||
case "DeviceUseStatement":
|
||||
case "DiagnosticReport":
|
||||
case "DocumentManifest":
|
||||
case "DocumentReference":
|
||||
case "Encounter":
|
||||
case "EnrollmentRequest":
|
||||
case "EpisodeOfCare":
|
||||
case "ExplanationOfBenefit":
|
||||
case "FamilyMemberHistory":
|
||||
case "Flag":
|
||||
case "Goal":
|
||||
case "Group":
|
||||
case "ImagingStudy":
|
||||
case "Immunization":
|
||||
case "ImmunizationEvaluation":
|
||||
case "ImmunizationRecommendation":
|
||||
case "Invoice":
|
||||
case "List":
|
||||
case "MeasureReport":
|
||||
case "Media":
|
||||
case "MedicationAdministration":
|
||||
case "MedicationDispense":
|
||||
case "MedicationRequest":
|
||||
case "MedicationStatement":
|
||||
case "MolecularSequence":
|
||||
case "NutritionOrder":
|
||||
case "Observation":
|
||||
case "Patient":
|
||||
case "Person":
|
||||
case "Procedure":
|
||||
case "Provenance":
|
||||
case "QuestionnaireResponse":
|
||||
case "RelatedPerson":
|
||||
case "RequestGroup":
|
||||
case "ResearchSubject":
|
||||
case "RiskAssessment":
|
||||
case "Schedule":
|
||||
case "ServiceRequest":
|
||||
case "Specimen":
|
||||
case "SupplyDelivery":
|
||||
case "SupplyRequest":
|
||||
case "VisionPrescription":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPatientSearchParam(String theDataType) {
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
return "subject";
|
||||
case "AdverseEvent":
|
||||
return "subject";
|
||||
case "AllergyIntolerance":
|
||||
return "patient";
|
||||
case "Appointment":
|
||||
return "actor";
|
||||
case "AppointmentResponse":
|
||||
return "actor";
|
||||
case "AuditEvent":
|
||||
return "patient";
|
||||
case "Basic":
|
||||
return "patient";
|
||||
case "BodyStructure":
|
||||
return "patient";
|
||||
case "CarePlan":
|
||||
return "patient";
|
||||
case "CareTeam":
|
||||
return "patient";
|
||||
case "ChargeItem":
|
||||
return "subject";
|
||||
case "Claim":
|
||||
return "patient";
|
||||
case "ClaimResponse":
|
||||
return "patient";
|
||||
case "ClinicalImpression":
|
||||
return "subject";
|
||||
case "Communication":
|
||||
return "subject";
|
||||
case "CommunicationRequest":
|
||||
return "subject";
|
||||
case "Composition":
|
||||
return "subject";
|
||||
case "Condition":
|
||||
return "patient";
|
||||
case "Consent":
|
||||
return "patient";
|
||||
case "Coverage":
|
||||
return "policy-holder";
|
||||
case "DetectedIssue":
|
||||
return "patient";
|
||||
case "DeviceRequest":
|
||||
return "subject";
|
||||
case "DeviceUseStatement":
|
||||
return "subject";
|
||||
case "DiagnosticReport":
|
||||
return "subject";
|
||||
case "DocumentManifest":
|
||||
return "subject";
|
||||
case "DocumentReference":
|
||||
return "subject";
|
||||
case "Encounter":
|
||||
return "patient";
|
||||
case "EnrollmentRequest":
|
||||
return "subject";
|
||||
case "EpisodeOfCare":
|
||||
return "patient";
|
||||
case "ExplanationOfBenefit":
|
||||
return "patient";
|
||||
case "FamilyMemberHistory":
|
||||
return "patient";
|
||||
case "Flag":
|
||||
return "patient";
|
||||
case "Goal":
|
||||
return "patient";
|
||||
case "Group":
|
||||
return "member";
|
||||
case "ImagingStudy":
|
||||
return "patient";
|
||||
case "Immunization":
|
||||
return "patient";
|
||||
case "ImmunizationRecommendation":
|
||||
return "patient";
|
||||
case "Invoice":
|
||||
return "subject";
|
||||
case "List":
|
||||
return "subject";
|
||||
case "MeasureReport":
|
||||
return "patient";
|
||||
case "Media":
|
||||
return "subject";
|
||||
case "MedicationAdministration":
|
||||
return "patient";
|
||||
case "MedicationDispense":
|
||||
return "patient";
|
||||
case "MedicationRequest":
|
||||
return "subject";
|
||||
case "MedicationStatement":
|
||||
return "subject";
|
||||
case "MolecularSequence":
|
||||
return "patient";
|
||||
case "NutritionOrder":
|
||||
return "patient";
|
||||
case "Observation":
|
||||
return "subject";
|
||||
case "Patient":
|
||||
return "_id";
|
||||
case "Person":
|
||||
return "patient";
|
||||
case "Procedure":
|
||||
return "patient";
|
||||
case "Provenance":
|
||||
return "patient";
|
||||
case "QuestionnaireResponse":
|
||||
return "subject";
|
||||
case "RelatedPerson":
|
||||
return "patient";
|
||||
case "RequestGroup":
|
||||
return "subject";
|
||||
case "ResearchSubject":
|
||||
return "individual";
|
||||
case "RiskAssessment":
|
||||
return "subject";
|
||||
case "Schedule":
|
||||
return "actor";
|
||||
case "ServiceRequest":
|
||||
return "patient";
|
||||
case "Specimen":
|
||||
return "subject";
|
||||
case "SupplyDelivery":
|
||||
return "patient";
|
||||
case "SupplyRequest":
|
||||
return "subject";
|
||||
case "VisionPrescription":
|
||||
return "patient";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,434 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - CDS Hooks
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.DataRequirement;
|
||||
import org.hl7.fhir.dstu3.model.Extension;
|
||||
import org.hl7.fhir.dstu3.model.Library;
|
||||
import org.hl7.fhir.dstu3.model.PlanDefinition;
|
||||
import org.hl7.fhir.dstu3.model.StringType;
|
||||
import org.hl7.fhir.dstu3.model.ValueSet;
|
||||
import org.opencds.cqf.fhir.api.Repository;
|
||||
import org.opencds.cqf.fhir.utility.dstu3.SearchHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CQF_FHIR_QUERY_PATTERN;
|
||||
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CRMI_EFFECTIVE_DATA_REQUIREMENTS;
|
||||
|
||||
public class PrefetchTemplateBuilderDstu3 extends BasePrefetchTemplateBuilder {
|
||||
|
||||
public PrefetchTemplateBuilderDstu3(Repository theRepository) {
|
||||
super(theRepository);
|
||||
}
|
||||
|
||||
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||
if (thePlanDefinition == null) return null;
|
||||
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||
// TODO: resolve data requirements
|
||||
if (!library.hasDataRequirement()) return null;
|
||||
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||
if (requestUrls != null) {
|
||||
prefetchList.addAll(requestUrls);
|
||||
}
|
||||
}
|
||||
|
||||
return prefetchList;
|
||||
}
|
||||
|
||||
protected Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||
Library library = null;
|
||||
Extension dataReqExt = thePlanDefinition.getExtensionByUrl(CRMI_EFFECTIVE_DATA_REQUIREMENTS);
|
||||
// Use a Module Definition Library with Effective Data Requirements for the Plan Definition if it exists
|
||||
if (dataReqExt != null && dataReqExt.hasValue()) {
|
||||
StringType moduleDefCanonical = (StringType) dataReqExt.getValue();
|
||||
library = (Library) SearchHelper.searchRepositoryByCanonical(myRepository, moduleDefCanonical);
|
||||
}
|
||||
// Otherwise use the primary Library
|
||||
if (library == null && thePlanDefinition.hasLibrary()) {
|
||||
// The CPGComputablePlanDefinition profile limits the cardinality of library to 1
|
||||
StringType canonical =
|
||||
new StringType(thePlanDefinition.getLibrary().get(0).getReference());
|
||||
library = (Library) SearchHelper.searchRepositoryByCanonical(myRepository, canonical);
|
||||
}
|
||||
return library;
|
||||
}
|
||||
|
||||
protected List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||
List<String> urlList = new ArrayList<>();
|
||||
// if we have a fhirQueryPattern extensions, use them
|
||||
List<Extension> fhirQueryExtList = theDataRequirement.getExtension().stream()
|
||||
.filter(e -> e.getUrl().equals(CQF_FHIR_QUERY_PATTERN) && e.hasValue())
|
||||
.collect(Collectors.toList());
|
||||
if (fhirQueryExtList != null && !fhirQueryExtList.isEmpty()) {
|
||||
for (Extension fhirQueryExt : fhirQueryExtList) {
|
||||
urlList.add(fhirQueryExt.getValueAsPrimitive().getValueAsString());
|
||||
}
|
||||
return urlList;
|
||||
}
|
||||
|
||||
// else build the query
|
||||
if (!isPatientCompartment(theDataRequirement.getType())) return null;
|
||||
String baseQuery = theDataRequirement.getType() + "?"
|
||||
+ getPatientSearchParam(theDataRequirement.getType())
|
||||
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||
|
||||
// TODO: Add valueFilter extension resolution
|
||||
|
||||
if (theDataRequirement.hasCodeFilter()) {
|
||||
resolveCodeFilter(theDataRequirement, urlList, baseQuery);
|
||||
} else {
|
||||
urlList.add(baseQuery);
|
||||
}
|
||||
return urlList;
|
||||
}
|
||||
|
||||
protected void resolveCodeFilter(DataRequirement theDataRequirement, List<String> theUrlList, String theBaseQuery) {
|
||||
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||
theDataRequirement.getCodeFilter()) {
|
||||
if (!codeFilterComponent.hasPath()) continue;
|
||||
String path = mapCodePathToSearchParam(theDataRequirement.getType(), codeFilterComponent.getPath());
|
||||
|
||||
StringType codeFilterComponentString = null;
|
||||
if (codeFilterComponent.hasValueSetStringType()) {
|
||||
codeFilterComponentString = codeFilterComponent.getValueSetStringType();
|
||||
} else if (codeFilterComponent.hasValueSetReference()) {
|
||||
codeFilterComponentString = new StringType(
|
||||
codeFilterComponent.getValueSetReference().getReference());
|
||||
} else if (codeFilterComponent.hasValueCoding()) {
|
||||
List<Coding> codeFilterValueCodings = codeFilterComponent.getValueCoding();
|
||||
boolean isFirstCodingInFilter = true;
|
||||
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||
if (isFirstCodingInFilter) {
|
||||
theUrlList.add(theBaseQuery + "&" + path + "=" + code);
|
||||
} else {
|
||||
theUrlList.add("," + code);
|
||||
}
|
||||
|
||||
isFirstCodingInFilter = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (codeFilterComponentString != null) {
|
||||
for (String codes : resolveValueSetCodes(codeFilterComponentString)) {
|
||||
theUrlList.add(theBaseQuery + "&" + path + "=" + codes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<String> resolveValueCodingCodes(List<Coding> theValueCodings) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
StringBuilder codes = new StringBuilder();
|
||||
for (Coding coding : theValueCodings) {
|
||||
if (coding.hasCode()) {
|
||||
String system = coding.getSystem();
|
||||
String code = coding.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected List<String> resolveValueSetCodes(StringType theValueSetId) {
|
||||
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, theValueSetId);
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder codes = new StringBuilder();
|
||||
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||
valueSet.getExpansion().getContains()) {
|
||||
String system = contains.getSystem();
|
||||
String code = contains.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||
String system = concepts.getSystem();
|
||||
if (concepts.hasConcept()) {
|
||||
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||
String code = concept.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected StringBuilder getCodesStringBuilder(
|
||||
List<String> theList, StringBuilder theCodes, String theSystem, String theCode) {
|
||||
String codeToken = theSystem + "|" + theCode;
|
||||
int postAppendLength = theCodes.length() + codeToken.length();
|
||||
|
||||
if (theCodes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||
theCodes.append(",");
|
||||
} else if (postAppendLength > myMaxUriLength) {
|
||||
theList.add(theCodes.toString());
|
||||
theCodes = new StringBuilder();
|
||||
}
|
||||
theCodes.append(codeToken);
|
||||
return theCodes;
|
||||
}
|
||||
|
||||
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||
switch (theDataType) {
|
||||
case "MedicationAdministration":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationDispense":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationRequest":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationStatement":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "ProcedureRequest":
|
||||
if (thePath.equals("bodySite")) return "body-site";
|
||||
break;
|
||||
default:
|
||||
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||
break;
|
||||
}
|
||||
return thePath.replace('.', '-').toLowerCase();
|
||||
}
|
||||
|
||||
protected static boolean isPatientCompartment(String theDataType) {
|
||||
if (theDataType == null) {
|
||||
return false;
|
||||
}
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
case "AdverseEvent":
|
||||
case "AllergyIntolerance":
|
||||
case "Appointment":
|
||||
case "AppointmentResponse":
|
||||
case "AuditEvent":
|
||||
case "Basic":
|
||||
case "BodySite":
|
||||
case "CarePlan":
|
||||
case "CareTeam":
|
||||
case "ChargeItem":
|
||||
case "Claim":
|
||||
case "ClaimResponse":
|
||||
case "ClinicalImpression":
|
||||
case "Communication":
|
||||
case "CommunicationRequest":
|
||||
case "Composition":
|
||||
case "Condition":
|
||||
case "Consent":
|
||||
case "Coverage":
|
||||
case "DetectedIssue":
|
||||
case "DeviceRequest":
|
||||
case "DeviceUseStatement":
|
||||
case "DiagnosticReport":
|
||||
case "DocumentManifest":
|
||||
case "EligibilityRequest":
|
||||
case "Encounter":
|
||||
case "EnrollmentRequest":
|
||||
case "EpisodeOfCare":
|
||||
case "ExplanationOfBenefit":
|
||||
case "FamilyMemberHistory":
|
||||
case "Flag":
|
||||
case "Goal":
|
||||
case "Group":
|
||||
case "ImagingManifest":
|
||||
case "ImagingStudy":
|
||||
case "Immunization":
|
||||
case "ImmunizationRecommendation":
|
||||
case "List":
|
||||
case "MeasureReport":
|
||||
case "Media":
|
||||
case "MedicationAdministration":
|
||||
case "MedicationDispense":
|
||||
case "MedicationRequest":
|
||||
case "MedicationStatement":
|
||||
case "NutritionOrder":
|
||||
case "Observation":
|
||||
case "Patient":
|
||||
case "Person":
|
||||
case "Procedure":
|
||||
case "ProcedureRequest":
|
||||
case "Provenance":
|
||||
case "QuestionnaireResponse":
|
||||
case "ReferralRequest":
|
||||
case "RelatedPerson":
|
||||
case "RequestGroup":
|
||||
case "ResearchSubject":
|
||||
case "RiskAssessment":
|
||||
case "Schedule":
|
||||
case "Specimen":
|
||||
case "SupplyDelivery":
|
||||
case "SupplyRequest":
|
||||
case "VisionPrescription":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected String getPatientSearchParam(String theDataType) {
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
return "subject";
|
||||
case "AdverseEvent":
|
||||
return "subject";
|
||||
case "AllergyIntolerance":
|
||||
return "patient";
|
||||
case "Appointment":
|
||||
return "actor";
|
||||
case "AppointmentResponse":
|
||||
return "actor";
|
||||
case "AuditEvent":
|
||||
return "patient";
|
||||
case "Basic":
|
||||
return "patient";
|
||||
case "BodySite":
|
||||
return "patient";
|
||||
case "CarePlan":
|
||||
return "patient";
|
||||
case "CareTeam":
|
||||
return "patient";
|
||||
case "ChargeItem":
|
||||
return "subject";
|
||||
case "Claim":
|
||||
return "patient";
|
||||
case "ClaimResponse":
|
||||
return "patient";
|
||||
case "ClinicalImpression":
|
||||
return "subject";
|
||||
case "Communication":
|
||||
return "subject";
|
||||
case "CommunicationRequest":
|
||||
return "subject";
|
||||
case "Composition":
|
||||
return "subject";
|
||||
case "Condition":
|
||||
return "patient";
|
||||
case "Consent":
|
||||
return "patient";
|
||||
case "Coverage":
|
||||
return "beneficiary";
|
||||
case "DetectedIssue":
|
||||
return "patient";
|
||||
case "DeviceRequest":
|
||||
return "subject";
|
||||
case "DeviceUseStatement":
|
||||
return "subject";
|
||||
case "DiagnosticReport":
|
||||
return "subject";
|
||||
case "DocumentManifest":
|
||||
return "subject";
|
||||
case "DocumentReference":
|
||||
return "subject";
|
||||
case "EligibilityRequest":
|
||||
return "patient";
|
||||
case "Encounter":
|
||||
return "patient";
|
||||
case "EnrollmentRequest":
|
||||
return "subject";
|
||||
case "EpisodeOfCare":
|
||||
return "patient";
|
||||
case "ExplanationOfBenefit":
|
||||
return "patient";
|
||||
case "FamilyMemberHistory":
|
||||
return "patient";
|
||||
case "Flag":
|
||||
return "patient";
|
||||
case "Goal":
|
||||
return "patient";
|
||||
case "Group":
|
||||
return "member";
|
||||
case "ImagingManifest":
|
||||
return "patient";
|
||||
case "ImagingStudy":
|
||||
return "patient";
|
||||
case "Immunization":
|
||||
return "patient";
|
||||
case "ImmunizationRecommendation":
|
||||
return "patient";
|
||||
case "List":
|
||||
return "subject";
|
||||
case "MeasureReport":
|
||||
return "patient";
|
||||
case "Media":
|
||||
return "subject";
|
||||
case "MedicationAdministration":
|
||||
return "patient";
|
||||
case "MedicationDispense":
|
||||
return "patient";
|
||||
case "MedicationRequest":
|
||||
return "subject";
|
||||
case "MedicationStatement":
|
||||
return "subject";
|
||||
case "NutritionOrder":
|
||||
return "patient";
|
||||
case "Observation":
|
||||
return "subject";
|
||||
case "Patient":
|
||||
return "_id";
|
||||
case "Person":
|
||||
return "patient";
|
||||
case "Procedure":
|
||||
return "patient";
|
||||
case "ProcedureRequest":
|
||||
return "patient";
|
||||
case "Provenance":
|
||||
return "patient";
|
||||
case "QuestionnaireResponse":
|
||||
return "subject";
|
||||
case "ReferralRequest":
|
||||
return "patient";
|
||||
case "RelatedPerson":
|
||||
return "patient";
|
||||
case "RequestGroup":
|
||||
return "subject";
|
||||
case "ResearchSubject":
|
||||
return "individual";
|
||||
case "RiskAssessment":
|
||||
return "subject";
|
||||
case "Schedule":
|
||||
return "actor";
|
||||
case "Specimen":
|
||||
return "subject";
|
||||
case "SupplyDelivery":
|
||||
return "patient";
|
||||
case "SupplyRequest":
|
||||
return "subject";
|
||||
case "VisionPrescription":
|
||||
return "patient";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,413 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - CDS Hooks
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||
|
||||
import org.hl7.fhir.r4.model.CanonicalType;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.r4.model.DataRequirement;
|
||||
import org.hl7.fhir.r4.model.Extension;
|
||||
import org.hl7.fhir.r4.model.Library;
|
||||
import org.hl7.fhir.r4.model.PlanDefinition;
|
||||
import org.hl7.fhir.r4.model.ValueSet;
|
||||
import org.opencds.cqf.fhir.api.Repository;
|
||||
import org.opencds.cqf.fhir.utility.SearchHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CQF_FHIR_QUERY_PATTERN;
|
||||
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CRMI_EFFECTIVE_DATA_REQUIREMENTS;
|
||||
|
||||
public class PrefetchTemplateBuilderR4 extends BasePrefetchTemplateBuilder {
|
||||
|
||||
public PrefetchTemplateBuilderR4(Repository theRepository) {
|
||||
super(theRepository);
|
||||
}
|
||||
|
||||
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||
if (thePlanDefinition == null) return null;
|
||||
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||
if (library == null || !library.hasDataRequirement()) return null;
|
||||
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||
if (requestUrls != null) {
|
||||
prefetchList.addAll(requestUrls);
|
||||
}
|
||||
}
|
||||
return prefetchList;
|
||||
}
|
||||
|
||||
protected Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||
Library library = null;
|
||||
Extension dataReqExt = thePlanDefinition.getExtensionByUrl(CRMI_EFFECTIVE_DATA_REQUIREMENTS);
|
||||
// Use a Module Definition Library with Effective Data Requirements for the Plan Definition if it exists
|
||||
if (dataReqExt != null && dataReqExt.hasValue()) {
|
||||
CanonicalType moduleDefCanonical = (CanonicalType) dataReqExt.getValue();
|
||||
library = (Library) SearchHelper.searchRepositoryByCanonical(myRepository, moduleDefCanonical);
|
||||
}
|
||||
// Otherwise use the primary Library
|
||||
if (library == null && thePlanDefinition.hasLibrary()) {
|
||||
// The CPGComputablePlanDefinition profile limits the cardinality of library to 1
|
||||
library = (Library) SearchHelper.searchRepositoryByCanonical(
|
||||
myRepository, thePlanDefinition.getLibrary().get(0));
|
||||
}
|
||||
return library;
|
||||
}
|
||||
|
||||
protected List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||
List<String> urlList = new ArrayList<>();
|
||||
// if we have a fhirQueryPattern extensions, use them
|
||||
List<Extension> fhirQueryExtList = theDataRequirement.getExtension().stream()
|
||||
.filter(e -> e.getUrl().equals(CQF_FHIR_QUERY_PATTERN) && e.hasValue())
|
||||
.collect(Collectors.toList());
|
||||
if (fhirQueryExtList != null && !fhirQueryExtList.isEmpty()) {
|
||||
for (Extension fhirQueryExt : fhirQueryExtList) {
|
||||
urlList.add(fhirQueryExt.getValueAsPrimitive().getValueAsString());
|
||||
}
|
||||
return urlList;
|
||||
}
|
||||
|
||||
// else build the query
|
||||
if (!isPatientCompartment(theDataRequirement.getType())) return null;
|
||||
String baseQuery = theDataRequirement.getType() + "?"
|
||||
+ getPatientSearchParam(theDataRequirement.getType())
|
||||
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||
|
||||
// TODO: Add valueFilter extension resolution
|
||||
|
||||
if (theDataRequirement.hasCodeFilter()) {
|
||||
resolveCodeFilter(theDataRequirement, urlList, baseQuery);
|
||||
} else {
|
||||
urlList.add(baseQuery);
|
||||
}
|
||||
return urlList;
|
||||
}
|
||||
|
||||
protected void resolveCodeFilter(DataRequirement theDataRequirement, List<String> theUrlList, String theBaseQuery) {
|
||||
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||
theDataRequirement.getCodeFilter()) {
|
||||
if (!codeFilterComponent.hasPath()) continue;
|
||||
String path = mapCodePathToSearchParam(theDataRequirement.getType(), codeFilterComponent.getPath());
|
||||
if (codeFilterComponent.hasValueSetElement()) {
|
||||
for (String codes : resolveValueSetCodes(codeFilterComponent.getValueSetElement())) {
|
||||
theUrlList.add(theBaseQuery + "&" + path + "=" + codes);
|
||||
}
|
||||
} else if (codeFilterComponent.hasCode()) {
|
||||
List<Coding> codeFilterValueCodings = codeFilterComponent.getCode();
|
||||
boolean isFirstCodingInFilter = true;
|
||||
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||
if (isFirstCodingInFilter) {
|
||||
theUrlList.add(theBaseQuery + "&" + path + "=" + code);
|
||||
} else {
|
||||
theUrlList.add("," + code);
|
||||
}
|
||||
|
||||
isFirstCodingInFilter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<String> resolveValueCodingCodes(List<Coding> valueCodings) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
StringBuilder codes = new StringBuilder();
|
||||
for (Coding coding : valueCodings) {
|
||||
if (coding.hasCode()) {
|
||||
String system = coding.getSystem();
|
||||
String code = coding.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected List<String> resolveValueSetCodes(CanonicalType valueSetId) {
|
||||
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, valueSetId);
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder codes = new StringBuilder();
|
||||
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||
valueSet.getExpansion().getContains()) {
|
||||
String system = contains.getSystem();
|
||||
String code = contains.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||
String system = concepts.getSystem();
|
||||
if (concepts.hasConcept()) {
|
||||
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||
String code = concept.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected StringBuilder getCodesStringBuilder(List<String> ret, StringBuilder codes, String system, String code) {
|
||||
String codeToken = system + "|" + code;
|
||||
int postAppendLength = codes.length() + codeToken.length();
|
||||
|
||||
if (codes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||
codes.append(",");
|
||||
} else if (postAppendLength > myMaxUriLength) {
|
||||
ret.add(codes.toString());
|
||||
codes = new StringBuilder();
|
||||
}
|
||||
codes.append(codeToken);
|
||||
return codes;
|
||||
}
|
||||
|
||||
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||
switch (theDataType) {
|
||||
case "MedicationAdministration":
|
||||
case "MedicationDispense":
|
||||
case "MedicationRequest":
|
||||
case "MedicationStatement":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
default:
|
||||
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||
break;
|
||||
}
|
||||
return thePath.replace('.', '-').toLowerCase();
|
||||
}
|
||||
|
||||
protected static boolean isPatientCompartment(String theDataType) {
|
||||
if (theDataType == null) {
|
||||
return false;
|
||||
}
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
case "AdverseEvent":
|
||||
case "AllergyIntolerance":
|
||||
case "Appointment":
|
||||
case "AppointmentResponse":
|
||||
case "AuditEvent":
|
||||
case "Basic":
|
||||
case "BodyStructure":
|
||||
case "CarePlan":
|
||||
case "CareTeam":
|
||||
case "ChargeItem":
|
||||
case "Claim":
|
||||
case "ClaimResponse":
|
||||
case "ClinicalImpression":
|
||||
case "Communication":
|
||||
case "CommunicationRequest":
|
||||
case "Composition":
|
||||
case "Condition":
|
||||
case "Consent":
|
||||
case "Coverage":
|
||||
case "CoverageEligibilityRequest":
|
||||
case "CoverageEligibilityResponse":
|
||||
case "DetectedIssue":
|
||||
case "DeviceRequest":
|
||||
case "DeviceUseStatement":
|
||||
case "DiagnosticReport":
|
||||
case "DocumentManifest":
|
||||
case "DocumentReference":
|
||||
case "Encounter":
|
||||
case "EnrollmentRequest":
|
||||
case "EpisodeOfCare":
|
||||
case "ExplanationOfBenefit":
|
||||
case "FamilyMemberHistory":
|
||||
case "Flag":
|
||||
case "Goal":
|
||||
case "Group":
|
||||
case "ImagingStudy":
|
||||
case "Immunization":
|
||||
case "ImmunizationEvaluation":
|
||||
case "ImmunizationRecommendation":
|
||||
case "Invoice":
|
||||
case "List":
|
||||
case "MeasureReport":
|
||||
case "Media":
|
||||
case "MedicationAdministration":
|
||||
case "MedicationDispense":
|
||||
case "MedicationRequest":
|
||||
case "MedicationStatement":
|
||||
case "MolecularSequence":
|
||||
case "NutritionOrder":
|
||||
case "Observation":
|
||||
case "Patient":
|
||||
case "Person":
|
||||
case "Procedure":
|
||||
case "Provenance":
|
||||
case "QuestionnaireResponse":
|
||||
case "RelatedPerson":
|
||||
case "RequestGroup":
|
||||
case "ResearchSubject":
|
||||
case "RiskAssessment":
|
||||
case "Schedule":
|
||||
case "ServiceRequest":
|
||||
case "Specimen":
|
||||
case "SupplyDelivery":
|
||||
case "SupplyRequest":
|
||||
case "VisionPrescription":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected String getPatientSearchParam(String theDataType) {
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
return "subject";
|
||||
case "AdverseEvent":
|
||||
return "subject";
|
||||
case "AllergyIntolerance":
|
||||
return "patient";
|
||||
case "Appointment":
|
||||
return "actor";
|
||||
case "AppointmentResponse":
|
||||
return "actor";
|
||||
case "AuditEvent":
|
||||
return "patient";
|
||||
case "Basic":
|
||||
return "patient";
|
||||
case "BodyStructure":
|
||||
return "patient";
|
||||
case "CarePlan":
|
||||
return "patient";
|
||||
case "CareTeam":
|
||||
return "patient";
|
||||
case "ChargeItem":
|
||||
return "subject";
|
||||
case "Claim":
|
||||
return "patient";
|
||||
case "ClaimResponse":
|
||||
return "patient";
|
||||
case "ClinicalImpression":
|
||||
return "subject";
|
||||
case "Communication":
|
||||
return "subject";
|
||||
case "CommunicationRequest":
|
||||
return "subject";
|
||||
case "Composition":
|
||||
return "subject";
|
||||
case "Condition":
|
||||
return "patient";
|
||||
case "Consent":
|
||||
return "patient";
|
||||
case "Coverage":
|
||||
return "beneficiary";
|
||||
case "DetectedIssue":
|
||||
return "patient";
|
||||
case "DeviceRequest":
|
||||
return "subject";
|
||||
case "DeviceUseStatement":
|
||||
return "subject";
|
||||
case "DiagnosticReport":
|
||||
return "subject";
|
||||
case "DocumentManifest":
|
||||
return "subject";
|
||||
case "DocumentReference":
|
||||
return "subject";
|
||||
case "Encounter":
|
||||
return "patient";
|
||||
case "EnrollmentRequest":
|
||||
return "subject";
|
||||
case "EpisodeOfCare":
|
||||
return "patient";
|
||||
case "ExplanationOfBenefit":
|
||||
return "patient";
|
||||
case "FamilyMemberHistory":
|
||||
return "patient";
|
||||
case "Flag":
|
||||
return "patient";
|
||||
case "Goal":
|
||||
return "patient";
|
||||
case "Group":
|
||||
return "member";
|
||||
case "ImagingStudy":
|
||||
return "patient";
|
||||
case "Immunization":
|
||||
return "patient";
|
||||
case "ImmunizationRecommendation":
|
||||
return "patient";
|
||||
case "Invoice":
|
||||
return "subject";
|
||||
case "List":
|
||||
return "subject";
|
||||
case "MeasureReport":
|
||||
return "patient";
|
||||
case "Media":
|
||||
return "subject";
|
||||
case "MedicationAdministration":
|
||||
return "patient";
|
||||
case "MedicationDispense":
|
||||
return "patient";
|
||||
case "MedicationRequest":
|
||||
return "subject";
|
||||
case "MedicationStatement":
|
||||
return "subject";
|
||||
case "MolecularSequence":
|
||||
return "patient";
|
||||
case "NutritionOrder":
|
||||
return "patient";
|
||||
case "Observation":
|
||||
return "subject";
|
||||
case "Patient":
|
||||
return "_id";
|
||||
case "Person":
|
||||
return "patient";
|
||||
case "Procedure":
|
||||
return "patient";
|
||||
case "Provenance":
|
||||
return "patient";
|
||||
case "QuestionnaireResponse":
|
||||
return "subject";
|
||||
case "RelatedPerson":
|
||||
return "patient";
|
||||
case "RequestGroup":
|
||||
return "subject";
|
||||
case "ResearchSubject":
|
||||
return "individual";
|
||||
case "RiskAssessment":
|
||||
return "subject";
|
||||
case "Schedule":
|
||||
return "actor";
|
||||
case "ServiceRequest":
|
||||
return "patient";
|
||||
case "Specimen":
|
||||
return "subject";
|
||||
case "SupplyDelivery":
|
||||
return "patient";
|
||||
case "SupplyRequest":
|
||||
return "subject";
|
||||
case "VisionPrescription":
|
||||
return "patient";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,421 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - CDS Hooks
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||
|
||||
import org.hl7.fhir.r5.model.CanonicalType;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.DataRequirement;
|
||||
import org.hl7.fhir.r5.model.Extension;
|
||||
import org.hl7.fhir.r5.model.Library;
|
||||
import org.hl7.fhir.r5.model.PlanDefinition;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.opencds.cqf.fhir.api.Repository;
|
||||
import org.opencds.cqf.fhir.utility.SearchHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CQF_FHIR_QUERY_PATTERN;
|
||||
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CRMI_EFFECTIVE_DATA_REQUIREMENTS;
|
||||
|
||||
public class PrefetchTemplateBuilderR5 extends BasePrefetchTemplateBuilder {
|
||||
|
||||
public PrefetchTemplateBuilderR5(Repository theRepository) {
|
||||
super(theRepository);
|
||||
}
|
||||
|
||||
public PrefetchUrlList getPrefetchUrlList(PlanDefinition thePlanDefinition) {
|
||||
if (thePlanDefinition == null) return null;
|
||||
PrefetchUrlList prefetchList = new PrefetchUrlList();
|
||||
Library library = resolvePrimaryLibrary(thePlanDefinition);
|
||||
if (library == null || !library.hasDataRequirement()) return null;
|
||||
for (DataRequirement dataRequirement : library.getDataRequirement()) {
|
||||
List<String> requestUrls = createRequestUrl(dataRequirement);
|
||||
if (requestUrls != null) {
|
||||
prefetchList.addAll(requestUrls);
|
||||
}
|
||||
}
|
||||
return prefetchList;
|
||||
}
|
||||
|
||||
protected Library resolvePrimaryLibrary(PlanDefinition thePlanDefinition) {
|
||||
Library library = null;
|
||||
Extension dataReqExt = thePlanDefinition.getExtensionByUrl(CRMI_EFFECTIVE_DATA_REQUIREMENTS);
|
||||
// Use a Module Definition Library with Effective Data Requirements for the Plan Definition if it exists
|
||||
if (dataReqExt != null && dataReqExt.hasValue()) {
|
||||
CanonicalType moduleDefCanonical = (CanonicalType) dataReqExt.getValue();
|
||||
library = (Library) SearchHelper.searchRepositoryByCanonical(myRepository, moduleDefCanonical);
|
||||
}
|
||||
// Otherwise use the primary Library
|
||||
if (library == null && thePlanDefinition.hasLibrary()) {
|
||||
// The CPGComputablePlanDefinition profile limits the cardinality of library to 1
|
||||
library = (Library) SearchHelper.searchRepositoryByCanonical(
|
||||
myRepository, thePlanDefinition.getLibrary().get(0));
|
||||
}
|
||||
return library;
|
||||
}
|
||||
|
||||
protected List<String> createRequestUrl(DataRequirement theDataRequirement) {
|
||||
List<String> urlList = new ArrayList<>();
|
||||
// if we have a fhirQueryPattern extensions, use them
|
||||
List<Extension> fhirQueryExtList = theDataRequirement.getExtension().stream()
|
||||
.filter(e -> e.getUrl().equals(CQF_FHIR_QUERY_PATTERN) && e.hasValue())
|
||||
.collect(Collectors.toList());
|
||||
if (fhirQueryExtList != null && !fhirQueryExtList.isEmpty()) {
|
||||
for (Extension fhirQueryExt : fhirQueryExtList) {
|
||||
urlList.add(fhirQueryExt.getValueAsPrimitive().getValueAsString());
|
||||
}
|
||||
return urlList;
|
||||
}
|
||||
|
||||
// else build the query
|
||||
if (!isPatientCompartment(theDataRequirement.getType().toCode())) return null;
|
||||
String patientRelatedResource = theDataRequirement.getType() + "?"
|
||||
+ getPatientSearchParam(theDataRequirement.getType().toCode())
|
||||
+ "=Patient/" + PATIENT_ID_CONTEXT;
|
||||
|
||||
// TODO: Add valueFilter extension resolution
|
||||
|
||||
if (theDataRequirement.hasCodeFilter()) {
|
||||
resolveCodeFilter(theDataRequirement, urlList, patientRelatedResource);
|
||||
} else {
|
||||
urlList.add(patientRelatedResource);
|
||||
}
|
||||
return urlList;
|
||||
}
|
||||
|
||||
protected void resolveCodeFilter(DataRequirement theDataRequirement, List<String> theUrlList, String theBaseQuery) {
|
||||
for (DataRequirement.DataRequirementCodeFilterComponent codeFilterComponent :
|
||||
theDataRequirement.getCodeFilter()) {
|
||||
if (!codeFilterComponent.hasPath()) continue;
|
||||
String path =
|
||||
mapCodePathToSearchParam(theDataRequirement.getType().toCode(), codeFilterComponent.getPath());
|
||||
if (codeFilterComponent.hasValueSetElement()) {
|
||||
for (String codes : resolveValueSetCodes(codeFilterComponent.getValueSetElement())) {
|
||||
theUrlList.add(theBaseQuery + "&" + path + "=" + codes);
|
||||
}
|
||||
} else if (codeFilterComponent.hasCode()) {
|
||||
List<Coding> codeFilterValueCodings = codeFilterComponent.getCode();
|
||||
boolean isFirstCodingInFilter = true;
|
||||
for (String code : resolveValueCodingCodes(codeFilterValueCodings)) {
|
||||
if (isFirstCodingInFilter) {
|
||||
theUrlList.add(theBaseQuery + "&" + path + "=" + code);
|
||||
} else {
|
||||
theUrlList.add("," + code);
|
||||
}
|
||||
|
||||
isFirstCodingInFilter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected List<String> resolveValueCodingCodes(List<Coding> theValueCodings) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
StringBuilder codes = new StringBuilder();
|
||||
for (Coding coding : theValueCodings) {
|
||||
if (coding.hasCode()) {
|
||||
String system = coding.getSystem();
|
||||
String code = coding.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected List<String> resolveValueSetCodes(CanonicalType theValueSetId) {
|
||||
ValueSet valueSet = (ValueSet) SearchHelper.searchRepositoryByCanonical(myRepository, theValueSetId);
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder codes = new StringBuilder();
|
||||
if (valueSet.hasExpansion() && valueSet.getExpansion().hasContains()) {
|
||||
for (ValueSet.ValueSetExpansionContainsComponent contains :
|
||||
valueSet.getExpansion().getContains()) {
|
||||
String system = contains.getSystem();
|
||||
String code = contains.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
} else if (valueSet.hasCompose() && valueSet.getCompose().hasInclude()) {
|
||||
for (ValueSet.ConceptSetComponent concepts : valueSet.getCompose().getInclude()) {
|
||||
String system = concepts.getSystem();
|
||||
if (concepts.hasConcept()) {
|
||||
for (ValueSet.ConceptReferenceComponent concept : concepts.getConcept()) {
|
||||
String code = concept.getCode();
|
||||
|
||||
codes = getCodesStringBuilder(result, codes, system, code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.add(codes.toString());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected StringBuilder getCodesStringBuilder(
|
||||
List<String> theList, StringBuilder theCodes, String theSystem, String theCode) {
|
||||
String codeToken = theSystem + "|" + theCode;
|
||||
int postAppendLength = theCodes.length() + codeToken.length();
|
||||
|
||||
if (theCodes.length() > 0 && postAppendLength < myMaxUriLength) {
|
||||
theCodes.append(",");
|
||||
} else if (postAppendLength > myMaxUriLength) {
|
||||
theList.add(theCodes.toString());
|
||||
theCodes = new StringBuilder();
|
||||
}
|
||||
theCodes.append(codeToken);
|
||||
return theCodes;
|
||||
}
|
||||
|
||||
protected String mapCodePathToSearchParam(String theDataType, String thePath) {
|
||||
switch (theDataType) {
|
||||
case "MedicationAdministration":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationDispense":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationRequest":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
case "MedicationStatement":
|
||||
if (thePath.equals("medication")) return "code";
|
||||
break;
|
||||
default:
|
||||
if (thePath.equals("vaccineCode")) return "vaccine-code";
|
||||
break;
|
||||
}
|
||||
return thePath.replace('.', '-').toLowerCase();
|
||||
}
|
||||
|
||||
public static boolean isPatientCompartment(String theDataType) {
|
||||
if (theDataType == null) {
|
||||
return false;
|
||||
}
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
case "AdverseEvent":
|
||||
case "AllergyIntolerance":
|
||||
case "Appointment":
|
||||
case "AppointmentResponse":
|
||||
case "AuditEvent":
|
||||
case "Basic":
|
||||
case "BodyStructure":
|
||||
case "CarePlan":
|
||||
case "CareTeam":
|
||||
case "ChargeItem":
|
||||
case "Claim":
|
||||
case "ClaimResponse":
|
||||
case "ClinicalImpression":
|
||||
case "Communication":
|
||||
case "CommunicationRequest":
|
||||
case "Composition":
|
||||
case "Condition":
|
||||
case "Consent":
|
||||
case "Coverage":
|
||||
case "CoverageEligibilityRequest":
|
||||
case "CoverageEligibilityResponse":
|
||||
case "DetectedIssue":
|
||||
case "DeviceRequest":
|
||||
case "DeviceUseStatement":
|
||||
case "DiagnosticReport":
|
||||
case "DocumentManifest":
|
||||
case "DocumentReference":
|
||||
case "Encounter":
|
||||
case "EnrollmentRequest":
|
||||
case "EpisodeOfCare":
|
||||
case "ExplanationOfBenefit":
|
||||
case "FamilyMemberHistory":
|
||||
case "Flag":
|
||||
case "Goal":
|
||||
case "Group":
|
||||
case "ImagingStudy":
|
||||
case "Immunization":
|
||||
case "ImmunizationEvaluation":
|
||||
case "ImmunizationRecommendation":
|
||||
case "Invoice":
|
||||
case "List":
|
||||
case "MeasureReport":
|
||||
case "Media":
|
||||
case "MedicationAdministration":
|
||||
case "MedicationDispense":
|
||||
case "MedicationRequest":
|
||||
case "MedicationStatement":
|
||||
case "MolecularSequence":
|
||||
case "NutritionOrder":
|
||||
case "Observation":
|
||||
case "Patient":
|
||||
case "Person":
|
||||
case "Procedure":
|
||||
case "Provenance":
|
||||
case "QuestionnaireResponse":
|
||||
case "RelatedPerson":
|
||||
case "RequestGroup":
|
||||
case "ResearchSubject":
|
||||
case "RiskAssessment":
|
||||
case "Schedule":
|
||||
case "ServiceRequest":
|
||||
case "Specimen":
|
||||
case "SupplyDelivery":
|
||||
case "SupplyRequest":
|
||||
case "VisionPrescription":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPatientSearchParam(String theDataType) {
|
||||
switch (theDataType) {
|
||||
case "Account":
|
||||
return "subject";
|
||||
case "AdverseEvent":
|
||||
return "subject";
|
||||
case "AllergyIntolerance":
|
||||
return "patient";
|
||||
case "Appointment":
|
||||
return "actor";
|
||||
case "AppointmentResponse":
|
||||
return "actor";
|
||||
case "AuditEvent":
|
||||
return "patient";
|
||||
case "Basic":
|
||||
return "patient";
|
||||
case "BodyStructure":
|
||||
return "patient";
|
||||
case "CarePlan":
|
||||
return "patient";
|
||||
case "CareTeam":
|
||||
return "patient";
|
||||
case "ChargeItem":
|
||||
return "subject";
|
||||
case "Claim":
|
||||
return "patient";
|
||||
case "ClaimResponse":
|
||||
return "patient";
|
||||
case "ClinicalImpression":
|
||||
return "subject";
|
||||
case "Communication":
|
||||
return "subject";
|
||||
case "CommunicationRequest":
|
||||
return "subject";
|
||||
case "Composition":
|
||||
return "subject";
|
||||
case "Condition":
|
||||
return "patient";
|
||||
case "Consent":
|
||||
return "patient";
|
||||
case "Coverage":
|
||||
return "beneficiary";
|
||||
case "DetectedIssue":
|
||||
return "patient";
|
||||
case "DeviceRequest":
|
||||
return "subject";
|
||||
case "DeviceUseStatement":
|
||||
return "subject";
|
||||
case "DiagnosticReport":
|
||||
return "subject";
|
||||
case "DocumentManifest":
|
||||
return "subject";
|
||||
case "DocumentReference":
|
||||
return "subject";
|
||||
case "Encounter":
|
||||
return "patient";
|
||||
case "EnrollmentRequest":
|
||||
return "subject";
|
||||
case "EpisodeOfCare":
|
||||
return "patient";
|
||||
case "ExplanationOfBenefit":
|
||||
return "patient";
|
||||
case "FamilyMemberHistory":
|
||||
return "patient";
|
||||
case "Flag":
|
||||
return "patient";
|
||||
case "Goal":
|
||||
return "patient";
|
||||
case "Group":
|
||||
return "member";
|
||||
case "ImagingStudy":
|
||||
return "patient";
|
||||
case "Immunization":
|
||||
return "patient";
|
||||
case "ImmunizationRecommendation":
|
||||
return "patient";
|
||||
case "Invoice":
|
||||
return "subject";
|
||||
case "List":
|
||||
return "subject";
|
||||
case "MeasureReport":
|
||||
return "patient";
|
||||
case "Media":
|
||||
return "subject";
|
||||
case "MedicationAdministration":
|
||||
return "patient";
|
||||
case "MedicationDispense":
|
||||
return "patient";
|
||||
case "MedicationRequest":
|
||||
return "subject";
|
||||
case "MedicationStatement":
|
||||
return "subject";
|
||||
case "MolecularSequence":
|
||||
return "patient";
|
||||
case "NutritionOrder":
|
||||
return "patient";
|
||||
case "Observation":
|
||||
return "subject";
|
||||
case "Patient":
|
||||
return "_id";
|
||||
case "Person":
|
||||
return "patient";
|
||||
case "Procedure":
|
||||
return "patient";
|
||||
case "Provenance":
|
||||
return "patient";
|
||||
case "QuestionnaireResponse":
|
||||
return "subject";
|
||||
case "RelatedPerson":
|
||||
return "patient";
|
||||
case "RequestGroup":
|
||||
return "subject";
|
||||
case "ResearchSubject":
|
||||
return "individual";
|
||||
case "RiskAssessment":
|
||||
return "subject";
|
||||
case "Schedule":
|
||||
return "actor";
|
||||
case "ServiceRequest":
|
||||
return "patient";
|
||||
case "Specimen":
|
||||
return "subject";
|
||||
case "SupplyDelivery":
|
||||
return "patient";
|
||||
case "SupplyRequest":
|
||||
return "subject";
|
||||
case "VisionPrescription":
|
||||
return "patient";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -40,5 +40,4 @@ public class CrDiscoveryServiceR4Test extends BaseCrTest {
|
|||
"}";
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package ca.uhn.hapi.fhir.cdshooks.svc.cr.discovery;
|
||||
|
||||
import ca.uhn.fhir.util.ClasspathUtil;
|
||||
import ca.uhn.hapi.fhir.cdshooks.svc.cr.BaseCrTest;
|
||||
import org.hl7.fhir.r4.model.CanonicalType;
|
||||
import org.hl7.fhir.r4.model.Library;
|
||||
import org.hl7.fhir.r4.model.PlanDefinition;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.opencds.cqf.fhir.api.Repository;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static ca.uhn.hapi.fhir.cdshooks.svc.cr.CdsCrConstants.CRMI_EFFECTIVE_DATA_REQUIREMENTS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PrefetchTemplateBuilderR4Test extends BaseCrTest {
|
||||
@Mock
|
||||
Repository myRepository;
|
||||
|
||||
@InjectMocks
|
||||
@Spy
|
||||
PrefetchTemplateBuilderR4 myFixture;
|
||||
|
||||
@Test
|
||||
public void testR4DiscoveryServiceWithEffectiveDataRequirements() {
|
||||
PlanDefinition planDefinition = new PlanDefinition();
|
||||
planDefinition.addExtension(CRMI_EFFECTIVE_DATA_REQUIREMENTS,
|
||||
new CanonicalType("http://hl7.org/fhir/uv/crmi/Library/moduledefinition-example"));
|
||||
planDefinition.setId("ModuleDefinitionTest");
|
||||
Library library = ClasspathUtil.loadResource(myFhirContext, Library.class, "ModuleDefinitionExample.json");
|
||||
doReturn(library).when(myFixture).resolvePrimaryLibrary(planDefinition);
|
||||
PrefetchUrlList actual = myFixture.getPrefetchUrlList(planDefinition);
|
||||
assertNotNull(actual);
|
||||
PrefetchUrlList expected = new PrefetchUrlList();
|
||||
expected.addAll(Arrays.asList("Patient?_id={{context.patientId}}",
|
||||
"Encounter?status=finished&subject=Patient/{{context.patientId}}&type:in=http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.292",
|
||||
"Coverage?policy-holder=Patient/{{context.patientId}}&type:in=http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591"));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
{
|
||||
"resourceType": "Library",
|
||||
"id": "moduledefinition-example",
|
||||
"meta": {
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/uv/crmi/StructureDefinition/crmi-moduledefinitionlibrary"
|
||||
]
|
||||
},
|
||||
"url": "http://hl7.org/fhir/uv/crmi/Library/moduledefinition-example",
|
||||
"identifier": [
|
||||
{
|
||||
"use": "official",
|
||||
"system": "http://example.org/fhir/cqi/ecqm/Library/Identifier",
|
||||
"value": "EXMLogic"
|
||||
},
|
||||
{
|
||||
"system": "urn:ietf:rfc:3986",
|
||||
"value": "urn:oid:2.16.840.1.113883.4.642.40.38.28.7"
|
||||
}
|
||||
],
|
||||
"version": "1.0.0-snapshot",
|
||||
"name": "EXMLogicModuleDefinition",
|
||||
"title": "Example Logic Library - Module Definition",
|
||||
"status": "active",
|
||||
"experimental": true,
|
||||
"type": {
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://terminology.hl7.org/CodeSystem/library-type",
|
||||
"code": "module-definition"
|
||||
}
|
||||
]
|
||||
},
|
||||
"date": "2019-09-03",
|
||||
"publisher": "HL7 International / Clinical Decision Support",
|
||||
"contact": [
|
||||
{
|
||||
"telecom": [
|
||||
{
|
||||
"system": "url",
|
||||
"value": "http://www.hl7.org/Special/committees/dss"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "This library is used as an example module definition in the FHIR Quality Measure Implementation Guide",
|
||||
"jurisdiction": [
|
||||
{
|
||||
"coding": [
|
||||
{
|
||||
"system": "http://unstats.un.org/unsd/methods/m49/m49.htm",
|
||||
"code": "001",
|
||||
"display": "World"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"relatedArtifact": [
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "FHIR model information",
|
||||
"resource": "http://fhir.org/guides/cqf/common/Library/FHIR-ModelInfo|4.0.1"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Library FHIRHelpers",
|
||||
"resource": "http://fhir.org/guides/cqf/common/Library/FHIRHelpers|4.0.1"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Code system Diagnosis Role",
|
||||
"resource": "http://terminology.hl7.org/CodeSystem/diagnosis-role"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Value set Emergency Department Visit",
|
||||
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.292"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Value set Psychiatric/Mental Health Patient",
|
||||
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.299"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Value set Hospital Settings",
|
||||
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1111.126"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Value set ONC Administrative Sex",
|
||||
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Value set Race",
|
||||
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.836"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Value set Ethnicity",
|
||||
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.837"
|
||||
},
|
||||
{
|
||||
"type": "depends-on",
|
||||
"display": "Value set Payer",
|
||||
"resource": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591"
|
||||
}
|
||||
],
|
||||
"parameter": [
|
||||
{
|
||||
"name": "Measurement Period",
|
||||
"use": "in",
|
||||
"min": 0,
|
||||
"max": "1",
|
||||
"type": "Period"
|
||||
},
|
||||
{
|
||||
"name": "Patient",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "1",
|
||||
"type": "Patient"
|
||||
},
|
||||
{
|
||||
"name": "Inpatient Encounter",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Encounter"
|
||||
},
|
||||
{
|
||||
"name": "Initial Population",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Encounter"
|
||||
},
|
||||
{
|
||||
"name": "Measure Population",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Encounter"
|
||||
},
|
||||
{
|
||||
"name": "Stratifier 1",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Encounter"
|
||||
},
|
||||
{
|
||||
"name": "Stratifier 2",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Encounter"
|
||||
},
|
||||
{
|
||||
"name": "Stratifier 3",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Encounter"
|
||||
},
|
||||
{
|
||||
"name": "Stratifier 4",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Encounter"
|
||||
},
|
||||
{
|
||||
"name": "SDE Ethnicity",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Coding"
|
||||
},
|
||||
{
|
||||
"name": "SDE Payer",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Resource"
|
||||
},
|
||||
{
|
||||
"name": "SDE Race",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "*",
|
||||
"type": "Coding"
|
||||
},
|
||||
{
|
||||
"name": "SDE Sex",
|
||||
"use": "out",
|
||||
"min": 0,
|
||||
"max": "1",
|
||||
"type": "Coding"
|
||||
}
|
||||
],
|
||||
"dataRequirement": [
|
||||
{
|
||||
"extension": [
|
||||
{
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/cqf-fhirQueryPattern",
|
||||
"valueString": "Patient?_id={{context.patientId}}"
|
||||
}
|
||||
],
|
||||
"type": "Patient",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-patient"
|
||||
],
|
||||
"mustSupport": [
|
||||
"extension('http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity')"
|
||||
],
|
||||
"_mustSupport": [
|
||||
{
|
||||
"extension": [
|
||||
{
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/rendered-value",
|
||||
"valueString": "ethnicity"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extension": [
|
||||
{
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/cqf-fhirQueryPattern",
|
||||
"valueString": "Encounter?status=finished&subject=Patient/{{context.patientId}}&type:in=http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.292"
|
||||
},
|
||||
{
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/cqf-isSelective",
|
||||
"valueBoolean": true
|
||||
},
|
||||
{
|
||||
"extension": [
|
||||
{
|
||||
"url": "path",
|
||||
"valueString": "status"
|
||||
},
|
||||
{
|
||||
"url": "comparator",
|
||||
"valueCode": "eq"
|
||||
},
|
||||
{
|
||||
"url": "value",
|
||||
"valueString": "finished"
|
||||
}
|
||||
],
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/cqf-valueFilter"
|
||||
}
|
||||
],
|
||||
"type": "Encounter",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-encounter"
|
||||
],
|
||||
"codeFilter": [
|
||||
{
|
||||
"path": "type",
|
||||
"valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.117.1.7.1.292"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Condition",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/StructureDefinition/Condition"
|
||||
],
|
||||
"codeFilter": [
|
||||
{
|
||||
"path": "id"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"extension": [
|
||||
{
|
||||
"url": "http://hl7.org/fhir/StructureDefinition/cqf-fhirQueryPattern",
|
||||
"valueString": "Coverage?policy-holder=Patient/{{context.patientId}}&type:in=http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591"
|
||||
}
|
||||
],
|
||||
"type": "Coverage",
|
||||
"profile": [
|
||||
"http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-coverage"
|
||||
],
|
||||
"codeFilter": [
|
||||
{
|
||||
"path": "type",
|
||||
"valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.114222.4.11.3591"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Clinical Reasoning
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.cr.r4;
|
||||
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Clinical Reasoning
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.cr.r4;
|
||||
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Clinical Reasoning
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.cr.r4.measure;
|
||||
|
||||
import ca.uhn.fhir.cr.r4.ICollectDataServiceFactory;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Clinical Reasoning
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.cr.r4.measure;
|
||||
|
||||
import ca.uhn.fhir.cr.r4.IDataRequirementsServiceFactory;
|
||||
|
|
Loading…
Reference in New Issue