fix parsing and validating of concept maps in structure maps + validate terminology part of structure maps

This commit is contained in:
Grahame Grieve 2023-03-05 18:29:11 +11:00
parent c42da7af68
commit ec72b1fcdd
10 changed files with 404 additions and 10 deletions

View File

@ -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")));

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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";
}

View File

@ -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}

View File

@ -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")) {

View File

@ -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;
}
}

View File

@ -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) {