Skip to content

Commit

Permalink
Issue #1107 - add support for Identifier pattern value constraints (#…
Browse files Browse the repository at this point in the history
…1115)

Signed-off-by: John T.E. Timm <johntimm@us.ibm.com>
  • Loading branch information
JohnTimm authored May 18, 2020
1 parent a8f150e commit 8e7575b
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 81 deletions.
2 changes: 1 addition & 1 deletion docs/src/pages/guides/FHIRValidationGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Given a FHIR profile (structure definition) as input, the IBM FHIR Server Profil

- Cardinality constraints (required and prohibited elements)
- Fixed value constraints (Code and Uri data types)
- Pattern value constraints (CodeableConcept daa type)
- Pattern value constraints (CodeableConcept and Identifier data types)
- Reference type constraints (FHIRPath resolve/is/conformsTo functions)
- Extension constraints (FHIRPath `conformsTo` function)
- Vocabulary constraints (FHIRPath `memberOf` function)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
OK json/profiles/fhir-ig-carin-bb/Coverage-Coverage1.json
OK json/profiles/fhir-ig-carin-bb/ExplanationOfBenefit-EOB1.json
OK json/profiles/fhir-ig-carin-bb/Organization-Org1.json
VALIDATION json/profiles/fhir-ig-carin-bb/Organization-Org45.json
OK json/profiles/fhir-ig-carin-bb/Organization-Org45.json
OK json/profiles/fhir-ig-carin-bb/Patient-Patient1.json
OK json/profiles/fhir-ig-carin-bb/PractitionerRole-PractitionerRole1.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
OK xml/profiles/fhir-ig-carin-bb/Coverage-Coverage1.xml
OK xml/profiles/fhir-ig-carin-bb/ExplanationOfBenefit-EOB1.xml
OK xml/profiles/fhir-ig-carin-bb/Organization-Org1.xml
VALIDATION xml/profiles/fhir-ig-carin-bb/Organization-Org45.xml
OK xml/profiles/fhir-ig-carin-bb/Organization-Org45.xml
OK xml/profiles/fhir-ig-carin-bb/Patient-Patient1.xml
OK xml/profiles/fhir-ig-carin-bb/PractitionerRole-PractitionerRole1.xml
12 changes: 12 additions & 0 deletions fhir-ig-carin-bb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
<artifactId>fhir-registry</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fhir-ig-us-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
Expand All @@ -31,6 +36,13 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fhir-validation</artifactId>
<version>${project.version}</version>
<scope>test</scope>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fhir-examples</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public void testBBValidation1() throws Exception {
List<Issue> issues = FHIRValidator.validator().validate(explanationOfBenefit);
issues.forEach(System.out::println);
Assert.assertEquals(countErrors(issues), 1);
Assert.assertEquals(countWarnings(issues), 8);
Assert.assertEquals(countWarnings(issues), 7);
}
}

Expand All @@ -51,7 +51,7 @@ public void testBBValidation3() throws Exception {
List<Issue> issues = FHIRValidator.validator().validate(explanationOfBenefit);
issues.forEach(System.out::println);
Assert.assertEquals(countErrors(issues), 1);
Assert.assertEquals(countWarnings(issues), 8);
Assert.assertEquals(countWarnings(issues), 7);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,64 +6,29 @@

package com.ibm.fhir.ig.carin.bb.test;

import static com.ibm.fhir.validation.util.FHIRValidationUtil.countErrors;
import static org.testng.Assert.assertEquals;

import java.io.BufferedReader;
import java.io.Reader;
import java.util.List;

import org.testng.annotations.Test;

import com.ibm.fhir.examples.ExamplesUtil;
import com.ibm.fhir.examples.Index;
import com.ibm.fhir.model.format.Format;
import com.ibm.fhir.model.parser.FHIRParser;
import com.ibm.fhir.model.resource.OperationOutcome.Issue;
import com.ibm.fhir.model.resource.Resource;
import com.ibm.fhir.model.spec.test.Expectation;
import com.ibm.fhir.validation.FHIRValidator;
import com.ibm.fhir.model.spec.test.R4ExamplesDriver;
import com.ibm.fhir.model.spec.test.SerializationProcessor;
import com.ibm.fhir.validation.test.ValidationProcessor;

public class ExamplesValidationTest {
@Test
public void testBBValidationJson() throws Exception {
try (Reader reader = ExamplesUtil.indexReader(Index.PROFILES_CARRIN_BB_JSON); BufferedReader br = new BufferedReader(reader);) {
String line = br.readLine();
while (line != null) {
String[] tokens = line.split("\\s+");
Expectation exp = Expectation.valueOf(tokens[0]);
try (Reader in = ExamplesUtil.resourceReader(tokens[1])) {
Resource resource = FHIRParser.parser(Format.JSON).parse(in);
List<Issue> issues = FHIRValidator.validator().validate(resource);
// Left for debug issues.forEach(System.out::println);

if(!exp.equals(Expectation.VALIDATION) && !exp.equals(Expectation.PROCESS)) {
assertEquals(countErrors(issues), 0);
}
}
line = br.readLine();
}
}
R4ExamplesDriver driver = new R4ExamplesDriver();
SerializationProcessor processor = new SerializationProcessor();
driver.setProcessor(processor);
driver.setValidator(new ValidationProcessor());
driver.processIndex(Index.PROFILES_CARRIN_BB_JSON);
}

@Test
public void testBBValidationXML() throws Exception {
try (Reader reader = ExamplesUtil.indexReader(Index.PROFILES_CARRIN_BB_XML); BufferedReader br = new BufferedReader(reader);) {
String line = br.readLine();
while (line != null) {
String[] tokens = line.split("\\s+");
Expectation exp = Expectation.valueOf(tokens[0]);
try (Reader in = ExamplesUtil.resourceReader(tokens[1])) {
Resource resource = FHIRParser.parser(Format.XML).parse(in);
List<Issue> issues = FHIRValidator.validator().validate(resource);
// Left for debug issues.forEach(System.out::println);

if(!exp.equals(Expectation.VALIDATION) && !exp.equals(Expectation.PROCESS)) {
assertEquals(countErrors(issues), 0);
}
}
line = br.readLine();
}
}
R4ExamplesDriver driver = new R4ExamplesDriver();
SerializationProcessor processor = new SerializationProcessor();
driver.setProcessor(processor);
driver.setValidator(new ValidationProcessor());
driver.processIndex(Index.PROFILES_CARRIN_BB_XML);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ibm.fhir.model.resource.CodeSystem;
import com.ibm.fhir.model.resource.ValueSet;
import com.ibm.fhir.model.resource.ValueSet.Expansion;
import com.ibm.fhir.model.resource.ValueSet.Expansion.Contains;
Expand Down Expand Up @@ -103,9 +102,8 @@ public Collection<FHIRPathNode> apply(EvaluationContext evaluationContext, Colle
if (!codeSetMap.isEmpty()) {
if (element.is(Code.class)) {
String system = getSystem(evaluationContext.getTree().getParent(elementNode));
String version = FHIRRegistry.getInstance().getLatestVersion(system, CodeSystem.class);
String code = element.as(Code.class).getValue();
if (contains(codeSetMap, system, version, code)) {
if (contains(codeSetMap, system, null, code)) {
return SINGLETON_TRUE;
}
} else if (element.is(Coding.class)) {
Expand Down Expand Up @@ -150,7 +148,7 @@ private Collection<FHIRPathNode> membershipCheckFailed(EvaluationContext evaluat

private boolean contains(Map<String, Set<String>> codeSetMap, Coding coding) {
String system = (coding.getSystem() != null) ? coding.getSystem().getValue() : null;
String version = (coding.getVersion() != null) ? coding.getVersion().getValue() : FHIRRegistry.getInstance().getLatestVersion(system, CodeSystem.class);
String version = (coding.getVersion() != null) ? coding.getVersion().getValue() : null;
String code = (coding.getCode() != null) ? coding.getCode().getValue() : null;
return contains(codeSetMap, system, version, code);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.ibm.fhir.model.type.ElementDefinition;
import com.ibm.fhir.model.type.ElementDefinition.Binding;
import com.ibm.fhir.model.type.ElementDefinition.Type;
import com.ibm.fhir.model.type.Identifier;
import com.ibm.fhir.model.type.Uri;
import com.ibm.fhir.model.type.code.BindingStrength;
import com.ibm.fhir.model.util.ModelSupport;
Expand Down Expand Up @@ -161,7 +162,7 @@ private String generate(Node node) {
ElementDefinition elementDefinition = node.elementDefinition;

if (hasValueConstraint(elementDefinition)) {
return generateValueConstraint(elementDefinition);
return generateValueConstraint(node);
}

if (hasReferenceTypeConstraint(elementDefinition)) {
Expand All @@ -170,7 +171,7 @@ private String generate(Node node) {

if (hasVocabularyConstraint(elementDefinition)) {
String expr = generateVocabularyConstraint(elementDefinition);
if (node.children.stream().noneMatch(child -> hasConstraint(child))) {
if (node.children.isEmpty()) {
// no constraints exist on the children of this node, the expression is complete
return expr;
}
Expand Down Expand Up @@ -219,18 +220,8 @@ private String generate(Node node) {
sb.append(".where(");
}

StringJoiner joiner = new StringJoiner(" and ");
for (Node child : node.children) {
if (isExtensionUrl(child.elementDefinition)) {
continue;
}
if (isOptional(child.elementDefinition)) {
joiner.add("(" + generate(child) + ")");
} else {
joiner.add(generate(child));
}
}
sb.append(joiner.toString());
sb.append(generate(node.children));

sb.append(")");

if (!isRepeating(elementDefinition) || isSlice(elementDefinition)) {
Expand All @@ -250,16 +241,32 @@ private String generate(Node node) {
return sb.toString();
}

private String generate(List<Node> nodes) {
StringJoiner joiner = new StringJoiner(" and ");
for (Node node : nodes) {
if (isExtensionUrl(node.elementDefinition)) {
continue;
}
if (isOptional(node.elementDefinition)) {
joiner.add("(" + generate(node) + ")");
} else {
joiner.add(generate(node));
}
}
return joiner.toString();
}

private String generateExtensionConstraint(ElementDefinition elementDefinition) {
StringBuilder sb = new StringBuilder();

Type type = getTypes(elementDefinition).get(0);
String profile = getProfiles(type).get(0);

sb.append("extension('").append(profile).append("').count()");

Integer min = elementDefinition.getMin().getValue();
String max = elementDefinition.getMax().getValue();

sb.append("extension('").append(profile).append("').count()");
if ("*".equals(max)) {
sb.append(" >= ").append(min);
} else if ("1".equals(max)) {
Expand Down Expand Up @@ -293,9 +300,11 @@ private String generateFixedValueConstraint(ElementDefinition elementDefinition)
return sb.toString();
}

private String generatePatternValueConstraint(ElementDefinition elementDefinition) {
private String generatePatternValueConstraint(Node node) {
StringBuilder sb = new StringBuilder();

ElementDefinition elementDefinition = node.elementDefinition;

String identifier = getIdentifier(elementDefinition);
sb.append(identifier);

Expand All @@ -304,13 +313,38 @@ private String generatePatternValueConstraint(ElementDefinition elementDefinitio
CodeableConcept codeableConcept = pattern.as(CodeableConcept.class);
Coding coding = codeableConcept.getCoding().get(0);
String system = (coding.getSystem() != null) ? coding.getSystem().getValue() : null;

sb.append(".where(coding.where(");

if (system != null) {
sb.append("system = '").append(system).append("' and ");
}
sb.append("code = '")
.append(coding.getCode().getValue())
.append("').exists()).exists()");

sb.append("code = '").append(coding.getCode().getValue()).append("').exists()).exists()");
} else if (pattern.is(Identifier.class)) {
Identifier _identifier = pattern.as(Identifier.class);
String system = _identifier.getSystem().getValue();

sb.append(".where(system = '").append(system).append("').count()");

Integer min = elementDefinition.getMin().getValue();
String max = elementDefinition.getMax().getValue();

if ("*".equals(max)) {
sb.append(" >= ").append(min);
} else if ("1".equals(max)) {
if (min == 0) {
sb.append(" <= 1");
} else {
sb.append(" = 1");
}
} else {
sb.append(" >= ").append(min).append(" and ").append(identifier).append(".where(system = '").append(system).append("').count() <= ").append(max);
}

if (!node.children.isEmpty()) {
sb.append(" and (").append(identifier).append(".where(system = '").append(system).append("').exists()").append(" implies (").append(identifier).append(".where(system = '").append(system).append("' and ").append(generate(node.children)).append(")))");
}
}

return sb.toString();
Expand Down Expand Up @@ -358,8 +392,8 @@ private String generateReferenceTypeConstraint(ElementDefinition elementDefiniti
return sb.toString();
}

private String generateValueConstraint(ElementDefinition elementDefinition) {
return hasFixedValueConstraint(elementDefinition) ? generateFixedValueConstraint(elementDefinition) : generatePatternValueConstraint(elementDefinition);
private String generateValueConstraint(Node node) {
return hasFixedValueConstraint(node.elementDefinition) ? generateFixedValueConstraint(node.elementDefinition) : generatePatternValueConstraint(node);
}

private String generateVocabularyConstraint(ElementDefinition elementDefinition) {
Expand Down Expand Up @@ -494,9 +528,13 @@ private boolean hasFixedValueConstraint(ElementDefinition elementDefinition) {
}

private boolean hasPatternValueConstraint(ElementDefinition elementDefinition) {
return (elementDefinition.getPattern() instanceof CodeableConcept) &&
(elementDefinition.getPattern().as(CodeableConcept.class).getCoding().stream()
.allMatch(coding -> (coding.getCode() != null && coding.getCode().getValue() != null)));
Element pattern = elementDefinition.getPattern();
return ((pattern instanceof CodeableConcept) &&
(pattern.as(CodeableConcept.class).getCoding().stream()
.allMatch(coding -> (coding.getCode() != null && coding.getCode().getValue() != null)))) ||
((pattern instanceof Identifier) &&
(pattern.as(Identifier.class).getSystem() != null) &&
(pattern.as(Identifier.class).getSystem().getValue() != null));
}

private boolean hasReferenceTypeConstraint(ElementDefinition elementDefinition) {
Expand Down

0 comments on commit 8e7575b

Please sign in to comment.