Validator enhancements
This commit is contained in:
parent
e436254c32
commit
552842e547
|
@ -1,10 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
import org.hl7.fhir.dstu21.hapi.validation.DefaultProfileValidationSupport;
|
||||
import org.hl7.fhir.dstu21.hapi.validation.FhirInstanceValidator;
|
||||
import org.hl7.fhir.dstu21.hapi.validation.FhirQuestionnaireResponseValidator;
|
||||
import org.hl7.fhir.dstu21.hapi.validation.IValidationSupport;
|
||||
import org.hl7.fhir.dstu21.hapi.validation.ValidationSupportChain;
|
||||
import org.hl7.fhir.dstu21.validation.IResourceValidator.BestPracticeWarningLevel;
|
||||
|
||||
/*
|
||||
|
@ -28,8 +26,6 @@ import org.hl7.fhir.dstu21.validation.IResourceValidator.BestPracticeWarningLeve
|
|||
*/
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -40,6 +36,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.jpa.dao.FhirSearchDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchDao;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu21;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
|
||||
@Configuration
|
||||
|
@ -94,15 +91,9 @@ public class BaseDstu21Config extends BaseConfig {
|
|||
return module;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Bean(autowire=Autowire.BY_NAME)
|
||||
public IValidationSupport validationSupportChainDstu21() {
|
||||
return new ValidationSupportChain(defaultProfileValidationSupport(), jpaValidationSupportDstu21());
|
||||
// return new ValidationSupportChain();
|
||||
}
|
||||
|
||||
@Bean(destroyMethod="flush")
|
||||
public DefaultProfileValidationSupport defaultProfileValidationSupport() {
|
||||
return new DefaultProfileValidationSupport();
|
||||
return new JpaValidationSupportChainDstu21();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package ca.uhn.fhir.jpa.validation;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.hl7.fhir.dstu21.hapi.validation.DefaultProfileValidationSupport;
|
||||
import org.hl7.fhir.dstu21.hapi.validation.ValidationSupportChain;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
public class JpaValidationSupportChainDstu21 extends ValidationSupportChain {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("myJpaValidationSupportDstu21")
|
||||
public ca.uhn.fhir.jpa.dao.IJpaValidationSupportDstu21 myJpaValidationSupportDstu21;
|
||||
|
||||
public JpaValidationSupportChainDstu21() {
|
||||
super();
|
||||
}
|
||||
|
||||
private DefaultProfileValidationSupport myDefaultProfileValidationSupport = new DefaultProfileValidationSupport();
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
addValidationSupport(myDefaultProfileValidationSupport);
|
||||
addValidationSupport(myJpaValidationSupportDstu21);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
myDefaultProfileValidationSupport.flush();
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.config.WebsocketDstu21Config;
|
|||
import ca.uhn.fhir.jpa.dao.dstu21.BaseJpaDstu21Test;
|
||||
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu21;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu21;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
|
||||
|
@ -46,7 +47,7 @@ public abstract class BaseResourceProviderDstu21Test extends BaseJpaDstu21Test {
|
|||
private static Server ourServer;
|
||||
protected static String ourServerBase;
|
||||
protected static RestfulServer ourRestServer;
|
||||
private static DefaultProfileValidationSupport myValidationSupport;
|
||||
private static JpaValidationSupportChainDstu21 myValidationSupport;
|
||||
|
||||
public BaseResourceProviderDstu21Test() {
|
||||
super();
|
||||
|
@ -133,7 +134,7 @@ public abstract class BaseResourceProviderDstu21Test extends BaseJpaDstu21Test {
|
|||
server.start();
|
||||
|
||||
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(subsServletHolder.getServlet().getServletConfig().getServletContext());
|
||||
myValidationSupport = wac.getBean(DefaultProfileValidationSupport.class);
|
||||
myValidationSupport = wac.getBean(JpaValidationSupportChainDstu21.class);
|
||||
|
||||
ourClient = myFhirCtx.newRestfulGenericClient(ourServerBase);
|
||||
ourClient.registerInterceptor(new LoggingInterceptor(true));
|
||||
|
|
|
@ -66,7 +66,7 @@ public class ResourceProviderQuestionnaireResponseDstu21Test extends BaseResourc
|
|||
ourClient.create().resource(qr1).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.toString(), containsString("Answer to question with linkId[link1] found of type [DecimalType] but this is invalid for question of type [string]"));
|
||||
assertThat(e.toString(), containsString("Answer value must be of type string"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ public class ResourceProviderQuestionnaireResponseDstu21Test extends BaseResourc
|
|||
ourClient.create().resource(qr1).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.toString(), containsString("Answer to question with linkId[link1] found of type [DecimalType] but this is invalid for question of type [string]"));
|
||||
assertThat(e.toString(), containsString("Answer value must be of type string"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package ca.uhn.fhirtest.config;
|
|||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
|
@ -10,10 +9,7 @@ import org.apache.commons.dbcp2.BasicDataSource;
|
|||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
@ -30,7 +26,6 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
|
||||
import ca.uhn.fhir.validation.IValidatorModule;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
|
||||
@Configuration
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package org.hl7.fhir.dstu21.validation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.dstu21.exceptions.DefinitionException;
|
||||
import org.hl7.fhir.dstu21.exceptions.FHIRException;
|
||||
import org.hl7.fhir.dstu21.formats.FormatUtilities;
|
||||
|
@ -13,6 +16,7 @@ import org.hl7.fhir.dstu21.model.Base;
|
|||
import org.hl7.fhir.dstu21.model.CodeableConcept;
|
||||
import org.hl7.fhir.dstu21.model.Coding;
|
||||
import org.hl7.fhir.dstu21.model.ContactPoint;
|
||||
import org.hl7.fhir.dstu21.model.DomainResource;
|
||||
import org.hl7.fhir.dstu21.model.ElementDefinition;
|
||||
import org.hl7.fhir.dstu21.model.ElementDefinition.ConstraintSeverity;
|
||||
import org.hl7.fhir.dstu21.model.ElementDefinition.ElementDefinitionBindingComponent;
|
||||
|
@ -28,6 +32,9 @@ import org.hl7.fhir.dstu21.model.OperationOutcome.IssueType;
|
|||
import org.hl7.fhir.dstu21.model.Period;
|
||||
import org.hl7.fhir.dstu21.model.Property;
|
||||
import org.hl7.fhir.dstu21.model.Quantity;
|
||||
import org.hl7.fhir.dstu21.model.Questionnaire;
|
||||
import org.hl7.fhir.dstu21.model.Questionnaire.QuestionnaireItemComponent;
|
||||
import org.hl7.fhir.dstu21.model.Questionnaire.QuestionnaireItemType;
|
||||
import org.hl7.fhir.dstu21.model.Range;
|
||||
import org.hl7.fhir.dstu21.model.Ratio;
|
||||
import org.hl7.fhir.dstu21.model.Reference;
|
||||
|
@ -461,7 +468,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
|
||||
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) {
|
||||
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
|
||||
ValueSet valueset = resolveBindingReference(binding.getValueSet());
|
||||
ValueSet valueset = resolveBindingReference(profile, binding.getValueSet());
|
||||
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
|
||||
try {
|
||||
CodeableConcept cc = readAsCodeableConcept(element);
|
||||
|
@ -532,7 +539,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
|
||||
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) {
|
||||
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
|
||||
ValueSet valueset = resolveBindingReference(binding.getValueSet());
|
||||
ValueSet valueset = resolveBindingReference(profile, binding.getValueSet());
|
||||
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
|
||||
try {
|
||||
Coding c = readAsCoding(element);
|
||||
|
@ -848,7 +855,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end");
|
||||
}
|
||||
|
||||
private void checkPrimitive(List<ValidationMessage> errors, String path, String type, ElementDefinition context, WrapperElement e) {
|
||||
private void checkPrimitive(List<ValidationMessage> errors, String path, String type, ElementDefinition context, WrapperElement e, StructureDefinition profile) {
|
||||
if (type.equals("uri")) {
|
||||
rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.getAttribute("value").startsWith("oid:"), "URI values cannot start with oid:");
|
||||
rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.getAttribute("value").startsWith("uuid:"), "URI values cannot start with uuid:");
|
||||
|
@ -882,13 +889,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
}
|
||||
|
||||
if (context.hasBinding()) {
|
||||
checkPrimitiveBinding(errors, path, type, context, e);
|
||||
checkPrimitiveBinding(errors, path, type, context, e, profile);
|
||||
}
|
||||
// for nothing to check
|
||||
}
|
||||
|
||||
// note that we don't check the type here; it could be string, uri or code.
|
||||
private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, WrapperElement element) {
|
||||
private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, WrapperElement element, StructureDefinition profile) {
|
||||
if (!element.hasAttribute("value"))
|
||||
return;
|
||||
|
||||
|
@ -898,7 +905,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
// firstly, resolve the value set
|
||||
ElementDefinitionBindingComponent binding = elementContext.getBinding();
|
||||
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
|
||||
ValueSet vs = resolveBindingReference(binding.getValueSet());
|
||||
ValueSet vs = resolveBindingReference(profile, binding.getValueSet());
|
||||
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found", describeReference(binding.getValueSet()))) {
|
||||
ValidationResult vr = context.validateCode(null, value, null, vs);
|
||||
if (!vr.isOk()) {
|
||||
|
@ -1157,6 +1164,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
private ElementDefinition getCriteriaForDiscriminator(String path, ElementDefinition ed, String discriminator, StructureDefinition profile) throws DefinitionException {
|
||||
List<ElementDefinition> childDefinitions = getChildMap(profile, ed);
|
||||
List<ElementDefinition> snapshot = null;
|
||||
int index;
|
||||
if (childDefinitions.isEmpty()) {
|
||||
// going to look at the type
|
||||
if (ed.getType().size() == 0)
|
||||
|
@ -1173,14 +1181,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
type = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode());
|
||||
snapshot = type.getSnapshot().getElement();
|
||||
ed = snapshot.get(0);
|
||||
index = 0;
|
||||
} else {
|
||||
snapshot = profile.getSnapshot().getElement();
|
||||
snapshot = childDefinitions;
|
||||
index = -1;
|
||||
}
|
||||
String originalPath = ed.getPath();
|
||||
String goal = originalPath + "." + discriminator;
|
||||
|
||||
int index = snapshot.indexOf(ed);
|
||||
assert(index > -1);
|
||||
index++;
|
||||
while (index < snapshot.size() && !snapshot.get(index).getPath().equals(originalPath)) {
|
||||
if (snapshot.get(index).getPath().equals(goal))
|
||||
|
@ -1344,11 +1352,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
}
|
||||
}
|
||||
|
||||
private ValueSet resolveBindingReference(Type reference) {
|
||||
private ValueSet resolveBindingReference(DomainResource ctxt, Type reference) {
|
||||
if (reference instanceof UriType)
|
||||
return context.fetchResource(ValueSet.class, ((UriType) reference).getValue().toString());
|
||||
else if (reference instanceof Reference)
|
||||
else if (reference instanceof Reference) {
|
||||
String s = ((Reference) reference).getReference();
|
||||
if (s.startsWith("#")) {
|
||||
for (Resource c : ctxt.getContained()) {
|
||||
if (c.getId().equals(s.substring(1)) && (c instanceof ValueSet))
|
||||
return (ValueSet) c;
|
||||
}
|
||||
return null;
|
||||
} else
|
||||
return context.fetchResource(ValueSet.class, ((Reference) reference).getReference());
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
@ -1490,9 +1507,239 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
validateBundle(errors, element, stack);
|
||||
if (element.getResourceType().equals("Observation"))
|
||||
validateObservation(errors, element, stack);
|
||||
if (element.getResourceType().equals("QuestionnaireResponse"))
|
||||
validateQuestionannaireResponse(errors, element, stack);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateQuestionannaireResponse(List<ValidationMessage> errors, WrapperElement element, NodeStack stack) {
|
||||
WrapperElement q = element.getNamedChild("questionnaire");
|
||||
if (hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), q != null, "No questionnaire is identified, so no validation can be performed against the base questionnaire")) {
|
||||
Questionnaire qsrc = context.fetchResource(Questionnaire.class, q.getNamedChildValue("reference"));
|
||||
if (warning(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, "The questionnaire could not be resolved, so no validation can be performed against the base questionnaire"))
|
||||
validateQuestionannaireResponseItems(qsrc, qsrc.getItem(), errors, element, stack);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, WrapperElement element, NodeStack stack) {
|
||||
String text = element.getNamedChildValue("text");
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), Utilities.noString(text) || text.equals(qItem.getText()), "If text exists, it must match the questionnaire definition for linkId "+qItem.getLinkId());
|
||||
|
||||
List<WrapperElement> answers = new ArrayList<InstanceValidator.WrapperElement>();
|
||||
element.getNamedChildren("answer", answers);
|
||||
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
|
||||
if (answers.size() > 1)
|
||||
rule(errors, IssueType.INVALID, answers.get(1).line(), answers.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response answer item with this linkId allowed");
|
||||
|
||||
for (WrapperElement answer : answers) {
|
||||
NodeStack ns = stack.push(answer, -1, null, null);
|
||||
switch (qItem.getType()) {
|
||||
case GROUP:
|
||||
rule(errors, IssueType.STRUCTURE, answer.line(), answer.col(), stack.getLiteralPath(), false, "Items of type group should not have answers");
|
||||
break;
|
||||
case DISPLAY: // nothing
|
||||
break;
|
||||
case BOOLEAN:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "boolean");
|
||||
break;
|
||||
case DECIMAL:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "decimal");
|
||||
break;
|
||||
case INTEGER:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "integer");
|
||||
break;
|
||||
case DATE:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "date");
|
||||
break;
|
||||
case DATETIME:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "dateTime");
|
||||
break;
|
||||
case INSTANT:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "instant");
|
||||
break;
|
||||
case TIME:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "time");
|
||||
break;
|
||||
case STRING:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "string");
|
||||
break;
|
||||
case TEXT:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "text");
|
||||
break;
|
||||
case URL:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "uri");
|
||||
break;
|
||||
case ATTACHMENT:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "Attachment");
|
||||
break;
|
||||
case REFERENCE:
|
||||
validateQuestionnaireResponseItemType(errors, answer, ns, "Reference");
|
||||
break;
|
||||
case QUANTITY:
|
||||
if (validateQuestionnaireResponseItemType(errors, answer, ns, "Quantity").equals("Quantity"))
|
||||
if (qItem.hasExtension("???"))
|
||||
validateQuestionnaireResponseItemQuantity(errors, answer, ns);
|
||||
break;
|
||||
case CHOICE:
|
||||
if (validateQuestionnaireResponseItemType(errors, answer, ns, "Coding").equals("Coding"))
|
||||
validateAnswerCode(errors, answer, ns, qsrc, qItem);
|
||||
break;
|
||||
case OPENCHOICE:
|
||||
if (validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "string").equals("Coding"))
|
||||
validateAnswerCode(errors, answer, ns, qsrc, qItem);
|
||||
break;
|
||||
}
|
||||
validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, answer, stack);
|
||||
}
|
||||
if (qItem.getType() == QuestionnaireItemType.GROUP)
|
||||
validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, element, stack);
|
||||
else {
|
||||
List<WrapperElement> items = new ArrayList<InstanceValidator.WrapperElement>();
|
||||
element.getNamedChildren("item", items);
|
||||
for (WrapperElement item : items) {
|
||||
NodeStack ns = stack.push(item, -1, null, null);
|
||||
rule(errors, IssueType.STRUCTURE, answers.get(0).line(), answers.get(0).col(), stack.getLiteralPath(), false, "Items not of type group should not have items");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, List<WrapperElement> elements, NodeStack stack) {
|
||||
if (elements.size() > 1)
|
||||
rule(errors, IssueType.INVALID, elements.get(1).line(), elements.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response item with this linkId allowed");
|
||||
for (WrapperElement element : elements) {
|
||||
NodeStack ns = stack.push(element, -1, null, null);
|
||||
validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns);
|
||||
}
|
||||
}
|
||||
|
||||
private int getLinkIdIndex(List<QuestionnaireItemComponent> qItems, String linkId) {
|
||||
for (int i = 0; i < qItems.size(); i++) {
|
||||
if (linkId.equals(qItems.get(i).getLinkId()))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void validateQuestionannaireResponseItems(Questionnaire qsrc, List<QuestionnaireItemComponent> qItems, List<ValidationMessage> errors, WrapperElement element, NodeStack stack) {
|
||||
List<WrapperElement> items = new ArrayList<WrapperElement>();
|
||||
element.getNamedChildren("item", items);
|
||||
// now, sort into stacks
|
||||
Map<String, List<WrapperElement>> map = new HashMap<String, List<WrapperElement>>();
|
||||
int lastIndex = -1;
|
||||
for (WrapperElement item : items) {
|
||||
String linkId = item.getNamedChildValue("linkId");
|
||||
if (rule(errors, IssueType.REQUIRED, item.line(), item.col(), stack.getLiteralPath(), !Utilities.noString(linkId), "No LinkId, so can't be validated")) {
|
||||
int index = getLinkIdIndex(qItems, linkId);
|
||||
if (index == -1) {
|
||||
QuestionnaireItemComponent qItem = findQuestionnaireItem(qsrc, linkId);
|
||||
if (qItem != null) {
|
||||
rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, "Structural Error: item is in the wrong place");
|
||||
NodeStack ns = stack.push(item, -1, null, null);
|
||||
validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns);
|
||||
}
|
||||
else
|
||||
rule(errors, IssueType.NOTFOUND, item.line(), item.col(), stack.getLiteralPath(), index > -1, "LinkId \""+linkId+"\" not found in questionnaire");
|
||||
}
|
||||
else
|
||||
{
|
||||
rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index >= lastIndex, "Structural Error: items are out of order");
|
||||
lastIndex = index;
|
||||
List<WrapperElement> mapItem = map.get(linkId);
|
||||
if (mapItem == null) {
|
||||
mapItem = new ArrayList<WrapperElement>();
|
||||
map.put(linkId, mapItem);
|
||||
}
|
||||
mapItem.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ok, now we have a list of known items, grouped by linkId. We"ve made an error for anything out of order
|
||||
for (QuestionnaireItemComponent qItem : qItems) {
|
||||
List<WrapperElement> mapItem = map.get(qItem.getLinkId());
|
||||
if (mapItem != null)
|
||||
validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack);
|
||||
else
|
||||
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateQuestionnaireResponseItemQuantity( List<ValidationMessage> errors, WrapperElement answer, NodeStack stack) {
|
||||
|
||||
}
|
||||
|
||||
private String validateQuestionnaireResponseItemType(List<ValidationMessage> errors, WrapperElement element, NodeStack stack, String... types) {
|
||||
List<WrapperElement> values = new ArrayList<WrapperElement>();
|
||||
element.getNamedChildrenWithWildcard("value[x]", values);
|
||||
if (values.size() > 0) {
|
||||
NodeStack ns = stack.push(values.get(0), -1, null, null);
|
||||
CommaSeparatedStringBuilder l = new CommaSeparatedStringBuilder();
|
||||
for (String s : types) {
|
||||
l.append(s);
|
||||
if (values.get(0).getName().equals("value"+Utilities.capitalize(s)))
|
||||
return(s);
|
||||
}
|
||||
if (types.length == 1)
|
||||
rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be of type "+types[0]);
|
||||
else
|
||||
rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be one of the types "+l.toString());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private QuestionnaireItemComponent findQuestionnaireItem(Questionnaire qSrc, String linkId) {
|
||||
return findItem(qSrc.getItem(), linkId);
|
||||
}
|
||||
|
||||
private QuestionnaireItemComponent findItem(List<QuestionnaireItemComponent> list, String linkId) {
|
||||
for (QuestionnaireItemComponent item : list) {
|
||||
if (linkId.equals(item.getLinkId()))
|
||||
return item;
|
||||
QuestionnaireItemComponent result = findItem(item.getItem(), linkId);
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void validateAnswerCode(List<ValidationMessage> errors, WrapperElement value, NodeStack stack, List<Coding> optionList) {
|
||||
String system = value.getNamedChildValue("system");
|
||||
String code = value.getNamedChildValue("code");
|
||||
boolean found = false;
|
||||
for (Coding c : optionList) {
|
||||
if (StringUtils.equals(c.getSystem(), system) && StringUtils.equals(c.getCode(), code)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rule(errors, IssueType.STRUCTURE, value.line(), value.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option");
|
||||
}
|
||||
|
||||
private void validateAnswerCode(List<ValidationMessage> errors, WrapperElement value, NodeStack stack, Questionnaire qSrc, Reference ref) {
|
||||
ValueSet vs = resolveBindingReference(qSrc, ref);
|
||||
if (warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), vs != null, "ValueSet " + describeReference(ref) + " not found")) {
|
||||
try {
|
||||
Coding c = readAsCoding(value);
|
||||
ValidationResult res = context.validateCode(c, vs);
|
||||
if (!res.isOk())
|
||||
rule(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "The value provided is not in the options value set in the questionnaire");
|
||||
} catch (Exception e) {
|
||||
warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "Error " + e.getMessage() + " validating Coding against Questionnaire Options");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAnswerCode( List<ValidationMessage> errors, WrapperElement answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem) {
|
||||
WrapperElement v = answer.getNamedChild("valueCoding");
|
||||
NodeStack ns = stack.push(v, -1, null, null);
|
||||
if (qItem.getOption().size() > 0)
|
||||
validateAnswerCode(errors, v, stack, qItem.getOption());
|
||||
else if (qItem.hasOptions())
|
||||
validateAnswerCode(errors, v, stack, qSrc, qItem.getOptions());
|
||||
else
|
||||
hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate options because no option or options are provided");
|
||||
}
|
||||
|
||||
private String tail(String path) {
|
||||
return path.substring(path.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
@ -1895,8 +2142,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), composition.getResourceType().equals("Composition"),
|
||||
"The first entry in a document must be a composition")) {
|
||||
// the composition subject and section references must resolve in the bundle
|
||||
validateBundleReference(errors, entries, composition.getNamedChild("subject"), "Composition Subject", stack.push(composition.getNamedChild("subject"), -1, null, null), fullUrl, "Composition",
|
||||
id);
|
||||
WrapperElement elem = composition.getNamedChild("subject");
|
||||
if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), elem == null, "A document composition must have a subject"))
|
||||
validateBundleReference(errors, entries, elem, "Composition Subject", stack.push(elem, -1, null, null), fullUrl, "Composition", id);
|
||||
validateSections(errors, entries, composition, stack, fullUrl, id);
|
||||
}
|
||||
}
|
||||
|
@ -1922,7 +2170,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
}
|
||||
rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), !empty(element), "Elements must have some content (@value, extensions, or children elements)");
|
||||
|
||||
checkInvariants(errors, stack.literalPath, profile, definition, null, null, resource, element);
|
||||
checkInvariants(errors, stack.getLiteralPath(), profile, definition, null, null, resource, element);
|
||||
|
||||
// get the list of direct defined children, including slices
|
||||
List<ElementDefinition> childDefinitions = getChildMap(profile, definition.getName(), definition.getPath(), definition.getNameReference());
|
||||
|
@ -2039,7 +2287,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
|
||||
if (type != null) {
|
||||
if (isPrimitiveType(type))
|
||||
checkPrimitive(errors, ei.path, type, ei.definition, ei.element);
|
||||
checkPrimitive(errors, ei.path, type, ei.definition, ei.element, profile);
|
||||
else {
|
||||
if (type.equals("Identifier"))
|
||||
checkIdentifier(errors, ei.path, ei.element, ei.definition);
|
||||
|
|
|
@ -9,6 +9,7 @@ import static org.mockito.Matchers.any;
|
|||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -23,6 +24,7 @@ import org.hl7.fhir.dstu21.hapi.validation.DefaultProfileValidationSupport;
|
|||
import org.hl7.fhir.dstu21.hapi.validation.FhirInstanceValidator;
|
||||
import org.hl7.fhir.dstu21.hapi.validation.IValidationSupport;
|
||||
import org.hl7.fhir.dstu21.hapi.validation.IValidationSupport.CodeValidationResult;
|
||||
import org.hl7.fhir.dstu21.hapi.validation.ValidationSupportChain;
|
||||
import org.hl7.fhir.dstu21.model.CodeType;
|
||||
import org.hl7.fhir.dstu21.model.Observation;
|
||||
import org.hl7.fhir.dstu21.model.Observation.ObservationStatus;
|
||||
|
@ -33,6 +35,7 @@ import org.hl7.fhir.dstu21.model.ValueSet.ConceptDefinitionComponent;
|
|||
import org.hl7.fhir.dstu21.model.ValueSet.ConceptSetComponent;
|
||||
import org.hl7.fhir.dstu21.model.ValueSet.ValueSetExpansionComponent;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
@ -62,6 +65,11 @@ public class FhirInstanceValidatorDstu21Test {
|
|||
myValidConcepts.add(theSystem + "___" + theCode);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDefaultValidationSupport.flush();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Before
|
||||
public void before() {
|
||||
|
@ -69,14 +77,17 @@ public class FhirInstanceValidatorDstu21Test {
|
|||
myVal.setValidateAgainstStandardSchema(false);
|
||||
myVal.setValidateAgainstStandardSchematron(false);
|
||||
|
||||
myInstanceVal = new FhirInstanceValidator(myDefaultValidationSupport);
|
||||
myMockSupport = mock(IValidationSupport.class);
|
||||
ValidationSupportChain validationSupport = new ValidationSupportChain(myMockSupport, myDefaultValidationSupport);
|
||||
myInstanceVal = new FhirInstanceValidator(validationSupport);
|
||||
|
||||
myVal.registerValidatorModule(myInstanceVal);
|
||||
|
||||
mySupportedCodeSystemsForExpansion = new HashMap<String, ValueSet.ValueSetExpansionComponent>();
|
||||
|
||||
myValidConcepts = new ArrayList<String>();
|
||||
|
||||
myMockSupport = mock(IValidationSupport.class);
|
||||
|
||||
when(myMockSupport.expandValueSet(any(FhirContext.class), any(ConceptSetComponent.class))).thenAnswer(new Answer<ValueSetExpansionComponent>() {
|
||||
@Override
|
||||
public ValueSetExpansionComponent answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
|
@ -100,9 +111,14 @@ public class FhirInstanceValidatorDstu21Test {
|
|||
when(myMockSupport.fetchResource(any(FhirContext.class), any(Class.class), any(String.class))).thenAnswer(new Answer<IBaseResource>() {
|
||||
@Override
|
||||
public IBaseResource answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
IBaseResource retVal = myDefaultValidationSupport.fetchResource((FhirContext) theInvocation.getArguments()[0], (Class<IBaseResource>) theInvocation.getArguments()[1],
|
||||
(String) theInvocation.getArguments()[2]);
|
||||
ourLog.info("fetchResource({}, {}) : {}", new Object[] { theInvocation.getArguments()[1], theInvocation.getArguments()[2], retVal });
|
||||
IBaseResource retVal;
|
||||
String id = (String) theInvocation.getArguments()[2];
|
||||
if ("Questionnaire/q_jon".equals(id)) {
|
||||
retVal = ourCtx.newJsonParser().parseResource(IOUtils.toString(FhirInstanceValidatorDstu21Test.class.getResourceAsStream("/q_jon.json")));
|
||||
} else {
|
||||
retVal = myDefaultValidationSupport.fetchResource((FhirContext) theInvocation.getArguments()[0], (Class<IBaseResource>) theInvocation.getArguments()[1], id);
|
||||
}
|
||||
ourLog.info("fetchResource({}, {}) : {}", new Object[] { theInvocation.getArguments()[1], id, retVal });
|
||||
return retVal;
|
||||
}
|
||||
});
|
||||
|
@ -214,7 +230,7 @@ public class FhirInstanceValidatorDstu21Test {
|
|||
ValidationResult output = null;
|
||||
int passes = 1;
|
||||
for (int i = 0; i < passes; i++) {
|
||||
ourLog.info("Pass {}", i+1);
|
||||
ourLog.info("Pass {}", i + 1);
|
||||
output = myVal.validateWithResult(input);
|
||||
}
|
||||
|
||||
|
@ -241,6 +257,16 @@ public class FhirInstanceValidatorDstu21Test {
|
|||
assertEquals("Element is unknown or does not match any slice", output.getMessages().get(0).getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateQuestionnaireResponse() throws IOException {
|
||||
String input = IOUtils.toString(FhirInstanceValidatorDstu21Test.class.getResourceAsStream("/qr_jon.xml"));
|
||||
|
||||
ValidationResult output = myVal.validateWithResult(input);
|
||||
assertEquals(output.toString(), 12, output.getMessages().size());
|
||||
ourLog.info(output.getMessages().get(0).getLocationString());
|
||||
ourLog.info(output.getMessages().get(0).getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateResourceFailingInvariant() {
|
||||
Observation input = new Observation();
|
||||
|
@ -322,7 +348,7 @@ public class FhirInstanceValidatorDstu21Test {
|
|||
addValidConcept("http://loinc.org", "1234567");
|
||||
|
||||
Observation input = new Observation();
|
||||
// input.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/devicemetricobservation");
|
||||
// input.getMeta().addProfile("http://hl7.org/fhir/StructureDefinition/devicemetricobservation");
|
||||
|
||||
input.addIdentifier().setSystem("http://acme").setValue("12345");
|
||||
input.getEncounter().setReference("http://foo.com/Encounter/9");
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,455 @@
|
|||
<QuestionnaireResponse xmlns="http://hl7.org/fhir">
|
||||
<identifier>
|
||||
<value value="1234567890"/>
|
||||
</identifier>
|
||||
<questionnaire>
|
||||
<reference value="Questionnaire/q_jon"/>
|
||||
</questionnaire>
|
||||
<status value="completed"/>
|
||||
<subject>
|
||||
<reference value="http://fhirtest.uhn.ca/baseDstu2.1/Patient/proband"/>
|
||||
</subject>
|
||||
<author>
|
||||
<reference value="http://fhirtest.uhn.ca/baseDstu2.1/Practitioner/f007"/>
|
||||
</author>
|
||||
<authored value="2016-01-08"/>
|
||||
<item>
|
||||
<linkId value="root"/>
|
||||
<item>
|
||||
<linkId value="g1"/>
|
||||
<text value="CLINICAL INFORMATION"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="1.1"/>
|
||||
</extension>
|
||||
<linkId value="1.1"/>
|
||||
<text value="Patient Clinical Information"/>
|
||||
<answer>
|
||||
<valueString value="Previous chest X-RAY was suspicious. Heavy smoker, 15 pack years."/>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="1.2"/>
|
||||
</extension>
|
||||
<linkId value="1.2"/>
|
||||
<text value="Previous Examination (Date and Modality)"/>
|
||||
<answer>
|
||||
<valueString value="Chest XR - September 23, 2015"/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<linkId value="g2"/>
|
||||
<text value="IMAGING PROCEDURE DESCRIPTION"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="2.1"/>
|
||||
</extension>
|
||||
<linkId value="2.1"/>
|
||||
<text value="Overall Image Quality:"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="2.1a"/>
|
||||
<display value="Adequate"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="2.2"/>
|
||||
</extension>
|
||||
<linkId value="2.2"/>
|
||||
<text value="Intravenous Contrast Used?"/>
|
||||
<answer>
|
||||
<valueBoolean value="false"/>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="2.3"/>
|
||||
</extension>
|
||||
<linkId value="2.3"/>
|
||||
<text value="Additional Comments"/>
|
||||
<answer>
|
||||
<valueString value="n/a."/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<linkId value="g3"/>
|
||||
<text value="FINDINGS"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3"/>
|
||||
</extension>
|
||||
<linkId value="g3.0"/>
|
||||
<text value="T Category"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.1"/>
|
||||
</extension>
|
||||
<linkId value="g3.1"/>
|
||||
<text value="Location of Main Nodule/Mass (Primary tumor, or Reference tumor)"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.1.1"/>
|
||||
</extension>
|
||||
<linkId value="q3.1.1"/>
|
||||
<text value="Location of main nodule/mass:"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="3.1.1a"/>
|
||||
<display value="Peripheral"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.1.2"/>
|
||||
</extension>
|
||||
<linkId value="3.1.2"/>
|
||||
<answer>
|
||||
<valueString value="Right Upper Lung, Posterior segment."/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.2"/>
|
||||
</extension>
|
||||
<linkId value="g3.2"/>
|
||||
<text value="Size and characteristics of main nodule/mass"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.2.1"/>
|
||||
</extension>
|
||||
<linkId value="3.2.1"/>
|
||||
<text value="Size of the nodule/mass:"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="3.2.1a"/>
|
||||
<display value="Solid nodule/mass"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
<item><!-- Measurement, image, series, etc for all 3 answer choices goes here. Conditonal. -->
|
||||
<linkId value="g3.2.1"/>
|
||||
<item><!-- Group for answer choice a -->
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen"><!-- Note that this extension is at the group level. -->
|
||||
<extension url="#question">
|
||||
<valueString value="3.2.1"/>
|
||||
</extension>
|
||||
<extension url="#answer">
|
||||
<valueCoding>
|
||||
<code value="3.2.1a"/>
|
||||
</valueCoding>
|
||||
</extension>
|
||||
</extension>
|
||||
<linkId value="g3.2.1a"/>
|
||||
<item>
|
||||
<linkId value="3.2.1a"/>
|
||||
<text value="largest dimension:"/>
|
||||
<answer>
|
||||
<valueInteger value="20"/>
|
||||
</answer>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl">
|
||||
<valueCodeableConcept>
|
||||
<coding>
|
||||
<system value="http://hl7.org/fhir/ValueSet/questionnaire-item-control"/>
|
||||
<code value="unit"/>
|
||||
</coding>
|
||||
</valueCodeableConcept>
|
||||
</extension>
|
||||
<text value="mm"/>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<linkId value="3.2.1a.image"/>
|
||||
<text value="image"/>
|
||||
<answer>
|
||||
<valueString value="32"/>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<linkId value="3.2.1a.series"/>
|
||||
<text value="series"/>
|
||||
<answer>
|
||||
<valueString value="3"/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.2.2"/>
|
||||
</extension>
|
||||
<linkId value="3.2.2"/>
|
||||
<text value="Plane in which the mass was measured:"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="3.2.2a"/>
|
||||
<display value="Axial"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.2.3"/>
|
||||
</extension>
|
||||
<linkId value="3.2.3"/>
|
||||
<answer>
|
||||
<valueString value="Solid attenuation of mass, margins spiculated."/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.3"/>
|
||||
</extension>
|
||||
<linkId value="g3.3"/>
|
||||
<text value="Structures directly involved"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.3.1"/>
|
||||
</extension>
|
||||
<linkId value="3.3.1"/>
|
||||
<text value="State if there is bronchial involvement:"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="3.3.1b"/>
|
||||
<display value="No"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.3.2"/>
|
||||
</extension>
|
||||
<linkId value="3.3.2"/>
|
||||
<text value="Is there direct involvement of any other anatomical structures?"/>
|
||||
<answer>
|
||||
<valueBoolean value="false"/>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.3.3"/>
|
||||
</extension>
|
||||
<linkId value="3.3.3"/>
|
||||
<text value="Are there additional suspicious pulmonary nodules?"/>
|
||||
<answer>
|
||||
<valueBoolean value="false"/>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="3.3.4"/>
|
||||
</extension>
|
||||
<linkId value="3.3.4"/>
|
||||
<text value="Other notable intrathoracic findings (eg lymphangitis carcinomatosis):"/>
|
||||
<answer>
|
||||
<valueString value="n/a"/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="4"/>
|
||||
</extension>
|
||||
<linkId value="4.0"/>
|
||||
<text value="N Category"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="4.1"/>
|
||||
</extension>
|
||||
<linkId value="4.1"/>
|
||||
<text value="Are there enlarged lymph nodes?"/>
|
||||
<answer>
|
||||
<valueBoolean value="false"/>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="4.2"/>
|
||||
</extension>
|
||||
<linkId value="4.2"/>
|
||||
<text value="Other notable findings:"/>
|
||||
<answer>
|
||||
<valueString value="Liner / scar atelectasis lingua, anterior segment of right upper lobe and bilateral lower lobes."/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="5"/>
|
||||
</extension>
|
||||
<linkId value="5.0"/>
|
||||
<text value="M Category (Suspicious Extrathoracic Findings (M1b))"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="5.1"/>
|
||||
</extension>
|
||||
<linkId value="5.1"/>
|
||||
<text value="Are there suspicious extrathoracic findings?"/>
|
||||
<answer>
|
||||
<valueBoolean value="true"/>
|
||||
</answer>
|
||||
<item>
|
||||
<linkId value="g5.1.yes"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen">
|
||||
<extension url="#question">
|
||||
<valueString value="5.1"/>
|
||||
</extension>
|
||||
<extension url="#answer">
|
||||
<valueBoolean value="true"/>
|
||||
</extension>
|
||||
</extension>
|
||||
<linkId value="5.1.yes"/>
|
||||
<text value="Applicable Structures and Descriptions:"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="5.1.yes.d"/>
|
||||
<display value="Other"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen">
|
||||
<extension url="#question">
|
||||
<valueString value="5.1.yes"/>
|
||||
</extension>
|
||||
<extension url="#answer">
|
||||
<valueCoding>
|
||||
<code value="5.1.yes.d"/>
|
||||
</valueCoding>
|
||||
</extension>
|
||||
</extension>
|
||||
<linkId value="5.1.yes.d.description"/>
|
||||
<text value="Description of structures:"/>
|
||||
<answer>
|
||||
<valueString value="3 cm calcified thyroid nodule on TUL left."/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="6"/>
|
||||
</extension>
|
||||
<linkId value="6.0"/>
|
||||
<text value="Additional Findings"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="6.1"/>
|
||||
</extension>
|
||||
<linkId value="6.1"/>
|
||||
<text value="Are there additional findings?"/>
|
||||
<answer>
|
||||
<valueBoolean value="true"/>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="6.2"/>
|
||||
</extension>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen">
|
||||
<extension url="#question">
|
||||
<valueString value="6.1"/>
|
||||
</extension>
|
||||
<extension url="#answer">
|
||||
<valueBoolean value="true"/>
|
||||
</extension>
|
||||
</extension>
|
||||
<linkId value="6.2"/>
|
||||
<text value="Findings and Descriptions:"/>
|
||||
<answer>
|
||||
<valueString value="Oligemic changes in right upper lobe from possible old pulmonary embolus."/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<linkId value="g7"/>
|
||||
<text value="IMPRESSIONS"/>
|
||||
<item>
|
||||
<linkId value="g7.1"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="7.1"/>
|
||||
</extension>
|
||||
<linkId value="7.1"/>
|
||||
<text value="Impression/Summary:"/>
|
||||
<answer>
|
||||
<valueString value="Spiculated mass in posterior segment of right upper lobe that has increasing size from 16 to 20 mm."/>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="7.2"/>
|
||||
</extension>
|
||||
<linkId value="g7.2"/>
|
||||
<text value="Radiologic Staging (TNM Version – 7th edition)"/>
|
||||
<item>
|
||||
<linkId value="g7.20"/>
|
||||
<text value="If this is a biopsy proven carcinoma, the preliminary radiologic stage is:"/>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="i)"/>
|
||||
</extension>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-questionControl" >
|
||||
<valueCodeableConcept>
|
||||
<coding>
|
||||
<code value="radio-button"/>
|
||||
<display value="Radio Button"/>
|
||||
</coding>
|
||||
</valueCodeableConcept>
|
||||
</extension>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation" >
|
||||
<valueCode value="horizontal"/>
|
||||
</extension>
|
||||
<linkId value="7.2i"/>
|
||||
<text value="Primary Tumour (T):"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="T1a"/>
|
||||
<display value="T1a"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="ii)"/>
|
||||
</extension>
|
||||
<linkId value="7.2ii"/>
|
||||
<text value="Regional Lymph Nodes (N):"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="N0"/>
|
||||
<display value="N0"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
</item>
|
||||
<item>
|
||||
<extension url="http://hl7.org/fhir/StructureDefinition/questionnaire-label">
|
||||
<valueString value="iii)"/>
|
||||
</extension>
|
||||
<linkId value="7.2iii"/>
|
||||
<text value="Distant Metastasis (M):"/>
|
||||
<answer>
|
||||
<valueCoding>
|
||||
<code value="M0"/>
|
||||
<display value="M0"/>
|
||||
</valueCoding>
|
||||
</answer>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
</item>
|
||||
</QuestionnaireResponse>
|
Loading…
Reference in New Issue