diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java index 5654c9105..2dd12f9eb 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java @@ -21868,7 +21868,7 @@ public class VersionConvertor_30_40 { public static boolean convertsResource(String rt) { return Utilities.existsInList(rt, "Parameters", "ActivityDefinition", "AllergyIntolerance", "Appointment", "AppointmentResponse", "AuditEvent", "Basic", "Binary", "BodyStructure", "Bundle", "CapabilityStatement", "CareTeam", "ClinicalImpression", "CodeSystem", "Communication", "CompartmentDefinition", "Composition", "ConceptMap", "Condition", "Consent", "DetectedIssue", "DeviceUseStatement", "DiagnosticReport", "DocumentReference", - "Encounter", "Endpoint", "EpisodeOfCare", "FamilyMemberHistory", "Flag", "Goal", "GraphDefinition", "Group", "HealthcareService", "Immunization", "ImplementationGuide", "Library", "Linkage", "ListResource", "Location", + "Encounter", "Endpoint", "EpisodeOfCare", "FamilyMemberHistory", "Flag", "Goal", "GraphDefinition", "Group", "HealthcareService", "ImagingStudy", "Immunization", "ImplementationGuide", "Library", "Linkage", "ListResource", "Location", "Medication", "MedicationAdministration", "MedicationDispense", "MedicationRequest", "MedicationStatement", "MessageDefinition", "MessageHeader", "NamingSystem", "Observation", "OperationDefinition", "OperationOutcome", "Organization", "Patient", "PaymentNotice", "Person", "PlanDefinition", "Practitioner", "PractitionerRole", "ProcessRequest", "Questionnaire", "QuestionnaireResponse", "RiskAssessment", "Schedule", "SearchParameter", "Sequence", "Slot", "Specimen", "StructureDefinition", "StructureMap", "Subscription", "Substance", "SupplyDelivery", "TestReport", "TestScript", "ValueSet"); diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_50.java index 43a94a6a3..27cf13525 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_50.java @@ -11896,6 +11896,327 @@ public class VersionConvertor_30_50 { return tgt; } + private static final String URN_DICOM_UID = "urn:dicom:uid"; + + public static org.hl7.fhir.r5.model.ImagingStudy convertImagingStudy(org.hl7.fhir.dstu3.model.ImagingStudy src) throws FHIRException { + if (src == null) + return null; + org.hl7.fhir.r5.model.ImagingStudy tgt = new org.hl7.fhir.r5.model.ImagingStudy(); + copyDomainResource(src, tgt); + + if (src.hasUid()) { + org.hl7.fhir.r5.model.Identifier i = new org.hl7.fhir.r5.model.Identifier(); + i.setSystem(URN_DICOM_UID); + i.setValue(src.getUid()); + tgt.addIdentifier(i); + } + for (org.hl7.fhir.dstu3.model.Identifier t : src.getIdentifier()) { + tgt.addIdentifier(convertIdentifier(t)); + } + + if (src.hasAccession()) + tgt.addIdentifier(convertIdentifier(src.getAccession())); + + if (src.hasAvailability()) { + org.hl7.fhir.dstu3.model.ImagingStudy.InstanceAvailability availability = src.getAvailability(); + switch (availability) { + case OFFLINE: + tgt.setStatus(org.hl7.fhir.r5.model.ImagingStudy.ImagingStudyStatus.REGISTERED); + break; + case UNAVAILABLE: + tgt.setStatus(org.hl7.fhir.r5.model.ImagingStudy.ImagingStudyStatus.CANCELLED); + break; + case ONLINE: + case NEARLINE: + tgt.setStatus(org.hl7.fhir.r5.model.ImagingStudy.ImagingStudyStatus.AVAILABLE); + break; + default: + break; + } + } else { + tgt.setStatus(org.hl7.fhir.r5.model.ImagingStudy.ImagingStudyStatus.UNKNOWN); + } + + for (org.hl7.fhir.dstu3.model.Coding t : src.getModalityList()) { + tgt.addModality(convertCoding(t)); + } + + if (src.hasPatient()) + tgt.setSubject(convertReference(src.getPatient())); + if (src.hasContext()) { + tgt.setEncounter(convertReference(src.getContext())); + } + if (src.hasStarted()) { + + tgt.setStartedElement(convertDateTime(src.getStartedElement())); + } + for (org.hl7.fhir.dstu3.model.Reference t : src.getBasedOn()) { + tgt.addBasedOn(convertReference(t)); + } + if (src.hasReferrer()) { + tgt.setReferrer(convertReference(src.getReferrer())); + } + for (org.hl7.fhir.dstu3.model.Reference t : src.getInterpreter()) { + tgt.addInterpreter(convertReference(t)); + } + for (org.hl7.fhir.dstu3.model.Reference t : src.getEndpoint()) { + tgt.addEndpoint(convertReference(t)); + } + if (src.hasNumberOfSeries()) { + tgt.setNumberOfSeries(src.getNumberOfSeries()); + } + if (src.hasNumberOfInstances()) { + tgt.setNumberOfInstances(src.getNumberOfInstances()); + } + List procedureReferences = src.getProcedureReference(); + if (procedureReferences.size() > 0) { + tgt.setProcedureReference(convertReference(procedureReferences.get(0))); + + if (procedureReferences.size() > 1) { + // TODO print a warning that only one procedure reference could be converted + } + } + for (org.hl7.fhir.dstu3.model.CodeableConcept t : src.getProcedureCode()) { + tgt.addProcedureCode(convertCodeableConcept(t)); + } + if (src.hasReason()) { + tgt.addReasonCode(convertCodeableConcept(src.getReason())); + } + if (src.hasDescription()) { + tgt.setDescription(src.getDescription()); + } + + for (org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesComponent t : src.getSeries()) { + tgt.addSeries(convertImagingStudySeriesComponent(t)); + } + + return tgt; + } + + public static org.hl7.fhir.dstu3.model.ImagingStudy convertImagingStudy(org.hl7.fhir.r5.model.ImagingStudy src) throws FHIRException { + if (src == null) + return null; + org.hl7.fhir.dstu3.model.ImagingStudy tgt = new org.hl7.fhir.dstu3.model.ImagingStudy(); + copyDomainResource(src, tgt); + for (org.hl7.fhir.r5.model.Identifier t : src.getIdentifier()) { + + // assuming that there is only one urn:dicom:uid identifier + if (URN_DICOM_UID.equals(t.getSystem())) { + tgt.setUid(t.getValue()); + } else { + tgt.addIdentifier(convertIdentifier(t)); + } + } + if (src.hasStatus()) { + org.hl7.fhir.r5.model.ImagingStudy.ImagingStudyStatus s = src.getStatus(); + switch (s) { + case REGISTERED: + tgt.setAvailability(org.hl7.fhir.dstu3.model.ImagingStudy.InstanceAvailability.OFFLINE); + break; + case AVAILABLE: + tgt.setAvailability(org.hl7.fhir.dstu3.model.ImagingStudy.InstanceAvailability.ONLINE); + break; + case CANCELLED: + tgt.setAvailability(org.hl7.fhir.dstu3.model.ImagingStudy.InstanceAvailability.UNAVAILABLE); + break; + default: + break; + } + } + for (org.hl7.fhir.r5.model.Coding t : src.getModality()) { + tgt.addModalityList(convertCoding(t)); + } + if (src.hasSubject()) { + tgt.setPatient(convertReference(src.getSubject())); + } + if (src.hasEncounter()) { + tgt.setContext(convertReference(src.getEncounter())); + } + if (src.hasStarted()) { + + tgt.setStartedElement(convertDateTime(src.getStartedElement())); + } + for (org.hl7.fhir.r5.model.Reference t : src.getBasedOn()) { + tgt.addBasedOn(convertReference(t)); + } + if (src.hasReferrer()) { + tgt.setReferrer(convertReference(src.getReferrer())); + } + for (org.hl7.fhir.r5.model.Reference t : src.getInterpreter()) { + tgt.addInterpreter(convertReference(t)); + } + for (org.hl7.fhir.r5.model.Reference t : src.getEndpoint()) { + tgt.addEndpoint(convertReference(t)); + } + if (src.hasNumberOfSeries()) { + tgt.setNumberOfSeries(src.getNumberOfSeries()); + } + if (src.hasNumberOfInstances()) { + tgt.setNumberOfInstances(src.getNumberOfInstances()); + } + if (src.hasProcedureReference()) { + tgt.addProcedureReference(convertReference(src.getProcedureReference())); + } + for (org.hl7.fhir.r5.model.CodeableConcept t : src.getProcedureCode()) { + tgt.addProcedureCode(convertCodeableConcept(t)); + } + // location was added in R4 and does not exist in DSTU3 + List reasonCodes = src.getReasonCode(); + if (reasonCodes.size() > 0) { + tgt.setReason(convertCodeableConcept(reasonCodes.get(0))); + + if (reasonCodes.size() > 1) { + // TODO print a warning that only one reason could be converted + } + } + // reasonReference was added in R4 and does not exist in DSTU3 + // node was added in R4 and does not exist in DSTU3 + if (src.hasDescription()) { + tgt.setDescription(src.getDescription()); + } + + for (org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesComponent t : src.getSeries()) { + tgt.addSeries(convertImagingStudySeriesComponent(t)); + } + + return tgt; + } + + public static org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesComponent convertImagingStudySeriesComponent(org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesComponent src) throws FHIRException { + if (src == null) + return null; + org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesComponent tgt = new org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesComponent(); + copyElement(src, tgt); + + if (src.hasUid()) { + tgt.setUid(src.getUid()); + } + if (src.hasNumber()) { + tgt.setNumber(src.getNumber()); + } + if (src.hasModality()) { + tgt.setModality(convertCoding(src.getModality())); + } + if (src.hasDescription()) { + tgt.setDescription(src.getDescription()); + } + if (src.hasNumberOfInstances()) { + tgt.setNumberOfInstances(src.getNumberOfInstances()); + } + for (org.hl7.fhir.dstu3.model.Reference t : src.getEndpoint()) { + tgt.addEndpoint(convertReference(t)); + } + if (src.hasBodySite()) { + tgt.setBodySite(convertCoding(src.getBodySite())); + } + if (src.hasLaterality()) { + tgt.setLaterality(convertCoding(src.getLaterality())); + } + // the specimen element was added in R4 and does not exist in DSTU3 + if (src.hasStarted()) { + tgt.setStartedElement(convertDateTime(src.getStartedElement())); + } + + for (org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesInstanceComponent t : src.getInstance()) { + tgt.addInstance(convertImagingStudySeriesInstanceComponent(t)); + } + + return tgt; + } + + public static org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesComponent convertImagingStudySeriesComponent(org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesComponent src) throws FHIRException { + if (src == null) + return null; + org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesComponent tgt = new org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesComponent(); + copyElement(src, tgt); + + if (src.hasUid()) { + tgt.setUid(src.getUid()); + } + if (src.hasNumber()) { + tgt.setNumber(src.getNumber()); + } + if (src.hasModality()) { + tgt.setModality(convertCoding(src.getModality())); + } + if (src.hasDescription()) { + tgt.setDescription(src.getDescription()); + } + if (src.hasNumberOfInstances()) { + tgt.setNumberOfInstances(src.getNumberOfInstances()); + } + for (org.hl7.fhir.r5.model.Reference t : src.getEndpoint()) { + tgt.addEndpoint(convertReference(t)); + } + if (src.hasBodySite()) { + tgt.setBodySite(convertCoding(src.getBodySite())); + } + if (src.hasLaterality()) { + tgt.setLaterality(convertCoding(src.getLaterality())); + } + if (src.hasStarted()) { + + tgt.setStartedElement(convertDateTime(src.getStartedElement())); + } + // the specimen element was added in R4 and does not exist in DSTU3 + + for (org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesInstanceComponent t : src.getInstance()) { + tgt.addInstance(convertImagingStudySeriesInstanceComponent(t)); + } + + return tgt; + } + + private static final String URN_IETF_RFC_3986 = "urn:ietf:rfc:3986"; + + private static org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesInstanceComponent convertImagingStudySeriesInstanceComponent(org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesInstanceComponent src) throws FHIRException { + if (src == null) + return null; + org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesInstanceComponent tgt = new org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesInstanceComponent(); + copyElement(src, tgt); + + if (src.hasUid()) { + tgt.setUid(src.getUid()); + } + if (src.hasSopClass()) { + org.hl7.fhir.r5.model.Coding c = new org.hl7.fhir.r5.model.Coding(); + c.setSystem(URN_IETF_RFC_3986); + c.setCode(src.getSopClass()); + tgt.setSopClass(c); + } + if (src.hasNumber()) { + tgt.setNumber(src.getNumber()); + } + if (src.hasTitle()) { + tgt.setTitle(src.getTitle()); + } + + return tgt; + } + + private static org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesInstanceComponent convertImagingStudySeriesInstanceComponent(org.hl7.fhir.r5.model.ImagingStudy.ImagingStudySeriesInstanceComponent src) throws FHIRException { + if (src == null) + return null; + org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesInstanceComponent tgt = new org.hl7.fhir.dstu3.model.ImagingStudy.ImagingStudySeriesInstanceComponent(); + copyElement(src, tgt); + + if (src.hasUid()) { + tgt.setUid(src.getUid()); + } + org.hl7.fhir.r5.model.Coding sop = src.getSopClass(); + if (URN_IETF_RFC_3986.equals(sop.getSystem())) { + tgt.setSopClass(sop.getCode()); + } + if (src.hasNumber()) { + tgt.setNumber(src.getNumber()); + } + if (src.hasTitle()) { + tgt.setTitle(src.getTitle()); + } + + return tgt; + } + public static org.hl7.fhir.r5.model.Immunization convertImmunization(org.hl7.fhir.dstu3.model.Immunization src) throws FHIRException { if (src == null) return null; @@ -21286,6 +21607,8 @@ public class VersionConvertor_30_50 { return convertGroup((org.hl7.fhir.dstu3.model.Group) src); if (src instanceof org.hl7.fhir.dstu3.model.HealthcareService) return convertHealthcareService((org.hl7.fhir.dstu3.model.HealthcareService) src); + if (src instanceof org.hl7.fhir.dstu3.model.ImagingStudy) + return convertImagingStudy((org.hl7.fhir.dstu3.model.ImagingStudy)src); if (src instanceof org.hl7.fhir.dstu3.model.Immunization) return convertImmunization((org.hl7.fhir.dstu3.model.Immunization) src); if (src instanceof org.hl7.fhir.dstu3.model.ImplementationGuide) @@ -21463,6 +21786,8 @@ public class VersionConvertor_30_50 { return convertGroup((org.hl7.fhir.r5.model.Group) src); if (src instanceof org.hl7.fhir.r5.model.HealthcareService) return convertHealthcareService((org.hl7.fhir.r5.model.HealthcareService) src); + if (src instanceof org.hl7.fhir.r5.model.ImagingStudy) + return convertImagingStudy((org.hl7.fhir.r5.model.ImagingStudy)src); if (src instanceof org.hl7.fhir.r5.model.Immunization) return convertImmunization((org.hl7.fhir.r5.model.Immunization) src); if (src instanceof org.hl7.fhir.r5.model.ImplementationGuide) @@ -21567,7 +21892,7 @@ public class VersionConvertor_30_50 { public static boolean convertsResource(String rt) { return Utilities.existsInList(rt, "Parameters", "ActivityDefinition", "AllergyIntolerance", "Appointment", "AppointmentResponse", "AuditEvent", "Basic", "Binary", "BodyStructure", "Bundle", "CapabilityStatement", "CareTeam", "ClinicalImpression", "CodeSystem", "Communication", "CompartmentDefinition", "Composition", "ConceptMap", "Condition", "Consent", "DetectedIssue", "DeviceUseStatement", "DiagnosticReport", "DocumentReference", - "Encounter", "Endpoint", "EpisodeOfCare", "FamilyMemberHistory", "Flag", "Goal", "GraphDefinition", "Group", "HealthcareService", "Immunization", "ImplementationGuide", "Library", "Linkage", "ListResource", "Location", + "Encounter", "Endpoint", "EpisodeOfCare", "FamilyMemberHistory", "Flag", "Goal", "GraphDefinition", "Group", "HealthcareService", "ImagingStudy", "Immunization", "ImplementationGuide", "Library", "Linkage", "ListResource", "Location", "Medication", "MedicationAdministration", "MedicationDispense", "MedicationRequest", "MedicationStatement", "MessageDefinition", "MessageHeader", "NamingSystem", "Observation", "OperationDefinition", "OperationOutcome", "Organization", "Patient", "PaymentNotice", "Person", "PlanDefinition", "Practitioner", "PractitionerRole", "ProcessRequest", "Questionnaire", "QuestionnaireResponse", "RiskAssessment", "Schedule", "SearchParameter", "Sequence", "Slot", "Specimen", "StructureDefinition", "StructureMap", "Subscription", "Substance", "SupplyDelivery", "TestReport", "TestScript", "ValueSet"); diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java index 1e08cdc01..6dd725395 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java @@ -34,11 +34,6 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import org.apache.commons.lang3.NotImplementedException; -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.FHIRFormatError; -import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r4.conformance.ProfileUtilities; import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider; import org.hl7.fhir.r4.context.IWorkerContext; @@ -91,10 +86,10 @@ import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleTargetComponent; import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleTargetParameterComponent; import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupTypeMode; import org.hl7.fhir.r4.model.StructureMap.StructureMapInputMode; -import org.hl7.fhir.r4.model.StructureMap.StructureMapModelMode; import org.hl7.fhir.r4.model.StructureMap.StructureMapSourceListMode; -import org.hl7.fhir.r4.model.StructureMap.StructureMapStructureComponent; import org.hl7.fhir.r4.model.StructureMap.StructureMapTargetListMode; +import org.hl7.fhir.r4.model.StructureMap.StructureMapModelMode; +import org.hl7.fhir.r4.model.StructureMap.StructureMapStructureComponent; import org.hl7.fhir.r4.model.StructureMap.StructureMapTransform; import org.hl7.fhir.r4.model.Type; import org.hl7.fhir.r4.model.TypeDetails; @@ -105,6 +100,11 @@ import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext; +import org.apache.commons.lang3.NotImplementedException; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; @@ -1170,11 +1170,13 @@ public class StructureMapUtilities { target.addListMode(StructureMapTargetListMode.SHARE); lexer.next(); target.setListRuleId(lexer.take()); - } else if (lexer.getCurrent().equals("first")) - target.addListMode(StructureMapTargetListMode.FIRST); - else - target.addListMode(StructureMapTargetListMode.LAST); - lexer.next(); + } else { + if (lexer.getCurrent().equals("first")) + target.addListMode(StructureMapTargetListMode.FIRST); + else + target.addListMode(StructureMapTargetListMode.LAST); + lexer.next(); + } } } @@ -1219,7 +1221,7 @@ public class StructureMapUtilities { } public enum VariableMode { - INPUT, OUTPUT + INPUT, OUTPUT, SHARED } public class Variable { @@ -1277,16 +1279,25 @@ public class StructureMapUtilities { return null; } - public String summary() { - CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); - CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); - for (Variable v : list) - if (v.mode == VariableMode.INPUT) - s.append(v.summary()); - else - t.append(v.summary()); - return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; - } + public String summary() { + CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder sh = new CommaSeparatedStringBuilder(); + for (Variable v : list) + switch(v.mode) { + case INPUT: + s.append(v.summary()); + break; + case OUTPUT: + t.append(v.summary()); + break; + case SHARED: + sh.append(v.summary()); + break; + } + return "source variables ["+s.toString()+"], target variables ["+t.toString()+"], shared variables ["+sh.toString()+"]"; + } + } public class TransformContext { @@ -1379,7 +1390,7 @@ public class StructureMapUtilities { if (source != null) { for (Variables v : source) { for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { - processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot); + processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot, vars); } if (rule.hasRule()) { for (StructureMapGroupRuleComponent childrule : rule.getRule()) { @@ -1781,7 +1792,7 @@ public class StructureMapUtilities { return false; } - private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot) throws FHIRException { + private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot, Variables sharedVars) throws FHIRException { Base dest = null; if (tgt.hasContext()) { dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); @@ -1795,8 +1806,17 @@ public class StructureMapUtilities { v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot); if (v != null && dest != null) v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value - } else if (dest != null) - v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + } else if (dest != null) { + if (tgt.hasListMode(StructureMapTargetListMode.SHARE)) { + v = sharedVars.get(VariableMode.SHARED, tgt.getListRuleId()); + if (v == null) { + v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + sharedVars.add(VariableMode.SHARED, tgt.getListRuleId(), v); + } + } else { + v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + } + } if (tgt.hasVariable() && v != null) vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); } diff --git a/org.hl7.fhir.r4/src/main/resources/fml/.gitignore b/org.hl7.fhir.r4/src/main/resources/fml/.gitignore new file mode 100644 index 000000000..fa929750c --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/.gitignore @@ -0,0 +1 @@ +*.out \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/resources/fml/manifest.xml b/org.hl7.fhir.r4/src/main/resources/fml/manifest.xml new file mode 100644 index 000000000..89b3d6b31 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/manifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr.json b/org.hl7.fhir.r4/src/main/resources/fml/qr.json new file mode 100644 index 000000000..f0d14a3c1 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr.json @@ -0,0 +1,39 @@ +{ + "resourceType": "QuestionnaireResponse", + "status": "in-progress", + "item": [ + { + "linkId": "patient", + "text": "Patient", + "item": [ + { + "linkId": "patient.lastname", + "text": "Name", + "answer": [ + { + "valueString": "Brönnimann-Bertholet" + } + ] + }, + { + "linkId": "patient.firstname", + "text": "Vorname", + "answer": [ + { + "valueString": "Elisabeth" + } + ] + }, + { + "linkId": "patient.sex", + "text": "Geschlecht", + "answer": [ + { + "valueString": "female" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-assignment-res.json b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-assignment-res.json new file mode 100644 index 000000000..c29553577 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-assignment-res.json @@ -0,0 +1,4 @@ +{ + "resourceType" : "Patient", + "gender" : "female" +} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-assignment.map b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-assignment.map new file mode 100644 index 000000000..79ddfb89b --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-assignment.map @@ -0,0 +1,8 @@ +map "http://github.com/hapifhir/org.hl7.fhir.core/org.hl7.fhir.r4.tests/qr2patassignment" = "qr2patassignment" + +uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QuestionnaireResponse as source +uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target + +group QuestionnaireResponse(source src : QuestionnaireResponse, target tgt : Patient) { + src -> tgt.gender = 'female' "Simple Assignment"; +} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-gender-res.json b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-gender-res.json new file mode 100644 index 000000000..c29553577 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-gender-res.json @@ -0,0 +1,4 @@ +{ + "resourceType" : "Patient", + "gender" : "female" +} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-gender.map b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-gender.map new file mode 100644 index 000000000..ee25265c6 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-gender.map @@ -0,0 +1,12 @@ +map "http://github.com/hapifhir/org.hl7.fhir.core/org.hl7.fhir.r4.tests/qr2patgender" = "qr2patgender" + +uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QuestionnaireResponse as source +uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target + +group QuestionnaireResponse(source src : QuestionnaireResponse, target tgt : Patient) { + src.item as item -> tgt as patient then item(item, patient); +} + +group item(source src, target tgt: Patient) { + src.item as item where linkId.value in ('patient.sex') -> tgt.gender = (item.answer.valueString); +} diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannameshared-res.json b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannameshared-res.json new file mode 100644 index 000000000..240a91c69 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannameshared-res.json @@ -0,0 +1,8 @@ +{ + "resourceType" : "Patient", + "name" : [{ + "family" : "Brönnimann-Bertholet", + "given" : ["Elisabeth"] + }], + "gender" : "female" +} diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannameshared.map b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannameshared.map new file mode 100644 index 000000000..178a5d658 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannameshared.map @@ -0,0 +1,26 @@ +map "http://github.com/hapifhir/org.hl7.fhir.core/org.hl7.fhir.r4.tests/qr2pathumannameshared" = "qr2pathumannametwice" + +uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QuestionnaireResponse as source +uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target + +group entry(source src : QuestionnaireResponse, target tgt : Patient) { + src.item as item then item(item, tgt); +} + +group item(source src, target tgt) { + src.item as item then item(item, tgt); + src.item as item where linkId.value = 'patient.lastname' -> tgt.name as name share patientName then humanNameFamily(item, name); + src.item as item where linkId.value = 'patient.firstname' -> tgt.name as name share patientName then humanNameGiven(item, name); + src.item as item where linkId.value = 'patient.sex' -> tgt.gender = (item.answer.valueString); +} + +group humanNameFamily(source src, target tgt: HumanName) { + src.answer as answer -> tgt.family = (answer.valueString); +} +group humanNameGiven(source src, target tgt: HumanName) { + src.answer as answer -> tgt.given = (answer.valueString); +} + +group administrativeGender(source src, target tgt: code) { + src.answer as answer -> tgt = (answer.valueString); +} diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannametwice-res.json b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannametwice-res.json new file mode 100644 index 000000000..9d9bab7db --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannametwice-res.json @@ -0,0 +1,10 @@ +{ + "resourceType" : "Patient", + "name" : [{ + "family" : "Brönnimann-Bertholet" + }, + { + "given" : ["Elisabeth"] + }], + "gender" : "female" +} \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannametwice.map b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannametwice.map new file mode 100644 index 000000000..6de8b3c88 --- /dev/null +++ b/org.hl7.fhir.r4/src/main/resources/fml/qr2pat-humannametwice.map @@ -0,0 +1,26 @@ +map "http://github.com/hapifhir/org.hl7.fhir.core/org.hl7.fhir.r4.tests/qr2pathumannametwice" = "qr2pathumannametwice" + +uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QuestionnaireResponse as source +uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target + +group entry(source src : QuestionnaireResponse, target tgt : Patient) { + src.item as item then item(item, tgt); +} + +group item(source src, target tgt) { + src.item as item then item(item, tgt); + src.item as item where linkId.value = 'patient.lastname' -> tgt.name as name then humanNameFamily(item, name); + src.item as item where linkId.value = 'patient.firstname' -> tgt.name as name then humanNameGiven(item, name); + src.item as item where linkId.value = 'patient.sex' -> tgt.gender = (item.answer.valueString); +} + +group humanNameFamily(source src, target tgt: HumanName) { + src.answer as answer -> tgt.family = (answer.valueString); +} +group humanNameGiven(source src, target tgt: HumanName) { + src.answer as answer -> tgt.given = (answer.valueString); +} + +group administrativeGender(source src, target tgt: code) { + src.answer as answer -> tgt = (answer.valueString); +} diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/AllTests.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/AllTests.java index d78d08463..cb6cc15b1 100644 --- a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/AllTests.java +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/AllTests.java @@ -18,7 +18,8 @@ import org.junit.runners.Suite.SuiteClasses; NarrativeGeneratorTests.class, ShexGeneratorTests.class, BaseDateTimeTypeTest.class, - SnapShotGenerationTests.class}) + SnapShotGenerationTests.class, + FHIRMappingLanguageTests.class}) public class AllTests { } diff --git a/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/FHIRMappingLanguageTests.java b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/FHIRMappingLanguageTests.java new file mode 100644 index 000000000..303be9aaa --- /dev/null +++ b/org.hl7.fhir.r4/src/test/java/org/hl7/fhir/r4/test/FHIRMappingLanguageTests.java @@ -0,0 +1,167 @@ +package org.hl7.fhir.r4.test; + +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.context.SimpleWorkerContext; +import org.hl7.fhir.r4.elementmodel.Manager; +import org.hl7.fhir.r4.elementmodel.Manager.FhirFormat; +import org.hl7.fhir.r4.formats.IParser.OutputStyle; +import org.hl7.fhir.r4.formats.JsonParser; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.ResourceFactory; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; +import org.hl7.fhir.r4.model.StructureMap; +import org.hl7.fhir.r4.terminologies.ConceptMapEngine; +import org.hl7.fhir.r4.test.utils.TestingUtilities; +import org.hl7.fhir.r4.utils.StructureMapUtilities; +import org.hl7.fhir.r4.utils.StructureMapUtilities.ITransformerServices; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.cache.PackageCacheManager; +import org.hl7.fhir.utilities.cache.ToolsVersion; +import org.hl7.fhir.utilities.xml.XMLUtil; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +@RunWith(Parameterized.class) + +public class FHIRMappingLanguageTests implements ITransformerServices { + + private List outputs = new ArrayList(); + + static private SimpleWorkerContext context; + static private JsonParser jsonParser; + + @Parameters(name = "{index}: {0}") + public static Iterable data() + throws FileNotFoundException, IOException, ParserConfigurationException, SAXException { + Document tests = XMLUtil.parseFileToDom(TestingUtilities.resourceNameToFile("fml", "manifest.xml")); + Element test = XMLUtil.getFirstChild(tests.getDocumentElement()); + List objects = new ArrayList(); + while (test != null && test.getNodeName().equals("test")) { + objects.add(new Object[] { test.getAttribute("name"), test.getAttribute("source"), test.getAttribute("map"), + test.getAttribute("output") }); + test = XMLUtil.getNextSibling(test); + } + return objects; + } + + private final String name; + private String source; + private String output; + private String map; + + public FHIRMappingLanguageTests(String name, String source, String map, String output) { + this.name = name; + this.source = source; + this.output = output; + this.map = map; + } + + @BeforeClass + static public void setUp() throws Exception { + if (context == null) { + PackageCacheManager pcm = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION); + context = SimpleWorkerContext.fromPackage(pcm.loadPackage("hl7.fhir.core", "4.0.0")); + jsonParser = new JsonParser(); + jsonParser.setOutputStyle(OutputStyle.PRETTY); + } + } + + @Test + public void test() throws Exception { + + String fileSource = TestingUtilities.resourceNameToFile("fml", source); + String fileMap = TestingUtilities.resourceNameToFile("fml", map); + String fileOutput = TestingUtilities.resourceNameToFile("fml", output); + String fileOutputRes = TestingUtilities.resourceNameToFile("fml", output)+".out"; + + outputs.clear(); + + boolean ok = false; + String msg = null; + Resource resource = null; + try { + StructureMapUtilities scu = new StructureMapUtilities(context, this); + org.hl7.fhir.r4.elementmodel.Element src = Manager.parse(context, + new ByteArrayInputStream(TextFile.fileToBytes(fileSource)), FhirFormat.JSON); + StructureMap structureMap = scu.parse(TextFile.fileToString(fileMap), name); + String typeName = scu.getTargetType(structureMap).getType(); + resource = ResourceFactory.createResource(typeName); + scu.transform(null, src, structureMap, resource); + ok = true; + } catch (Exception e) { + ok = false; + msg = e.getMessage(); + } + if (ok) { + ByteArrayOutputStream boas = new ByteArrayOutputStream(); + jsonParser.compose(boas, resource); + log(boas.toString()); + TextFile.bytesToFile(boas.toByteArray(), fileOutputRes); + msg = TestingUtilities.checkJsonIsSame(fileOutputRes,fileOutput); + assertTrue(msg, Utilities.noString(msg)); + } else + assertTrue("Error, but proper output was expected (" + msg + ")", output.equals("$error")); + } + + @Override + public void log(String message) { + System.out.println(message); + } + + @Override + public Base createType(Object appInfo, String name) throws FHIRException { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, name); + if (sd != null && sd.getKind() == StructureDefinitionKind.LOGICAL) { + return Manager.build(context, sd); + } else { + if (name.startsWith("http://hl7.org/fhir/StructureDefinition/")) + name = name.substring("http://hl7.org/fhir/StructureDefinition/".length()); + return ResourceFactory.createResourceOrType(name); + } + } + + @Override + public Base createResource(Object appInfo, Base res, boolean atRootofTransform) { + if (atRootofTransform) + outputs.add((Resource) res); + return res; + } + + @Override + public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException { + ConceptMapEngine cme = new ConceptMapEngine(context); + return cme.translate(source, conceptMapUrl); + } + + @Override + public Base resolveReference(Object appContext, String url) throws FHIRException { + throw new FHIRException("resolveReference is not supported yet"); + } + + @Override + public List performSearch(Object appContext, String url) throws FHIRException { + throw new FHIRException("performSearch is not supported yet"); + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/StructureMapUtilities.java index 9c047ea71..de8895ce3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/StructureMapUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/StructureMapUtilities.java @@ -1170,11 +1170,13 @@ public class StructureMapUtilities { target.addListMode(StructureMapTargetListMode.SHARE); lexer.next(); target.setListRuleId(lexer.take()); - } else if (lexer.getCurrent().equals("first")) - target.addListMode(StructureMapTargetListMode.FIRST); - else - target.addListMode(StructureMapTargetListMode.LAST); - lexer.next(); + } else { + if (lexer.getCurrent().equals("first")) + target.addListMode(StructureMapTargetListMode.FIRST); + else + target.addListMode(StructureMapTargetListMode.LAST); + lexer.next(); + } } } @@ -1219,7 +1221,7 @@ public class StructureMapUtilities { } public enum VariableMode { - INPUT, OUTPUT + INPUT, OUTPUT, SHARED } public class Variable { @@ -1277,16 +1279,25 @@ public class StructureMapUtilities { return null; } - public String summary() { - CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); - CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); - for (Variable v : list) - if (v.mode == VariableMode.INPUT) - s.append(v.summary()); - else - t.append(v.summary()); - return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; - } + public String summary() { + CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder sh = new CommaSeparatedStringBuilder(); + for (Variable v : list) + switch(v.mode) { + case INPUT: + s.append(v.summary()); + break; + case OUTPUT: + t.append(v.summary()); + break; + case SHARED: + sh.append(v.summary()); + break; + } + return "source variables ["+s.toString()+"], target variables ["+t.toString()+"], shared variables ["+sh.toString()+"]"; + } + } public class TransformContext { @@ -1379,7 +1390,7 @@ public class StructureMapUtilities { if (source != null) { for (Variables v : source) { for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { - processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot); + processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot, vars); } if (rule.hasRule()) { for (StructureMapGroupRuleComponent childrule : rule.getRule()) { @@ -1781,7 +1792,7 @@ public class StructureMapUtilities { return false; } - private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot) throws FHIRException { + private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot, Variables sharedVars) throws FHIRException { Base dest = null; if (tgt.hasContext()) { dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); @@ -1795,8 +1806,17 @@ public class StructureMapUtilities { v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot); if (v != null && dest != null) v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value - } else if (dest != null) - v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + } else if (dest != null) { + if (tgt.hasListMode(StructureMapTargetListMode.SHARE)) { + v = sharedVars.get(VariableMode.SHARED, tgt.getListRuleId()); + if (v == null) { + v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + sharedVars.add(VariableMode.SHARED, tgt.getListRuleId(), v); + } + } else { + v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + } + } if (tgt.hasVariable() && v != null) vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); } diff --git a/org.hl7.fhir.r5/src/main/resources/fml/.gitignore b/org.hl7.fhir.r5/src/main/resources/fml/.gitignore new file mode 100644 index 000000000..fa929750c --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/.gitignore @@ -0,0 +1 @@ +*.out \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/resources/fml/manifest.xml b/org.hl7.fhir.r5/src/main/resources/fml/manifest.xml new file mode 100644 index 000000000..89b3d6b31 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/manifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr.json b/org.hl7.fhir.r5/src/main/resources/fml/qr.json new file mode 100644 index 000000000..f0d14a3c1 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr.json @@ -0,0 +1,39 @@ +{ + "resourceType": "QuestionnaireResponse", + "status": "in-progress", + "item": [ + { + "linkId": "patient", + "text": "Patient", + "item": [ + { + "linkId": "patient.lastname", + "text": "Name", + "answer": [ + { + "valueString": "Brönnimann-Bertholet" + } + ] + }, + { + "linkId": "patient.firstname", + "text": "Vorname", + "answer": [ + { + "valueString": "Elisabeth" + } + ] + }, + { + "linkId": "patient.sex", + "text": "Geschlecht", + "answer": [ + { + "valueString": "female" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-assignment-res.json b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-assignment-res.json new file mode 100644 index 000000000..c29553577 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-assignment-res.json @@ -0,0 +1,4 @@ +{ + "resourceType" : "Patient", + "gender" : "female" +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-assignment.map b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-assignment.map new file mode 100644 index 000000000..79ddfb89b --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-assignment.map @@ -0,0 +1,8 @@ +map "http://github.com/hapifhir/org.hl7.fhir.core/org.hl7.fhir.r4.tests/qr2patassignment" = "qr2patassignment" + +uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QuestionnaireResponse as source +uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target + +group QuestionnaireResponse(source src : QuestionnaireResponse, target tgt : Patient) { + src -> tgt.gender = 'female' "Simple Assignment"; +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-gender-res.json b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-gender-res.json new file mode 100644 index 000000000..c29553577 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-gender-res.json @@ -0,0 +1,4 @@ +{ + "resourceType" : "Patient", + "gender" : "female" +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-gender.map b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-gender.map new file mode 100644 index 000000000..ee25265c6 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-gender.map @@ -0,0 +1,12 @@ +map "http://github.com/hapifhir/org.hl7.fhir.core/org.hl7.fhir.r4.tests/qr2patgender" = "qr2patgender" + +uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QuestionnaireResponse as source +uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target + +group QuestionnaireResponse(source src : QuestionnaireResponse, target tgt : Patient) { + src.item as item -> tgt as patient then item(item, patient); +} + +group item(source src, target tgt: Patient) { + src.item as item where linkId.value in ('patient.sex') -> tgt.gender = (item.answer.valueString); +} diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannameshared-res.json b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannameshared-res.json new file mode 100644 index 000000000..240a91c69 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannameshared-res.json @@ -0,0 +1,8 @@ +{ + "resourceType" : "Patient", + "name" : [{ + "family" : "Brönnimann-Bertholet", + "given" : ["Elisabeth"] + }], + "gender" : "female" +} diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannameshared.map b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannameshared.map new file mode 100644 index 000000000..178a5d658 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannameshared.map @@ -0,0 +1,26 @@ +map "http://github.com/hapifhir/org.hl7.fhir.core/org.hl7.fhir.r4.tests/qr2pathumannameshared" = "qr2pathumannametwice" + +uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QuestionnaireResponse as source +uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target + +group entry(source src : QuestionnaireResponse, target tgt : Patient) { + src.item as item then item(item, tgt); +} + +group item(source src, target tgt) { + src.item as item then item(item, tgt); + src.item as item where linkId.value = 'patient.lastname' -> tgt.name as name share patientName then humanNameFamily(item, name); + src.item as item where linkId.value = 'patient.firstname' -> tgt.name as name share patientName then humanNameGiven(item, name); + src.item as item where linkId.value = 'patient.sex' -> tgt.gender = (item.answer.valueString); +} + +group humanNameFamily(source src, target tgt: HumanName) { + src.answer as answer -> tgt.family = (answer.valueString); +} +group humanNameGiven(source src, target tgt: HumanName) { + src.answer as answer -> tgt.given = (answer.valueString); +} + +group administrativeGender(source src, target tgt: code) { + src.answer as answer -> tgt = (answer.valueString); +} diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannametwice-res.json b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannametwice-res.json new file mode 100644 index 000000000..9d9bab7db --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannametwice-res.json @@ -0,0 +1,10 @@ +{ + "resourceType" : "Patient", + "name" : [{ + "family" : "Brönnimann-Bertholet" + }, + { + "given" : ["Elisabeth"] + }], + "gender" : "female" +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannametwice.map b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannametwice.map new file mode 100644 index 000000000..6de8b3c88 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/resources/fml/qr2pat-humannametwice.map @@ -0,0 +1,26 @@ +map "http://github.com/hapifhir/org.hl7.fhir.core/org.hl7.fhir.r4.tests/qr2pathumannametwice" = "qr2pathumannametwice" + +uses "http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse" alias QuestionnaireResponse as source +uses "http://hl7.org/fhir/StructureDefinition/Patient" alias Patient as target + +group entry(source src : QuestionnaireResponse, target tgt : Patient) { + src.item as item then item(item, tgt); +} + +group item(source src, target tgt) { + src.item as item then item(item, tgt); + src.item as item where linkId.value = 'patient.lastname' -> tgt.name as name then humanNameFamily(item, name); + src.item as item where linkId.value = 'patient.firstname' -> tgt.name as name then humanNameGiven(item, name); + src.item as item where linkId.value = 'patient.sex' -> tgt.gender = (item.answer.valueString); +} + +group humanNameFamily(source src, target tgt: HumanName) { + src.answer as answer -> tgt.family = (answer.valueString); +} +group humanNameGiven(source src, target tgt: HumanName) { + src.answer as answer -> tgt.given = (answer.valueString); +} + +group administrativeGender(source src, target tgt: code) { + src.answer as answer -> tgt = (answer.valueString); +} diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/AllTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/AllR5Tests.java similarity index 92% rename from org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/AllTests.java rename to org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/AllR5Tests.java index 3e1495668..552a66157 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/AllTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/AllR5Tests.java @@ -19,6 +19,6 @@ import org.junit.runners.Suite.SuiteClasses; ShexGeneratorTests.class, BaseDateTimeTypeTest.class, SnapShotGenerationTests.class}) -public class AllTests { +public class AllR5Tests { } diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java new file mode 100644 index 000000000..906aba456 --- /dev/null +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java @@ -0,0 +1,167 @@ +package org.hl7.fhir.r5.test; + +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.context.SimpleWorkerContext; +import org.hl7.fhir.r5.elementmodel.Manager; +import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; +import org.hl7.fhir.r5.formats.IParser.OutputStyle; +import org.hl7.fhir.r5.formats.JsonParser; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.ResourceFactory; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; +import org.hl7.fhir.r5.model.StructureMap; +import org.hl7.fhir.r5.terminologies.ConceptMapEngine; +import org.hl7.fhir.r5.test.utils.TestingUtilities; +import org.hl7.fhir.r5.utils.StructureMapUtilities; +import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.cache.PackageCacheManager; +import org.hl7.fhir.utilities.cache.ToolsVersion; +import org.hl7.fhir.utilities.xml.XMLUtil; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +@RunWith(Parameterized.class) + +public class FHIRMappingLanguageTests implements ITransformerServices { + + private List outputs = new ArrayList(); + + static private SimpleWorkerContext context; + static private JsonParser jsonParser; + + @Parameters(name = "{index}: {0}") + public static Iterable data() + throws FileNotFoundException, IOException, ParserConfigurationException, SAXException { + Document tests = XMLUtil.parseFileToDom(TestingUtilities.resourceNameToFile("fml", "manifest.xml")); + Element test = XMLUtil.getFirstChild(tests.getDocumentElement()); + List objects = new ArrayList(); + while (test != null && test.getNodeName().equals("test")) { + objects.add(new Object[] { test.getAttribute("name"), test.getAttribute("source"), test.getAttribute("map"), + test.getAttribute("output") }); + test = XMLUtil.getNextSibling(test); + } + return objects; + } + + private final String name; + private String source; + private String output; + private String map; + + public FHIRMappingLanguageTests(String name, String source, String map, String output) { + this.name = name; + this.source = source; + this.output = output; + this.map = map; + } + + @BeforeClass + static public void setUp() throws Exception { + if (context == null) { + PackageCacheManager pcm = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION); + context = SimpleWorkerContext.fromPackage(pcm.loadPackage("hl7.fhir.core", "4.0.0")); + jsonParser = new JsonParser(); + jsonParser.setOutputStyle(OutputStyle.PRETTY); + } + } + + @Test + public void test() throws Exception { + + String fileSource = TestingUtilities.resourceNameToFile("fml", source); + String fileMap = TestingUtilities.resourceNameToFile("fml", map); + String fileOutput = TestingUtilities.resourceNameToFile("fml", output); + String fileOutputRes = TestingUtilities.resourceNameToFile("fml", output)+".out"; + + outputs.clear(); + + boolean ok = false; + String msg = null; + Resource resource = null; + try { + StructureMapUtilities scu = new StructureMapUtilities(context, this); + org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, + new ByteArrayInputStream(TextFile.fileToBytes(fileSource)), FhirFormat.JSON); + StructureMap structureMap = scu.parse(TextFile.fileToString(fileMap), name); + String typeName = scu.getTargetType(structureMap).getType(); + resource = ResourceFactory.createResource(typeName); + scu.transform(null, src, structureMap, resource); + ok = true; + } catch (Exception e) { + ok = false; + msg = e.getMessage(); + } + if (ok) { + ByteArrayOutputStream boas = new ByteArrayOutputStream(); + jsonParser.compose(boas, resource); + log(boas.toString()); + TextFile.bytesToFile(boas.toByteArray(), fileOutputRes); + msg = TestingUtilities.checkJsonIsSame(fileOutputRes,fileOutput); + assertTrue(msg, Utilities.noString(msg)); + } else + assertTrue("Error, but proper output was expected (" + msg + ")", output.equals("$error")); + } + + @Override + public void log(String message) { + System.out.println(message); + } + + @Override + public Base createType(Object appInfo, String name) throws FHIRException { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, name); + if (sd != null && sd.getKind() == StructureDefinitionKind.LOGICAL) { + return Manager.build(context, sd); + } else { + if (name.startsWith("http://hl7.org/fhir/StructureDefinition/")) + name = name.substring("http://hl7.org/fhir/StructureDefinition/".length()); + return ResourceFactory.createResourceOrType(name); + } + } + + @Override + public Base createResource(Object appInfo, Base res, boolean atRootofTransform) { + if (atRootofTransform) + outputs.add((Resource) res); + return res; + } + + @Override + public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException { + ConceptMapEngine cme = new ConceptMapEngine(context); + return cme.translate(source, conceptMapUrl); + } + + @Override + public Base resolveReference(Object appContext, String url) throws FHIRException { + throw new FHIRException("resolveReference is not supported yet"); + } + + @Override + public List performSearch(Object appContext, String url) throws FHIRException { + throw new FHIRException("performSearch is not supported yet"); + } + +} diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ProfileUtilitiesTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ProfileUtilitiesTests.java index c7a0ccf96..a21dbeb12 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ProfileUtilitiesTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ProfileUtilitiesTests.java @@ -57,6 +57,8 @@ public class ProfileUtilitiesTests { else { b.setBase(null); f.setBase(null); + b.setRequirements(null); + f.setRequirements(null); ok = Base.compareDeep(b, f, true); } } @@ -98,6 +100,12 @@ public class ProfileUtilitiesTests { else { f.setBase(null); b.setBase(null); + b.setRequirements(null); + f.setRequirements(null); + b.setComment(null); + f.setComment(null); + b.setDefinition(null); + f.setDefinition(null); ok = Base.compareDeep(b, f, true); } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/InstanceValidator.java index 9d1c9dbba..fd093cecc 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/InstanceValidator.java @@ -121,6 +121,7 @@ import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException; import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; @@ -872,15 +873,55 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return true; return false; } - - private void checkCodeableConcept(List errors, String path, Element focus, CodeableConcept fixed) { + + + private boolean hasErrors(List errors) { + if (errors!=null) { + for (ValidationMessage vm : errors) { + if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) { + return true; + } + } + } + return false; + } + + private void checkCodeableConcept(List errors, String path, Element focus, CodeableConcept fixed, + boolean pattern) { checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus); List codings = new ArrayList(); focus.getNamedChildren("coding", codings); - if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(), - "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size()) + " coding elements")) { - for (int i = 0; i < codings.size(); i++) - checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), "coding", focus); + if (pattern) { + if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() >= fixed.getCoding().size(), + "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size()) + + " coding elements")) { + for (int i = 0; i < fixed.getCoding().size(); i++) { + Coding fixedCoding = fixed.getCoding().get(i); + boolean found = false; + List errorsFixed = null; + for (int j = 0; j < codings.size() && !found; ++j) { + errorsFixed = new ArrayList(); + checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, "coding", focus); + if (!hasErrors(errorsFixed)) { + found = true; + } + } + if (!found) { + rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, + "Expected patternCodeableConcept not found for"+ + " system: " + fixedCoding.getSystemElement().asStringValue() + + " code: " + fixedCoding.getCodeElement().asStringValue() + + " display: " + fixedCoding.getDisplayElement().asStringValue()); + } + } + } + } else { + if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(), + "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size()) + + " coding elements")) { + for (int i = 0; i < codings.size(); i++) + checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), "coding", focus); + } } } @@ -1292,8 +1333,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // return b.toString(); // } // - + private void checkFixedValue(List errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String propName, Element parent) { + checkFixedValue(errors, path, focus, fixed, propName, parent, false); + } + + private void checkFixedValue(List errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String propName, Element parent, boolean pattern) { if ((fixed == null || fixed.isEmpty()) && focus == null) ; // this is all good else if (fixed == null && focus != null) @@ -1356,7 +1401,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat else if (fixed instanceof HumanName) checkHumanName(errors, path, focus, (HumanName) fixed); else if (fixed instanceof CodeableConcept) - checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed); + checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, pattern); else if (fixed instanceof Timing) checkTiming(errors, path, focus, (Timing) fixed); else if (fixed instanceof Period) @@ -1622,8 +1667,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } - if (context.hasFixed()) - checkFixedValue(errors,path,e, context.getFixed(), context.getSliceName(), null); + if (context.hasFixed()) { + checkFixedValue(errors,path,e, context.getFixed(), context.getSliceName(), null, false); + } + if (context.hasPattern()) { + checkFixedValue(errors, path, e, context.getPattern(), context.getSliceName(), null, true); + } // for nothing to check } @@ -3823,6 +3872,9 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L if (ei.definition.hasFixed()) { checkFixedValue(errors,ei.path, ei.element, ei.definition.getFixed(), ei.definition.getSliceName(), null); } + if (ei.definition.hasPattern()) { + checkFixedValue(errors,ei.path, ei.element, ei.definition.getPattern(), ei.definition.getSliceName(), null, true); + } } if (type.equals("Identifier")) { checkIdentifier(errors, ei.path, ei.element, ei.definition); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/AllTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/AllR5ValidationTests.java similarity index 85% rename from org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/AllTests.java rename to org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/AllR5ValidationTests.java index 31a20c44e..8297eba66 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/AllTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/AllR5ValidationTests.java @@ -10,6 +10,6 @@ import org.junit.runners.Suite.SuiteClasses; ValidationEngineTests.class, JsonSchemaTests.class, CDAValidationTestCase.class}) -public class AllTests { +public class AllR5ValidationTests { } diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/manifest.json b/org.hl7.fhir.validation/src/test/resources/validation-examples/manifest.json index 71d23394d..b16ef0cfe 100644 --- a/org.hl7.fhir.validation/src/test/resources/validation-examples/manifest.json +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/manifest.json @@ -467,7 +467,40 @@ "source" : "extension-slice-profile.xml", "errorCount": 0 } + }, + "slice-by-polymorphic-type2.xml" : { + "errorCount": 0, + "profile" : { + "source" : "slice-by-polymorphic-type2-profile.xml", + "errorCount": 0 + } }, + "observation-bp-validationfails.xml" : { + "errorCount": 4, + "errors": [ + "ERROR: Observation.component[1]: Error in discriminator at Observation.component:SystolicBP.code.coding: slicing found", + "ERROR: Observation.component[2]: Error in discriminator at Observation.component:SystolicBP.code.coding: slicing found", + "ERROR: Observation.component[1]: Error in discriminator at Observation.component:DiastolicBP.code.coding: slicing found", + "ERROR: Observation.component[2]: Error in discriminator at Observation.component:DiastolicBP.code.coding: slicing found" + ] + }, + "observation-bp.xml" : { + "errorCount": 0, + "profile" : { + "source" : "observation-bp-profile.xml", + "errorCount": 0 + } + }, + "observation-bp-bad-onlydiastolic.xml" : { + "errorCount": 0, + "profile" : { + "source" : "observation-bp-profile.xml", + "errorCount": 1, + "errors": [ + "ERROR: Observation: Profile http://hl7.org/fhir/StructureDefinition/observation-bp-profile, Element 'Observation.component[SystolicBP]': minimum required = 1, but only found 0" + ] + } + }, "observation-cholesterol-good.xml" : { "errorCount": 0 }, @@ -489,7 +522,21 @@ "source" : "invariant.profile.xml", "errorCount": 1 } + }, + "observation-triglyceride-good.xml" : { + "warningCount": 1, + "errorCount": 0, + "warnings": ["WARNING: Observation.code.coding[1]: The display \"Triglyceride [Moles/​volume] in Serum or Plasma\" is not a valid display for the code {http://loinc.org}35217-9 - should be one of [\"Triglyceride [Mass or Moles/volume] in Serum or Plasma\",\"Trigl SerPl-msCnc\""] + }, + "observation-triglyceride-good2.xml" : { + "warningCount": 1, + "errorCount": 0, + "warnings": ["WARNING: Observation.code.coding[2]: The display \"Triglyceride [Moles/​volume] in Serum or Plasma\" is not a valid display for the code {http://loinc.org}35217-9 - should be one of [\"Triglyceride [Mass or Moles/volume] in Serum or Plasma\",\"Trigl SerPl-msCnc\""] + }, + "observation-triglyceride-bad-wrongcode.xml" : { + "errorCount": 1, + "errors": ["ERROR: Observation.code: Expected patternCodeableConcept not found for system: http://loinc.org code: 35217-9 display: Triglyceride [Moles/​volume] in Serum or Plasma"] } - } + } } } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-bad-onlydiastolic.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-bad-onlydiastolic.xml new file mode 100644 index 000000000..17bace2c6 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-bad-onlydiastolic.xml @@ -0,0 +1,250 @@ + + + + + +
+

+ Generated Narrative with Details +

+

+ id + : blood-pressure +

+

+ meta + : +

+

+ identifier + : urn:uuid:187e0c12-8dd2-67e2-99b2-bf273c878281 +

+

+ basedOn + : +

+

+ status + : final +

+

+ category + : Vital Signs + (Details : + {http://terminology.hl7.org/CodeSystem/observation-category code + 'vital-signs' = 'Vital Signs', given as 'Vital + Signs'}) +

+

+ code: + Blood pressure systolic & diastolic + (Details : {LOINC code '85354-9' = 'Blood pressure + panel with all children optional', given as 'Blood pressure + panel with all children optional'}) +

+

+ subject + : + Patient/example +

+

+ effective + : 17/09/2012 +

+

+ performer + : + Practitioner/example +

+

+ interpretation + : Below low normal + (Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'L' = 'Low', given as 'low'}) +

+

+ bodySite + : Right arm + (Details : {SNOMED CT code '368209003' = 'Right + upper arm', given as 'Right arm'}) +

+
+

+ component +

+

+ code + : Systolic blood pressure + (Details : {LOINC code '8480-6' = 'Systolic blood + pressure', given as 'Systolic blood pressure'}; + {SNOMED CT code '271649006' = 'Systolic blood + pressure', given as 'Systolic blood pressure'}; + {http://acme.org/devices/clinical-codes code 'bp-s' = + 'bp-s', given as 'Systolic Blood pressure'}) + +

+

+ value + : 107 mmHg + (Details: UCUM code mm[Hg] = 'mmHg') +

+

+ interpretation + : Normal + (Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'N' = 'Normal', given as 'normal'}) + +

+
+
+

+ component +

+

+ code + : Diastolic blood pressure + (Details : {LOINC code '8462-4' = 'Diastolic + blood pressure', given as 'Diastolic blood pressure'}) + +

+

+ value + : 60 mmHg + (Details: UCUM code mm[Hg] = 'mmHg') +

+

+ interpretation + : Below low normal + (Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'L' = 'Low', given as 'low'}) +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-profile.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-profile.xml new file mode 100644 index 000000000..2cde58b0c --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-profile.xml @@ -0,0 +1,243 @@ + + + + +
reworked official bp profile to use patternCodeableConcept
+
+ + + + + <status value="draft" /> + <experimental value="false" /> + <date value="2019-05-06" /> + <publisher + value="Health Level Seven International (Orders and Observations Workgroup)" /> + <contact> + <telecom> + <system value="url" /> + <value + value="http://www.hl7.org/Special/committees/orders/index.cfm Orders and Observations" /> + </telecom> + </contact> + <description value="FHIR Blood Pressure Profile" /> + <fhirVersion value="4.0.0" /> + <mapping> + <identity value="workflow" /> + <uri value="http://hl7.org/fhir/workflow" /> + <name value="Workflow Pattern" /> + </mapping> + <mapping> + <identity value="sct-concept" /> + <uri value="http://snomed.info/conceptdomain" /> + <name value="SNOMED CT Concept Domain Binding" /> + </mapping> + <mapping> + <identity value="v2" /> + <uri value="http://hl7.org/v2" /> + <name value="HL7 v2 Mapping" /> + </mapping> + <mapping> + <identity value="rim" /> + <uri value="http://hl7.org/v3" /> + <name value="RIM Mapping" /> + </mapping> + <mapping> + <identity value="w5" /> + <uri value="http://hl7.org/fhir/fivews" /> + <name value="FiveWs Pattern Mapping" /> + </mapping> + <mapping> + <identity value="sct-attr" /> + <uri value="http://snomed.org/attributebinding" /> + <name value="SNOMED CT Attribute Binding" /> + </mapping> + <kind value="resource" /> + <abstract value="false" /> + <type value="Observation" /> + <baseDefinition + value="http://hl7.org/fhir/StructureDefinition/vitalsigns" /> + <derivation value="constraint" /> + <differential> + <element id="Observation"> + <path value="Observation" /> + <short value="FHIR Blood Pressure Profile" /> + <definition + value="This profile defines how to represent Blood Pressure observations in FHIR using a standard LOINC code and UCUM units of measure. This is a grouping structure. It has no value in Observation.valueQuantity but contains at least one component (systolic and/or diastolic)." /> + <min value="0" /> + <max value="*" /> + </element> + <element id="Observation.code"> + <path value="Observation.code" /> + <short value="Blood Pressure" /> + <definition value="Blood Pressure" /> + <min value="1" /> + <max value="1" /> + <type> + <code value="CodeableConcept" /> + </type> + <patternCodeableConcept> + <coding> + <system value="http://loinc.org" /> + <code value="85354-9" /> + </coding> + </patternCodeableConcept> + <mustSupport value="true" /> + </element> + <element id="Observation.valueQuantity"> + <path value="Observation.valueQuantity" /> + <min value="0" /> + <max value="0" /> + </element> + <element id="Observation.component"> + <path value="Observation.component" /> + <slicing> + <discriminator> + <type value="pattern" /> + <path value="code" /> + </discriminator> + <rules value="open" /> + </slicing> + <min value="2" /> + <max value="*" /> + </element> + <element id="Observation.component:SystolicBP"> + <path value="Observation.component" /> + <sliceName value="SystolicBP" /> + <short value="SystolicBP" /> + <min value="1" /> + <max value="1" /> + </element> + <element id="Observation.component:SystolicBP.code"> + <path value="Observation.component.code" /> + <min value="1" /> + <patternCodeableConcept> + <coding> + <system value="http://loinc.org" /> + <code value="8480-6" /> + </coding> + </patternCodeableConcept> + </element> + <element id="Observation.component:SystolicBP.valueQuantity"> + <path value="Observation.component.valueQuantity" /> + <type> + <code value="Quantity" /> + </type> + </element> + <element + id="Observation.component:SystolicBP.valueQuantity.value"> + <path value="Observation.component.valueQuantity.value" /> + <min value="1" /> + <max value="1" /> + <type> + <code value="decimal" /> + </type> + <mustSupport value="true" /> + </element> + <element + id="Observation.component:SystolicBP.valueQuantity.unit"> + <path value="Observation.component.valueQuantity.unit" /> + <min value="1" /> + <max value="1" /> + <type> + <code value="string" /> + </type> + <mustSupport value="true" /> + </element> + <element + id="Observation.component:SystolicBP.valueQuantity.system"> + <path value="Observation.component.valueQuantity.system" /> + <min value="1" /> + <max value="1" /> + <type> + <code value="uri" /> + </type> + <fixedUri value="http://unitsofmeasure.org" /> + <mustSupport value="true" /> + </element> + <element + id="Observation.component:SystolicBP.valueQuantity.code"> + <path value="Observation.component.valueQuantity.code" /> + <short + value="Coded responses from the common UCUM units for vital signs value set." /> + <definition + value="Coded responses from the common UCUM units for vital signs value set." /> + <min value="1" /> + <max value="1" /> + <type> + <code value="code" /> + </type> + <fixedCode value="mm[Hg]" /> + <mustSupport value="true" /> + </element> + <element id="Observation.component:DiastolicBP"> + <path value="Observation.component" /> + <sliceName value="DiastolicBP" /> + <short value="DiastolicBP" /> + <min value="1" /> + <max value="1" /> + </element> + <element id="Observation.component:DiastolicBP"> + <path value="Observation.component.code" /> + <min value="1" /> + <patternCodeableConcept> + <coding> + <system value="http://loinc.org" /> + <code value="8462-4" /> + </coding> + </patternCodeableConcept> + </element> + <element id="Observation.component:DiastolicBP.valueQuantity"> + <path value="Observation.component.valueQuantity" /> + <type> + <code value="Quantity" /> + </type> + </element> + <element + id="Observation.component:DiastolicBP.valueQuantity.value"> + <path value="Observation.component.valueQuantity.value" /> + <min value="1" /> + <max value="1" /> + <type> + <code value="decimal" /> + </type> + <mustSupport value="true" /> + </element> + <element + id="Observation.component:DiastolicBP.valueQuantity.unit"> + <path value="Observation.component.valueQuantity.unit" /> + <min value="1" /> + <max value="1" /> + <type> + <code value="string" /> + </type> + <mustSupport value="true" /> + </element> + <element + id="Observation.component:DiastolicBP.valueQuantity.system"> + <path value="Observation.component.valueQuantity.system" /> + <min value="1" /> + <max value="1" /> + <type> + <code value="uri" /> + </type> + <fixedUri value="http://unitsofmeasure.org" /> + <mustSupport value="true" /> + </element> + <element + id="Observation.component:DiastolicBP.valueQuantity.code"> + <path value="Observation.component.valueQuantity.code" /> + <short + value="Coded responses from the common UCUM units for vital signs value set." /> + <definition + value="Coded responses from the common UCUM units for vital signs value set." /> + <min value="1" /> + <max value="1" /> + <type> + <code value="code" /> + </type> + <fixedCode value="mm[Hg]" /> + <mustSupport value="true" /> + </element> + </differential> +</StructureDefinition> \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-validationfails.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-validationfails.xml new file mode 100644 index 000000000..e42332282 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp-validationfails.xml @@ -0,0 +1,253 @@ +<Observation xmlns="http://hl7.org/fhir"> + <!-- extract from https://www.hl7.org/fhir/observation-example-bloodpressure.xml, + changed id from blood-pressure to observation-bp-validationfails and added explicit profile + reference to http://hl7.org/fhir/StructureDefinition/bp --> + <id value="observation-bp-validationfails" /> + <meta> + <profile value="http://hl7.org/fhir/StructureDefinition/bp" /> + </meta> + <text> + <status value="generated" /> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p> + <b>Generated Narrative with Details</b> + </p> + <p> + <b>id</b> + : blood-pressure + </p> + <p> + <b>meta</b> + : + </p> + <p> + <b>identifier</b> + : urn:uuid:187e0c12-8dd2-67e2-99b2-bf273c878281 + </p> + <p> + <b>basedOn</b> + : + </p> + <p> + <b>status</b> + : final + </p> + <p> + <b>category</b> + : Vital Signs + <span>(Details : + {http://terminology.hl7.org/CodeSystem/observation-category code + 'vital-signs' = 'Vital Signs', given as 'Vital + Signs'})</span> + </p> + <p> + <b>code</b>: + Blood pressure systolic & diastolic + <span>(Details : {LOINC code '85354-9' = 'Blood pressure + panel with all children optional', given as 'Blood pressure + panel with all children optional'})</span> + </p> + <p> + <b>subject</b> + : + <a>Patient/example</a> + </p> + <p> + <b>effective</b> + : 17/09/2012 + </p> + <p> + <b>performer</b> + : + <a>Practitioner/example</a> + </p> + <p> + <b>interpretation</b> + : Below low normal + <span>(Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'L' = 'Low', given as 'low'})</span> + </p> + <p> + <b>bodySite</b> + : Right arm + <span>(Details : {SNOMED CT code '368209003' = 'Right + upper arm', given as 'Right arm'})</span> + </p> + <blockquote> + <p> + <b>component</b> + </p> + <p> + <b>code</b> + : Systolic blood pressure + <span>(Details : {LOINC code '8480-6' = 'Systolic blood + pressure', given as 'Systolic blood pressure'}; + {SNOMED CT code '271649006' = 'Systolic blood + pressure', given as 'Systolic blood pressure'}; + {http://acme.org/devices/clinical-codes code 'bp-s' = + 'bp-s', given as 'Systolic Blood pressure'}) + </span> + </p> + <p> + <b>value</b> + : 107 mmHg + <span> (Details: UCUM code mm[Hg] = 'mmHg')</span> + </p> + <p> + <b>interpretation</b> + : Normal + <span>(Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'N' = 'Normal', given as 'normal'}) + </span> + </p> + </blockquote> + <blockquote> + <p> + <b>component</b> + </p> + <p> + <b>code</b> + : Diastolic blood pressure + <span>(Details : {LOINC code '8462-4' = 'Diastolic + blood pressure', given as 'Diastolic blood pressure'}) + </span> + </p> + <p> + <b>value</b> + : 60 mmHg + <span> (Details: UCUM code mm[Hg] = 'mmHg')</span> + </p> + <p> + <b>interpretation</b> + : Below low normal + <span>(Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'L' = 'Low', given as 'low'})</span> + </p> + </blockquote> + </div> + </text> + <identifier> + <system value="urn:ietf:rfc:3986" /> + <value value="urn:uuid:187e0c12-8dd2-67e2-99b2-bf273c878281" /> + </identifier><!-- demonstrating the use of the baseOn element with a fictive + identifier --> + <basedOn> + <identifier> + <system value="https://acme.org/identifiers" /> + <value value="1234" /> + </identifier> + </basedOn> + <status value="final" /> + <category> + <coding> + <system + value="http://terminology.hl7.org/CodeSystem/observation-category" /> + <code value="vital-signs" /> + <display value="Vital Signs" /> + </coding> + </category> + <code><!-- replaced by 85354-9 <coding> <system value="http://loinc.org"/> + <code value="85354-9"/> <display value="Blood pressure systolic & diastolic"/> + </coding> --> + <coding> + <system value="http://loinc.org" /> + <code value="85354-9" /> + <display + value="Blood pressure panel with all children optional" /> + </coding> + <text value="Blood pressure systolic & diastolic" /> + </code> + <subject> + <reference value="Patient/example" /> + </subject> + <effectiveDateTime value="2012-09-17" /> + <performer> + <reference value="Practitioner/example" /> + </performer><!-- an interpretation offered to the combination observation + generally, it would only be appropriate to offer an interpretation of an + observation that has no value if it has "COMP" (component) observations --> + <interpretation> + <coding> + <system + value="http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation" /> + <code value="L" /> + <display value="low" /> + </coding> + <text value="Below low normal" /> + </interpretation><!-- The BodySite can be captured in a LOINC code but am + showing it here to demonstrate populating the element --> + <bodySite> + <coding> + <system value="http://snomed.info/sct" /> + <code value="368209003" /> + <display value="Right arm" /> + </coding> + </bodySite> + <component><!-- Observations are often coded in multiple code systems. - + LOINC provides a very specific code (though not more specific in this particular + case) - snomed provides a clinically relevant code that is usually less granular + than LOINC - the source system provides its own code, which may be less or + more granular than LOINC this is shown here to demonstrate the concept of + translations within the codeableConcept datatype. The diastolic code below + only has a LOINC code --> + <code><!-- LOINC -code --> + <coding> + <system value="http://loinc.org" /> + <code value="8480-6" /> + <display value="Systolic blood pressure" /> + </coding><!-- SNOMED CT Codes --> + <coding> + <system value="http://snomed.info/sct" /> + <code value="271649006" /> + <display value="Systolic blood pressure" /> + </coding><!-- Also, a local code specific to the source system --> + <coding> + <system value="http://acme.org/devices/clinical-codes" /> + <code value="bp-s" /> + <display value="Systolic Blood pressure" /> + </coding> + </code> + <valueQuantity> + <value value="107" /> + <unit value="mmHg" /> + <system value="http://unitsofmeasure.org" /> + <code value="mm[Hg]" /> + </valueQuantity><!-- an interpretation for the individual composite observation --> + <interpretation> + <coding> + <system + value="http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation" /> + <code value="N" /> + <display value="normal" /> + </coding> + <text value="Normal" /> + </interpretation><!-- Should have a refrange as well --> + </component> + <component><!-- this codes only has a LOINC code --> + <code> + <coding> + <system value="http://loinc.org" /> + <code value="8462-4" /> + <display value="Diastolic blood pressure" /> + </coding> + </code> + <valueQuantity> + <value value="60" /> + <unit value="mmHg" /> + <system value="http://unitsofmeasure.org" /> + <code value="mm[Hg]" /> + </valueQuantity><!-- an interpretation for the individual composite observation --> + <interpretation> + <coding> + <system + value="http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation" /> + <code value="L" /> + <display value="low" /> + </coding> + <text value="Below low normal" /> + </interpretation><!-- Should have a refrange as well --> + </component> +</Observation> \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp.xml new file mode 100644 index 000000000..46e63a465 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-bp.xml @@ -0,0 +1,249 @@ +<Observation xmlns="http://hl7.org/fhir"> + <!-- extract from https://www.hl7.org/fhir/observation-example-bloodpressure.xml, + changed id from blood-pressure to observation-bp --> + <id value="observation-bp" /> + <text> + <status value="generated" /> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p> + <b>Generated Narrative with Details</b> + </p> + <p> + <b>id</b> + : blood-pressure + </p> + <p> + <b>meta</b> + : + </p> + <p> + <b>identifier</b> + : urn:uuid:187e0c12-8dd2-67e2-99b2-bf273c878281 + </p> + <p> + <b>basedOn</b> + : + </p> + <p> + <b>status</b> + : final + </p> + <p> + <b>category</b> + : Vital Signs + <span>(Details : + {http://terminology.hl7.org/CodeSystem/observation-category code + 'vital-signs' = 'Vital Signs', given as 'Vital + Signs'})</span> + </p> + <p> + <b>code</b>: + Blood pressure systolic & diastolic + <span>(Details : {LOINC code '85354-9' = 'Blood pressure + panel with all children optional', given as 'Blood pressure + panel with all children optional'})</span> + </p> + <p> + <b>subject</b> + : + <a>Patient/example</a> + </p> + <p> + <b>effective</b> + : 17/09/2012 + </p> + <p> + <b>performer</b> + : + <a>Practitioner/example</a> + </p> + <p> + <b>interpretation</b> + : Below low normal + <span>(Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'L' = 'Low', given as 'low'})</span> + </p> + <p> + <b>bodySite</b> + : Right arm + <span>(Details : {SNOMED CT code '368209003' = 'Right + upper arm', given as 'Right arm'})</span> + </p> + <blockquote> + <p> + <b>component</b> + </p> + <p> + <b>code</b> + : Systolic blood pressure + <span>(Details : {LOINC code '8480-6' = 'Systolic blood + pressure', given as 'Systolic blood pressure'}; + {SNOMED CT code '271649006' = 'Systolic blood + pressure', given as 'Systolic blood pressure'}; + {http://acme.org/devices/clinical-codes code 'bp-s' = + 'bp-s', given as 'Systolic Blood pressure'}) + </span> + </p> + <p> + <b>value</b> + : 107 mmHg + <span> (Details: UCUM code mm[Hg] = 'mmHg')</span> + </p> + <p> + <b>interpretation</b> + : Normal + <span>(Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'N' = 'Normal', given as 'normal'}) + </span> + </p> + </blockquote> + <blockquote> + <p> + <b>component</b> + </p> + <p> + <b>code</b> + : Diastolic blood pressure + <span>(Details : {LOINC code '8462-4' = 'Diastolic + blood pressure', given as 'Diastolic blood pressure'}) + </span> + </p> + <p> + <b>value</b> + : 60 mmHg + <span> (Details: UCUM code mm[Hg] = 'mmHg')</span> + </p> + <p> + <b>interpretation</b> + : Below low normal + <span>(Details : + {http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation + code 'L' = 'Low', given as 'low'})</span> + </p> + </blockquote> + </div> + </text> + <identifier> + <system value="urn:ietf:rfc:3986" /> + <value value="urn:uuid:187e0c12-8dd2-67e2-99b2-bf273c878281" /> + </identifier><!-- demonstrating the use of the baseOn element with a fictive + identifier --> + <basedOn> + <identifier> + <system value="https://acme.org/identifiers" /> + <value value="1234" /> + </identifier> + </basedOn> + <status value="final" /> + <category> + <coding> + <system + value="http://terminology.hl7.org/CodeSystem/observation-category" /> + <code value="vital-signs" /> + <display value="Vital Signs" /> + </coding> + </category> + <code><!-- replaced by 85354-9 <coding> <system value="http://loinc.org"/> + <code value="85354-9"/> <display value="Blood pressure systolic & diastolic"/> + </coding> --> + <coding> + <system value="http://loinc.org" /> + <code value="85354-9" /> + <display + value="Blood pressure panel with all children optional" /> + </coding> + <text value="Blood pressure systolic & diastolic" /> + </code> + <subject> + <reference value="Patient/example" /> + </subject> + <effectiveDateTime value="2012-09-17" /> + <performer> + <reference value="Practitioner/example" /> + </performer><!-- an interpretation offered to the combination observation + generally, it would only be appropriate to offer an interpretation of an + observation that has no value if it has "COMP" (component) observations --> + <interpretation> + <coding> + <system + value="http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation" /> + <code value="L" /> + <display value="low" /> + </coding> + <text value="Below low normal" /> + </interpretation><!-- The BodySite can be captured in a LOINC code but am + showing it here to demonstrate populating the element --> + <bodySite> + <coding> + <system value="http://snomed.info/sct" /> + <code value="368209003" /> + <display value="Right arm" /> + </coding> + </bodySite> + <component><!-- Observations are often coded in multiple code systems. - + LOINC provides a very specific code (though not more specific in this particular + case) - snomed provides a clinically relevant code that is usually less granular + than LOINC - the source system provides its own code, which may be less or + more granular than LOINC this is shown here to demonstrate the concept of + translations within the codeableConcept datatype. The diastolic code below + only has a LOINC code --> + <code><!-- LOINC -code --> + <coding> + <system value="http://loinc.org" /> + <code value="8480-6" /> + <display value="Systolic blood pressure" /> + </coding><!-- SNOMED CT Codes --> + <coding> + <system value="http://snomed.info/sct" /> + <code value="271649006" /> + <display value="Systolic blood pressure" /> + </coding><!-- Also, a local code specific to the source system --> + <coding> + <system value="http://acme.org/devices/clinical-codes" /> + <code value="bp-s" /> + <display value="Systolic Blood pressure" /> + </coding> + </code> + <valueQuantity> + <value value="107" /> + <unit value="mmHg" /> + <system value="http://unitsofmeasure.org" /> + <code value="mm[Hg]" /> + </valueQuantity><!-- an interpretation for the individual composite observation --> + <interpretation> + <coding> + <system + value="http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation" /> + <code value="N" /> + <display value="normal" /> + </coding> + <text value="Normal" /> + </interpretation><!-- Should have a refrange as well --> + </component> + <component><!-- this codes only has a LOINC code --> + <code> + <coding> + <system value="http://loinc.org" /> + <code value="8462-4" /> + <display value="Diastolic blood pressure" /> + </coding> + </code> + <valueQuantity> + <value value="60" /> + <unit value="mmHg" /> + <system value="http://unitsofmeasure.org" /> + <code value="mm[Hg]" /> + </valueQuantity><!-- an interpretation for the individual composite observation --> + <interpretation> + <coding> + <system + value="http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation" /> + <code value="L" /> + <display value="low" /> + </coding> + <text value="Below low normal" /> + </interpretation><!-- Should have a refrange as well --> + </component> +</Observation> \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-bad-wrongcode.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-bad-wrongcode.xml new file mode 100644 index 000000000..247425206 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-bad-wrongcode.xml @@ -0,0 +1,103 @@ +<Observation xmlns="http://hl7.org/fhir"> + <!-- extract from http://hl7.org/fhir/triglyceride-examples.html, changed + id from triglyceride and added explivit profile reference to http://hl7.org/fhir/StructureDefinition/triglyceride + and added snomed ct code for triglyceride --> + <id value="observation-triglyceride-bad-wrongcode" /> + <meta> + <profile + value="http://hl7.org/fhir/StructureDefinition/triglyceride" /> + </meta> + <text> + <status value="generated" /> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p> + <b> Generated Narrative with Details</b> + </p> + <p> + <b> id</b> + : triglyceride + </p> + <p> + <b> status</b> + : final + </p> + <p> + <b> code</b> + : Triglyceride + <span> (Details : {LOINC code '35217-9' = 'Triglyceride [Mass or + Moles/volume] in Serum or Plasma', + given as 'Triglyceride + [Moles/​volume] in Serum or Plasma'}) + </span> + </p> + <p> + <b> subject</b> + : + <a> Patient/pat2</a> + </p> + <p> + <b> performer</b> + : + <a> Acme Laboratory, Inc</a> + </p> + <p> + <b> value</b> + : 1.3 mmol/L + <span> (Details: UCUM code mmol/L = 'mmol/L')</span> + </p> + <h3> ReferenceRanges</h3> + <table> + <tr> + <td> -</td> + <td> + <b> High</b> + </td> + </tr> + <tr> + <td> *</td> + <td> + 2.0 mmol/L + <span> (Details: UCUM code mmol/L = 'mmol/L')</span> + </td> + </tr> + </table> + </div> + </text> + <status value="final" /> + <code> + <coding> + <system value="http://loinc.org" /> + <code value="35200-5" /> + <display + value="Cholest SerPl-msCnc" /> + </coding> + <coding> + <system value="http://snomed.info/sct" /> + <code value="85600001" /> + <display value="Triacylglycerol" /> + </coding> + <text value="Cholesterol" /> + </code> + <subject> + <reference value="Patient/pat2" /> + </subject> + <performer> + <reference + value="Organization/1832473e-2fe0-452d-abe9-3cdb9879522f" /> + <display value="Acme Laboratory, Inc" /> + </performer> + <valueQuantity> + <value value="1.3" /> + <unit value="mmol/L" /> + <system value="http://unitsofmeasure.org" /> + <code value="mmol/L" /> + </valueQuantity> + <referenceRange> + <high> + <value value="2.0" /> + <unit value="mmol/L" /> + <system value="http://unitsofmeasure.org" /> + <code value="mmol/L" /> + </high> + </referenceRange> +</Observation> \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-good.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-good.xml new file mode 100644 index 000000000..6c4ccc179 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-good.xml @@ -0,0 +1,103 @@ +<Observation xmlns="http://hl7.org/fhir"> + <!-- extract from http://hl7.org/fhir/triglyceride-examples.html, changed + id from triglyceride and added explicit profile reference to http://hl7.org/fhir/StructureDefinition/triglyceride + and added snomed ct code for triglyceride --> + <id value="observation-triglyceride-good" /> + <meta> + <profile + value="http://hl7.org/fhir/StructureDefinition/triglyceride" /> + </meta> + <text> + <status value="generated" /> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p> + <b> Generated Narrative with Details</b> + </p> + <p> + <b> id</b> + : triglyceride + </p> + <p> + <b> status</b> + : final + </p> + <p> + <b> code</b> + : Triglyceride + <span> (Details : {LOINC code '35217-9' = 'Triglyceride [Mass or + Moles/volume] in Serum or Plasma', + given as 'Triglyceride + [Moles/​volume] in Serum or Plasma'}) + </span> + </p> + <p> + <b> subject</b> + : + <a> Patient/pat2</a> + </p> + <p> + <b> performer</b> + : + <a> Acme Laboratory, Inc</a> + </p> + <p> + <b> value</b> + : 1.3 mmol/L + <span> (Details: UCUM code mmol/L = 'mmol/L')</span> + </p> + <h3> ReferenceRanges</h3> + <table> + <tr> + <td> -</td> + <td> + <b> High</b> + </td> + </tr> + <tr> + <td> *</td> + <td> + 2.0 mmol/L + <span> (Details: UCUM code mmol/L = 'mmol/L')</span> + </td> + </tr> + </table> + </div> + </text> + <status value="final" /> + <code> + <coding> + <system value="http://loinc.org" /> + <code value="35217-9" /> + <display + value="Triglyceride [Moles/​volume] in Serum or Plasma" /> + </coding> + <coding> + <system value="http://snomed.info/sct" /> + <code value="85600001" /> + <display value="Triacylglycerol" /> + </coding> + <text value="Triglyceride" /> + </code> + <subject> + <reference value="Patient/pat2" /> + </subject> + <performer> + <reference + value="Organization/1832473e-2fe0-452d-abe9-3cdb9879522f" /> + <display value="Acme Laboratory, Inc" /> + </performer> + <valueQuantity> + <value value="1.3" /> + <unit value="mmol/L" /> + <system value="http://unitsofmeasure.org" /> + <code value="mmol/L" /> + </valueQuantity> + <referenceRange> + <high> + <value value="2.0" /> + <unit value="mmol/L" /> + <system value="http://unitsofmeasure.org" /> + <code value="mmol/L" /> + </high> + </referenceRange> +</Observation> \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-good2.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-good2.xml new file mode 100644 index 000000000..87d0026a2 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/observation-triglyceride-good2.xml @@ -0,0 +1,108 @@ +<Observation xmlns="http://hl7.org/fhir"> + <!-- extract from http://hl7.org/fhir/triglyceride-examples.html, changed + id from triglyceride and added explicit profile reference to http://hl7.org/fhir/StructureDefinition/triglyceride + and added snomed ct code for triglyceride --> + <id value="observation-triglyceride-good2" /> + <meta> + <profile + value="http://hl7.org/fhir/StructureDefinition/triglyceride" /> + </meta> + <text> + <status value="generated" /> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p> + <b> Generated Narrative with Details</b> + </p> + <p> + <b> id</b> + : triglyceride + </p> + <p> + <b> status</b> + : final + </p> + <p> + <b> code</b> + : Triglyceride + <span> (Details : {LOINC code '35217-9' = 'Triglyceride [Mass or + Moles/volume] in Serum or Plasma', + given as 'Triglyceride + [Moles/​volume] in Serum or Plasma'}) + </span> + </p> + <p> + <b> subject</b> + : + <a> Patient/pat2</a> + </p> + <p> + <b> performer</b> + : + <a> Acme Laboratory, Inc</a> + </p> + <p> + <b> value</b> + : 1.3 mmol/L + <span> (Details: UCUM code mmol/L = 'mmol/L')</span> + </p> + <h3> ReferenceRanges</h3> + <table> + <tr> + <td> -</td> + <td> + <b> High</b> + </td> + </tr> + <tr> + <td> *</td> + <td> + 2.0 mmol/L + <span> (Details: UCUM code mmol/L = 'mmol/L')</span> + </td> + </tr> + </table> + </div> + </text> + <status value="final" /> + <code> + <coding> + <system value="http://snomed.info/sct" /> + <code value="85600001" /> + <display value="Triacylglycerol" /> + </coding> + <coding> + <system value="http://loinc.org" /> + <code value="35217-9" /> + <display + value="Triglyceride [Moles/​volume] in Serum or Plasma" /> + </coding> + <coding> + <system value="http://snomed.info/sct" /> + <code value="85600001" /> + <display value="Triacylglycerol" /> + </coding> + <text value="Triglyceride" /> + </code> + <subject> + <reference value="Patient/pat2" /> + </subject> + <performer> + <reference + value="Organization/1832473e-2fe0-452d-abe9-3cdb9879522f" /> + <display value="Acme Laboratory, Inc" /> + </performer> + <valueQuantity> + <value value="1.3" /> + <unit value="mmol/L" /> + <system value="http://unitsofmeasure.org" /> + <code value="mmol/L" /> + </valueQuantity> + <referenceRange> + <high> + <value value="2.0" /> + <unit value="mmol/L" /> + <system value="http://unitsofmeasure.org" /> + <code value="mmol/L" /> + </high> + </referenceRange> +</Observation> \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/slice-by-polymorphic-type2-profile.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/slice-by-polymorphic-type2-profile.xml new file mode 100644 index 000000000..a5fcf199b --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/slice-by-polymorphic-type2-profile.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<StructureDefinition xmlns="http://hl7.org/fhir"> + <id value="slice-by-polymorphic-type2-profile"/> + <url value="http://hl7.org/fhir/StructureDefinition/slice-by-polymorphic-type2-profile"/> + <name value="SliceByPolymorphicType2Profile"/> + <title value="Test slicing by polymorphic type"/> + <status value="draft"/> + <kind value="resource"/> + <abstract value="false"/> + <type value="PlanDefinition"/> + <baseDefinition value="http://hl7.org/fhir/StructureDefinition/PlanDefinition"/> + <derivation value="constraint"/> + <differential> + <element id="PlanDefinition"> + <path value="PlanDefinition"/> + </element> + <element id="PlanDefinition.goal"> + <path value="PlanDefinition.goal"/> + </element> + <element id="PlanDefinition.goal.target"> + <path value="PlanDefinition.goal.target"/> + </element> + <element id="PlanDefinition.goal.target.detail[x]"> + <path value="PlanDefinition.goal.target.detail[x]"/> + <type> + <code value="CodeableConcept"/> + </type> + <type> + <code value="Range"/> + </type> + </element> + <element id="PlanDefinition.goal.target.detailCodeableConcept"> + <path value="PlanDefinition.goal.target.detailCodeableConcept"/> + <type> + <code value="CodeableConcept"/> + </type> + <mustSupport value="true"/> + </element> + </differential> +</StructureDefinition> diff --git a/org.hl7.fhir.validation/src/test/resources/validation-examples/slice-by-polymorphic-type2.xml b/org.hl7.fhir.validation/src/test/resources/validation-examples/slice-by-polymorphic-type2.xml new file mode 100644 index 000000000..18d834624 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/validation-examples/slice-by-polymorphic-type2.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!--<PlanDefinition xmlns="http://hl7.org/fhir" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://hl7.org/fhir ../../../../../../FHIR/schema/fhir-single.xsd">--> +<PlanDefinition xmlns="http://hl7.org/fhir"> + <id value="slice-by-polymorphic-type2"/> + <status value="active"/> + <goal> + <description> + <text value="Some goal"/> + </description> + <target> + <detailCodeableConcept> + <text value="Some Text"/> + </detailCodeableConcept> + </target> + </goal> +</PlanDefinition>