fix parsing and validating of concept maps in structure maps + validate terminology part of structure maps
This commit is contained in:
parent
c42da7af68
commit
ec72b1fcdd
|
@ -12,8 +12,10 @@ import org.hl7.fhir.exceptions.DefinitionException;
|
|||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
|
||||
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
|
||||
import org.hl7.fhir.r5.model.ExpressionNode;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode;
|
||||
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
|
||||
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
|
||||
|
@ -78,6 +80,10 @@ public class FmlParser extends ParserBase {
|
|||
}
|
||||
}
|
||||
lexer.setMetadataFormat(false);
|
||||
if (!result.hasChild("status")) {
|
||||
result.makeElement("status").setValue("draft");
|
||||
}
|
||||
|
||||
while (lexer.hasToken("conceptmap"))
|
||||
parseConceptMap(result, lexer);
|
||||
|
||||
|
@ -86,6 +92,9 @@ public class FmlParser extends ParserBase {
|
|||
while (lexer.hasToken("imports"))
|
||||
parseImports(result, lexer);
|
||||
|
||||
while (lexer.hasToken("conceptmap"))
|
||||
parseConceptMap(result, lexer);
|
||||
|
||||
while (!lexer.done()) {
|
||||
parseGroup(result, lexer);
|
||||
}
|
||||
|
@ -94,24 +103,22 @@ public class FmlParser extends ParserBase {
|
|||
} catch (Exception e) {
|
||||
logError("2023-02-24", -1, -1, "?", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL);
|
||||
}
|
||||
|
||||
if (!result.hasChild("status")) {
|
||||
result.makeElement("status").setValue("draft");
|
||||
}
|
||||
result.setIgnorePropertyOrder(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void parseConceptMap(Element result, FHIRLexer lexer) throws FHIRLexerException {
|
||||
private void parseConceptMap(Element structureMap, FHIRLexer lexer) throws FHIRLexerException {
|
||||
lexer.token("conceptmap");
|
||||
Element map = Manager.build(context, context.fetchTypeDefinition("ConceptMap"));
|
||||
Element map = structureMap.makeElement("contained");
|
||||
StructureDefinition sd = context.fetchTypeDefinition("ConceptMap");
|
||||
map.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd), SpecialElement.fromProperty(map.getProperty()), map.getProperty());
|
||||
map.setType("ConceptMap");
|
||||
Element eid = map.makeElement("id").markLocation(lexer.getCurrentLocation());
|
||||
String id = lexer.readConstant("map id");
|
||||
if (id.startsWith("#"))
|
||||
throw lexer.error("Concept Map identifier must start with #");
|
||||
eid.setValue(id);
|
||||
map.makeElement("status").setValue(PublicationStatus.DRAFT.toCode()); // todo: how to add this to the text format
|
||||
result.makeElement("contained").setElement("resource", map);
|
||||
map.makeElement("status").setValue(structureMap.getChildValue("status"));
|
||||
lexer.token("{");
|
||||
// lexer.token("source");
|
||||
// map.setSource(new UriType(lexer.readConstant("source")));
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.hl7.fhir.r5.model.CanonicalResource;
|
|||
import org.hl7.fhir.r5.model.CanonicalType;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyType;
|
||||
|
@ -678,5 +679,18 @@ public class CodeSystemUtilities {
|
|||
public static boolean isNotCurrent(CodeSystem cs, ConceptDefinitionComponent c) {
|
||||
return isInactive(cs, c) || isDeprecated(cs, c, false);
|
||||
}
|
||||
|
||||
public static List<String> getDisplays(CodeSystem srcCS, ConceptDefinitionComponent cd) {
|
||||
List<String> list = new ArrayList<>();
|
||||
if (cd.hasDisplay()) {
|
||||
list.add(cd.getDisplay());
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) {
|
||||
if (!list.contains(d.getValue())) {
|
||||
list.add(d.getValue());
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
package org.hl7.fhir.r5.terminologies;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.ConceptMap;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
|
||||
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
|
||||
import org.hl7.fhir.r5.model.Identifier;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
|
||||
|
@ -30,5 +37,34 @@ public class ConceptMapUtilities {
|
|||
cm.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid);
|
||||
}
|
||||
|
||||
public static boolean hasMappingForSource(ConceptMap cm, String system, String version, String code) {
|
||||
for (ConceptMapGroupComponent grp : cm.getGroup()) {
|
||||
if (system.equals(grp.getSource())) { // to do: version
|
||||
for (SourceElementComponent e : grp.getElement()) {
|
||||
if (code.equals(e.getCode())) {
|
||||
return true; // doesn't matter if it's actually unmapped
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<Coding> listTargets(ConceptMap cm, List<String> systems) {
|
||||
List<Coding> list = new ArrayList<>();
|
||||
for (ConceptMapGroupComponent grp : cm.getGroup()) {
|
||||
if (systems.isEmpty() || systems.contains(grp.getSource())) { // to do: version
|
||||
for (SourceElementComponent e : grp.getElement()) {
|
||||
for (TargetElementComponent t : e.getTarget()) {
|
||||
if (t.hasCode()) {
|
||||
list.add(new Coding(grp.getTarget(), t.getCode(), t.getDisplay()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import org.hl7.fhir.r5.model.ValueSet;
|
|||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeType;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
|
||||
|
@ -311,5 +312,21 @@ public class ValueSetUtilities {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean hasCodeInExpansion(ValueSet vs, Coding code) {
|
||||
return hasCodeInExpansion(vs.getExpansion().getContains(), code);
|
||||
}
|
||||
|
||||
private static boolean hasCodeInExpansion(List<ValueSetExpansionContainsComponent> list, Coding code) {
|
||||
for (ValueSetExpansionContainsComponent c : list) {
|
||||
if (c.getSystem().equals(code.getSystem()) && c.getCode().equals(code.getCode())) {
|
||||
return true;
|
||||
}
|
||||
if (hasCodeInExpansion(c.getContains(), code)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -660,6 +660,9 @@ public class StructureMapUtilities {
|
|||
while (lexer.hasToken("imports"))
|
||||
parseImports(result, lexer);
|
||||
|
||||
while (lexer.hasToken("conceptmap"))
|
||||
parseConceptMap(result, lexer);
|
||||
|
||||
while (!lexer.done()) {
|
||||
parseGroup(result, lexer);
|
||||
}
|
||||
|
|
|
@ -659,6 +659,10 @@ public class I18nConstants {
|
|||
public static final String CODESYSTEM_SHAREABLE_MISSING_HL7 = "CODESYSTEM_SHAREABLE_MISSING_HL7";
|
||||
public static final String CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7 = "CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7";
|
||||
public static final String CODESYSTEM_SHAREABLE_EXTRA_MISSING = "CODESYSTEM_SHAREABLE_EXTRA_MISSING";
|
||||
public static final String CONCEPTMAP_SHAREABLE_MISSING = "CONCEPTMAP_SHAREABLE_MISSING";
|
||||
public static final String CONCEPTMAP_SHAREABLE_MISSING_HL7 = "CONCEPTMAP_SHAREABLE_MISSING_HL7";
|
||||
public static final String CONCEPTMAP_SHAREABLE_EXTRA_MISSING_HL7 = "CONCEPTMAP_SHAREABLE_EXTRA_MISSING_HL7";
|
||||
public static final String CONCEPTMAP_SHAREABLE_EXTRA_MISSING = "CONCEPTMAP_SHAREABLE_EXTRA_MISSING";
|
||||
public static final String MEASURE_SHAREABLE_MISSING = "MEASURE_SHAREABLE_MISSING";
|
||||
public static final String MEASURE_SHAREABLE_MISSING_HL7 = "MEASURE_SHAREABLE_MISSING_HL7";
|
||||
public static final String MEASURE_SHAREABLE_EXTRA_MISSING_HL7 = "MEASURE_SHAREABLE_EXTRA_MISSING_HL7";
|
||||
|
@ -814,6 +818,27 @@ public class I18nConstants {
|
|||
public static final String SM_SOURCE_TYPE_NOT_FOUND = "SM_SOURCE_TYPE_NOT_FOUND";
|
||||
public static final String SM_TARGET_TYPE_NOT_FOUND = "SM_TARGET_TYPE_NOT_FOUND";
|
||||
public static final String SM_MATCHING_RULEGROUP_NOT_FOUND = "SM_MATCHING_RULEGROUP_NOT_FOUND";
|
||||
public static final String SM_TARGET_TRANSFORM_MISSING_PARAMS = "SM_TARGET_TRANSFORM_MISSING_PARAMS";
|
||||
public static final String SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM = "SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM";
|
||||
public static final String SM_TARGET_TRANSFORM_TRANSLATE_UNKNOWN_SOURCE = "SM_TARGET_TRANSFORM_TRANSLATE_UNKNOWN_SOURCE";
|
||||
public static final String SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND = "SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND";
|
||||
public static final String SM_TARGET_TRANSFORM_TRANSLATE_CM_BAD_MODE = "SM_TARGET_TRANSFORM_TRANSLATE_CM_BAD_MODE";
|
||||
public static final String SM_TARGET_TRANSLATE_BINDING_SOURCE = "SM_TARGET_TRANSLATE_BINDING_SOURCE";
|
||||
public static final String SM_TARGET_TRANSLATE_BINDING_VS_SOURCE = "SM_TARGET_TRANSLATE_BINDING_VS_SOURCE";
|
||||
public static final String SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE = "SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE";
|
||||
public static final String SM_TARGET_TRANSLATE_BINDING_SOURCE_UNMAPPED = "SM_TARGET_TRANSLATE_BINDING_SOURCE_UNMAPPED";
|
||||
public static final String SM_TARGET_TRANSLATE_BINDING_TARGET = "SM_TARGET_TRANSLATE_BINDING_TARGET";
|
||||
public static final String SM_TARGET_TRANSLATE_BINDING_VS_TARGET = "SM_TARGET_TRANSLATE_BINDING_VS_TARGET";
|
||||
public static final String SM_TARGET_TRANSLATE_BINDING_VSE_TARGET = "SM_TARGET_TRANSLATE_BINDING_VSE_TARGET";
|
||||
public static final String SM_TARGET_TRANSLATE_BINDING_TARGET_WRONG = "SM_TARGET_TRANSLATE_BINDING_TARGET_WRONG";
|
||||
public static final String CONCEPTMAP_GROUP_SOURCE_MISSING = "CONCEPTMAP_GROUP_SOURCE_MISSING";
|
||||
public static final String CONCEPTMAP_GROUP_SOURCE_UNKNOWN = "CONCEPTMAP_GROUP_SOURCE_UNKNOWN";
|
||||
public static final String CONCEPTMAP_GROUP_TARGET_MISSING = "CONCEPTMAP_GROUP_TARGET_MISSING";
|
||||
public static final String CONCEPTMAP_GROUP_TARGET_UNKNOWN = "CONCEPTMAP_GROUP_TARGET_UNKNOWN";
|
||||
public static final String CONCEPTMAP_GROUP_SOURCE_CODE_INVALID = "CONCEPTMAP_GROUP_SOURCE_CODE_INVALID";
|
||||
public static final String CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID = "CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID";
|
||||
public static final String CONCEPTMAP_GROUP_TARGET_CODE_INVALID = "CONCEPTMAP_GROUP_TARGET_CODE_INVALID";
|
||||
public static final String CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID = "CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID";
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -778,6 +778,10 @@ CODESYSTEM_SHAREABLE_MISSING = The ShareableCodeSystem profile says that the {0}
|
|||
CODESYSTEM_SHAREABLE_EXTRA_MISSING = The ShareableCodeSystem profile recommends that the {0} element is populated, but it is not present. Published code systems SHOULD conform to the ShareableCodeSystem profile
|
||||
CODESYSTEM_SHAREABLE_MISSING_HL7 = The ShareableCodeSystem profile says that the {0} element is mandatory, but it is not found. HL7 Published code systems SHALL conform to the ShareableCodeSystem profile
|
||||
CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7 = The ShareableCodeSystem profile recommends that the {0} element is populated, but it is not found. HL7 Published code systems SHALL conform to the ShareableCodeSystem profile
|
||||
CONCEPTMAP_SHAREABLE_MISSING = The ShareableConceptMap profile says that the {0} element is mandatory, but it is not present. Published concept maps SHOULD conform to the ShareableConceptMap profile
|
||||
CONCEPTMAP_SHAREABLE_EXTRA_MISSING = The ShareableConceptMap profile recommends that the {0} element is populated, but it is not present. Published concept maps SHOULD conform to the ShareableConceptMap profile
|
||||
CONCEPTMAP_SHAREABLE_MISSING_HL7 = The ShareableConceptMap profile says that the {0} element is mandatory, but it is not found. HL7 Published concept maps SHALL conform to the ShareableConceptMap profile
|
||||
CONCEPTMAP_SHAREABLE_EXTRA_MISSING_HL7 = The ShareableConceptMap profile recommends that the {0} element is populated, but it is not found. HL7 Published concept maps SHALL conform to the ShareableConceptMap profile
|
||||
MEASURE_SHAREABLE_MISSING = The ShareableMeasure profile says that the {0} element is mandatory, but it is not present. Published measures SHOULD conform to the ShareableMeasure profile
|
||||
MEASURE_SHAREABLE_EXTRA_MISSING = The ShareableMeasure profile recommends that the {0} element is populated, but it is not present. Published measures SHOULD conform to the ShareableMeasure profile
|
||||
MEASURE_SHAREABLE_MISSING_HL7 = The ShareableMeasure profile says that the {0} element is mandatory, but it is not found. HL7 Published measures SHALL conform to the ShareableMeasure profile
|
||||
|
@ -851,7 +855,7 @@ SM_TARGET_PATH_MULTIPLE_MATCHES = The target path {0}.{1} refers to the path {2}
|
|||
SM_SOURCE_TYPE_INVALID = The type {0} is not valid in this source context {1}. The possible types are [{2}]
|
||||
SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE = Transform {0} takes {1}-{2} parameter(s) but {3} were found
|
||||
SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE = Transform {0} takes {1} parameter(s) but {2} were found
|
||||
SM_TARGET_TRANSFORM_NOT_CHECKED = Transform {0} not checked yet
|
||||
SM_TARGET_TRANSFORM_NOT_CHECKED = Transform {0} not checked dyet
|
||||
SM_TARGET_NO_TRANSFORM_NO_CHECKED = When there is no transform, parameters can''t be provided
|
||||
SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE = The value of the type parameter could not be processed
|
||||
SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE = The parameter at index {0} could not be processed (type = {1})
|
||||
|
@ -864,7 +868,27 @@ SM_ORPHAN_GROUP = This group is not called from within this mapping script, and
|
|||
SM_SOURCE_TYPE_NOT_FOUND = No source type was found, so the default group for this implied dependent rule could not be determined
|
||||
SM_TARGET_TYPE_NOT_FOUND = No target type was found, so the default group for this implied dependent rule could not be determined
|
||||
SM_MATCHING_RULEGROUP_NOT_FOUND = Unable to find a default rule for the type pair source={0} and target={1}
|
||||
|
||||
SM_TARGET_TRANSFORM_MISSING_PARAMS = One or more parameters to the translate operation are missing (should be 3, was {0})
|
||||
SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM = No value for the {0} parameter found
|
||||
SM_TARGET_TRANSFORM_TRANSLATE_UNKNOWN_SOURCE = The source variable {0} is unknown
|
||||
SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND = The map_uri ''{0}'' could not be resolved, so the map can't be checked
|
||||
SM_TARGET_TRANSFORM_TRANSLATE_CM_BAD_MODE = The value ''{0}'' for the output parameter is incorrect
|
||||
SM_TARGET_TRANSLATE_BINDING_SOURCE = The source variable does not have a required binding, so this concept map can''t be checked
|
||||
SM_TARGET_TRANSLATE_BINDING_VS_SOURCE = The source variable refers to an unknown value set ''{0}'', so this concept map can''t be checked
|
||||
SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE = There was an error expanding the source value set, so this concept map can''t be checked: ''{0}''
|
||||
SM_TARGET_TRANSLATE_BINDING_SOURCE_UNMAPPED = The source value set includes one or more codes that the map does not translate: {0}
|
||||
SM_TARGET_TRANSLATE_BINDING_TARGET = The target variable does not have a required binding, so this concept map can''t be checked
|
||||
SM_TARGET_TRANSLATE_BINDING_VS_TARGET = The target variable refers to an unknown value set ''{0}'', so this concept map can''t be checked
|
||||
SM_TARGET_TRANSLATE_BINDING_VSE_TARGET = There was an error expanding the target value set, so this concept map can''t be checked: ''{0}''
|
||||
SM_TARGET_TRANSLATE_BINDING_TARGET_WRONG = The map produces one or more codes that the target value set does not include: {0}
|
||||
CONCEPTMAP_GROUP_SOURCE_MISSING = No Source Code System, so the source codes cannot be checked
|
||||
CONCEPTMAP_GROUP_SOURCE_UNKNOWN = Unknown Source Code System, so the source codes cannot be checked
|
||||
CONCEPTMAP_GROUP_TARGET_MISSING = No Target Code System, so the source codes cannot be checked
|
||||
CONCEPTMAP_GROUP_TARGET_UNKNOWN = Unknown Target Code System, so the source codes cannot be checked
|
||||
CONCEPTMAP_GROUP_SOURCE_CODE_INVALID = The source code ''{0}'' is not valid in the code system {1}
|
||||
CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID = The source display ''{0}'' is not valid. Possible codes {1}
|
||||
CONCEPTMAP_GROUP_TARGET_CODE_INVALID =The target code ''{0}'' is not valid in the code system {1}
|
||||
CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID = The target display ''{0}'' is not valid. Possible codes {1}
|
||||
|
||||
|
||||
|
|
@ -185,6 +185,7 @@ import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
|
|||
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
|
||||
import org.hl7.fhir.validation.instance.type.BundleValidator;
|
||||
import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
|
||||
import org.hl7.fhir.validation.instance.type.ConceptMapValidator;
|
||||
import org.hl7.fhir.validation.instance.type.MeasureValidator;
|
||||
import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
|
||||
import org.hl7.fhir.validation.instance.type.SearchParameterValidator;
|
||||
|
@ -5036,6 +5037,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
return validateCapabilityStatement(errors, element, stack);
|
||||
} else if (element.getType().equals("CodeSystem")) {
|
||||
return new CodeSystemValidator(context, timeTracker, this, xverManager, jurisdiction).validateCodeSystem(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang()));
|
||||
} else if (element.getType().equals("ConceptMap")) {
|
||||
return new ConceptMapValidator(context, timeTracker, this, xverManager, jurisdiction).validateConceptMap(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang()));
|
||||
} else if (element.getType().equals("SearchParameter")) {
|
||||
return new SearchParameterValidator(context, timeTracker, fpe, xverManager, jurisdiction).validateSearchParameter(errors, element, stack);
|
||||
} else if (element.getType().equals("StructureDefinition")) {
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
package org.hl7.fhir.validation.instance.type;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
import org.hl7.fhir.validation.BaseValidator;
|
||||
import org.hl7.fhir.validation.TimeTracker;
|
||||
import org.hl7.fhir.validation.instance.InstanceValidator;
|
||||
import org.hl7.fhir.validation.instance.utils.NodeStack;
|
||||
|
||||
public class ConceptMapValidator extends BaseValidator {
|
||||
|
||||
private InstanceValidator parent;
|
||||
|
||||
public ConceptMapValidator(IWorkerContext context, TimeTracker timeTracker, InstanceValidator parent, XVerExtensionManager xverManager, Coding jurisdiction) {
|
||||
super(context, xverManager);
|
||||
source = Source.InstanceValidator;
|
||||
this.timeTracker = timeTracker;
|
||||
this.jurisdiction = jurisdiction;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public boolean validateConceptMap(List<ValidationMessage> errors, Element cm, NodeStack stack, ValidationOptions options) {
|
||||
boolean ok = true;
|
||||
Map<String, String> props = new HashMap<>();
|
||||
Map<String, String> attribs = new HashMap<>();
|
||||
List<Element> groups = cm.getChildrenByName("group");
|
||||
int ci = 0;
|
||||
for (Element group : groups) {
|
||||
ok = validateGroup(errors, group, stack.push(group, ci, null, null), props, attribs) && ok;
|
||||
ci++;
|
||||
}
|
||||
|
||||
if (!stack.isContained()) {
|
||||
ok = checkShareableConceptMap(errors, cm, stack) && ok;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
private boolean validateGroup(List<ValidationMessage> errors, Element grp, NodeStack stack, Map<String, String> props, Map<String, String> attribs) {
|
||||
boolean ok = true;
|
||||
CodeSystem srcCS = null;
|
||||
CodeSystem tgtCS = null;
|
||||
Element e = grp.getNamedChild("source");
|
||||
if (warning(errors, "2023-03-05", IssueType.REQUIRED, grp.line(), grp.col(), stack.getLiteralPath(), e != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_MISSING)) {
|
||||
srcCS = context.fetchCodeSystem(e.getValue());
|
||||
warning(errors, "2023-03-05", IssueType.NOTFOUND, grp.line(), grp.col(), stack.push(e, -1, null, null).getLiteralPath(), srcCS != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_UNKNOWN, e.getValue());
|
||||
}
|
||||
e = grp.getNamedChild("target");
|
||||
if (warning(errors, "2023-03-05", IssueType.REQUIRED, grp.line(), grp.col(), stack.getLiteralPath(), e != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_MISSING)) {
|
||||
tgtCS = context.fetchCodeSystem(e.getValue());
|
||||
warning(errors, "2023-03-05", IssueType.NOTFOUND, grp.line(), grp.col(), stack.push(e, -1, null, null).getLiteralPath(), tgtCS != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_UNKNOWN, e.getValue());
|
||||
}
|
||||
List<Element> elements = grp.getChildrenByName("element");
|
||||
int ci = 0;
|
||||
for (Element element : elements) {
|
||||
ok = validateGroupElement(errors, element, stack.push(element, ci, null, null), srcCS, tgtCS, props, attribs) && ok;
|
||||
ci++;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private boolean validateGroupElement(List<ValidationMessage> errors, Element src, NodeStack stack, CodeSystem srcCS, CodeSystem tgtCS, Map<String, String> props, Map<String, String> attribs) {
|
||||
boolean ok = true;
|
||||
|
||||
Element code = src.getNamedChild("code");
|
||||
if (code != null && srcCS != null) {
|
||||
String c = code.getValue();
|
||||
ConceptDefinitionComponent cd = CodeSystemUtilities.getCode(srcCS, c);
|
||||
if (rule(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), stack.push(code, -1, null, null).getLiteralPath(), cd != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_CODE_INVALID, c, srcCS.getVersionedUrl())) {
|
||||
Element display = src.getNamedChild("display");
|
||||
if (display != null) {
|
||||
List<String> displays = CodeSystemUtilities.getDisplays(srcCS, cd);
|
||||
ok = rule(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), stack.push(code, -1, null, null).getLiteralPath(), displays.contains(display.getValue()), I18nConstants.CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID, display.getValue(), displays) && ok;
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
List<Element> targets = src.getChildrenByName("target");
|
||||
int ci = 0;
|
||||
for (Element target : targets) {
|
||||
ok = validateGroupTargetElement(errors, target, stack.push(target, ci, null, null), srcCS, tgtCS, props, attribs) && ok;
|
||||
ci++;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private boolean validateGroupTargetElement(List<ValidationMessage> errors, Element tgt, NodeStack stack, CodeSystem srcCS, CodeSystem tgtCS, Map<String, String> props, Map<String, String> attribs) {
|
||||
boolean ok = true;
|
||||
|
||||
Element code = tgt.getNamedChild("code");
|
||||
if (code != null && tgtCS != null) {
|
||||
String c = code.getValue();
|
||||
ConceptDefinitionComponent cd = CodeSystemUtilities.getCode(tgtCS, c);
|
||||
if (rule(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), stack.push(code, -1, null, null).getLiteralPath(), cd != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_CODE_INVALID, c, tgtCS.getVersionedUrl())) {
|
||||
Element display = tgt.getNamedChild("display");
|
||||
if (display != null) {
|
||||
List<String> displays = CodeSystemUtilities.getDisplays(tgtCS, cd);
|
||||
ok = rule(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), stack.push(code, -1, null, null).getLiteralPath(), displays.contains(display.getValue()), I18nConstants.CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID, display.getValue(), displays) && ok;
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
private boolean checkShareableConceptMap(List<ValidationMessage> errors, Element cs, NodeStack stack) {
|
||||
if (parent.isForPublication()) {
|
||||
if (isHL7(cs)) {
|
||||
boolean ok = true;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "url") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "version") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "title") && ok;
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.CONCEPTMAP_SHAREABLE_EXTRA_MISSING_HL7, "name");
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "status") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "experimental") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "description") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("content"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "content") && ok;
|
||||
if (!"supplement".equals(cs.getChildValue("content"))) {
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("caseSensitive"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "caseSensitive") && ok;
|
||||
}
|
||||
return ok;
|
||||
} else {
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "url");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "version");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "title");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.CONCEPTMAP_SHAREABLE_EXTRA_MISSING, "name");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "status");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "experimental");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "description");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("content"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "content");
|
||||
if (!"supplement".equals(cs.getChildValue("content"))) {
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("caseSensitive"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "caseSensitive");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -4,13 +4,18 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
|
||||
import org.hl7.fhir.r5.context.ContextUtilities;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.ConceptMap;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition;
|
||||
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
|
||||
import org.hl7.fhir.r5.model.Property;
|
||||
import org.hl7.fhir.r5.model.Resource;
|
||||
import org.hl7.fhir.r5.model.StructureDefinition;
|
||||
import org.hl7.fhir.r5.model.StructureMap;
|
||||
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupComponent;
|
||||
|
@ -20,6 +25,12 @@ import org.hl7.fhir.r5.model.StructureMap.StructureMapInputMode;
|
|||
import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode;
|
||||
import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent;
|
||||
import org.hl7.fhir.r5.model.TypeDetails;
|
||||
import org.hl7.fhir.r5.model.ValueSet;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
|
||||
import org.hl7.fhir.r5.terminologies.ConceptMapUtilities;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.r5.utils.XVerExtensionManager;
|
||||
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
|
||||
|
@ -668,6 +679,46 @@ public class StructureMapValidator extends BaseValidator {
|
|||
ok = false;
|
||||
}
|
||||
break;
|
||||
case "translate":
|
||||
ok = rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 3, I18nConstants.SM_TARGET_TRANSFORM_MISSING_PARAMS, transform) && ok;
|
||||
Element srcE = params.size() > 0 ? params.get(0).getNamedChild("value") : null;
|
||||
Element mapE = params.size() > 1? params.get(1).getNamedChild("value") : null;
|
||||
Element modeE = params.size() > 2 ? params.get(2).getNamedChild("value") : null;
|
||||
VariableDefn sv = null;
|
||||
// srcE - if it's an id, the variable must exist
|
||||
if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), srcE != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM, "source")) {
|
||||
if ("id".equals(srcE.fhirType())) {
|
||||
sv = variables.getVariable(srcE.getValue(), true);
|
||||
rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), sv != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_UNKNOWN_SOURCE, srcE.getValue());
|
||||
}
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
// mapE - it must resolve (may be reference to contained)
|
||||
if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), mapE != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM, "map_uri")) {
|
||||
String ref = mapE.getValue();
|
||||
ConceptMap cm = null;
|
||||
if (ref.startsWith("#")) {
|
||||
cm = (ConceptMap) loadContainedResource(errors, stack.getLiteralPath(), src, ref.substring(1), ConceptMap.class);
|
||||
ok = rule(errors, "2023-03-01", IssueType.NOTFOUND, target.line(), target.col(), stack.getLiteralPath(), srcE != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND, ref) && ok;
|
||||
} else {
|
||||
// todo: look in Bundle?
|
||||
cm = this.context.fetchResource(ConceptMap.class, ref);
|
||||
warning(errors, "2023-03-01", IssueType.NOTFOUND, target.line(), target.col(), stack.getLiteralPath(), srcE != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND, ref);
|
||||
}
|
||||
if (cm != null && (v != null && v.hasTypeInfo() || (sv != null && sv.hasTypeInfo()))) {
|
||||
ok = checkConceptMap(errors, target.line(), target.col(), stack.getLiteralPath(), cm, sv == null ? null : sv.getEd(), el == null ? null : el.getEd()) && ok;
|
||||
}
|
||||
}
|
||||
if (modeE != null) {
|
||||
String t = modeE.getValue();
|
||||
if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), Utilities.existsInList(t, "code", "system", "display", "Coding", "CodeableConcept"), I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_CM_BAD_MODE, t)) {
|
||||
// cross check the type
|
||||
} else {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_NOT_CHECKED, transform);
|
||||
ok = false;
|
||||
|
@ -688,6 +739,58 @@ public class StructureMapValidator extends BaseValidator {
|
|||
}
|
||||
}
|
||||
return ok;
|
||||
|
||||
}
|
||||
|
||||
private boolean checkConceptMap(List<ValidationMessage> errors, int line, int col, String literalPath, ConceptMap cm, ElementDefinition srcED, ElementDefinition tgtED) {
|
||||
boolean ok = true;
|
||||
ValueSet srcVS = null;
|
||||
if (srcED != null) {
|
||||
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcED.getBinding().hasValueSet() && srcED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_SOURCE)) {
|
||||
srcVS = context.fetchResource(ValueSet.class, srcED.getBinding().getValueSet());
|
||||
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcVS != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_SOURCE)) {
|
||||
ValueSetExpansionOutcome vse = context.expandVS(srcVS, true, false);
|
||||
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vse.isOk(), I18nConstants.SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE, vse.getError())) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (ValueSetExpansionContainsComponent c : vse.getValueset().getExpansion().getContains()) {
|
||||
if (ConceptMapUtilities.hasMappingForSource(cm, c.getSystem(), c.getVersion(), c.getCode())) {
|
||||
b.append(c.getCode());
|
||||
}
|
||||
}
|
||||
if (b.count() > 0) {
|
||||
warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcED.getBinding().hasValueSet() && srcED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_SOURCE_UNMAPPED, b.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (srcED != null) {
|
||||
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, tgtED.getBinding().hasValueSet() && tgtED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_TARGET)) {
|
||||
ValueSet vs = context.fetchResource(ValueSet.class, tgtED.getBinding().getValueSet());
|
||||
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vs != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_TARGET)) {
|
||||
ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
|
||||
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vse.isOk(), I18nConstants.SM_TARGET_TRANSLATE_BINDING_VSE_TARGET, vse.getError())) {
|
||||
List<String> systems = new ArrayList<>();
|
||||
if (srcVS != null) {
|
||||
for (ConceptSetComponent inc : srcVS.getCompose().getInclude()) {
|
||||
systems.add(inc.getSystem());
|
||||
}
|
||||
}
|
||||
List<Coding> codes = ConceptMapUtilities.listTargets(cm, systems);
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (Coding code : codes) {
|
||||
if (ValueSetUtilities.hasCodeInExpansion(vse.getValueset(), code)) {
|
||||
b.append(code.getCode());
|
||||
}
|
||||
}
|
||||
if (b.count() > 0) {
|
||||
warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcED.getBinding().hasValueSet() && srcED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_TARGET_WRONG, b.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private String inferType(RuleInformation ruleInfo, VariableSet variables, Element rule, String transform, List<Element> params) {
|
||||
|
|
Loading…
Reference in New Issue