Refactor how observation profiles are registered in order to generalise management of extra profiles

This commit is contained in:
Grahame Grieve 2024-06-01 22:54:17 +10:00
parent 04e95f4e05
commit 2f38cb544c
12 changed files with 190 additions and 171 deletions

View File

@ -0,0 +1,11 @@
package org.hl7.fhir.r5.utils.validation;
import java.util.List;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
public interface IMessagingServices {
ValidationMessage signpost(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String theMessage, Object... theMessageArguments);
}

View File

@ -31,6 +31,7 @@ package org.hl7.fhir.r5.utils.validation;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -54,6 +55,8 @@ import java.util.List;
*/
public interface IResourceValidator {
IWorkerContext getContext();
/**
* how much to check displays for coded elements
*/

View File

@ -10,7 +10,6 @@ import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.instance.BasePolicyAdvisorForMandatoryProfiles;
import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ElementValidationAction;
import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
@ -135,7 +134,7 @@ public interface IValidationPolicyAdvisor {
/**
* This is called after a resource has been validated against the base structure,
* but before any profiles specified in .meta.profile or in the parameters are applied.
* but before it's validated against any profiles specified in .meta.profile or in the parameters.
* This can be used to determine what additional profiles should be applied, for instance
* those derived from the http://hl7.org/fhir/tools/StructureDefinition/profile-mapping extension
*
@ -144,9 +143,9 @@ public interface IValidationPolicyAdvisor {
* might be any version from R2-R6)
*
* The base implementation applies the mandatory vital signs to observations that have LOINC or SNOMED CT
* codes that indicate that they are vital signs. Note that these profiles are not optional; all resources
* codes that indicate that they are vital signs. Note that these profiles are not optional; all vital sign resources
* are required to conform to them. For this reason, if you're providing your own policy advisor, you should
* keep a reference to the default one, or call BasePolicyAdvisorForMandatoryProfiles. You can choose not to,
* keep a reference to the default one, or call BasePolicyAdvisorForFullValidation directly. You can choose not to,
* but if you do, you are allowing for resources that deviate from the FHIR specification (in a way that the
* community considers clinically unsafe, since it means that software (probably) will miss vital signs for
* patients).
@ -161,13 +160,14 @@ public interface IValidationPolicyAdvisor {
* @param messages all the validation messages. Implementations can inspect this, but the real purpose is to populate the messages with information messages explaining why profiles were (or weren't) applied
* @return
*/
List<StructureDefinition> getImpliedProfilesForInstance(IResourceValidator validator,
List<StructureDefinition> getImpliedProfilesForResource(IResourceValidator validator,
Object appContext,
String stackPath,
ElementDefinition definition,
StructureDefinition structure,
Element resource,
boolean valid,
IMessagingServices msgServices,
List<ValidationMessage> messages);
}

View File

@ -67,6 +67,7 @@ import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
import org.hl7.fhir.r5.utils.validation.IMessagingServices;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader;
@ -87,7 +88,7 @@ import org.hl7.fhir.validation.cli.utils.ValidationLevel;
import org.hl7.fhir.validation.instance.utils.IndexedElement;
import org.hl7.fhir.validation.instance.utils.NodeStack;
public class BaseValidator implements IValidationContextResourceLoader {
public class BaseValidator implements IValidationContextResourceLoader, IMessagingServices {
public static class BooleanHolder {
private boolean value = true;
@ -411,7 +412,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
return thePass;
}
protected ValidationMessage signpost(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String theMessage, Object... theMessageArguments) {
public ValidationMessage signpost(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String theMessage, Object... theMessageArguments) {
String message = context.formatMessage(theMessage, theMessageArguments);
return addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage).setSignpost(true);
}

View File

@ -67,6 +67,7 @@ import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
import org.hl7.fhir.r5.utils.validation.IMessagingServices;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
@ -104,6 +105,7 @@ import org.hl7.fhir.validation.cli.utils.ProfileLoader;
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
import org.hl7.fhir.validation.cli.utils.SchemaValidator;
import org.hl7.fhir.validation.cli.utils.ValidationLevel;
import org.hl7.fhir.validation.instance.BasePolicyAdvisorForFullValidation;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
import org.hl7.fhir.utilities.ByteProvider;
@ -1253,4 +1255,12 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
return res;
}
@Override
public List<StructureDefinition> getImpliedProfilesForResource(IResourceValidator validator, Object appContext,
String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid,
IMessagingServices msgServices, List<ValidationMessage> messages) {
return new BasePolicyAdvisorForFullValidation().getImpliedProfilesForResource(validator, appContext, stackPath,
definition, structure, resource, valid, msgServices, messages);
}
}

View File

@ -42,11 +42,12 @@ import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.validation.cli.utils.Common;
import org.hl7.fhir.validation.instance.BasePolicyAdvisorForFullValidation;
import javax.annotation.Nonnull;
public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator {
public class StandAloneValidatorFetcher extends BasePolicyAdvisorForFullValidation implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator {
List<String> mappingsUris = new ArrayList<>();
private FilesystemPackageCacheManager pcm;
@ -76,31 +77,6 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
return ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS;
}
@Override
public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator,
Object appContext,
StructureDefinition structure,
ElementDefinition element,
String containerType,
String containerId,
Element.SpecialElement containingResourceType,
String path,
String url) {
return ContainedReferenceValidationPolicy.CHECK_VALID;
}
@Override
public EnumSet<ResourceValidationAction> policyForResource(IResourceValidator validator, Object appContext,
StructureDefinition type, String path) {
return EnumSet.allOf(ResourceValidationAction.class);
}
@Override
public EnumSet<ElementValidationAction> policyForElement(IResourceValidator validator, Object appContext,
StructureDefinition structure, ElementDefinition element, String path) {
return EnumSet.allOf(ElementValidationAction.class);
}
@Override
public boolean resolveURL(IResourceValidator validator, Object appContext, String path, String url, String type, boolean canonical) throws IOException, FHIRException {
if (!Utilities.isAbsoluteUrl(url)) {
@ -316,19 +292,6 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
}
}
@Override
public EnumSet<CodedContentValidationAction> policyForCodedContent(IResourceValidator validator,
Object appContext,
String stackPath,
ElementDefinition definition,
StructureDefinition structure,
BindingKind kind,
AdditionalBindingPurpose purpose,
ValueSet valueSet,
List<String> systems) {
return EnumSet.allOf(CodedContentValidationAction.class);
}
@Override
public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
return new HashSet<>();

View File

@ -1,65 +1,166 @@
package org.hl7.fhir.validation.instance;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.validation.IMessagingServices;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.AdditionalBindingPurpose;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.CodedContentValidationAction;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ElementValidationAction;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ResourceValidationAction;
import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
public class BasePolicyAdvisorForFullValidation implements IValidationPolicyAdvisor {
@Override
public ReferenceValidationPolicy policyForReference(IResourceValidator validator, Object appContext, String path,
String url) {
// TODO Auto-generated method stub
return null;
public ReferenceValidationPolicy policyForReference(IResourceValidator validator, Object appContext, String path, String url) {
return ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS;
}
@Override
public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator, Object appContext,
StructureDefinition structure, ElementDefinition element, String containerType, String containerId,
SpecialElement containingResourceType, String path, String url) {
// TODO Auto-generated method stub
return null;
return ContainedReferenceValidationPolicy.CHECK_VALID;
}
@Override
public EnumSet<ResourceValidationAction> policyForResource(IResourceValidator validator, Object appContext,
StructureDefinition type, String path) {
// TODO Auto-generated method stub
return null;
return EnumSet.allOf(ResourceValidationAction.class);
}
@Override
public EnumSet<ElementValidationAction> policyForElement(IResourceValidator validator, Object appContext,
StructureDefinition structure, ElementDefinition element, String path) {
// TODO Auto-generated method stub
return null;
return EnumSet.allOf(ElementValidationAction.class);
}
@Override
public EnumSet<CodedContentValidationAction> policyForCodedContent(IResourceValidator validator, Object appContext,
String stackPath, ElementDefinition definition, StructureDefinition structure, BindingKind kind,
AdditionalBindingPurpose purpose, ValueSet valueSet, List<String> systems) {
// TODO Auto-generated method stub
return null;
public EnumSet<CodedContentValidationAction> policyForCodedContent(IResourceValidator validator,
Object appContext,
String stackPath,
ElementDefinition definition,
StructureDefinition structure,
BindingKind kind,
AdditionalBindingPurpose purpose,
ValueSet valueSet,
List<String> systems) {
return EnumSet.allOf(CodedContentValidationAction.class);
}
@Override
public List<StructureDefinition> getImpliedProfilesForInstance(IResourceValidator validator, Object appContext,
public List<StructureDefinition> getImpliedProfilesForResource(IResourceValidator validator, Object appContext,
String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid,
List<ValidationMessage> messages) {
// TODO Auto-generated method stub
return null;
IMessagingServices msgServices, List<ValidationMessage> messages) {
List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
if ("Observation".equals(resource.fhirType()) && VersionUtilities.isR4Plus(validator.getContext().getVersion())) {
getImpliedProfilesForObservation(profiles, msgServices, messages, validator.getContext(), stackPath, resource);
}
return profiles;
}
private void getImpliedProfilesForObservation(List<StructureDefinition> profiles, IMessagingServices msgServices, List<ValidationMessage> messages, IWorkerContext context, String stackPath, Element resource) {
Element code = resource.getNamedChild("code", false);
List<String> codes = new ArrayList<>();
if (hasLoincCode(code, codes, "85353-1")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/vitalspanel", "Vital Signs Panel", "LOINC", codes);
} else if (hasLoincCode(code, codes, "9279-1", "76170-0", "76172-6", "76171-8", "19840-8", "33438-3", "76270-8", "11291-2")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/resprate", "Respiratory Rate", "LOINC", codes);
} else if (hasLoincCode(code, codes, "60978-4", "73795-7", "73799-9", "76476-1", "76477-9", "8867-4", "8889-8", "8890-6", "8891-4", "8892-2", "8893-0", "40443-4", "55425-3", "68999-2", "11328-2", "69000-8", "69000-8", "60978-4", "60978-4", "8890-6", "8886-4", "68999-2", "68999-2")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/heartrate", "Heart rate", "LOINC", codes);
} else if (hasLoincCode(code, codes, "2708-6", "19224-5", "20564-1", "2709-4", "2710-2", "2713-6", "51733-4", "59408-5", "59417-6", "89276-0", "97549-0")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/oxygensat", "Oxygen saturation", "LOINC", codes);
} else if (hasLoincCode(code, codes, "8310-5", "60834-9", "60835-6", "60836-4", "60838-0", "60955-2", "61009-7", "75539-7", "75987-8", "76010-8", "76011-6", "76278-1", "8309-7", "8310-5", "8328-7", "8329-5", "8330-3", "8331-1", "8332-9", "8333-7", "8334-5", "91371-5", "98657-0", "98663-8")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodytemp", "Body temperature", "LOINC", codes);
} else if (hasLoincCode(code, codes, "8302-2", "3137-7", "3138-5", "8302-2", "8306-3", "8308-9")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodyheight", "Body height", "LOINC", codes);
} else if (hasLoincCode(code, codes, "9843-4", "8287-5", "9843-4")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/headcircum", "Head circumference", "LOINC", codes);
} else if (hasLoincCode(code, codes, "29463-7", "29463-7", "3141-9", "3142-7", "75292-3", "79348-9", "8350-1", "8351-9")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "LOINC", codes);
} else if (hasLoincCode(code, codes, "39156-5", "39156-5", "59574-4", "89270-3")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "LOINC", codes);
} else if (hasLoincCode(code, codes, "85354-9", "35094-2", "8459-0", "85354-9", "76534-7", "55284-4", "8480-6")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "LOINC", codes);
} else if (hasSctCode(code, codes, "46680005")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/vitalspanel", "Vital Signs Panel", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "86290005", "271625008", "271306003")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "271306003", "249043002", "444981005", "399017001", "251670001", "429525003", "429614003")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/heartrate", "Heart rate", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "103228002", "103228002", "442349007", "442476006", "442440005", "431314004", "442734002", "713194001")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/oxygensat", "Oxygen saturation", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "386725007", "276885007", "300076005", "1222808002", "364246006", "307047009", "708499008", "708499008", "431598003", "698831002", "698832009", "415882003", "415974002", "415929009", "415945006")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodytemp", "Body temperature", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "1153637007", "1162419008", "50373000", "1162418000", "1230278008", "1162392001", "1162417005")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodyheight", "Body height", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "363812007", "169876006", "1269262007", "363811000")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/headcircum", "Head circumference", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "363811000", "60621009", "735395000", "425024002", "424927000", "784399000", "1162416001", "1162415002")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "60621009")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "75367002", "251076008", "163033001", "163035008", "386534000", "386536003", "271649006", "271649006", "271650006", "407556006", "407554009", "716579001", "399304008")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "SNOMED CT", codes);
}
}
private void addProfile(List<StructureDefinition> profiles, IMessagingServices msgServices, List<ValidationMessage> messages, IWorkerContext context, String stackPath, Element resource, String url, String name, String systemName, List<String> codes) {
resource.addMessage(msgServices.signpost(messages, null, IssueType.INFORMATIONAL, resource.line(), resource.col(), stackPath, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_OBS, url, name, systemName, codes.get(0)));
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd != null) {
profiles.add(sd);
} else {
// complain?
}
}
protected boolean hasLoincCode(Element code, List<String> codes, String... values) {
if (code != null) {
List<Element> codings = code.getChildren("coding");
for (Element coding : codings) {
if ("http://loinc.org".equals(coding.getNamedChildValue("system", false)) && Utilities.existsInList(coding.getNamedChildValue("code", false), values)) {
codes.add(coding.getNamedChildValue("code", false));
return true;
}
}
}
return false;
}
protected boolean hasSctCode(Element code, List<String> codes, String... values) {
if (code != null) {
List<Element> codings = code.getChildren("coding");
for (Element coding : codings) {
if ("http://snomed.info/sct".equals(coding.getNamedChildValue("system", false)) && Utilities.existsInList(coding.getNamedChildValue("code", false), values)) {
codes.add(coding.getNamedChildValue("code", false));
return true;
}
}
}
return false;
}
}

View File

@ -173,6 +173,7 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
import org.hl7.fhir.r5.utils.sql.Validator;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
import org.hl7.fhir.r5.utils.validation.IMessagingServices;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidationProfileUsageTracker;
@ -595,7 +596,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean noBindingMsgSuppressed;
private Map<String, Element> fetchCache = new HashMap<>();
private HashMap<Element, ResourceValidationTracker> resourceTracker = new HashMap<>();
private IValidationPolicyAdvisor policyAdvisor;
private IValidationPolicyAdvisor policyAdvisor = new BasePolicyAdvisorForFullValidation();
long time = 0;
long start = 0;
long lastlog = 0;
@ -691,6 +692,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
@Override
public IResourceValidator setPolicyAdvisor(IValidationPolicyAdvisor advisor) {
if (advisor == null) {
throw new Error("Cannot set advisor to null");
}
this.policyAdvisor = advisor;
return this;
}
@ -3111,7 +3115,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
if (!found) {
if (type.equals("canonical")) {
ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, valContext, path, url);
ReferenceValidationPolicy rp = policyAdvisor.policyForReference(this, valContext, path, url);
if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url) && ok;
} else {
@ -3128,7 +3132,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
} else {
if (type.equals("canonical")) {
ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, valContext, path, url);
ReferenceValidationPolicy rp = policyAdvisor.policyForReference(this, valContext, path, url);
if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) {
try {
Resource r = null;
@ -3581,8 +3585,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = false;
}
} else {
EnumSet<CodedContentValidationAction> validationPolicy = getPolicyAdvisor() == null ?
EnumSet.allOf(CodedContentValidationAction.class) : getPolicyAdvisor().policyForCodedContent(this, valContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, null, vs, new ArrayList<>());
EnumSet<CodedContentValidationAction> validationPolicy = policyAdvisor.policyForCodedContent(this, valContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, null, vs, new ArrayList<>());
if (!validationPolicy.isEmpty()) {
long t = System.nanoTime();
@ -3917,13 +3920,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ReferenceValidationPolicy pol;
if (refType.equals("contained") || refType.equals("bundled")) {
pol = ReferenceValidationPolicy.CHECK_VALID;
} else {
if (policyAdvisor == null) {
pol = ReferenceValidationPolicy.IGNORE;
} else {
pol = policyAdvisor.policyForReference(this, valContext.getAppContext(), path, ref);
}
}
if (conditional) {
String query = ref.substring(ref.indexOf("?"));
@ -5705,15 +5704,22 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = false;
}
if (checkSpecials) {
ok = checkSpecials(valContext, errors, element, stack, checkSpecials, pct, mode, fromContained) && ok;
ok = checkSpecials(valContext, errors, element, stack, checkSpecials, pct, mode, fromContained, ok) && ok;
ok = validateResourceRules(errors, element, stack) && ok;
}
return ok;
}
public boolean checkSpecials(ValidationContext valContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode, boolean contained) {
public boolean checkSpecials(ValidationContext valContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode, boolean contained, boolean isOk) {
boolean ok = true;
// first, does the policy advisor have profiles it wants us to check?
List<StructureDefinition> profiles = policyAdvisor.getImpliedProfilesForResource(this, valContext.getAppContext(), stack.getLiteralPath(),
element.getProperty().getDefinition(), element.getProperty().getStructure(), element, isOk, this, errors);
for (StructureDefinition sd : profiles) {
ok = startInner(valContext, errors, element, element, sd, stack, false, pct, mode, false) && ok;
}
long t = System.nanoTime();
try {
if (VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(element.getType())) {
@ -5966,8 +5972,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
SpecialElement special = element.getSpecial();
ContainedReferenceValidationPolicy containedValidationPolicy = getPolicyAdvisor() == null ?
ContainedReferenceValidationPolicy.CHECK_VALID : getPolicyAdvisor().policyForContained(this,
ContainedReferenceValidationPolicy containedValidationPolicy = policyAdvisor.policyForContained(this,
valContext, parentProfile, child, context.fhirType(), context.getId(), special, path, parentProfile.getUrl());
if (containedValidationPolicy.ignore()) {
@ -6021,7 +6026,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
checkSpecials(valContext, errors, element, stack, ok, pct, mode, true);
checkSpecials(valContext, errors, element, stack, ok, pct, mode, true, ok);
if (typeForResource.getProfile().size() == 1) {
long t = System.nanoTime();
@ -6427,8 +6432,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (debug) {
System.out.println(" check " + localStack.getLiteralPath()+" against "+ei.getDefinition().getId()+" in profile "+profile.getVersionedUrl()+time());
}
EnumSet<ElementValidationAction> actionSet = policyAdvisor == null ? EnumSet.allOf(ElementValidationAction.class) :
policyAdvisor.policyForElement(this, valContext.getAppContext(), profile, ei.getDefinition(), localStack.getLiteralPath());
EnumSet<ElementValidationAction> actionSet = policyAdvisor.policyForElement(this, valContext.getAppContext(), profile, ei.getDefinition(), localStack.getLiteralPath());
String localStackLiteralPath = localStack.getLiteralPath();
String eiPath = ei.getPath();

View File

@ -181,7 +181,7 @@ public class BundleValidator extends BaseValidator {
}
}
// also, while we're here, check the specials, since this doesn't happen anywhere else
((InstanceValidator) parent).checkSpecials(hostContext, errors, res, rstack, true, pct, mode, true);
((InstanceValidator) parent).checkSpecials(hostContext, errors, res, rstack, true, pct, mode, true, ok);
}
// todo: check specials

View File

@ -38,92 +38,8 @@ public class ObservationValidator extends BaseValidator {
element.getNamedChild("effectiveTiming", false) != null || element.getNamedChild("effectiveInstant", false) != null,
I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_AN_EFFECTIVEDATETIME_OR_AN_EFFECTIVEPERIOD, element.getProperty().typeSummary()) && ok;
// hook in the vital signs
if (VersionUtilities.isR4Plus(context.getVersion())) {
Element code = element.getNamedChild("code", false);
List<String> codes = new ArrayList<>();
if (hasLoincCode(code, codes, "85353-1")) {
ok = checkObservationAgainstProfile(valContext, errors, element, stack, "http://hl7.org/fhir/StructureDefinition/vitalspanel", "Vital Signs Panel", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "9279-1", "76170-0", "76172-6", "76171-8", "19840-8", "33438-3", "76270-8", "11291-2")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/resprate", "Respiratory Rate", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "60978-4", "73795-7", "73799-9", "76476-1", "76477-9", "8867-4", "8889-8", "8890-6", "8891-4", "8892-2", "8893-0", "40443-4", "55425-3", "68999-2", "11328-2", "69000-8", "69000-8", "60978-4", "60978-4", "8890-6", "8886-4", "68999-2", "68999-2")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/heartrate", "Heart rate", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "2708-6", "19224-5", "20564-1", "2709-4", "2710-2", "2713-6", "51733-4", "59408-5", "59417-6", "89276-0", "97549-0")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/oxygensat", "Oxygen saturation", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "8310-5", "60834-9", "60835-6", "60836-4", "60838-0", "60955-2", "61009-7", "75539-7", "75987-8", "76010-8", "76011-6", "76278-1", "8309-7", "8310-5", "8328-7", "8329-5", "8330-3", "8331-1", "8332-9", "8333-7", "8334-5", "91371-5", "98657-0", "98663-8")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodytemp", "Body temperature", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "8302-2", "3137-7", "3138-5", "8302-2", "8306-3", "8308-9")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyheight", "Body height", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "9843-4", "8287-5", "9843-4")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/headcircum", "Head circumference", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "29463-7", "29463-7", "3141-9", "3142-7", "75292-3", "79348-9", "8350-1", "8351-9")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "39156-5", "39156-5", "59574-4", "89270-3")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "85354-9", "35094-2", "8459-0", "85354-9", "76534-7", "55284-4", "8480-6")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "LOINC", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "46680005")) {
ok = checkObservationAgainstProfile(valContext, errors, element, stack, "http://hl7.org/fhir/StructureDefinition/vitalspanel", "Vital Signs Panel", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "86290005", "271625008", "271306003")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "Blood pressure systolic and diastolic", "Respiratory Rate", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "271306003", "249043002", "444981005", "399017001", "251670001", "429525003", "429614003")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/heartrate", "Heart rate", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "103228002", "103228002", "442349007", "442476006", "442440005", "431314004", "442734002", "713194001")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/oxygensat", "Oxygen saturation", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "386725007", "276885007", "300076005", "1222808002", "364246006", "307047009", "708499008", "708499008", "431598003", "698831002", "698832009", "415882003", "415974002", "415929009", "415945006")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodytemp", "Body temperature", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "1153637007", "1162419008", "50373000", "1162418000", "1230278008", "1162392001", "1162417005")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyheight", "Body height", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "363812007", "169876006", "1269262007", "363811000")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/headcircum", "Head circumference", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "363811000", "60621009", "735395000", "425024002", "424927000", "784399000", "1162416001", "1162415002")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "60621009")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "75367002", "251076008", "163033001", "163035008", "386534000", "386536003", "271649006", "271649006", "271650006", "407556006", "407554009", "716579001", "399304008")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "SNOMED CT", codes, pct, mode) && ok;
}
}
// Looking for the vital signs code? It's moved to BasePolicyAdvisorForFullValidation.getImpliedProfilesForObservation
return ok;
}
private boolean checkObservationAgainstProfile(ValidationContext valContext, List<ValidationMessage> errors, Element element, NodeStack stack, String url, String name, String sys, List<String> loinc, PercentageTracker pct, ValidationMode mode) {
element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_OBS, url, name, sys, loinc.get(0)));
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) {
return false;
} else {
return ((InstanceValidator) parent).startInner(valContext, errors, element, element, sd, stack, false, pct, mode, false);
}
}
private boolean hasLoincCode(Element code, List<String> codes, String... values) {
if (code != null) {
List<Element> codings = code.getChildren("coding");
for (Element coding : codings) {
if ("http://loinc.org".equals(coding.getNamedChildValue("system", false)) && Utilities.existsInList(coding.getNamedChildValue("code", false), values)) {
codes.add(coding.getNamedChildValue("code", false));
return true;
}
}
}
return false;
}
private boolean hasSctCode(Element code, List<String> codes, String... values) {
if (code != null) {
List<Element> codings = code.getChildren("coding");
for (Element coding : codings) {
if ("http://snomed.info/sct".equals(coding.getNamedChildValue("system", false)) && Utilities.existsInList(coding.getNamedChildValue("code", false), values)) {
codes.add(coding.getNamedChildValue("code", false));
return true;
}
}
}
return false;
}
}

View File

@ -59,6 +59,7 @@ import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.OperationOutcomeUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
import org.hl7.fhir.r5.utils.validation.IMessagingServices;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
@ -90,6 +91,7 @@ import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.ValidatorUtils;
import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher;
import org.hl7.fhir.validation.instance.BasePolicyAdvisorForFullValidation;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
import org.junit.AfterClass;
@ -469,7 +471,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
private ValidationEngine buildVersionEngine(String ver, String txLog) throws Exception {
String server = FhirSettings.getTxFhirLocal();
String server = FhirSettings.getTxFhirDevelopment();
switch (ver) {
case "1.0": return TestUtilities.getValidationEngine("hl7.fhir.r2.core#1.0.2", server, txLog, FhirPublication.DSTU2, true, "1.0.2");
case "1.4": return TestUtilities.getValidationEngine("hl7.fhir.r2b.core#1.4.0", server, txLog, FhirPublication.DSTU2016May, true, "1.4.0");
@ -887,4 +889,12 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
return new HashSet<>();
}
@Override
public List<StructureDefinition> getImpliedProfilesForResource(IResourceValidator validator, Object appContext,
String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid,
IMessagingServices msgServices, List<ValidationMessage> messages) {
return new BasePolicyAdvisorForFullValidation().getImpliedProfilesForResource(validator, appContext, stackPath,
definition, structure, resource, valid, msgServices, messages);
}
}

View File

@ -21,7 +21,7 @@
<commons_compress_version>1.26.0</commons_compress_version>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.5.10</validator_test_case_version>
<validator_test_case_version>1.5.11-SNAPSHOT</validator_test_case_version>
<jackson_version>2.17.0</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>