diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5442-validation-fetcher-fetch-by-canonical.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5442-validation-fetcher-fetch-by-canonical.yaml new file mode 100644 index 00000000000..226bb7cd153 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5442-validation-fetcher-fetch-by-canonical.yaml @@ -0,0 +1,4 @@ +--- +type: add +issue: 5442 +title: "The ValidatorResourceFetcher will now resolve canonical URL references as well as simple local references." diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java index 171e5cfeb8e..176983551fb 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java @@ -327,18 +327,29 @@ public interface IFhirResourceDao extends IDao { /** * @deprecated Use {@link #search(SearchParameterMap, RequestDetails)} instead + * @throws InvalidRequestException If a SearchParameter is not known to the server */ - IBundleProvider search(SearchParameterMap theParams); + IBundleProvider search(SearchParameterMap theParams) throws InvalidRequestException; - IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails); + /** + * * + * @throws InvalidRequestException If a SearchParameter is not known to the server + */ + IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails) + throws InvalidRequestException; + /** + * * + * @throws InvalidRequestException If a SearchParameter is not known to the server + */ IBundleProvider search( - SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse); + SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) + throws InvalidRequestException; /** * Search for IDs for processing a match URLs, etc. */ - default List searchForIds( + default List searchForIds( SearchParameterMap theParams, RequestDetails theRequest) { return searchForIds(theParams, theRequest, null); } @@ -350,7 +361,7 @@ public interface IFhirResourceDao extends IDao { * create/update, this is the resource being searched for * @since 5.5.0 */ - default List searchForIds( + default List searchForIds( SearchParameterMap theParams, RequestDetails theRequest, @Nullable IBaseResource theConditionalOperationTargetOrNull) { diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcher.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcher.java index d7fc7b40ab7..f988532989a 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcher.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcher.java @@ -24,12 +24,14 @@ import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper; -import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r5.elementmodel.Element; @@ -37,12 +39,11 @@ import org.hl7.fhir.r5.elementmodel.JsonParser; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; +import org.hl7.fhir.utilities.CanonicalPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; +import java.util.List; import java.util.Locale; public class ValidatorResourceFetcher implements IValidatorResourceFetcher { @@ -50,22 +51,19 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher { private static final Logger ourLog = LoggerFactory.getLogger(ValidatorResourceFetcher.class); private final FhirContext myFhirContext; - private final IValidationSupport myValidationSupport; private final DaoRegistry myDaoRegistry; private final VersionSpecificWorkerContextWrapper myVersionSpecificContextWrapper; public ValidatorResourceFetcher( FhirContext theFhirContext, IValidationSupport theValidationSupport, DaoRegistry theDaoRegistry) { myFhirContext = theFhirContext; - myValidationSupport = theValidationSupport; myDaoRegistry = theDaoRegistry; myVersionSpecificContextWrapper = - VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myValidationSupport); + VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(theValidationSupport); } @Override - public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl) - throws FHIRFormatError, DefinitionException, FHIRException, IOException { + public Element fetch(IResourceValidator iResourceValidator, Object appContext, String theUrl) throws FHIRException { IdType id = new IdType(theUrl); String resourceType = id.getResourceType(); IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceType); @@ -74,9 +72,13 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher { target = dao.read(id, (RequestDetails) appContext); } catch (ResourceNotFoundException e) { ourLog.info("Failed to resolve local reference: {}", theUrl); - return null; + try { + target = fetchByUrl(theUrl, dao, (RequestDetails) appContext); + } catch (ResourceNotFoundException e2) { + ourLog.info("Failed to find resource by URL: {}", theUrl); + return null; + } } - try { return new JsonParser(myVersionSpecificContextWrapper) .parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType); @@ -85,15 +87,40 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher { } } + private IBaseResource fetchByUrl(String url, IFhirResourceDao dao, RequestDetails requestDetails) + throws ResourceNotFoundException { + CanonicalPair pair = new CanonicalPair(url); + SearchParameterMap searchParameterMap = new SearchParameterMap(); + searchParameterMap.add("url", new UriParam(pair.getUrl())); + String version = pair.getVersion(); + if (version != null && !version.isEmpty()) { + searchParameterMap.add("version", new TokenParam(version)); + } + List results = null; + try { + results = dao.search(searchParameterMap, requestDetails).getAllResources(); + } catch (InvalidRequestException e) { + ourLog.info("Resource does not support 'url' or 'version' Search Parameters"); + } + if (results != null && results.size() > 0) { + if (results.size() > 1) { + ourLog.warn( + String.format("Multiple results found for URL '%s', only the first will be considered.", url)); + } + return results.get(0); + } else { + throw new ResourceNotFoundException(Msg.code(2444) + "Failed to find resource by URL: " + url); + } + } + @Override public boolean resolveURL( - IResourceValidator iResourceValidator, Object o, String s, String s1, String s2, boolean isCanonical) - throws IOException, FHIRException { + IResourceValidator iResourceValidator, Object o, String s, String s1, String s2, boolean isCanonical) { return true; } @Override - public byte[] fetchRaw(IResourceValidator iResourceValidator, String s) throws MalformedURLException, IOException { + public byte[] fetchRaw(IResourceValidator iResourceValidator, String s) throws UnsupportedOperationException { throw new UnsupportedOperationException(Msg.code(577)); } @@ -104,8 +131,7 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher { } @Override - public CanonicalResource fetchCanonicalResource(IResourceValidator iResourceValidator, String s) - throws URISyntaxException { + public CanonicalResource fetchCanonicalResource(IResourceValidator iResourceValidator, String s) { return null; } diff --git a/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java b/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java new file mode 100644 index 00000000000..9009f6137fa --- /dev/null +++ b/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java @@ -0,0 +1,65 @@ +package ca.uhn.fhir.jpa.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; +import ca.uhn.fhir.rest.server.SimpleBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.test.BaseTest; +import ca.uhn.fhir.util.ClasspathUtil; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.utils.XVerExtensionManager; +import org.hl7.fhir.validation.instance.InstanceValidator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + + +public class ValidatorResourceFetcherTest extends BaseTest { + private static final FhirContext ourCtx = FhirContext.forR4(); + private static final DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx); + private static ValidatorResourceFetcher fetcher; + private static DaoRegistry mockDaoRegistry; + private static IFhirResourceDao mockResourceDao; + + @SuppressWarnings("unchecked") + @BeforeEach + public void before() { + mockDaoRegistry = mock(DaoRegistry.class); + mockResourceDao = mock(IFhirResourceDao.class); + fetcher = new ValidatorResourceFetcher(ourCtx, myDefaultValidationSupport, mockDaoRegistry); + } + + @Test + public void checkFetchByUrl() { + // setup mocks + String resource = ClasspathUtil.loadResource("/q_jon_with_url_version.json"); + doReturn(mockResourceDao).when(mockDaoRegistry).getResourceDao("Questionnaire"); + doThrow(new ResourceNotFoundException("Not Found")).when(mockResourceDao).read(any(),any()); + doReturn(new SimpleBundleProvider(List.of( + ourCtx.newJsonParser().parseResource(resource) + ))).when(mockResourceDao).search(any(),any()); + VersionSpecificWorkerContextWrapper wrappedWorkerContext = VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(myDefaultValidationSupport); + InstanceValidator v = new InstanceValidator( + wrappedWorkerContext, + new FhirInstanceValidator.NullEvaluationContext(), + new XVerExtensionManager(null)); + RequestDetails r = new SystemRequestDetails(); + // test + Element returnedResource = fetcher.fetch(v, r,"http://www.test-url-for-questionnaire.com/Questionnaire/test-id|1.0.0"); + assertNotNull(returnedResource); + } +} diff --git a/hapi-fhir-storage/src/test/resources/q_jon_with_url_version.json b/hapi-fhir-storage/src/test/resources/q_jon_with_url_version.json new file mode 100644 index 00000000000..7ca33e5e290 --- /dev/null +++ b/hapi-fhir-storage/src/test/resources/q_jon_with_url_version.json @@ -0,0 +1,1388 @@ +{ + "resourceType": "Questionnaire", + "status": "draft", + "date": "2015-10-29", + "publisher": "Cancer Care Ontario", + "id": "test-id", + "telecom": [ + { + "system": "email", + "value": "jon.zammit@cancercare.on.ca" + } + ], + "title": "CT Lung for Cancer Staging Template - DRAFT (Short Version)", + "url": "http://www.test-url-for-questionnaire.com/Questionnaire/test-id", + "version": "1.0.0", + "item": [ + { + "text": "Patient with high suspicion of cancer as per the PEBC document (EBS #24-2) or radiological/laboratory tests suggesting cancer. Excluding: patients with synchronous lung primary, previous diagnosis of lung cancer, lung cancer surgery or therapy. New single lung primary only.", + "type": "display" + }, + { + "linkId": "root", + "type": "group", + "required": true, + "item": [ + { + "linkId": "g1", + "concept": [ + { + "system": "http://loinc.org", + "code": "55752-0", + "display": "Clinical Information" + } + ], + "text": "CLINICAL INFORMATION", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "1.1" + } + ], + "linkId": "1.1", + "text": "Patient Clinical Information", + "type": "text" + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "1.2" + } + ], + "linkId": "1.2", + "text": "Previous Examination (Date and Modality)", + "type": "text" + } + ] + }, + { + "linkId": "g2", + "text": "IMAGING PROCEDURE DESCRIPTION", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "2.1" + } + ], + "linkId": "2.1", + "text": "Overall Image Quality:", + "type": "choice", + "option": [ + { + "valueCoding": { + "code": "2.1a", + "display": "Adequate" + } + }, + { + "valueCoding": { + "code": "2.1b", + "display": "Suboptimal" + } + }, + { + "valueCoding": { + "code": "2.1c", + "display": "Non-diagnostic" + } + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "2.2" + } + ], + "linkId": "2.2", + "text": "Intravenous Contrast Used?", + "type": "boolean" + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "2.3" + } + ], + "linkId": "2.3", + "text": "Additional Comments", + "type": "text" + } + ] + }, + { + "linkId": "g3", + "text": "FINDINGS", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3" + } + ], + "linkId": "g3.0", + "text": "T Category", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.1" + } + ], + "linkId": "g3.1", + "text": "Location of Main Nodule/Mass (Primary tumor, or Reference tumor)", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.1.1" + } + ], + "linkId": "q3.1.1", + "text": "Location of main nodule/mass:", + "type": "choice", + "option": [ + { + "valueCoding": { + "code": "3.1.1a", + "display": "Peripheral" + } + }, + { + "valueCoding": { + "code": "3.1.1b", + "display": "Central*" + } + }, + { + "valueCoding": { + "code": "3.1.1c", + "display": "Both*" + } + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "q3.1.1" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.1.1b" + } + } + ] + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "q3.1.1" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.1.1c" + } + } + ] + } + ], + "linkId": "g3.1.1i", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "i)" + } + ], + "linkId": "q3.1.1i", + "text": "What is the distance of the nodule/mass to the carina?", + "type": "integer", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-item-control", + "code": "unit" + } + ] + } + } + ], + "text": "mm", + "type": "display" + } + ] + }, + { + "linkId": "3.1.1i.image", + "text": "image", + "type": "string" + }, + { + "linkId": "3.1.1i.series", + "text": "series", + "type": "string" + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.1.2" + } + ], + "linkId": "3.1.2", + "text": "State the lobe(s) and segment(s) where the nodule/mass is located", + "type": "text" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.2" + } + ], + "linkId": "g3.2", + "text": "Size and characteristics of main nodule/mass", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.2.1" + } + ], + "linkId": "3.2.1", + "text": "Size of the nodule/mass:", + "type": "choice", + "option": [ + { + "valueCoding": { + "code": "3.2.1a", + "display": "Solid nodule/mass" + } + }, + { + "valueCoding": { + "code": "3.2.1b", + "display": "Part-solid nodule/mass" + } + }, + { + "valueCoding": { + "code": "3.2.1c", + "display": "Pure Ground glass" + } + } + ], + "item": [ + { + "linkId": "g3.2.1", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.2.1" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.2.1a" + } + } + ] + } + ], + "linkId": "g3.2.1a", + "type": "group", + "item": [ + { + "linkId": "3.2.1a", + "text": "largest dimension:", + "type": "integer", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-item-control", + "code": "unit" + } + ] + } + } + ], + "text": "mm", + "type": "display" + } + ] + }, + { + "linkId": "3.2.1a.image", + "text": "image", + "type": "string" + }, + { + "linkId": "3.2.1a.series", + "text": "series", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.2.2" + } + ], + "linkId": "3.2.2", + "text": "Plane in which the mass was measured:", + "type": "choice", + "option": [ + { + "code": "3.2.2a", + "display": "Axial" + }, + { + "code": "3.2.2b", + "display": "Coronal" + }, + { + "code": "3.2.2c", + "display": "Sagittal" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.2.3" + } + ], + "linkId": "3.2.3", + "text": "Other characteristics of the main nodule/mass:", + "type": "text" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3" + } + ], + "linkId": "g3.3", + "text": "Structures directly involved", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3.1" + } + ], + "linkId": "3.3.1", + "text": "State if there is bronchial involvement:", + "type": "choice", + "option": [ + { + "code": "3.3.1a", + "display": "*Yes" + }, + { + "code": "3.3.1b", + "display": "No" + }, + { + "code": "3.3.1c", + "display": "Uncertain" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, answer i and ii.", + "type": "display" + }, + { + "linkId": "g3.3.1", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.1" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.3.1a" + } + } + ] + } + ], + "linkId": "g3.3.1.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "i)" + } + ], + "linkId": "3.3.1i", + "text": "Type of involvement:", + "type": "text" + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "ii)" + } + ], + "linkId": "3.3.1ii", + "text": "State if there is endobronchial involvement:", + "type": "choice", + "option": [ + { + "code": "3.3.1.iia", + "display": "*Yes" + }, + { + "code": "3.3.1.iib", + "display": "No" + }, + { + "code": "3.3.1.iic", + "display": "Uncertain" + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.1ii" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.3.1.iia" + } + } + ] + } + ], + "linkId": "3.3.1iic", + "text": "Describe:", + "type": "text" + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3.2" + } + ], + "linkId": "3.3.2", + "text": "Is there direct involvement of any other anatomical structures?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe all ipsilateral anatomical structures involved by the mass. If no, go to 3.3.3.", + "type": "display" + }, + { + "linkId": "g3.3.2.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.2" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "3.3.2.Yes", + "text": "Indicate and describe all ipsilateral anatomical structures involved by the mass:", + "type": "choice", + "repeats": true, + "option": [ + { + "code": "3.3.2.Yes.a", + "display": "Pleura" + }, + { + "code": "3.3.2.Yes.b", + "display": "Brachial Plexus" + }, + { + "code": "3.3.2.Yes.c", + "display": "Diaphragm" + }, + { + "code": "3.3.2.Yes.d", + "display": "Mediastinal fat" + }, + { + "code": "3.3.2.Yes.e", + "display": "Aorta" + }, + { + "code": "3.3.2.Yes.f", + "display": "Pulmonary Vessel" + }, + { + "code": "3.3.2.Yes.g", + "display": "Pericardium or Heart" + }, + { + "code": "3.3.2.Yes.h", + "display": "Mediastinal Vessels (including SVC)" + }, + { + "code": "3.3.2.Yes.i", + "display": "Trachea/carina" + }, + { + "code": "3.3.2.Yes.j", + "display": "Esophagus" + }, + { + "code": "3.3.2.Yes.k", + "display": "Trachea esophageal groove" + }, + { + "code": "3.3.2.Yes.l", + "display": "Vertebral Body" + }, + { + "code": "3.3.2.Yes.m", + "display": "Chest wall and Ribs" + }, + { + "code": "3.3.2.Yes.n", + "display": "Other structures" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.2.Yes" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.3.2.Yes.a" + } + } + ] + } + ], + "linkId": "3.3.2.Yes.a.text", + "text": "Pleura, description:", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3.3" + } + ], + "linkId": "3.3.3", + "text": "Are there additional suspicious pulmonary nodules?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe pulmonary nodules which apply. If no, go to 3.3.4.", + "type": "display" + }, + { + "linkId": "g3.3.3.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.3" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "3.3.3.Yes", + "text": "Indicate and describe pulmonary nodules which apply:", + "type": "choice", + "option": [ + { + "code": "3.3.3.Yes.a", + "display": "In the same lobe" + }, + { + "code": "3.3.3.Yes.b", + "display": "In a different lobe, same lung" + }, + { + "code": "3.3.3.Yes.c", + "display": "In the opposite lung (M1a)" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "3.3.3.Yes" + }, + { + "url": "#answer", + "valueCoding": { + "code": "3.3.3.Yes.a" + } + } + ] + } + ], + "linkId": "3.3.3.Yes.a.text", + "text": "In the same lobe, description:", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "3.3.4" + } + ], + "linkId": "3.3.4", + "text": "Other notable intrathoracic findings (eg lymphangitis carcinomatosis):", + "type": "text" + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "4" + } + ], + "linkId": "4.0", + "text": "N Category", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "4.1" + } + ], + "linkId": "4.1", + "text": "Are there enlarged lymph nodes?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe the nodes which apply. If no, go to 4.2.", + "type": "display" + }, + { + "linkId": "g4.1.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "4.1" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "4.1.yes", + "text": "Nodes and Descriptions:", + "type": "choice", + "repeats": true, + "option": [ + { + "code": "4.1.yes.a", + "display": "1 Low Cervical, supraclavicular, and sternal notch nodes" + }, + { + "code": "4.1.yes.b", + "display": "2R Upper Paratracheal (right)" + }, + { + "code": "4.1.yes.c", + "display": "2L Upper paratracheal (left)" + }, + { + "code": "4.1.yes.d", + "display": "3a Pre-vascular" + }, + { + "code": "4.1.yes.e", + "display": "3p Retrotracheal" + }, + { + "code": "4.1.yes.f", + "display": "4R Lower paratracheal (right)" + }, + { + "code": "4.1.yes.g", + "display": "4L Upper paratracheal (left)" + }, + { + "code": "4.1.yes.h", + "display": "5 Subaortic" + }, + { + "code": "4.1.yes.i", + "display": "6 Para-aortic (ascending aorta or phrenic)" + }, + { + "code": "4.1.yes.j", + "display": "7 Subcarinal" + }, + { + "code": "4.1.yes.k", + "display": "8 Paraesophageal (below carina)" + }, + { + "code": "4.1.yes.l", + "display": "9 Pulmonary ligament" + }, + { + "code": "4.1.yes.m", + "display": "10 Hilar" + }, + { + "code": "4.1.yes.n", + "display": "11 Interlobar" + }, + { + "code": "4.1.yes.o", + "display": "12 Lobar" + }, + { + "code": "4.1.yes.p", + "display": "13 Segmental" + }, + { + "code": "4.1.yes.q", + "display": "14 Subsegmental" + }, + { + "code": "4.1.yes.r", + "display": "Other Nodes (axilla, sub-diaphragmatic)" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "4.1.yes" + }, + { + "url": "#answer", + "valueCoding": { + "code": "4.1.yes.a" + } + } + ] + } + ], + "linkId": "4.1.yes.description", + "text": "Description of node:", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "4.2" + } + ], + "linkId": "4.2", + "text": "Other notable findings:", + "type": "text" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "5" + } + ], + "linkId": "5.0", + "text": "M Category (Suspicious Extrathoracic Findings (M1b))", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "5.1" + } + ], + "linkId": "5.1", + "text": "Are there suspicious extrathoracic findings?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe the structures below which apply. If no, go to 6.", + "type": "display" + }, + { + "linkId": "g5.1.yes", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "5.1" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "5.1.yes", + "text": "Applicable Structures and Descriptions:", + "type": "choice", + "repeats": true, + "option": [ + { + "code": "5.1.yes.a", + "display": "Adrenals" + }, + { + "code": "5.1.yes.b", + "display": "Liver" + }, + { + "code": "5.1.yes.c", + "display": "Bone" + }, + { + "code": "5.1.yes.d", + "display": "Other" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "5.1.yes" + }, + { + "url": "#answer", + "valueCoding": { + "code": "5.1.yes.d" + } + } + ] + } + ], + "linkId": "5.1.yes.d.description", + "text": "Description of structures:", + "type": "string" + } + ] + } + ] + } + ] + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "6" + } + ], + "linkId": "6.0", + "text": "Additional Findings", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "6.1" + } + ], + "linkId": "6.1", + "text": "Are there additional findings?", + "type": "boolean", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory", + "valueCodeableConcept": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/questionnaire-display-category", + "code": "instructions" + } + ] + } + } + ], + "text": "*If yes, indicate and describe the findings below which apply. If no, go to Impressions.", + "type": "display" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "6.2" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "6.1" + }, + { + "url": "#answer", + "valueBoolean": true + } + ] + } + ], + "linkId": "6.2", + "text": "Findings and Descriptions:", + "type": "open-choice", + "repeats": true, + "option": [ + { + "code": "6.2a", + "display": "Emphysema" + }, + { + "code": "6.2b", + "display": "Fibrosis" + }, + { + "code": "6.2c", + "display": "Coronary artery classification" + }, + { + "code": "6.2d", + "display": "Asbestos related pleural disease" + }, + { + "code": "6.2e", + "display": "Interstitial lung disease" + }, + { + "code": "6.2f", + "display": "Atherosclerosis" + }, + { + "code": "6.2g", + "display": "Pulmonary Embolism" + } + ], + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-enableWhen", + "extension": [ + { + "url": "#question", + "valueString": "6.2" + }, + { + "url": "#answer", + "valueCoding": { + "code": "6.2a" + } + } + ] + } + ], + "linkId": "6.2a", + "text": "Pulmonary, description:", + "type": "string" + } + ] + } + ] + } + ] + }, + { + "linkId": "g7", + "text": "IMPRESSIONS", + "type": "group", + "item": [ + { + "linkId": "g7.1", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "7.1" + } + ], + "linkId": "7.1", + "text": "Impression/Summary:", + "type": "text" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "7.2" + } + ], + "linkId": "g7.2", + "text": "Radiologic Staging (TNM Version – 7th edition)", + "type": "group", + "item": [ + { + "linkId": "g7.20", + "text": "If this is a biopsy proven carcinoma, the preliminary radiologic stage is:", + "type": "group", + "item": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "i)" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-questionControl", + "valueCodeableConcept": { + "coding": [ + { + "code": "radio-button", + "display": "Radio Button" + } + ] + } + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation", + "valueCode": "horizontal" + } + ], + "linkId": "7.2i", + "text": "Primary Tumour (T):", + "type": "choice", + "option": [ + { + "code": "T1a", + "display": "T1a" + }, + { + "code": "T1b", + "display": "T1b" + }, + { + "code": "T2", + "display": "T2" + }, + { + "code": "T2a", + "display": "T2a" + }, + { + "code": "T2b", + "display": "T2b" + }, + { + "code": "T3", + "display": "T3" + }, + { + "code": "T4", + "display": "T4" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "ii)" + } + ], + "linkId": "7.2ii", + "text": "Regional Lymph Nodes (N):", + "type": "choice", + "option": [ + { + "code": "NX", + "display": "NX" + }, + { + "code": "N0", + "display": "N0" + }, + { + "code": "N1", + "display": "N1" + }, + { + "code": "N2", + "display": "N2" + }, + { + "code": "N3", + "display": "N3" + } + ] + }, + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/questionnaire-label", + "valueString": "iii)" + } + ], + "linkId": "7.2iii", + "text": "Distant Metastasis (M):", + "type": "choice", + "option": [ + { + "code": "M0", + "display": "M0" + }, + { + "code": "M1", + "display": "M1" + }, + { + "code": "M1a", + "display": "M1a" + }, + { + "code": "M1b", + "display": "M1b" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +}