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_FIXED = "MEASURE_MR_SCORE_FIXED";
|
||||
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()));
|
||||
}
|
||||
|
||||
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 {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(false);
|
||||
|
|
|
@ -469,4 +469,15 @@ MEASURE_MR_SCORE_UNIT_PROHIBITED = A measureScore for this Measure Scoring ({0})
|
|||
MEASURE_MR_SCORE_VALUE_REQUIRED = A value is required when the Measure.scoring={0}
|
||||
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_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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
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.Library;
|
||||
import org.hl7.fhir.r5.model.Measure;
|
||||
import org.hl7.fhir.r5.model.Measure.MeasureGroupComponent;
|
||||
import org.hl7.fhir.utilities.xml.XMLUtil;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
public class MeasureContext {
|
||||
|
||||
|
||||
public static final String USER_DATA_ELM = "validator.ELM";
|
||||
private List<Library> libs = new ArrayList<>();
|
||||
private Measure measure;
|
||||
private Element report;
|
||||
|
@ -26,6 +33,15 @@ public class MeasureContext {
|
|||
|
||||
public void seeLibrary(Library 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() {
|
||||
|
@ -42,5 +58,8 @@ public class MeasureContext {
|
|||
public String scoring() {
|
||||
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.IssueType;
|
||||
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.BaseValidator;
|
||||
import org.hl7.fhir.validation.TimeTracker;
|
||||
import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import net.sf.saxon.tree.tiny.LargeStringBuffer;
|
||||
|
||||
|
@ -104,12 +106,70 @@ public class MeasureValidator extends BaseValidator {
|
|||
}
|
||||
|
||||
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 {
|
||||
Element m = element.getNamedChild("measure");
|
||||
String measure = null;
|
||||
|
|
Loading…
Reference in New Issue