Start building support for measure validation
This commit is contained in:
parent
092a694a3f
commit
232f1ff8ff
|
@ -57,6 +57,7 @@ import org.hl7.fhir.r5.model.ElementDefinition;
|
|||
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
|
||||
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
|
||||
import org.hl7.fhir.r5.model.ImplementationGuide;
|
||||
import org.hl7.fhir.r5.model.Measure;
|
||||
import org.hl7.fhir.r5.model.CanonicalResource;
|
||||
import org.hl7.fhir.r5.model.NamingSystem;
|
||||
import org.hl7.fhir.r5.model.NamingSystem.NamingSystemIdentifierType;
|
||||
|
@ -142,6 +143,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
private CanonicalResourceManager<ConceptMap> maps = new CanonicalResourceManager<ConceptMap>(false);
|
||||
protected CanonicalResourceManager<StructureMap> transforms = new CanonicalResourceManager<StructureMap>(false);
|
||||
private CanonicalResourceManager<StructureDefinition> structures = new CanonicalResourceManager<StructureDefinition>(false);
|
||||
private CanonicalResourceManager<Measure> measures = new CanonicalResourceManager<Measure>(false);
|
||||
private CanonicalResourceManager<ImplementationGuide> guides = new CanonicalResourceManager<ImplementationGuide>(false);
|
||||
private CanonicalResourceManager<CapabilityStatement> capstmts = new CanonicalResourceManager<CapabilityStatement>(false);
|
||||
private CanonicalResourceManager<SearchParameter> searchParameters = new CanonicalResourceManager<SearchParameter>(false);
|
||||
|
@ -207,6 +209,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
systems.addAll(other.systems);
|
||||
guides.copy(other.guides);
|
||||
capstmts.copy(other.capstmts);
|
||||
measures.copy(other.measures);
|
||||
|
||||
allowLoadingDuplicates = other.allowLoadingDuplicates;
|
||||
tsServer = other.tsServer;
|
||||
|
@ -257,6 +260,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
guides.see((ImplementationGuide) m);
|
||||
else if (r instanceof CapabilityStatement)
|
||||
capstmts.see((CapabilityStatement) m);
|
||||
else if (r instanceof Measure)
|
||||
measures.see((Measure) m);
|
||||
else if (r instanceof SearchParameter)
|
||||
searchParameters.see((SearchParameter) m);
|
||||
else if (r instanceof PlanDefinition)
|
||||
|
@ -817,6 +822,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
return (T) guides.get(uri);
|
||||
if (capstmts.has(uri))
|
||||
return (T) capstmts.get(uri);
|
||||
if (measures.has(uri))
|
||||
return (T) measures.get(uri);
|
||||
if (valueSets.has(uri))
|
||||
return (T) valueSets.get(uri);
|
||||
if (codeSystems.has(uri))
|
||||
|
@ -847,6 +854,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
return (T) guides.get(uri);
|
||||
} else if (class_ == CapabilityStatement.class) {
|
||||
return (T) capstmts.get(uri);
|
||||
} else if (class_ == Measure.class) {
|
||||
return (T) measures.get(uri);
|
||||
} else if (class_ == StructureDefinition.class) {
|
||||
return (T) structures.get(uri);
|
||||
} else if (class_ == StructureMap.class) {
|
||||
|
@ -1024,6 +1033,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
json.addProperty("structures-count", structures.size());
|
||||
json.addProperty("guides-count", guides.size());
|
||||
json.addProperty("statements-count", capstmts.size());
|
||||
json.addProperty("measures-count", measures.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1049,6 +1059,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
guides.drop(id);
|
||||
else if (fhirType.equals("CapabilityStatement"))
|
||||
capstmts.drop(id);
|
||||
else if (fhirType.equals("Measure"))
|
||||
measures.drop(id);
|
||||
else if (fhirType.equals("ValueSet"))
|
||||
valueSets.drop(id);
|
||||
else if (fhirType.equals("CodeSystem"))
|
||||
|
@ -1088,6 +1100,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
structures.listAllM(result);
|
||||
guides.listAllM(result);
|
||||
capstmts.listAllM(result);
|
||||
measures.listAllM(result);
|
||||
codeSystems.listAllM(result);
|
||||
valueSets.listAllM(result);
|
||||
maps.listAllM(result);
|
||||
|
@ -1269,6 +1282,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
|
||||
if (capstmts.has(url))
|
||||
return capstmts.get(url).getUserString("path");
|
||||
|
||||
if (measures.has(url))
|
||||
return measures.get(url).getUserString("path");
|
||||
|
||||
if (searchParameters.has(url))
|
||||
return searchParameters.get(url).getUserString("path");
|
||||
|
|
|
@ -59,14 +59,17 @@ MustSupport_VAL_MustSupport = The element {0} is not marked as ''mustSupport'' i
|
|||
Profile_EXT_Not_Here = The extension {0} is not allowed to be used at this point (based on context invariant "{1}")
|
||||
Profile_VAL_MissingElement = Missing element "{0}" - required by fixed value assigned in profile {1}
|
||||
Profile_VAL_NotAllowed = The element {0} is present in the instance but not allowed in the applicable {1} specified in profile
|
||||
Measure_MR_M_None = No Measure is identified, so no validation can be performed against the Measure
|
||||
Measure_MR_M_NotFound = The Measure "{0}" could not be resolved, so no validation can be performed against the Measure
|
||||
Questionnaire_QR_Item_BadOption = The value provided ({0}::{1}) is not in the options value set in the questionnaire
|
||||
Questionnaire_QR_Item_Coding = Error {0} validating Coding against Questionnaire Options
|
||||
Questionnaire_QR_Item_CodingNoOptions = Cannot validate Coding option because no option list is provided
|
||||
Questionnaire_QR_Item_DateNoOptions = Cannot validate date answer option because no option list is provided
|
||||
Questionnaire_QR_Item_Display = Items not of type DISPLAY should not have items - linkId {0}
|
||||
Questionnaire_QR_Item_Group = Items of type group should not have answers
|
||||
Questionnaire_QR_Item_GroupAnswer = Items not of type group should not have items outside answers (use answer.item not .item)
|
||||
Questionnaire_QR_Item_IntNoOptions = Cannot validate integer answer option because no option list is provided
|
||||
Questionnaire_QR_Item_Missing = No response answer found for required item {0}
|
||||
Questionnaire_QR_Item_Missing = No response answer found for required item "{0}"
|
||||
Questionnaire_QR_Item_NoCoding = The code {0}::{1} is not a valid option
|
||||
Questionnaire_QR_Item_NoDate = The date {0} is not a valid option
|
||||
Questionnaire_QR_Item_NoInteger = The integer {0} is not a valid option
|
||||
|
@ -235,6 +238,8 @@ Unable_to_find_element_with_id_ = Unable to find element with id "{0}"
|
|||
Slice_encountered_midway_through_set_path___id___ = Slice encountered midway through set (path = {0}, id = {1}); {2}
|
||||
Unable_to_resolve_actual_type_ = Unable to resolve actual type {0}
|
||||
Unsupported_version_R1 = Unsupported version R1
|
||||
Unsupported_version_R2 = Unsupported version R2
|
||||
Unsupported_version_R2B = Unsupported version R2B
|
||||
Unsupported_fixed_value_type_for_discriminator_for_slice__ = Unsupported fixed value type for discriminator({0}) for slice {1}: {2}
|
||||
Unsupported_CodeableConcept_pattern__extensions_are_not_allowed__for_discriminator_for_slice_ = Unsupported CodeableConcept pattern - extensions are not allowed - for discriminator({0}) for slice {1}
|
||||
Unsupported_CodeableConcept_pattern__must_have_at_least_one_coding__for_discriminator_for_slice_ = Unsupported CodeableConcept pattern - must have at least one coding - for discriminator({0}) for slice {1}
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.stream.*;
|
|||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.model.*;
|
||||
import org.hl7.fhir.r5.model.MeasureReport.MeasureReportGroupComponent;
|
||||
import org.hl7.fhir.r5.model.Questionnaire.*;
|
||||
import org.hl7.fhir.r5.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
|
||||
|
@ -93,6 +94,58 @@ public class EnableWhenEvaluator {
|
|||
}
|
||||
}
|
||||
|
||||
public static class MeasurePair {
|
||||
private MeasureReportGroupComponent g;
|
||||
private Element a;
|
||||
|
||||
public MeasurePair(MeasureReportGroupComponent g, Element a) {
|
||||
super();
|
||||
this.g = g;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
public MeasureReportGroupComponent getGroup() {
|
||||
return g;
|
||||
}
|
||||
|
||||
public Element getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class MStack extends ArrayList<MeasurePair> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Measure m;
|
||||
private Element a;
|
||||
|
||||
public MStack(Measure m, Element a) {
|
||||
super();
|
||||
this.m = m;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
|
||||
public Measure getM() {
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
public Element getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
public MStack push(MeasureReportGroupComponent g, Element a) {
|
||||
MStack self = new MStack(this.m, this.a);
|
||||
self.addAll(this);
|
||||
self.add(new MeasurePair(g, a));
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EnableWhenResult {
|
||||
private final boolean enabled;
|
||||
private final QuestionnaireItemEnableWhenComponent enableWhenCondition;
|
||||
|
|
|
@ -95,6 +95,7 @@ import org.hl7.fhir.r5.model.HumanName;
|
|||
import org.hl7.fhir.r5.model.Identifier;
|
||||
import org.hl7.fhir.r5.model.InstantType;
|
||||
import org.hl7.fhir.r5.model.IntegerType;
|
||||
import org.hl7.fhir.r5.model.Measure;
|
||||
import org.hl7.fhir.r5.model.Period;
|
||||
import org.hl7.fhir.r5.model.Quantity;
|
||||
import org.hl7.fhir.r5.model.Questionnaire;
|
||||
|
@ -130,6 +131,7 @@ import org.hl7.fhir.r5.utils.ToolingExtensions;
|
|||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.validation.BaseValidator;
|
||||
import org.hl7.fhir.validation.instance.EnableWhenEvaluator.QStack;
|
||||
import org.hl7.fhir.validation.instance.EnableWhenEvaluator.MStack;
|
||||
import org.hl7.fhir.validation.XVerExtensionManager;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
@ -3491,6 +3493,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
validateQuestionannaireItem(errors, element, element, stack, parents);
|
||||
} else if (element.getType().equals("QuestionnaireResponse")) {
|
||||
validateQuestionannaireResponse(hostContext, errors, element, stack);
|
||||
} else if (element.getType().equals("MeasureReport")) {
|
||||
validateMeasureReport(hostContext, errors, element, stack);
|
||||
} else if (element.getType().equals("CapabilityStatement")) {
|
||||
validateCapabilityStatement(errors, element, stack);
|
||||
} else if (element.getType().equals("CodeSystem")) {
|
||||
|
@ -3717,6 +3721,77 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
}
|
||||
}
|
||||
|
||||
private void validateMeasureReport(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack) throws FHIRException {
|
||||
Element m = element.getNamedChild("measure");
|
||||
String measure = null;
|
||||
if (m != null) {
|
||||
/*
|
||||
* q.getValue() is correct for R4 content, but we'll also accept the second
|
||||
* option just in case we're validating raw STU3 content. Being lenient here
|
||||
* isn't the end of the world since if someone is actually doing the reference
|
||||
* wrong in R4 content it'll get flagged elsewhere by the validator too
|
||||
*/
|
||||
if (isNotBlank(m.getValue())) {
|
||||
measure = m.getValue();
|
||||
} else if (isNotBlank(m.getChildValue("reference"))) {
|
||||
measure = m.getChildValue("reference");
|
||||
}
|
||||
}
|
||||
if (hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), measure != null, I18nConstants.MEASURE_MR_M_NONE)) {
|
||||
long t = System.nanoTime();
|
||||
Measure msrc = measure.startsWith("#") ? loadMeasure(element, measure.substring(1)) : context.fetchResource(Measure.class, measure);
|
||||
sdTime = sdTime + (System.nanoTime() - t);
|
||||
if (warning(errors, IssueType.REQUIRED, m.line(), m.col(), stack.getLiteralPath(), msrc != null, I18nConstants.MEASURE_MR_M_NOTFOUND, measure)) {
|
||||
boolean inComplete = !"complete".equals(element.getNamedChildValue("status"));
|
||||
//validateMeasureReportGroup(hostContext, msrc, msrc.getGroup(), errors, element, stack, inComplete, element, new MStack(msrc, element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Measure loadMeasure(Element resource, String id) throws FHIRException {
|
||||
try {
|
||||
for (Element contained : resource.getChildren("contained")) {
|
||||
if (contained.getIdBase().equals(id)) {
|
||||
FhirPublication v = FhirPublication.fromCode(context.getVersion());
|
||||
ByteArrayOutputStream bs = new ByteArrayOutputStream();
|
||||
new JsonParser(context).compose(contained, bs, OutputStyle.NORMAL, id);
|
||||
byte[] json = bs.toByteArray();
|
||||
switch (v) {
|
||||
case DSTU1:
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNSUPPORTED_VERSION_R1));
|
||||
case DSTU2:
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNSUPPORTED_VERSION_R2));
|
||||
case DSTU2016May:
|
||||
throw new FHIRException(context.formatMessage(I18nConstants.UNSUPPORTED_VERSION_R2B));
|
||||
case STU3:
|
||||
org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(json);
|
||||
Resource r5 = VersionConvertor_30_50.convertResource(r3, false);
|
||||
if (r5 instanceof Measure)
|
||||
return (Measure) r5;
|
||||
else
|
||||
return null;
|
||||
case R4:
|
||||
org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(json);
|
||||
r5 = VersionConvertor_40_50.convertResource(r4);
|
||||
if (r5 instanceof Measure)
|
||||
return (Measure) r5;
|
||||
else
|
||||
return null;
|
||||
case R5:
|
||||
r5 = new org.hl7.fhir.r5.formats.JsonParser().parse(json);
|
||||
if (r5 instanceof Measure)
|
||||
return (Measure) r5;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
throw new FHIRException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Questionnaire loadQuestionnaire(Element resource, String id) throws FHIRException {
|
||||
try {
|
||||
for (Element contained : resource.getChildren("contained")) {
|
||||
|
@ -3782,7 +3857,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
else if (myEnableWhenEvaluator.isQuestionEnabled(hostContext, qItem, qstack, fpe)) {
|
||||
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), isAnswerRequirementFulfilled(qItem, answers), I18nConstants.QUESTIONNAIRE_QR_ITEM_MISSING, qItem.getLinkId());
|
||||
} else if (!answers.isEmpty()) { // items without answers should be allowed, but not items with answers to questions that are disabled
|
||||
// it appears that this is always a duplicate error - it will always already have beeb reported, so no need to report it again?
|
||||
// it appears that this is always a duplicate error - it will always already have been reported, so no need to report it again?
|
||||
// GDG 2019-07-13
|
||||
// rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), !isAnswerRequirementFulfilled(qItem, answers), I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTENABLED, qItem.getLinkId());
|
||||
}
|
||||
|
@ -3866,7 +3941,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
break;
|
||||
}
|
||||
}
|
||||
validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, answer, stack, inProgress, questionnaireResponseRoot, qstack);
|
||||
if (qItem.getType() != QuestionnaireItemType.GROUP) {
|
||||
// if it's a group, we already have an error before getting here, so no need to hammer away on that
|
||||
validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, answer, stack, inProgress, questionnaireResponseRoot, qstack);
|
||||
}
|
||||
}
|
||||
if (qItem.getType() == null) {
|
||||
fail(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTYPE, qItem.getLinkId());
|
||||
|
@ -3874,8 +3952,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
List<Element> items = new ArrayList<Element>();
|
||||
element.getNamedChildren("item", items);
|
||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), items.isEmpty(), I18nConstants.QUESTIONNAIRE_QR_ITEM_DISPLAY, qItem.getLinkId());
|
||||
} else if (qItem.getType() != QuestionnaireItemType.GROUP) {
|
||||
List<Element> items = new ArrayList<Element>();
|
||||
element.getNamedChildren("item", items);
|
||||
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), items.isEmpty(), I18nConstants.QUESTIONNAIRE_QR_ITEM_GROUP_ANSWER, qItem.getLinkId());
|
||||
} else {
|
||||
validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, element, stack, inProgress, questionnaireResponseRoot, qstack);
|
||||
validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, element, stack, inProgress, questionnaireResponseRoot, qstack);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3960,7 +4042,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
|
||||
// item is missing, is the question enabled?
|
||||
if (enabled && qItem.getRequired()) {
|
||||
String message = "No response found for required item with id = '" + qItem.getLinkId() + "'";
|
||||
String message = context.formatMessage(I18nConstants.QUESTIONNAIRE_QR_ITEM_MISSING, qItem.getLinkId());
|
||||
if (inProgress) {
|
||||
warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, message);
|
||||
} else {
|
||||
|
@ -4510,7 +4592,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
|
||||
}
|
||||
|
||||
if (ref != null && !Utilities.noString(reference)) {
|
||||
if (ref != null && !Utilities.noString(reference) && !reference.startsWith("#")) {
|
||||
Element target = resolveInBundle(entries, reference, fullUrl, type, id);
|
||||
rule(errors, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), target != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOTFOUND, reference, name);
|
||||
}
|
||||
|
@ -4620,6 +4702,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
composition.getNamedChildren(propName, list);
|
||||
int i = 1;
|
||||
for (Element elem : list) {
|
||||
|
||||
validateBundleReference(errors, entries, elem, title + "." + propName, stack.push(elem, i, null, null), fullUrl, "Composition", id);
|
||||
i++;
|
||||
}
|
||||
|
|
|
@ -146,6 +146,11 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
|
|||
String contents = TestingUtilities.loadTestResource("validator", filename);
|
||||
vCurr.getContext().cacheResource(loadResource(filename, contents));
|
||||
}
|
||||
if (content.has("measure")) {
|
||||
String filename = content.get("measure").getAsString();
|
||||
String contents = TestingUtilities.loadTestResource("validator", filename);
|
||||
vCurr.getContext().cacheResource(loadResource(filename, contents));
|
||||
}
|
||||
if (content.has("codesystems")) {
|
||||
for (JsonElement je : content.getAsJsonArray("codesystems")) {
|
||||
String filename = je.getAsString();
|
||||
|
|
Loading…
Reference in New Issue