Merge pull request #1362 from hapifhir/2023-07-gg-ips_parameter

fix -ips parameter and add -check-ips-codes parameter
This commit is contained in:
Grahame Grieve 2023-07-26 08:30:28 +10:00 committed by GitHub
commit 9705627916
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 407 additions and 149 deletions

View File

@ -1017,6 +1017,106 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
}
}
@Override
public void validateCodeBatchByRef(ValidationOptions options, List<? extends CodingValidationRequest> codes, String vsUrl) {
if (options == null) {
options = ValidationOptions.defaults();
}
// 1st pass: what is in the cache?
// 2nd pass: What can we do internally
// 3rd pass: hit the server
for (CodingValidationRequest t : codes) {
t.setCacheToken(txCache != null ? txCache.generateValidationToken(options, t.getCoding(), vsUrl, expParameters) : null);
if (t.getCoding().hasSystem()) {
codeSystemsUsed.add(t.getCoding().getSystem());
}
if (txCache != null) {
t.setResult(txCache.getValidation(t.getCacheToken()));
}
}
if (options.isUseClient()) {
ValueSet vs = fetchResource(ValueSet.class, vsUrl);
if (vs != null) {
for (CodingValidationRequest t : codes) {
if (!t.hasResult()) {
try {
ValueSetValidator vsc = constructValueSetCheckerSimple(options, vs);
vsc.setThrowToServer(options.isUseServer() && tcc.getClient() != null);
ValidationResult res = vsc.validateCode("Coding", t.getCoding());
if (txCache != null) {
txCache.cacheValidation(t.getCacheToken(), res, TerminologyCache.TRANSIENT);
}
t.setResult(res);
} catch (Exception e) {
}
}
}
}
}
for (CodingValidationRequest t : codes) {
if (!t.hasResult()) {
String codeKey = t.getCoding().hasVersion() ? t.getCoding().getSystem()+"|"+t.getCoding().getVersion() : t.getCoding().getSystem();
if (!options.isUseServer()) {
t.setResult(new ValidationResult(IssueSeverity.WARNING,formatMessage(I18nConstants.UNABLE_TO_VALIDATE_CODE_WITHOUT_USING_SERVER), TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS, null));
} else if (unsupportedCodeSystems.contains(codeKey)) {
t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, t.getCoding().getSystem()), TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED, null));
} else if (noTerminologyServer) {
t.setResult(new ValidationResult(IssueSeverity.ERROR,formatMessage(I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES), TerminologyServiceErrorClass.NOSERVICE, null));
}
}
}
if (expParameters == null)
throw new Error(formatMessage(I18nConstants.NO_EXPANSIONPROFILE_PROVIDED));
// for those that that failed, we try to validate on the server
Bundle batch = new Bundle();
batch.setType(BundleType.BATCH);
Set<String> systems = new HashSet<>();
for (CodingValidationRequest codingValidationRequest : codes) {
if (!codingValidationRequest.hasResult()) {
Parameters pIn = constructParameters(options, codingValidationRequest, vsUrl);
setTerminologyOptions(options, pIn);
BundleEntryComponent be = batch.addEntry();
be.setResource(pIn);
be.getRequest().setMethod(HTTPVerb.POST);
if (vsUrl != null) {
be.getRequest().setUrl("ValueSet/$validate-code");
} else {
be.getRequest().setUrl("CodeSystem/$validate-code");
}
be.setUserData("source", codingValidationRequest);
systems.add(codingValidationRequest.getCoding().getSystem());
}
}
if (batch.getEntry().size() > 0) {
txLog("$batch validate for "+batch.getEntry().size()+" codes on systems "+systems.toString());
if (tcc.getClient() == null) {
throw new FHIRException(formatMessage(I18nConstants.ATTEMPT_TO_USE_TERMINOLOGY_SERVER_WHEN_NO_TERMINOLOGY_SERVER_IS_AVAILABLE));
}
if (txLog != null) {
txLog.clearLastId();
}
Bundle resp = tcc.getClient().validateBatch(batch);
if (resp == null) {
throw new FHIRException(formatMessage(I18nConstants.TX_SERVER_NO_BATCH_RESPONSE));
}
for (int i = 0; i < batch.getEntry().size(); i++) {
CodingValidationRequest t = (CodingValidationRequest) batch.getEntry().get(i).getUserData("source");
BundleEntryComponent r = resp.getEntry().get(i);
if (r.getResource() instanceof Parameters) {
t.setResult(processValidationResult((Parameters) r.getResource()));
if (txCache != null) {
txCache.cacheValidation(t.getCacheToken(), t.getResult(), TerminologyCache.PERMANENT);
}
} else {
t.setResult(new ValidationResult(IssueSeverity.ERROR, getResponseText(r.getResource()), null).setTxLink(txLog == null ? null : txLog.getLastId()));
}
}
}
}
private String getResponseText(Resource resource) {
if (resource instanceof OperationOutcome) {
return OperationOutcomeRenderer.toString((OperationOutcome) resource);
@ -1197,6 +1297,19 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return pIn;
}
protected Parameters constructParameters(ValidationOptions options, CodingValidationRequest codingValidationRequest, String vsUrl) {
Parameters pIn = new Parameters();
pIn.addParameter().setName("coding").setValue(codingValidationRequest.getCoding());
if (options.isGuessSystem()) {
pIn.addParameter().setName("implySystem").setValue(new BooleanType(true));
}
if (vsUrl != null) {
pIn.addParameter().setName("url").setValue(new CanonicalType(vsUrl));
}
pIn.addParameter().setName("profile").setResource(expParameters);
return pIn;
}
private void updateUnsupportedCodeSystems(ValidationResult res, Coding code, String codeKey) {
if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED && !code.hasVersion()) {
unsupportedCodeSystems.add(codeKey);

View File

@ -321,12 +321,23 @@ public interface IWorkerContext {
private Coding coding;
private ValidationResult result;
private CacheToken cacheToken;
private String vs;
public CodingValidationRequest(Coding coding) {
super();
this.coding = coding;
}
public CodingValidationRequest(Coding coding, String vs) {
super();
this.coding = coding;
this.vs = vs;
}
public String getVs() {
return vs;
}
public ValidationResult getResult() {
return result;
}
@ -826,6 +837,7 @@ public interface IWorkerContext {
* @param vs
*/
public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs);
public void validateCodeBatchByRef(ValidationOptions options, List<? extends CodingValidationRequest> codes, String vsUrl);
// todo: figure these out

View File

@ -278,6 +278,28 @@ public class TerminologyCache {
}
}
public CacheToken generateValidationToken(ValidationOptions options, Coding code, String vsUrl, Parameters expParameters) {
try {
CacheToken ct = new CacheToken();
if (code.hasSystem()) {
ct.setName(code.getSystem());
ct.hasVersion = code.hasVersion();
}
else
ct.name = NAME_FOR_NO_SYSTEM;
ct.setName(vsUrl);
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
String expJS = json.composeString(expParameters);
ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+(vsUrl == null ? "null" : vsUrl)+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}";
ct.key = String.valueOf(hashJson(ct.request));
return ct;
} catch (IOException e) {
throw new Error(e);
}
}
public String extracted(JsonParser json, ValueSet vsc) throws IOException {
String s = null;
if (vsc.getExpansion().getContains().size() > 1000 || vsc.getCompose().getIncludeFirstRep().getConcept().size() > 1000) {

View File

@ -225,6 +225,7 @@ public class I18nConstants {
public static final String MEASURE_MR_GRP_POP_NO_COUNT = "MEASURE_MR_GRP_POP_NO_COUNT";
public static final String MEASURE_MR_GRP_POP_NO_SUBJECTS = "MEASURE_MR_GRP_POP_NO_SUBJECTS";
public static final String MEASURE_MR_GRP_POP_UNK_CODE = "MEASURE_MR_GRP_POP_UNK_CODE";
public static final String MEASURE_MR_GRPST_POP_UNK_CODE = "MEASURE_MR_GRPST_POP_UNK_CODE";
public static final String MEASURE_MR_GRP_UNK_CODE = "MEASURE_MR_GRP_UNK_CODE";
public static final String MEASURE_MR_M_NONE = "Measure_MR_M_None";
public static final String MEASURE_MR_M_NOTFOUND = "Measure_MR_M_NotFound";
@ -924,6 +925,7 @@ public class I18nConstants {
public static final String SD_ED_TYPE_PROFILE_WRONG_TYPE = "SD_ED_TYPE_PROFILE_WRONG_TYPE";
public static final String VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED = "VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED";
public static final String VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED = "VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED";
public static final String CS_SCT_IPS_NOT_IPS = "CS_SCT_IPS_NOT_IPS";
}

View File

@ -501,6 +501,7 @@ DUPLICATE_ID = Duplicate id value ''{0}''
TERMINOLOGY_TX_SYSTEM_NO_CODE = A code with no system has no defined meaning. A system should be provided
MEASURE_MR_GRP_POP_NO_CODE = Group should have a code that matches the group population definition in the measure
MEASURE_MR_GRP_POP_UNK_CODE = The code for this group population has no match in the measure definition
MEASURE_MR_GRPST_POP_UNK_CODE = The code for this group stratifier has no match in the measure definition
MEASURE_MR_GRP_POP_DUPL_CODE = The code for this group population is duplicated with another group
MEASURE_MR_GRP_POP_MISSING_BY_CODE = The MeasureReport does not include a population group for the population group {0}
MEASURE_MR_GRP_POP_COUNT_MISMATCH = Mismatch between count {0} and number of subjects {1}
@ -979,3 +980,4 @@ SD_ED_TYPE_WRONG_TYPE_one = The element has a type {0} which is different to the
SD_ED_TYPE_WRONG_TYPE_other = The element has a type {0} which is not in the types {1} on the base profile {2}
VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED = This include has some concepts with displays and some without - check that this is what is intended
VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED = This SNOMED-CT based include has some concepts with semantic tags (FSN terms) and some without (preferred terms) - check that this is what is intended
CS_SCT_IPS_NOT_IPS = The Snomed CT code {0} ({1}) is not a member of the IPS free set

View File

@ -212,6 +212,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
@Getter @Setter private boolean doImplicitFHIRPathStringConversion;
@Getter @Setter private HtmlInMarkdownCheck htmlInMarkdownCheck;
@Getter @Setter private boolean allowDoubleQuotesInFHIRPath;
@Getter @Setter private boolean checkIPSCodes;
@Getter @Setter private Locale locale;
@Getter @Setter private List<ImplementationGuide> igs = new ArrayList<>();
@Getter @Setter private List<String> extensionDomains = new ArrayList<>();
@ -264,6 +265,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
doImplicitFHIRPathStringConversion = other.doImplicitFHIRPathStringConversion;
htmlInMarkdownCheck = other.htmlInMarkdownCheck;
allowDoubleQuotesInFHIRPath = other.allowDoubleQuotesInFHIRPath;
checkIPSCodes = other.checkIPSCodes;
locale = other.locale;
igs.addAll(other.igs);
extensionDomains.addAll(other.extensionDomains);
@ -853,6 +855,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
validator.setAllowDoubleQuotesInFHIRPath(allowDoubleQuotesInFHIRPath);
validator.setNoUnicodeBiDiControlChars(noUnicodeBiDiControlChars);
validator.setDoImplicitFHIRPathStringConversion(doImplicitFHIRPathStringConversion);
validator.setCheckIPSCodes(checkIPSCodes);
if (format == FhirFormat.SHC) {
igLoader.loadIg(getIgs(), getBinaries(), SHCParser.CURRENT_PACKAGE, true);
}

View File

@ -141,7 +141,6 @@ public class ValidatorCli {
TimeTracker tt = new TimeTracker();
TimeTracker.Session tts = tt.start("Loading");
args = addAdditionalParamsForIpsParam(args);
setJavaSystemProxyParamsFromParams(args);
Display.displayVersion(System.out);
@ -200,6 +199,8 @@ public class ValidatorCli {
public static void main(String[] args) throws Exception {
final ValidatorCli validatorCli = new ValidatorCli(validationService);
args = addAdditionalParamsForIpsParam(args);
final CliContext cliContext = Params.loadCliContext(args);
validatorCli.readParamsAndExecuteTask(cliContext, args);
}
@ -259,6 +260,7 @@ public class ValidatorCli {
if (a.equals("-ips")) {
res.add("-version");
res.add("4.0");
res.add("-check-ips-codes");
res.add("-ig");
res.add("hl7.fhir.uv.ips#1.1.0");
res.add("-profile");
@ -266,6 +268,7 @@ public class ValidatorCli {
} else if (a.equals("-ips#")) {
res.add("-version");
res.add("4.0");
res.add("-check-ips-codes");
res.add("-ig");
res.add("hl7.fhir.uv.ips#"+a.substring(5));
res.add("-profile");
@ -273,6 +276,7 @@ public class ValidatorCli {
} else if (a.startsWith("-ips$")) {
res.add("-version");
res.add("4.0");
res.add("-check-ips-codes");
res.add("-ig");
res.add("hl7.fhir.uv.ips#current$"+a.substring(5));
res.add("-profile");

View File

@ -56,6 +56,8 @@ public class CliContext {
private HtmlInMarkdownCheck htmlInMarkdownCheck = HtmlInMarkdownCheck.WARNING;
@JsonProperty("allowDoubleQuotesInFHIRPath")
private boolean allowDoubleQuotesInFHIRPath = false;
@JsonProperty("checkIPSCodes")
private boolean checkIPSCodes;
@JsonProperty("langTransform")
private String langTransform = null;
@JsonProperty("map")
@ -315,6 +317,14 @@ public class CliContext {
this.allowDoubleQuotesInFHIRPath = allowDoubleQuotesInFHIRPath;
}
public boolean isCheckIPSCodes() {
return checkIPSCodes;
}
public void setCheckIPSCodes(boolean checkIPSCodes) {
this.checkIPSCodes = checkIPSCodes;
}
@JsonProperty("locale")
public String getLanguageCode() {
return locale;
@ -727,6 +737,7 @@ public class CliContext {
displayWarnings == that.displayWarnings &&
wantInvariantsInMessages == that.wantInvariantsInMessages &&
allowDoubleQuotesInFHIRPath == that.allowDoubleQuotesInFHIRPath &&
checkIPSCodes == that.checkIPSCodes &&
Objects.equals(extensions, that.extensions) &&
Objects.equals(map, that.map) &&
Objects.equals(htmlInMarkdownCheck, that.htmlInMarkdownCheck) &&
@ -768,7 +779,7 @@ public class CliContext {
return Objects.hash(doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT,
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars, watchMode, watchScanDelay, watchSettleTime,
htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath);
htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
}
@Override
@ -821,6 +832,7 @@ public class CliContext {
", bundleValidationRules=" + bundleValidationRules +
", htmlInMarkdownCheck=" + htmlInMarkdownCheck +
", allowDoubleQuotesInFHIRPath=" + allowDoubleQuotesInFHIRPath +
", checkIPSCodes=" + checkIPSCodes +
", watchMode=" + watchMode +
", watchSettleTime=" + watchSettleTime +
", watchScanDelay=" + watchScanDelay +

View File

@ -476,6 +476,7 @@ public class ValidationService {
validationEngine.setNoUnicodeBiDiControlChars(cliContext.isNoUnicodeBiDiControlChars());
validationEngine.setNoInvariantChecks(cliContext.isNoInvariants());
validationEngine.setDisplayWarnings(cliContext.isDisplayWarnings());
validationEngine.setCheckIPSCodes(cliContext.isCheckIPSCodes());
validationEngine.setWantInvariantInMessage(cliContext.isWantInvariantsInMessages());
validationEngine.setSecurityChecks(cliContext.isSecurityChecks());
validationEngine.setCrumbTrails(cliContext.isCrumbTrails());

View File

@ -86,6 +86,7 @@ public class Params {
public static final String SRC_LANG = "-src-lang";
public static final String TGT_LANG = "-tgt-lang";
public static final String ALLOW_DOUBLE_QUOTES = "-allow-double-quotes-in-fhirpath";
public static final String CHECK_IPS_CODES = "-check-ips-codes";
public static final String RUN_TESTS = "-run-tests";
@ -247,7 +248,9 @@ public class Params {
} else if (args[i].equals(NO_EXTENSIBLE_BINDING_WARNINGS)) {
cliContext.setNoExtensibleBindingMessages(true);
} else if (args[i].equals(ALLOW_DOUBLE_QUOTES)) {
cliContext.setAllowDoubleQuotesInFHIRPath(true);
cliContext.setAllowDoubleQuotesInFHIRPath(true);
} else if (args[i].equals(CHECK_IPS_CODES)) {
cliContext.setCheckIPSCodes(true);
} else if (args[i].equals(NO_UNICODE_BIDI_CONTROL_CHARS)) {
cliContext.setNoUnicodeBiDiControlChars(true);
} else if (args[i].equals(NO_INVARIANTS)) {

View File

@ -0,0 +1,38 @@
package org.hl7.fhir.validation.codesystem;
import java.util.List;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.Utilities;
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.validation.BaseValidator;
import org.hl7.fhir.validation.instance.utils.NodeStack;
public class CodeSystemChecker extends BaseValidator {
private boolean noDisplay = false;
private boolean hasDisplay = false;
protected List<ValidationMessage> errors;
protected CodeSystemChecker(IWorkerContext context, XVerExtensionManager xverManager, boolean debug, List<ValidationMessage> errors) {
super(context, xverManager, debug);
this.errors = errors;
}
public void checkConcept(String code, String display) {
if (Utilities.noString(display)) {
noDisplay = true;
} else {
hasDisplay = true;
}
}
public void finish(Element inc, NodeStack stack) {
hint(errors, "2023-07-21", IssueType.BUSINESSRULE, inc.line(), inc.col(), stack.getLiteralPath(), !(noDisplay && hasDisplay), I18nConstants.VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED);
}
}

View File

@ -1,76 +0,0 @@
package org.hl7.fhir.validation.codesystem;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.validation.BaseValidator;
public class CodeSystemValidator extends BaseValidator {
public CodeSystemValidator(IWorkerContext context, XVerExtensionManager xverManager) {
super(context, xverManager, false);
}
public List<ValidationMessage> validate(CodeSystem cs, boolean forBuild) {
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
// this is an invariant on CodeSystem, but the invariant is wrong in R3, and doesn't work
checkCodesUnique(cs, errors);
return errors;
}
private void checkCodesUnique(CodeSystem cs, List<ValidationMessage> errors) {
Set<String> codes = new HashSet<String>();
checkCodes(codes, cs.getConcept(), "CodeSystem.where(id = '"+cs.getId()+"')", errors);
}
private void checkCodes(Set<String> codes, List<ConceptDefinitionComponent> list, String path, List<ValidationMessage> errors) {
for (ConceptDefinitionComponent cc : list) {
String npath = path+".concept.where(code = '"+cc.getCode()+"')";
if (codes.contains(cc.getCode())) {
rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, npath, false, "Duplicate Code "+cc.getCode());
}
codes.add(cc.getCode());
checkCodes(codes, cc.getConcept(), npath, errors);
}
}
}

View File

@ -0,0 +1,109 @@
package org.hl7.fhir.validation.codesystem;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.CodingValidationRequest;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
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.validation.BaseValidator;
import org.hl7.fhir.validation.instance.utils.NodeStack;
public class CodingsObserver extends BaseValidator {
private class CodingUsage {
private NodeStack stack;
private Coding c;
public CodingUsage(NodeStack stack, Coding c) {
this.stack = stack;
this.c = c;
}
}
public CodingsObserver(IWorkerContext context, XVerExtensionManager xverManager, boolean debug) {
super(context, xverManager, debug);
this.context = context;
}
private IWorkerContext context;
private List<CodingUsage> list = new ArrayList<>();
private boolean checkIPSCodes;
public void seeCode(NodeStack stack, String system, String version, String code, String display) {
seeCode(stack, new Coding().setSystem(system).setCode(code).setVersion(version).setDisplay(display));
}
public boolean isCheckIPSCodes() {
return checkIPSCodes;
}
public void setCheckIPSCodes(boolean checkIPSCodes) {
this.checkIPSCodes = checkIPSCodes;
}
public void seeCode(NodeStack stack, CodeableConcept cc) {
for (Coding c : cc.getCoding()) {
seeCode(stack, c);
}
}
public void seeCode(NodeStack stack, Coding c) {
list.add(new CodingUsage(stack, c));
}
public void finish(List<ValidationMessage> errors, NodeStack rootStack) {
if (checkIPSCodes) {
System.out.println("");
System.out.println("Checking SCT codes for IPS");
Set<String> snomedCTCodes = new HashSet<>();
for (CodingUsage c : list) {
if ("http://snomed.info/sct".equals(c.c.getSystem()) && c.c.getCode() != null) {
snomedCTCodes.add(c.c.getCode());
}
}
if (!snomedCTCodes.isEmpty()) {
Map<String, String> nonIPSCodes = checkSCTCodes(snomedCTCodes);
if (!nonIPSCodes.isEmpty()) {
for (String s : nonIPSCodes.keySet()) {
hint(errors, "2023-07-25", IssueType.BUSINESSRULE, rootStack, false, I18nConstants.CS_SCT_IPS_NOT_IPS, s, nonIPSCodes.get(s));
}
}
}
System.out.println("Done Checking SCT codes for IPS");
}
}
private Map<String, String> checkSCTCodes(Set<String> codes) {
List<CodingValidationRequest> serverList = new ArrayList<>();
for (String s : codes) {
serverList.add(new CodingValidationRequest(new Coding("http://snomed.info/sct", s, null)));
}
context.validateCodeBatchByRef(null, serverList, "http://terminology.hl7.org/ValueSet/snomed-intl-ips");
Map<String, String> results = new HashMap<>();
for (CodingValidationRequest vr : serverList) {
if (!vr.getResult().isOk()) {
results.put(vr.getCoding().getCode(), vr.getResult().getDisplay() != null ? vr.getResult().getDisplay() : vr.getCoding().getDisplay());
}
}
return results;
}
}

View File

@ -0,0 +1,17 @@
package org.hl7.fhir.validation.codesystem;
import java.util.List;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.validation.ValidationMessage;
public class GeneralCodeSystemChecker extends CodeSystemChecker {
public GeneralCodeSystemChecker(IWorkerContext context, XVerExtensionManager xverManager, boolean debug,
List<ValidationMessage> errors) {
super(context, xverManager, debug, errors);
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,37 @@
package org.hl7.fhir.validation.codesystem;
import java.util.List;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.Utilities;
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.validation.instance.utils.NodeStack;
public class SnomedCTChecker extends CodeSystemChecker {
private boolean noTag = false;
private boolean hasTag = false;
public SnomedCTChecker(IWorkerContext context, XVerExtensionManager xverManager, boolean debug, List<ValidationMessage> errors) {
super(context, xverManager, debug, errors);
}
public void checkConcept(String code, String display) {
super.checkConcept(code, display);
if (!Utilities.noString(display)) {
boolean tagged = display.endsWith(")") && display.indexOf("(") > display.length() - 20;
if (tagged) {
hasTag = true;
} else {
noTag = true;
}
}
}
public void finish(Element inc, NodeStack stack) {
super.finish(inc, stack);
hint(errors, "2023-07-21", IssueType.BUSINESSRULE, inc.line(), inc.col(), stack.getLiteralPath(), !(noTag && hasTag), I18nConstants.VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED);
}
}

View File

@ -185,6 +185,7 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
import org.hl7.fhir.validation.codesystem.CodingsObserver;
import org.hl7.fhir.validation.instance.type.BundleValidator;
import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
import org.hl7.fhir.validation.instance.type.ConceptMapValidator;
@ -504,6 +505,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private ValidationOptions baseOptions = new ValidationOptions();
private Map<String, CanonicalResourceLookupResult> crLookups = new HashMap<>();
private boolean logProgress;
private CodingsObserver codingObserver;
public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices, XVerExtensionManager xverManager) {
super(theContext, xverManager, false);
@ -518,6 +520,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
source = Source.InstanceValidator;
fpe.setDoNotEnforceAsSingletonRule(!VersionUtilities.isR5VerOrLater(theContext.getVersion()));
fpe.setAllowDoubleQuotes(allowDoubleQuotesInFHIRPath);
codingObserver = new CodingsObserver(theContext, xverManager, debug);
}
@Override
@ -911,6 +914,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (hintAboutNonMustSupport) {
checkElementUsage(errors, element, stack);
}
codingObserver.finish(errors, stack);
errors.removeAll(messagesToRemove);
timeTracker.overall(t);
if (DEBUG_ELEMENT) {
@ -6509,10 +6513,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (lang == null) {
lang = "en"; // ubiquitious default languauge
}
codingObserver.seeCode(stack, system, version, code, display);
return context.validateCode(baseOptions.withLanguage(lang), system, version, code, checkDisplay ? display : null);
}
public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, Coding c, boolean checkMembership) {
codingObserver.seeCode(stack, c);
if (checkMembership) {
return context.validateCode(baseOptions.withLanguage(stack.getWorkingLang()).withCheckValueSetOnly(), c, valueset);
} else {
@ -6521,6 +6527,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc, boolean vsOnly) {
codingObserver.seeCode(stack, cc);
if (vsOnly) {
return context.validateCode(baseOptions.withLanguage(stack.getWorkingLang()).withCheckValueSetOnly(), cc, valueset);
} else {
@ -6657,6 +6664,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
baseOptions.setDisplayWarningMode(displayWarnings);
}
public boolean isCheckIPSCodes() {
return codingObserver.isCheckIPSCodes();
}
public void setCheckIPSCodes(boolean checkIPSCodes) {
codingObserver.setCheckIPSCodes(checkIPSCodes);
}
public InstanceValidator setForPublication(boolean forPublication) {
this.forPublication = forPublication;

View File

@ -531,7 +531,7 @@ public class MeasureValidator extends BaseValidator {
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(mrgs.getNamedChild("code"));
if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, mrgs.line(), mrgs.col(), ns.getLiteralPath(), cc != null, I18nConstants.MEASURE_MR_GRP_POP_NO_CODE)) {
MeasureGroupStratifierComponent mgs = getGroupStratifierForCode(cc, mg);
if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), mgs != null, I18nConstants.MEASURE_MR_GRP_POP_UNK_CODE)) {
if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), mgs != null, I18nConstants.MEASURE_MR_GRPST_POP_UNK_CODE)) {
if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, mrg.line(), mrg.col(), ns.getLiteralPath(), !strats.contains(mgs), I18nConstants.MEASURE_MR_GRP_POP_DUPL_CODE)) {
strats.add(mgs);
ok = validateMeasureReportGroupStratifier(hostContext, m, mgs, errors, mrgs, ns, inProgress) && ok;

View File

@ -22,76 +22,20 @@ import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.instance.type.ValueSetValidator.SystemLevelValidator;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.codesystem.CodeSystemChecker;
import org.hl7.fhir.validation.codesystem.GeneralCodeSystemChecker;
import org.hl7.fhir.validation.codesystem.SnomedCTChecker;
public class ValueSetValidator extends BaseValidator {
public class SystemLevelValidator {
protected List<ValidationMessage> errors;
protected Element inc;
protected NodeStack stack;
private boolean noDisplay = false;
private boolean hasDisplay = false;
protected SystemLevelValidator(List<ValidationMessage> errors, Element inc, NodeStack stack) {
super();
this.errors = errors;
this.inc = inc;
this.stack = stack;
}
public void checkConcept(String code, String display) {
if (Utilities.noString(display)) {
noDisplay = true;
} else {
hasDisplay = true;
}
}
public void finish() {
hint(errors, "2023-07-21", IssueType.BUSINESSRULE, inc.line(), inc.col(), stack.getLiteralPath(), !(noDisplay && hasDisplay), I18nConstants.VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED);
}
}
public class SnomedCTValidator extends SystemLevelValidator {
private boolean noTag = false;
private boolean hasTag = false;
protected SnomedCTValidator(List<ValidationMessage> errors, Element inc, NodeStack stack) {
super(errors, inc, stack);
}
public void checkConcept(String code, String display) {
super.checkConcept(code, display);
if (!Utilities.noString(display)) {
boolean tagged = display.endsWith(")") && display.indexOf("(") > display.length() - 20;
if (tagged) {
hasTag = true;
} else {
noTag = true;
}
}
}
public void finish() {
hint(errors, "2023-07-21", IssueType.BUSINESSRULE, inc.line(), inc.col(), stack.getLiteralPath(), !(noTag && hasTag), I18nConstants.VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED);
}
}
public class GeneralValidator extends SystemLevelValidator {
protected GeneralValidator(List<ValidationMessage> errors, Element inc, NodeStack stack) {
super(errors, inc, stack);
}
}
private SystemLevelValidator getSystemValidator(String system, List<ValidationMessage> errors, Element inc, NodeStack stack) {
private CodeSystemChecker getSystemValidator(String system, List<ValidationMessage> errors) {
if (system == null) {
return new GeneralValidator(errors, inc, stack);
return new GeneralCodeSystemChecker(context, xverManager, debug, errors);
}
switch (system) {
case "http://snomed.info/sct" :return new SnomedCTValidator(errors, inc, stack);
default: return new GeneralValidator(errors, inc, stack);
case "http://snomed.info/sct" :return new SnomedCTChecker(context, xverManager, debug, errors);
default: return new GeneralCodeSystemChecker(context, xverManager, debug, errors);
}
}
@ -209,7 +153,7 @@ public class ValueSetValidator extends BaseValidator {
List<Element> concepts = include.getChildrenByName("concept");
List<Element> filters = include.getChildrenByName("filter");
SystemLevelValidator slv = getSystemValidator(system, errors, include, stack);
CodeSystemChecker slv = getSystemValidator(system, errors);
if (!Utilities.noString(system)) {
boolean systemOk = true;
int cc = 0;
@ -256,7 +200,7 @@ public class ValueSetValidator extends BaseValidator {
}
cf++;
}
slv.finish();
slv.finish(include, stack);
} else {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), filters.size() == 0 && concepts.size() == 0, I18nConstants.VALUESET_NO_SYSTEM_WARNING);
}
@ -264,7 +208,7 @@ public class ValueSetValidator extends BaseValidator {
}
private boolean validateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stackInc, NodeStack stack, String system, String version, SystemLevelValidator slv) {
private boolean validateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stackInc, NodeStack stack, String system, String version, CodeSystemChecker slv) {
String code = concept.getChildValue("code");
String display = concept.getChildValue("display");
slv.checkConcept(code, display);
@ -305,7 +249,7 @@ public class ValueSetValidator extends BaseValidator {
return true;
}
private VSCodingValidationRequest prepareValidateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String system, String version, SystemLevelValidator slv) {
private VSCodingValidationRequest prepareValidateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String system, String version, CodeSystemChecker slv) {
String code = concept.getChildValue("code");
String display = concept.getChildValue("display");
slv.checkConcept(code, display);
@ -317,7 +261,7 @@ public class ValueSetValidator extends BaseValidator {
return new VSCodingValidationRequest(stack, c);
}
private boolean validateValueSetIncludeFilter(List<ValidationMessage> errors, Element filter, NodeStack push, String system, String version, SystemLevelValidator slv) {
private boolean validateValueSetIncludeFilter(List<ValidationMessage> errors, Element filter, NodeStack push, String system, String version, CodeSystemChecker slv) {
//
// String display = concept.getChildValue("display");
// slv.checkConcept(code, display);

View File

@ -20,7 +20,7 @@
<properties>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.3.17</validator_test_case_version>
<validator_test_case_version>1.3.18-SNAPSHOT</validator_test_case_version>
<jackson_version>2.15.2</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>