fix to CDA xsi:type validation per SD decision + apply regex pattern to literal format if defined + improvements to vital signs related messages

This commit is contained in:
Grahame Grieve 2023-12-11 14:09:57 +11:00
parent 2cfc7f3df7
commit e73404fe8a
1 changed files with 91 additions and 15 deletions

View File

@ -103,6 +103,7 @@ import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.DateTimeType;
@ -2932,9 +2933,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String regext = FHIRPathExpressionFixer.fixRegex(getRegexFromType(e.fhirType()));
if (regext != null) {
try {
boolean matches = e.primitiveValue().matches(regext);
String pt = e.primitiveValue();
String ptFmt = null;
if (e.getProperty().getDefinition().hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) {
ptFmt = convertForDateFormatToExternal(ToolingExtensions.readStringExtension(e.getProperty().getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), pt);
}
boolean matches = pt.matches(regext) || (ptFmt != null && ptFmt.matches(regext));
if (!matches) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE, e.primitiveValue(), e.fhirType(), regext) && ok;
if (ptFmt == null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE, pt, e.fhirType(), regext) && ok;
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE_ALT, pt, ptFmt, e.fhirType(), regext) && ok;
}
}
} catch (Throwable ex) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_EXCEPTION, regext, e.fhirType(), ex.getMessage()) && ok;
@ -2946,6 +2956,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return ok;
}
private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException {
if ("v3".equals(fmt) || "YYYYMMDDHHMMSS.UUUU[+|-ZZzz]".equals(fmt)) {
DateTimeType d = new DateTimeType(av);
return d.getAsV3();
} else
throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATE_FORMAT_, fmt));
}
private boolean isCoreDefinition(StructureDefinition profile) {
return profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/") && profile.getKind() != StructureDefinitionKind.LOGICAL;
}
@ -4289,7 +4307,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (sd != null && (sd.getTypeTail().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) {
return sd;
}
if (sd != null && sd.getAbstract()) {
if (sd != null && (sd.getAbstract() || sd.getUrl().startsWith(Constants.NS_CDA_ROOT))) {
StructureDefinition sdt = context.fetchTypeDefinition(type);
StructureDefinition tt = sdt;
while (tt != null) {
@ -4687,10 +4705,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
timeTracker.sd(t);
if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot())
return sd.getSnapshot().getElement().get(0);
if (sd != null) {
StructureDefinition sdt = context.fetchTypeDefinition(type);
if (inheritsFrom(sdt, sd) && sdt.hasSnapshot()) {
return sdt.getSnapshot().getElement().get(0);
}
}
}
return null;
}
private boolean inheritsFrom(StructureDefinition sdt, StructureDefinition sd) {
while (sdt != null) {
if (sdt == sd) {
return true;
}
sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition());
}
return false;
}
public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) {
this.anyExtensionsAllowed = anyExtensionsAllowed;
}
@ -6024,8 +6058,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkMustSupport(profile, ei);
long s = System.currentTimeMillis();
if (checkDefn.getType().size() == 1 && !"*".equals(checkDefn.getType().get(0).getWorkingCode()) && !"Element".equals(checkDefn.getType().get(0).getWorkingCode())
&& !"BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode())) {
boolean hasType = checkDefn.getType().size() > 0;
boolean isAbstract = hasType && Utilities.existsInList(checkDefn.getType().get(0).getWorkingCode(), "Element", "BackboneElement");
boolean isChoice = checkDefn.getType().size() > 1 || (hasType && "*".equals(checkDefn.getType().get(0).getWorkingCode()));
boolean isCDAChoice = profile.getUrl().startsWith(Constants.NS_CDA_ROOT) && ei.getElement().getExplicitType() != null;
if (hasType && !isChoice && !isAbstract && !isCDAChoice) {
type = checkDefn.getType().get(0).getWorkingCode();
typeName = type;
if (Utilities.isAbsoluteUrl(type)) {
@ -6075,12 +6113,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
profiles.add(p.getValue());
}
}
} else if (checkDefn.getType().size() > 1) {
} else if (checkDefn.getType().size() > 1 || isCDAChoice) {
String prefix = tail(checkDefn.getPath());
assert typesAreAllReference(checkDefn.getType()) || checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB();
assert typesAreAllReference(checkDefn.getType()) || isCDAChoice|| checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB();
if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
if (isCDAChoice) {
type = ei.getElement().getExplicitType();
typeName = type;
} else if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
type = ei.getElement().getType();
typeName = type;
} else if (ei.getElement().isResource()) {
@ -6476,7 +6517,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
hintPlural(errors, NO_RULE_DATE, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), count, I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMIN, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin()));
else {
if (count < ed.getMin()) {
ok = rulePlural(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, count, I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin())) && ok;
if (isObservationMagicValue(profile, ed)) {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM_MAGIC, ed.getSliceName(), getFixedLOINCCode(ed, profile), profile.getVersionedUrl()) && ok;
} else {
ok = rulePlural(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, count, I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin())) && ok;
}
}
}
}
@ -6492,6 +6537,28 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return ok;
}
private String getFixedLOINCCode(ElementDefinition ed, StructureDefinition profile) {
if (ed.hasFixedCoding() && "http://loinc.org".equals(ed.getFixedCoding().getSystem())) {
return ed.getFixedCoding().getCode();
}
SourcedChildDefinitions children = profileUtilities.getChildMap(profile, ed);
if (children != null) {
for (ElementDefinition t : children.getList()) {
if (t.getPath().endsWith(".code") && t.hasFixed()) {
return t.getFixed().primitiveValue();
}
}
}
return "??";
}
private boolean isObservationMagicValue(StructureDefinition profile, ElementDefinition ed) {
if (profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/") && ed.hasSliceName() && ed.getPath().equals("Observation.code.coding")) {
return true;
}
return false;
}
public List<String> assignChildren(ValidationContext valContext, List<ValidationMessage> errors, StructureDefinition profile, Element resource,
NodeStack stack, SourcedChildDefinitions childDefinitions, List<ElementInfo> children, BooleanHolder bh) throws DefinitionException {
// 2. assign children to a definition
@ -6545,12 +6612,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!unsupportedSlicing) {
if (ei.additionalSlice && ei.definition != null) {
if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) ||
ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) {
slicingHint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo),
context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_,
profile == null ? "" : "defined in the profile " + profile.getVersionedUrl()),
context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : context.formatMessage(I18nConstants.DEFINED_IN_THE_PROFILE) + " "+profile.getVersionedUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo),
errorSummaryForSlicingAsText(ei.sliceInfo));
ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) {
if (!ignoreSlicingHint(ei.definition, profile)) {
slicingHint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo),
context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_,
profile == null ? "" : "defined in the profile " + profile.getVersionedUrl()),
context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : context.formatMessage(I18nConstants.DEFINED_IN_THE_PROFILE) + " "+profile.getVersionedUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo),
errorSummaryForSlicingAsText(ei.sliceInfo));
}
} else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) {
bh.see(rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : " defined in the profile " + profile.getVersionedUrl()), errorSummaryForSlicing(ei.sliceInfo)));
}
@ -6593,6 +6662,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
private boolean ignoreSlicingHint(ElementDefinition definition, StructureDefinition profile) {
if (profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/") && "Observation.code.coding".equals(definition.getPath())) {
return true;
}
return false;
}
public List<ElementInfo> listChildren(Element element, NodeStack stack) {
// 1. List the children, and remember their exact path (convenience)
List<ElementInfo> children = new ArrayList<ElementInfo>();