refactor validation options, and improve codeableConcept validation, and improve code system rendering

This commit is contained in:
Grahame Grieve 2019-12-12 10:33:22 +11:00
parent 64d4e7598d
commit ea7f3a7957
16 changed files with 387 additions and 214 deletions

View File

@ -173,7 +173,7 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
return new ValidationResult(cc);
}
}
return new ValidationResult(IssueSeverity.WARNING, "Display Name for "+code.getSystem()+"#"+code.getCode()+" should be one of '"+b.toString()+"'", cc);
return new ValidationResult(IssueSeverity.WARNING, "Display Name for "+code.getSystem()+"#"+code.getCode()+" should be one of '"+b.toString()+"' instead of "+code.getDisplay(), cc);
}
private ConceptReferenceComponent findValueSetRef(String system, String code) {

View File

@ -95,6 +95,7 @@ import org.hl7.fhir.r5.utils.formats.XLSXWriter;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
@ -254,7 +255,7 @@ public class ProfileUtilities extends TranslatingUtilities {
private ProfileKnowledgeProvider pkp;
private boolean igmode;
private boolean exception;
private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions();
private ValidationOptions terminologyServiceOptions = new ValidationOptions();
private boolean newSlicingProcessing;
private String defWebRoot;
@ -5161,12 +5162,12 @@ public class ProfileUtilities extends TranslatingUtilities {
}
public TerminologyServiceOptions getTerminologyServiceOptions() {
public ValidationOptions getTerminologyServiceOptions() {
return terminologyServiceOptions;
}
public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) {
public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
this.terminologyServiceOptions = terminologyServiceOptions;
}

View File

@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -83,6 +84,7 @@ import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
@ -512,38 +514,25 @@ public abstract class BaseWorkerContext implements IWorkerContext {
// --- validate code -------------------------------------------------------------------------------
@Override
public ValidationResult validateCode(TerminologyServiceOptions options, String system, String code, String display) {
public ValidationResult validateCode(ValidationOptions options, String system, String code, String display) {
Coding c = new Coding(system, code, display);
return validateCode(options, c, null);
}
@Override
public ValidationResult validateCode(TerminologyServiceOptions options, String system, String code, String display, ValueSet vs) {
public ValidationResult validateCode(ValidationOptions options, String system, String code, String display, ValueSet vs) {
Coding c = new Coding(system, code, display);
return validateCode(options, c, vs);
}
@Override
public ValidationResult validateCode(TerminologyServiceOptions options, String code, ValueSet vs) {
public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs) {
Coding c = new Coding(null, code, null);
return doValidateCode(options, c, vs, true);
return validateCode(options.guessSystem(), c, vs);
}
@Override
public ValidationResult validateCode(TerminologyServiceOptions options, String system, String code, String display, ConceptSetComponent vsi) {
Coding c = new Coding(system, code, display);
ValueSet vs = new ValueSet();
vs.setUrl(Utilities.makeUuidUrn());
vs.getCompose().addInclude(vsi);
return validateCode(options, c, vs);
}
@Override
public ValidationResult validateCode(TerminologyServiceOptions options, Coding code, ValueSet vs) {
return doValidateCode(options, code, vs, false);
}
public ValidationResult doValidateCode(TerminologyServiceOptions options, Coding code, ValueSet vs, boolean implySystem) {
public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs) {
CacheToken cacheToken = txCache != null ? txCache.generateValidationToken(options, code, vs) : null;
ValidationResult res = null;
if (txCache != null)
@ -551,31 +540,38 @@ public abstract class BaseWorkerContext implements IWorkerContext {
if (res != null)
return res;
// ok, first we try to validate locally
try {
ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this);
res = vsc.validateCode(code);
if (txCache != null)
txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
return res;
} catch (Exception e) {
if (options.isUseClient()) {
// ok, first we try to validate locally
try {
ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this);
res = vsc.validateCode(code);
if (txCache != null)
txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
return res;
} catch (Exception e) {
}
}
if (!options.isUseServer()) {
return new ValidationResult(IssueSeverity.WARNING, "Unable to validate code without using server", TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS);
}
// if that failed, we try to validate on the server
if (noTerminologyServer)
if (noTerminologyServer) {
return new ValidationResult(IssueSeverity.ERROR, "Error validating code: running without terminology services", TerminologyServiceErrorClass.NOSERVICE);
}
String csumm = txCache != null ? txCache.summary(code) : null;
if (txCache != null)
if (txCache != null) {
tlog("$validate "+csumm+" for "+ txCache.summary(vs));
else
} else {
tlog("$validate "+csumm+" before cache exists");
}
try {
Parameters pIn = new Parameters();
pIn.addParameter().setName("coding").setValue(code);
if (implySystem)
if (options.isGuessSystem())
pIn.addParameter().setName("implySystem").setValue(new BooleanType(true));
if (options != null)
setTerminologyOptions(options, pIn);
setTerminologyOptions(options, pIn);
res = validateOnServer(vs, pIn);
} catch (Exception e) {
res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog == null ? null : txLog.getLastId());
@ -585,27 +581,31 @@ public abstract class BaseWorkerContext implements IWorkerContext {
return res;
}
private void setTerminologyOptions(TerminologyServiceOptions options, Parameters pIn) {
if (options != null) {
if (!Utilities.noString(options.getLanguage()))
pIn.addParameter("displayLanguage", options.getLanguage());
}
private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
if (!Utilities.noString(options.getLanguage()))
pIn.addParameter("displayLanguage", options.getLanguage());
}
@Override
public ValidationResult validateCode(TerminologyServiceOptions options, CodeableConcept code, ValueSet vs) {
public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs) {
CacheToken cacheToken = txCache.generateValidationToken(options, code, vs);
ValidationResult res = txCache.getValidation(cacheToken);
if (res != null)
return res;
// ok, first we try to validate locally
try {
ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this);
res = vsc.validateCode(code);
txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
return res;
} catch (Exception e) {
if (options.isUseClient()) {
// ok, first we try to validate locally
try {
ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this);
res = vsc.validateCode(code);
txCache.cacheValidation(cacheToken, res, TerminologyCache.TRANSIENT);
return res;
} catch (Exception e) {
}
}
if (!options.isUseServer()) {
return new ValidationResult(IssueSeverity.WARNING, "Unable to validate code without using server", TerminologyServiceErrorClass.BLOCKED_BY_OPTIONS);
}
// if that failed, we try to validate on the server
@ -615,8 +615,7 @@ public abstract class BaseWorkerContext implements IWorkerContext {
try {
Parameters pIn = new Parameters();
pIn.addParameter().setName("codeableConcept").setValue(code);
if (options != null)
setTerminologyOptions(options, pIn);
setTerminologyOptions(options, pIn);
res = validateOnServer(vs, pIn);
} catch (Exception e) {
res = new ValidationResult(IssueSeverity.ERROR, e.getMessage() == null ? e.getClass().getName() : e.getMessage()).setTxLink(txLog.getLastId());

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.r5.context;
import java.util.EnumSet;
/*-
* #%L
* org.hl7.fhir.r5
@ -50,6 +52,7 @@ import org.hl7.fhir.r5.utils.INarrativeGenerator;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@ -209,17 +212,57 @@ public interface IWorkerContext {
// -- profile services ---------------------------------------------------------
/**
* @return a list of the resource names defined for this version
*/
public List<String> getResourceNames();
/**
* @return a set of the resource names defined for this version
*/
public Set<String> getResourceNamesAsSet();
/**
* @return a list of the resource and type names defined for this version
*/
public List<String> getTypeNames();
public List<StructureDefinition> allStructures(); // ensure snapshot exists...
/**
* @return a list of all structure definitions, with snapshots generated (if possible)
*/
public List<StructureDefinition> allStructures();
/**
* @return a list of all structure definitions, without trying to generate snapshots
*/
public List<StructureDefinition> getStructures();
/**
* @return a list of all conformance resources
*/
public List<MetadataResource> allConformanceResources();
/**
* Given a structure definition, generate a snapshot (or regenerate it)
* @param p
* @throws DefinitionException
* @throws FHIRException
*/
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException;
// -- Terminology services ------------------------------------------------------
/**
* Set the expansion parameters passed through the terminology server when txServer calls are made
*
* Note that the Validation Options override these when they are specified on validateCode
*/
public Parameters getExpansionParameters();
/**
* Get the expansion parameters passed through the terminology server when txServer calls are made
*
* Note that the Validation Options override these when they are specified on validateCode
*/
public void setExpansionProfile(Parameters expParameters);
// these are the terminology services used internally by the tools
@ -272,6 +315,7 @@ public interface IWorkerContext {
* @throws FHIRException
*/
public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException;
/**
* Value set expanion inside the internal expansion engine - used
* for references to supported system (see "supportsSystem") for
@ -364,68 +408,108 @@ public interface IWorkerContext {
}
/**
* Validation of a code - consult the terminology service
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param system
* @param code
* @param display
* in this case, the system will be inferred from the value set. It's an error to call this one without the value set
*
* @param options - validation options (required)
* @param code he code to validate (required)
* @param vs the applicable valueset (required)
* @return
*/
public ValidationResult validateCode(TerminologyServiceOptions options, String system, String code, String display);
public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs);
/**
* Validation of a code - consult the terminology service
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
* Also, check whether it's in the provided value set
*
* note: always return a result, with either an error or a code description, or both (e.g. known code, but not in the value set)
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param system
* @param code
* @param display
* @param options - validation options (required)
* @param system - equals Coding.system (required)
* @param code - equals Coding.code (required)
* @param display - equals Coding.display (optional)
* @return
*/
public ValidationResult validateCode(TerminologyServiceOptions options, String system, String code, String display, ValueSet vs);
public ValidationResult validateCode(TerminologyServiceOptions options, String code, ValueSet vs);
public ValidationResult validateCode(TerminologyServiceOptions options, Coding code, ValueSet vs);
public ValidationResult validateCode(TerminologyServiceOptions options, CodeableConcept code, ValueSet vs);
public ValidationResult validateCode(ValidationOptions options, String system, String code, String display);
/**
* Validation of a code - consult the terminology service
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
* Also, check whether it's in the provided value set fragment (for supported systems with no value set definition)
*
* note: always return a result, with either an error or a code description, or both (e.g. known code, but not in the value set)
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param system
* @param code
* @param display
* @param options - validation options (required)
* @param system - equals Coding.system (required)
* @param code - equals Coding.code (required)
* @param display - equals Coding.display (optional)
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(TerminologyServiceOptions options, String system, String code, String display, ConceptSetComponent vsi);
public ValidationResult validateCode(ValidationOptions options, String system, String code, String display, ValueSet vs);
/**
* returns the recommended tla for the type
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* Note that this doesn't validate binding strength (e.g. is just text allowed?)
*
* @param options - validation options (required)
* @param code - CodeableConcept to validate
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* in this case, the system will be inferred from the value set. It's an error to call this one without the value set
*
* @param options - validation options (required)
* @param code - Coding to validate
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs);
/**
* returns the recommended tla for the type (from the structure definitions)
*
* @param name
* @return
*/
public String getAbbreviation(String name);
// return a set of types that have tails
public Set<String> typeTails();
/**
* translate an OID to a URI (look through known NamingSystems)
* @param code
* @return
*/
public String oid2Uri(String code);
/**
* @return true if the contxt has a terminology caching service internally
*/
public boolean hasCache();
public interface ILoggingService {
@ -458,4 +542,5 @@ public interface IWorkerContext {
public void setUcumService(UcumService ucumService);
public String getLinkForUrl(String corePath, String s);
}

View File

@ -464,11 +464,6 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
this.questionnaire = questionnaire;
}
@Override
public Set<String> typeTails() {
return new HashSet<String>(Arrays.asList("Integer","Integer64","UnsignedInt","PositiveInt","Decimal","DateTime","Date","Time","Instant","String","Uri","Url","Canonical","Oid","Uuid","Id","Boolean","Code","Markdown","Base64Binary","Coding","CodeableConcept","Attachment","Identifier","Quantity","SampledData","Range","Period","Ratio","HumanName","Address","ContactPoint","Timing","Reference","Annotation","Signature","Meta"));
}
@Override
public List<StructureDefinition> allStructures() {
List<StructureDefinition> result = new ArrayList<StructureDefinition>();

View File

@ -27,6 +27,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -46,9 +47,9 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import com.google.gson.JsonElement;
@ -112,7 +113,7 @@ public class TerminologyCache {
load();
}
public CacheToken generateValidationToken(TerminologyServiceOptions options, Coding code, ValueSet vs) {
public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) {
CacheToken ct = new CacheToken();
if (code.hasSystem())
ct.name = getNameForSystem(code.getSystem());
@ -130,7 +131,7 @@ public class TerminologyCache {
return ct;
}
public CacheToken generateValidationToken(TerminologyServiceOptions options, CodeableConcept code, ValueSet vs) {
public CacheToken generateValidationToken(ValidationOptions options, CodeableConcept code, ValueSet vs) {
CacheToken ct = new CacheToken();
for (Coding c : code.getCoding()) {
if (c.hasSystem())

View File

@ -88,7 +88,6 @@ public class CodeSystemUtilities {
processed.add(cd.getCode());
}
}
System.out.println("children of (root): "+res);
return res;
} else {
return cs.getConcept();
@ -106,7 +105,6 @@ public class CodeSystemUtilities {
processed.add(cd.getCode());
}
}
System.out.println("children of "+context+": "+res);
return res;
} else {
return context.getConcept();
@ -131,7 +129,6 @@ public class CodeSystemUtilities {
res.add(cd);
}
}
System.out.println("non-children of "+context+": "+res);
return res;
}
}

View File

@ -22,6 +22,7 @@ package org.hl7.fhir.r5.terminologies;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -45,6 +46,8 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
public class ValueSetCheckerSimple implements ValueSetChecker {
@ -52,9 +55,9 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
private ValueSet valueset;
private IWorkerContext context;
private Map<String, ValueSetCheckerSimple> inner = new HashMap<>();
private TerminologyServiceOptions options;
private ValidationOptions options;
public ValueSetCheckerSimple(TerminologyServiceOptions options, ValueSet source, IWorkerContext context) {
public ValueSetCheckerSimple(ValidationOptions options, ValueSet source, IWorkerContext context) {
this.valueset = source;
this.context = context;
this.options = options;
@ -66,19 +69,18 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
List<String> warnings = new ArrayList<String>();
for (Coding c : code.getCoding()) {
if (!c.hasSystem())
warnings.add("Coding has no system");
warnings.add("Coding has no system - cannot validate");
CodeSystem cs = context.fetchCodeSystem(c.getSystem());
if (cs == null)
warnings.add("Unsupported system "+c.getSystem()+" - system is not specified or implicit");
else if (cs.getContent() != CodeSystemContentMode.COMPLETE)
warnings.add("Unable to resolve system "+c.getSystem()+" - system is not complete");
else {
ValidationResult res = validateCode(c, cs);
if (!res.isOk())
errors.add(res.getMessage());
else if (res.getMessage() != null)
warnings.add(res.getMessage());
ValidationResult res = null;
if (cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) {
res = context.validateCode(options.noClient(), c, null);
} else {
res = validateCode(c, cs);
}
if (!res.isOk())
errors.add(res.getMessage());
else if (res.getMessage() != null)
warnings.add(res.getMessage());
}
if (valueset != null) {
boolean ok = false;
@ -182,7 +184,7 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
return new ValidationResult(cc);
}
}
return new ValidationResult(IssueSeverity.WARNING, "Display Name for "+code.getSystem()+"#"+code.getCode()+" should be one of '"+b.toString()+"'", cc);
return new ValidationResult(IssueSeverity.WARNING, "Display Name for "+code.getSystem()+"#"+code.getCode()+" should be one of '"+b.toString()+"' instead of '"+code.getDisplay()+"'", cc);
}
private ConceptReferenceComponent findValueSetRef(String system, String code) {
@ -346,36 +348,40 @@ public class ValueSetCheckerSimple implements ValueSetChecker {
if (!system.equals(vsi.getSystem()))
return false;
if (vsi.hasFilter()) {
boolean ok = true;
for (ConceptSetFilterComponent f : vsi.getFilter())
if (!codeInFilter(system, f, code)) {
ok = false;
break;
}
if (ok)
return true;
}
CodeSystem def = context.fetchCodeSystem(system);
if (def.getContent() != CodeSystemContentMode.COMPLETE)
throw new FHIRException("Unable to resolve system "+vsi.getSystem()+" - system is not complete");
List<ConceptDefinitionComponent> list = def.getConcept();
boolean ok = validateCodeInConceptList(code, def, list);
if (ok && vsi.hasConcept()) {
for (ConceptReferenceComponent cc : vsi.getConcept())
if (cc.getCode().equals(code))
// ok, we need the code system
CodeSystem cs = context.fetchCodeSystem(system);
if (cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) {
// make up a transient value set with
ValueSet vs = new ValueSet();
vs.setUrl(Utilities.makeUuidUrn());
vs.getCompose().addInclude(vsi);
ValidationResult res = context.validateCode(options.noClient(), new Coding(system, code, null), vs);
return res.isOk();
} else {
if (vsi.hasFilter()) {
boolean ok = true;
for (ConceptSetFilterComponent f : vsi.getFilter())
if (!codeInFilter(cs, system, f, code)) {
ok = false;
break;
}
if (ok)
return true;
return false;
} else
return ok;
}
List<ConceptDefinitionComponent> list = cs.getConcept();
boolean ok = validateCodeInConceptList(code, cs, list);
if (ok && vsi.hasConcept()) {
for (ConceptReferenceComponent cc : vsi.getConcept())
if (cc.getCode().equals(code))
return true;
return false;
} else
return ok;
}
}
private boolean codeInFilter(String system, ConceptSetFilterComponent f, String code) throws FHIRException {
CodeSystem cs = context.fetchCodeSystem(system);
if (cs == null)
throw new FHIRException("Unable to evaluate filters on unknown code system '"+system+"'");
private boolean codeInFilter(CodeSystem cs, String system, ConceptSetFilterComponent f, String code) throws FHIRException {
if ("concept".equals(f.getProperty()))
return codeInConceptFilter(cs, f, code);
else {

View File

@ -29,7 +29,7 @@ import org.hl7.fhir.r5.model.ValueSet;
public interface ValueSetExpander {
public enum TerminologyServiceErrorClass {
UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED;
UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, BLOCKED_BY_OPTIONS;
public boolean isInfrastructure() {
return this == NOSERVICE || this == SERVER_ERROR || this == VALUESET_UNSUPPORTED;

View File

@ -24,6 +24,7 @@ import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import java.math.BigDecimal;
import java.util.*;
@ -153,7 +154,7 @@ public class FHIRPathEngine {
private Set<String> primitiveTypes = new HashSet<String>();
private Map<String, StructureDefinition> allTypes = new HashMap<String, StructureDefinition>();
private boolean legacyMode; // some R2 and R3 constraints assume that != is valid for emptty sets, so when running for R2/R3, this is set ot true
private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions();
private ValidationOptions terminologyServiceOptions = new ValidationOptions();
// if the fhir path expressions are allowed to use constants beyond those defined in the specification
// the application can implement them by providing a constant resolver
@ -4353,7 +4354,7 @@ public class FHIRPathEngine {
}
public TerminologyServiceOptions getTerminologyServiceOptions() {
public ValidationOptions getTerminologyServiceOptions() {
return terminologyServiceOptions;
}

View File

@ -187,6 +187,7 @@ import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
@ -1006,7 +1007,7 @@ public class NarrativeGenerator implements INarrativeGenerator {
private List<ConceptMapRenderInstructions> renderingMaps = new ArrayList<ConceptMapRenderInstructions>();
private boolean pretty;
private boolean canonicalUrlsAsLinks;
private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions();
private ValidationOptions terminologyServiceOptions = new ValidationOptions();
private boolean noSlowLookup;
private List<String> codeSystemPropList = new ArrayList<>();
@ -2773,9 +2774,11 @@ public class NarrativeGenerator implements INarrativeGenerator {
hierarchy = hierarchy || c.hasConcept();
}
CodeSystemNavigator csNav = new CodeSystemNavigator(cs);
hierarchy = hierarchy || csNav.isRestructure();
addMapHeaders(addTableHeaderRowStandard(t, hierarchy, display, true, commentS, version, deprecated, lang, properties), maps);
for (ConceptDefinitionComponent c : csNav.getConcepts(null)) {
hasExtensions = addDefineRowToTable(t, c, 0, hierarchy || csNav.isRestructure(), display, commentS, version, deprecated, maps, cs.getUrl(), cs, lang, properties, csNav) || hasExtensions;
hasExtensions = addDefineRowToTable(t, c, 0, hierarchy, display, commentS, version, deprecated, maps, cs.getUrl(), cs, lang, properties, csNav) || hasExtensions;
}
// if (langs.size() > 0) {
// Collections.sort(langs);
@ -3463,14 +3466,14 @@ public class NarrativeGenerator implements INarrativeGenerator {
return "??Lang";
}
private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int i, boolean hasHierarchy, boolean hasDisplay, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, String lang, List<PropertyComponent> properties, CodeSystemNavigator csNav) throws FHIRFormatError, DefinitionException, IOException {
private boolean addDefineRowToTable(XhtmlNode t, ConceptDefinitionComponent c, int level, boolean hasHierarchy, boolean hasDisplay, boolean comment, boolean version, boolean deprecated, List<UsedConceptMap> maps, String system, CodeSystem cs, String lang, List<PropertyComponent> properties, CodeSystemNavigator csNav) throws FHIRFormatError, DefinitionException, IOException {
boolean hasExtensions = false;
XhtmlNode tr = t.tr();
XhtmlNode td = tr.td();
if (hasHierarchy) {
td.addText(Integer.toString(i+1));
td.addText(Integer.toString(level+1));
td = tr.td();
String s = Utilities.padLeft("", '\u00A0', i*2);
String s = Utilities.padLeft("", '\u00A0', level*2);
td.addText(s);
}
td.attribute("style", "white-space:nowrap").addText(c.getCode());
@ -3481,31 +3484,7 @@ public class NarrativeGenerator implements INarrativeGenerator {
if (hasDisplay) {
td = tr.td();
if (c.hasDisplayElement()) {
if (lang == null) {
td.addText(c.getDisplay());
} else if (lang.equals("*")) {
boolean sl = false;
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue()))
sl = true;
td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDisplay());
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue())) {
td.br();
td.addText(cd.getLanguage()+": "+cd.getValue());
}
}
} else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) {
td.addText(c.getDisplay());
} else {
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) {
td.addText(cd.getValue());
}
}
}
}
renderDisplayName(c, cs, lang, td);
}
td = tr.td();
if (c != null &&
@ -3632,20 +3611,61 @@ public class NarrativeGenerator implements INarrativeGenerator {
td.i().tx("("+mapping.comp.getComment()+")");
}
}
for (ConceptDefinitionComponent cc : csNav.getOtherChildren(c)) {
List<ConceptDefinitionComponent> ocl = csNav.getOtherChildren(c);
for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) {
hasExtensions = addDefineRowToTable(t, cc, level+1, hasHierarchy, hasDisplay, comment, version, deprecated, maps, system, cs, lang, properties, csNav) || hasExtensions;
}
for (ConceptDefinitionComponent cc : ocl) {
tr = t.tr();
td = tr.td();
String s = Utilities.padLeft("", '.', i*2);
td.addText(Integer.toString(level+2));
td = tr.td();
String s = Utilities.padLeft("", '\u00A0', (level+1)*2);
td.addText(s);
a = td.ah("#"+Utilities.nmtokenize(cc.getCode()));
a.addText(c.getCode());
}
for (ConceptDefinitionComponent cc : csNav.getConcepts(c)) {
hasExtensions = addDefineRowToTable(t, cc, i+1, hasHierarchy, hasDisplay, comment, version, deprecated, maps, system, cs, lang, properties, csNav) || hasExtensions;
td.attribute("style", "white-space:nowrap");
a = td.ah("#"+cs.getId()+"-" + Utilities.nmtokenize(cc.getCode()));
a.addText(cc.getCode());
if (hasDisplay) {
td = tr.td();
renderDisplayName(c, cs, lang, td);
}
int w = 1 + (deprecated ? 1 : 0) + (comment ? 1 : 0) + (version ? 1 : 0) + maps.size();
if (properties != null) {
w = w + properties.size();
}
td = tr.td().colspan(Integer.toString(w));
}
return hasExtensions;
}
public void renderDisplayName(ConceptDefinitionComponent c, CodeSystem cs, String lang, XhtmlNode td) {
if (c.hasDisplayElement()) {
if (lang == null) {
td.addText(c.getDisplay());
} else if (lang.equals("*")) {
boolean sl = false;
for (ConceptDefinitionDesignationComponent cd : c.getDesignation())
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue()))
sl = true;
td.addText((sl ? cs.getLanguage("en")+": " : "")+c.getDisplay());
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && !c.getDisplay().equalsIgnoreCase(cd.getValue())) {
td.br();
td.addText(cd.getLanguage()+": "+cd.getValue());
}
}
} else if (lang.equals(cs.getLanguage()) || (lang.equals("en") && !cs.hasLanguage())) {
td.addText(c.getDisplay());
} else {
for (ConceptDefinitionDesignationComponent cd : c.getDesignation()) {
if (cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display") && cd.hasLanguage() && cd.getLanguage().equals(lang)) {
td.addText(cd.getValue());
}
}
}
}
}
private boolean hasMarkdownInDefinitions(CodeSystem cs) {
return ToolingExtensions.readBoolExtension(cs, "http://hl7.org/fhir/StructureDefinition/codesystem-use-markdown");
@ -4922,11 +4942,11 @@ public class NarrativeGenerator implements INarrativeGenerator {
return this;
}
public TerminologyServiceOptions getTerminologyServiceOptions() {
public ValidationOptions getTerminologyServiceOptions() {
return terminologyServiceOptions;
}
public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) {
public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
this.terminologyServiceOptions = terminologyServiceOptions;
}

View File

@ -108,6 +108,7 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
@ -228,7 +229,7 @@ public class StructureMapUtilities {
private ITransformerServices services;
private ProfileKnowledgeProvider pkp;
private Map<String, Integer> ids = new HashMap<String, Integer>();
private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions();
private ValidationOptions terminologyServiceOptions = new ValidationOptions();
public StructureMapUtilities(IWorkerContext worker, ITransformerServices services, ProfileKnowledgeProvider pkp) {
super();
@ -2969,11 +2970,11 @@ public class StructureMapUtilities {
return null;
}
public TerminologyServiceOptions getTerminologyServiceOptions() {
public ValidationOptions getTerminologyServiceOptions() {
return terminologyServiceOptions;
}
public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) {
public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
this.terminologyServiceOptions = terminologyServiceOptions;
}

View File

@ -1,5 +1,7 @@
package org.hl7.fhir.utilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
/*-
* #%L
* org.hl7.fhir.utilities
@ -20,28 +22,19 @@ package org.hl7.fhir.utilities;
* #L%
*/
public class TerminologyServiceOptions {
private String language;
/**
* This class is superceded by TerminologyValidationOptions but retained here for backwards compatibility
* @author graha
*
*/
public class TerminologyServiceOptions extends ValidationOptions {
public TerminologyServiceOptions() {
super();
}
public TerminologyServiceOptions(String language) {
super();
this.language = language;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String toJson() {
return "\"lang\":\""+language+"\"";
public TerminologyServiceOptions(String lang) {
super(lang);
}
}

View File

@ -0,0 +1,72 @@
package org.hl7.fhir.utilities.validation;
public class ValidationOptions {
private String language;
private boolean useServer = true;
private boolean useClient = true;
private boolean guessSystem = false;
public ValidationOptions() {
super();
}
public ValidationOptions(String language) {
super();
this.language = language;
}
public String getLanguage() {
return language;
}
public boolean isUseServer() {
return useServer;
}
public boolean isUseClient() {
return useClient;
}
public boolean isGuessSystem() {
return guessSystem;
}
private ValidationOptions copy() {
ValidationOptions n = new ValidationOptions(language);
n.useServer = useServer;
n.useClient = useClient;
n.guessSystem = guessSystem;
return n;
}
public ValidationOptions setLanguage(String language) {
ValidationOptions n = this.copy();
n.language = language;
return n;
}
public ValidationOptions noServer() {
ValidationOptions n = this.copy();
n.useServer = false;
return n;
}
public ValidationOptions noClient() {
ValidationOptions n = this.copy();
n.useClient = false;
return n;
}
public ValidationOptions guessSystem() {
ValidationOptions n = this.copy();
n.guessSystem = false;
return n;
}
public String toJson() {
return "\"lang\":\""+language+"\", \"useServer\":\""+Boolean.toString(useServer)+"\", \"useClient\":\""+Boolean.toString(useClient)+"\", \"guessSystem\":\""+Boolean.toString(guessSystem)+"\"";
}
}

View File

@ -136,6 +136,7 @@ import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.Utilities.DecimalStatus;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
@ -914,7 +915,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
txTime = txTime + (System.nanoTime() - t);
if (ss) {
t = System.nanoTime();
ValidationResult s = context.validateCode(new TerminologyServiceOptions(stack.workingLang), system, code, checkDisplay ? display : null);
ValidationResult s = context.validateCode(new ValidationOptions(stack.workingLang), system, code, checkDisplay ? display : null);
txTime = txTime + (System.nanoTime() - t);
if (s == null)
return true;
@ -1071,7 +1072,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) {
// ignore this since we can't validate but it doesn't matter..
} else {
ValidationResult vr = context.validateCode(new TerminologyServiceOptions(stack.workingLang), cc, valueset);
ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), cc, valueset); // we're going to validate the codings directly
if (!vr.isOk()) {
bindingsOk = false;
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
@ -1090,13 +1091,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
else if (!noExtensibleWarnings)
if (!noExtensibleWarnings)
txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code) (codes = "+ccSummary(cc)+")");
} else if (binding.getStrength() == BindingStrength.PREFERRED) {
txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set) (codes = "+ccSummary(cc)+")");
}
}
} else if (vr.getMessage()!=null) {
res = false;
txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
} else {
res = false;
@ -1109,7 +1111,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String nextCode = nextCoding.getCode();
String nextSystem = nextCoding.getSystem();
if (isNotBlank(nextCode) && isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
ValidationResult vr = context.validateCode(new TerminologyServiceOptions(stack.workingLang), nextSystem, nextCode, null);
ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), nextSystem, nextCode, null);
if (!vr.isOk()) {
txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Code {0} is not a valid code in code system {1}", nextCode, nextSystem);
}
@ -1139,7 +1141,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found by validator")) {
try {
long t = System.nanoTime();
ValidationResult vr = context.validateCode(new TerminologyServiceOptions(stack.workingLang), cc, valueset);
ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), cc, valueset);
txTime = txTime + (System.nanoTime() - t);
if (!vr.isOk()) {
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
@ -1159,7 +1161,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found by validator")) {
try {
long t = System.nanoTime();
ValidationResult vr = context.validateCode(new TerminologyServiceOptions(stack.workingLang), c, valueset);
ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), c, valueset);
txTime = txTime + (System.nanoTime() - t);
if (!vr.isOk()) {
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
@ -1179,7 +1181,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found by validator")) {
try {
long t = System.nanoTime();
ValidationResult vr = context.validateCode(new TerminologyServiceOptions(stack.workingLang), value, valueset);
ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), value, valueset);
txTime = txTime + (System.nanoTime() - t);
if (!vr.isOk()) {
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
@ -1229,7 +1231,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
long t = System.nanoTime();
ValidationResult vr = null;
if (binding.getStrength() != BindingStrength.EXAMPLE) {
vr = context.validateCode(new TerminologyServiceOptions(stack.workingLang), c, valueset);
vr = context.validateCode(new ValidationOptions(stack.workingLang), c, valueset);
}
txTime = txTime + (System.nanoTime() - t);
if (vr != null && !vr.isOk()) {
@ -1862,7 +1864,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
long t = System.nanoTime();
ValidationResult vr = null;
if (binding.getStrength() != BindingStrength.EXAMPLE) {
vr = context.validateCode(new TerminologyServiceOptions(stack.workingLang), value, vs);
vr = context.validateCode(new ValidationOptions(stack.workingLang), value, vs);
}
txTime = txTime + (System.nanoTime() - t);
if (vr != null && !vr.isOk()) {
@ -3502,7 +3504,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
}
long t = System.nanoTime();
ValidationResult res = context.validateCode(new TerminologyServiceOptions(stack.workingLang), c, vs);
ValidationResult res = context.validateCode(new ValidationOptions(stack.workingLang), c, vs);
txTime = txTime + (System.nanoTime() - t);
if (!res.isOk()) {
txRule(errors, res.getTxLink(), IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "The value provided (" + c.getSystem() + "::" + c.getCode() + ") is not in the options value set in the questionnaire");

View File

@ -17,7 +17,7 @@
<properties>
<hapi_fhir_version>4.1.0</hapi_fhir_version>
<validator_test_case_version>1.0.15-SNAPSHOT</validator_test_case_version>
<validator_test_case_version>1.0.16-SNAPSHOT</validator_test_case_version>
</properties>
<artifactId>org.hl7.fhir.core</artifactId>