complete CQL based Measure Validation
This commit is contained in:
parent
971b59b7af
commit
b179c482e7
|
@ -470,4 +470,36 @@ public class I18nConstants {
|
||||||
public static final String MEASURE_MR_SCORE_VALUE_INVALID_01 = "MEASURE_MR_SCORE_VALUE_INVALID_01";
|
public static final String MEASURE_MR_SCORE_VALUE_INVALID_01 = "MEASURE_MR_SCORE_VALUE_INVALID_01";
|
||||||
public static final String MEASURE_MR_SCORE_FIXED = "MEASURE_MR_SCORE_FIXED";
|
public static final String MEASURE_MR_SCORE_FIXED = "MEASURE_MR_SCORE_FIXED";
|
||||||
public static final String MEASURE_MR_SCORE_UNIT_REQUIRED = "MEASURE_MR_SCORE_UNIT_REQUIRED";
|
public static final String MEASURE_MR_SCORE_UNIT_REQUIRED = "MEASURE_MR_SCORE_UNIT_REQUIRED";
|
||||||
|
public static final String MEASURE_M_CRITERIA_UNKNOWN = "MEASURE_M_CRITERIA_UNKNOWN";
|
||||||
|
public static final String MEASURE_M_CQL_NOT_FOUND = "MEASURE_M_CQL_NOT_FOUND";
|
||||||
|
public static final String MEASURE_M_CRITERIA_CQL_ERROR = "MEASURE_M_CRITERIA_CQL_ERROR";
|
||||||
|
public static final String MEASURE_M_CRITERIA_CQL_ONLY_ONE_LIB = "MEASURE_M_CRITERIA_CQL_ONLY_ONE_LIB";
|
||||||
|
public static final String MEASURE_M_CRITERIA_CQL_NO_LIB = "MEASURE_M_CRITERIA_CQL_NO_LIB";
|
||||||
|
public static final String MEASURE_M_CRITERIA_CQL_LIB_NOT_FOUND = "MEASURE_M_CRITERIA_CQL_LIB_NOT_FOUND";
|
||||||
|
public static final String MEASURE_M_CRITERIA_CQL_LIB_DUPL = "MEASURE_M_CRITERIA_CQL_LIB_DUPL";
|
||||||
|
public static final String MEASURE_M_CRITERIA_CQL_NO_ELM = "MEASURE_M_CRITERIA_CQL_NO_ELM";
|
||||||
|
public static final String MEASURE_M_CRITERIA_CQL_ELM_NOT_VALID = "MEASURE_M_CRITERIA_CQL_ELM_NOT_VALID";
|
||||||
|
public static final String MEASURE_M_CRITERIA_CQL_NOT_FOUND = "MEASURE_M_CRITERIA_CQL_NOT_FOUND";
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
|
//public static final String
|
||||||
}
|
}
|
|
@ -458,6 +458,20 @@ public class XMLUtil {
|
||||||
return builder.parse(new ByteArrayInputStream(content.getBytes()));
|
return builder.parse(new ByteArrayInputStream(content.getBytes()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Document parseToDom(byte[] content) throws ParserConfigurationException, SAXException, IOException {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
factory.setNamespaceAware(false);
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
return builder.parse(new ByteArrayInputStream(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Document parseToDom(byte[] content, boolean ns) throws ParserConfigurationException, SAXException, IOException {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
factory.setNamespaceAware(ns);
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
return builder.parse(new ByteArrayInputStream(content));
|
||||||
|
}
|
||||||
|
|
||||||
public static Document parseFileToDom(String filename) throws ParserConfigurationException, SAXException, IOException {
|
public static Document parseFileToDom(String filename) throws ParserConfigurationException, SAXException, IOException {
|
||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
factory.setNamespaceAware(false);
|
factory.setNamespaceAware(false);
|
||||||
|
|
|
@ -470,3 +470,14 @@ MEASURE_MR_SCORE_VALUE_REQUIRED = A value is required when the Measure.scoring={
|
||||||
MEASURE_MR_SCORE_VALUE_INVALID_01 = The value is invalid - it must be between 0 and 1
|
MEASURE_MR_SCORE_VALUE_INVALID_01 = The value is invalid - it must be between 0 and 1
|
||||||
MEASURE_MR_SCORE_FIXED = This value is fixed by the Measure to ''{0]''
|
MEASURE_MR_SCORE_FIXED = This value is fixed by the Measure to ''{0]''
|
||||||
MEASURE_MR_SCORE_UNIT_REQUIRED = A unit should be present when the scoring type is {0}
|
MEASURE_MR_SCORE_UNIT_REQUIRED = A unit should be present when the scoring type is {0}
|
||||||
|
MEASURE_M_CRITERIA_UNKNOWN = The expression language {0} is not supported, so can''t be validated
|
||||||
|
MEASURE_M_CQL_NOT_FOUND = None of the include CQL Libraries define a function {0}
|
||||||
|
MEASURE_M_CRITERIA_CQL_NO_LIB = No CQL Libraries found on this Measure
|
||||||
|
MEASURE_M_CRITERIA_CQL_ONLY_ONE_LIB = If the CQL expression does not include a namespace, there can only be one Library for the measure
|
||||||
|
MEASURE_M_CRITERIA_CQL_LIB_NOT_FOUND = No matching Library found for the namespace {0}
|
||||||
|
MEASURE_M_CRITERIA_CQL_LIB_DUPL = Multiple matching libraies found for the namespace {0}
|
||||||
|
MEASURE_M_CRITERIA_CQL_ERROR = Error in {0}: ''{1}''
|
||||||
|
MEASURE_M_CRITERIA_CQL_NO_ELM = Error in {0}: No compiled version of CQL found
|
||||||
|
MEASURE_M_CRITERIA_CQL_ELM_NOT_VALID = = Error in {0}: Compiled version of CQL is not valid
|
||||||
|
MEASURE_M_CRITERIA_CQL_NOT_FOUND = The function {1} does not exist in the library {0}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
package org.hl7.fhir.validation.instance.type;
|
package org.hl7.fhir.validation.instance.type;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
import org.hl7.fhir.r5.elementmodel.Element;
|
import org.hl7.fhir.r5.elementmodel.Element;
|
||||||
|
import org.hl7.fhir.r5.model.Attachment;
|
||||||
import org.hl7.fhir.r5.model.Coding;
|
import org.hl7.fhir.r5.model.Coding;
|
||||||
import org.hl7.fhir.r5.model.Library;
|
import org.hl7.fhir.r5.model.Library;
|
||||||
import org.hl7.fhir.r5.model.Measure;
|
import org.hl7.fhir.r5.model.Measure;
|
||||||
import org.hl7.fhir.r5.model.Measure.MeasureGroupComponent;
|
import org.hl7.fhir.r5.model.Measure.MeasureGroupComponent;
|
||||||
|
import org.hl7.fhir.utilities.xml.XMLUtil;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class MeasureContext {
|
public class MeasureContext {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String USER_DATA_ELM = "validator.ELM";
|
||||||
private List<Library> libs = new ArrayList<>();
|
private List<Library> libs = new ArrayList<>();
|
||||||
private Measure measure;
|
private Measure measure;
|
||||||
private Element report;
|
private Element report;
|
||||||
|
@ -26,6 +33,15 @@ public class MeasureContext {
|
||||||
|
|
||||||
public void seeLibrary(Library l) {
|
public void seeLibrary(Library l) {
|
||||||
libs.add(l);
|
libs.add(l);
|
||||||
|
for (Attachment att : l.getContent()) {
|
||||||
|
if ("application/elm+xml".equals(att.getContentType())) {
|
||||||
|
try {
|
||||||
|
l.setUserData(USER_DATA_ELM, XMLUtil.parseToDom(att.getData(), true));
|
||||||
|
} catch (Exception e) {
|
||||||
|
l.setUserData(USER_DATA_ELM, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MeasureGroupComponent> groups() {
|
public List<MeasureGroupComponent> groups() {
|
||||||
|
@ -42,5 +58,8 @@ public class MeasureContext {
|
||||||
public String scoring() {
|
public String scoring() {
|
||||||
return measure.getScoring().getCodingFirstRep().getCode();
|
return measure.getScoring().getCodingFirstRep().getCode();
|
||||||
}
|
}
|
||||||
|
public List<Library> libraries() {
|
||||||
|
return libs;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,10 +32,12 @@ import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||||
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
import org.hl7.fhir.utilities.validation.ValidationMessage;
|
||||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
|
||||||
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
|
||||||
|
import org.hl7.fhir.utilities.xml.XMLUtil;
|
||||||
import org.hl7.fhir.validation.instance.utils.NodeStack;
|
import org.hl7.fhir.validation.instance.utils.NodeStack;
|
||||||
import org.hl7.fhir.validation.BaseValidator;
|
import org.hl7.fhir.validation.BaseValidator;
|
||||||
import org.hl7.fhir.validation.TimeTracker;
|
import org.hl7.fhir.validation.TimeTracker;
|
||||||
import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
|
import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
import net.sf.saxon.tree.tiny.LargeStringBuffer;
|
import net.sf.saxon.tree.tiny.LargeStringBuffer;
|
||||||
|
|
||||||
|
@ -104,10 +106,68 @@ public class MeasureValidator extends BaseValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateMeasureCriteria(ValidatorHostContext hostContext, List<ValidationMessage> errors, MeasureContext mctxt, Element crit, NodeStack nsc) {
|
private void validateMeasureCriteria(ValidatorHostContext hostContext, List<ValidationMessage> errors, MeasureContext mctxt, Element crit, NodeStack nsc) {
|
||||||
// TODO Auto-generated method stub
|
String mimeType = crit.getChildValue("language");
|
||||||
|
if (!Utilities.noString(mimeType)) { // that would be an error elsewhere
|
||||||
|
if ("text/cql".equals(mimeType)) {
|
||||||
|
String cqlRef = crit.getChildValue("expression");
|
||||||
|
Library lib = null;
|
||||||
|
if (rule(errors, IssueType.INVALID, crit.line(), crit.col(), nsc.getLiteralPath(), mctxt.libraries().size()> 0, I18nConstants.MEASURE_M_CRITERIA_CQL_NO_LIB)) {
|
||||||
|
if (cqlRef.contains(".")) {
|
||||||
|
String name = cqlRef.substring(0, cqlRef.indexOf("."));
|
||||||
|
cqlRef = cqlRef.substring(cqlRef.indexOf(".")+1);
|
||||||
|
for (Library l : mctxt.libraries()) {
|
||||||
|
if (l.getName().equals(name)) {
|
||||||
|
if (rule(errors, IssueType.INVALID, crit.line(), crit.col(), nsc.getLiteralPath(), lib == null, I18nConstants.MEASURE_M_CRITERIA_CQL_LIB_DUPL)) {
|
||||||
|
lib = l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rule(errors, IssueType.INVALID, crit.line(), crit.col(), nsc.getLiteralPath(), lib != null, I18nConstants.MEASURE_M_CRITERIA_CQL_LIB_NOT_FOUND, name);
|
||||||
|
} else {
|
||||||
|
if (rule(errors, IssueType.INVALID, crit.line(), crit.col(), nsc.getLiteralPath(), mctxt.libraries().size() == 1, I18nConstants.MEASURE_M_CRITERIA_CQL_ONLY_ONE_LIB)) {
|
||||||
|
lib = mctxt.libraries().get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lib != null) {
|
||||||
|
if (rule(errors, IssueType.INVALID, crit.line(), crit.col(), nsc.getLiteralPath(), lib.hasUserData(MeasureContext.USER_DATA_ELM), I18nConstants.MEASURE_M_CRITERIA_CQL_NO_ELM, lib.getUrl())) {
|
||||||
|
if (lib.getUserData(MeasureContext.USER_DATA_ELM) instanceof String) {
|
||||||
|
rule(errors, IssueType.INVALID, crit.line(), crit.col(), nsc.getLiteralPath(), false, I18nConstants.MEASURE_M_CRITERIA_CQL_ERROR, lib.getUrl(), lib.getUserString(MeasureContext.USER_DATA_ELM));
|
||||||
|
} else if (lib.getUserData(MeasureContext.USER_DATA_ELM) instanceof Document) {
|
||||||
|
org.w3c.dom.Element elm = ((Document)lib.getUserData(MeasureContext.USER_DATA_ELM)).getDocumentElement();
|
||||||
|
if (rule(errors, IssueType.INVALID, crit.line(), crit.col(), nsc.getLiteralPath(), isValidElm(elm), I18nConstants.MEASURE_M_CRITERIA_CQL_ELM_NOT_VALID, lib.getUrl(), cqlRef)) {
|
||||||
|
rule(errors, IssueType.INVALID, crit.line(), crit.col(), nsc.getLiteralPath(), hasCqlTarget(elm, cqlRef), I18nConstants.MEASURE_M_CRITERIA_CQL_NOT_FOUND, lib.getUrl(), cqlRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ("text/fhirpath".equals(mimeType)) {
|
||||||
|
warning(errors, IssueType.REQUIRED, crit.line(), crit.col(), nsc.getLiteralPath(), false, I18nConstants.MEASURE_M_CRITERIA_UNKNOWN, mimeType);
|
||||||
|
} else if ("application/x-fhir-query".equals(mimeType)) {
|
||||||
|
warning(errors, IssueType.REQUIRED, crit.line(), crit.col(), nsc.getLiteralPath(), false, I18nConstants.MEASURE_M_CRITERIA_UNKNOWN, mimeType);
|
||||||
|
} else {
|
||||||
|
warning(errors, IssueType.REQUIRED, crit.line(), crit.col(), nsc.getLiteralPath(), false, I18nConstants.MEASURE_M_CRITERIA_UNKNOWN, mimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isValidElm(org.w3c.dom.Element elm) {
|
||||||
|
return elm != null && "library".equals(elm.getNodeName()) && "urn:hl7-org:elm:r1".equals(elm.getNamespaceURI());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasCqlTarget(org.w3c.dom.Element element, String cqlRef) {
|
||||||
|
org.w3c.dom.Element stmts = XMLUtil.getNamedChild(element, "statements");
|
||||||
|
if (stmts != null) {
|
||||||
|
for (org.w3c.dom.Element def : XMLUtil.getNamedChildren(stmts, "def")) {
|
||||||
|
if (cqlRef.equals(def.getAttribute("name"))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
public void validateMeasureReport(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack) throws FHIRException {
|
public void validateMeasureReport(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack) throws FHIRException {
|
||||||
|
|
Loading…
Reference in New Issue