Skip to content

Commit 0b08755

Browse files
authored
Merge pull request #67 from mcode/extract-radiation-procedures
mCODE Radiation Procedure Extractor
2 parents 7ae1ae0 + afc7d75 commit 0b08755

8 files changed

+356
-46
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
const path = require('path');
2+
const { Extractor } = require('./Extractor');
3+
const { FHIRProcedureExtractor } = require('./FHIRProcedureExtractor');
4+
// const { getBundleEntriesByResourceType } = require('../helpers/fhirUtils');
5+
const { checkCodeInVs } = require('../helpers/valueSetUtils');
6+
const logger = require('../helpers/logger');
7+
8+
function getMCODERadiationProcedures(fhirProcedures) {
9+
const radiationProcedureVSFilepath = path.resolve(__dirname, '..', 'helpers', 'valueSets', 'ValueSet-mcode-cancer-related-radiation-procedure-vs.json');
10+
return fhirProcedures.filter((procedure) => {
11+
const coding = procedure.resource.code ? procedure.resource.code.coding : [];
12+
// NOTE: Update when checkCodeInVS checks code and system (might be able to pass in the full Coding)
13+
return coding.some((c) => checkCodeInVs(c.code, radiationProcedureVSFilepath));
14+
});
15+
}
16+
17+
class MCODERadiationProcedureExtractor extends Extractor {
18+
constructor({ baseFhirUrl, requestHeaders }) {
19+
super({ baseFhirUrl, requestHeaders });
20+
this.fhirProcedureExtractor = new FHIRProcedureExtractor({ baseFhirUrl, requestHeaders });
21+
}
22+
23+
updateRequestHeaders(newHeaders) {
24+
this.fhirProcedureExtractor.updateRequestHeaders(newHeaders);
25+
}
26+
27+
async getFHIRProcedures(mrn, context) {
28+
// NOTE: This is commented out while we discuss the future of Context moving forward
29+
// const proceduresInContext = getBundleEntriesByResourceType(context, 'Procedure', {});
30+
// if (proceduresInContext.length !== 0) {
31+
// logger.debug('Procedure resources found in context.');
32+
// return proceduresInContext;
33+
// }
34+
35+
logger.debug('Getting procedures available for patient');
36+
const procedureBundle = await this.fhirProcedureExtractor.get({ mrn, context });
37+
38+
logger.debug(`Found ${procedureBundle.entry.length} result(s) in Procedure search`);
39+
return procedureBundle.entry;
40+
}
41+
42+
async get({ mrn, context }) {
43+
const fhirProcedures = await this.getFHIRProcedures(mrn, context);
44+
45+
// Filter to only include procedures that are from MCODE radiation procedure VS
46+
const radiationProcedures = getMCODERadiationProcedures(fhirProcedures);
47+
48+
return {
49+
resourceType: 'Bundle',
50+
type: 'collection',
51+
entry: radiationProcedures,
52+
};
53+
}
54+
}
55+
56+
module.exports = {
57+
MCODERadiationProcedureExtractor,
58+
};

src/extractors/MCODESurgicalProcedureExtractor.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const path = require('path');
22
const { Extractor } = require('./Extractor');
33
const { FHIRProcedureExtractor } = require('./FHIRProcedureExtractor');
4-
const { getBundleEntriesByResourceType } = require('../helpers/fhirUtils');
4+
// const { getBundleEntriesByResourceType } = require('../helpers/fhirUtils');
55
const { checkCodeInVs } = require('../helpers/valueSetUtils');
66
const logger = require('../helpers/logger');
77

@@ -25,11 +25,12 @@ class MCODESurgicalProcedureExtractor extends Extractor {
2525
}
2626

2727
async getFHIRProcedures(mrn, context) {
28-
const proceduresInContext = getBundleEntriesByResourceType(context, 'Procedure', {});
29-
if (proceduresInContext.length !== 0) {
30-
logger.debug('Procedure resources found in context.');
31-
return proceduresInContext;
32-
}
28+
// NOTE: This is commented out while we discuss the future of Context moving forward
29+
// const proceduresInContext = getBundleEntriesByResourceType(context, 'Procedure', {});
30+
// if (proceduresInContext.length !== 0) {
31+
// logger.debug('Procedure resources found in context.');
32+
// return proceduresInContext;
33+
// }
3334

3435
logger.debug('Getting procedures available for patient');
3536
const procedureBundle = await this.fhirProcedureExtractor.get({ mrn, context });

src/extractors/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const { FHIRMedicationStatementExtractor } = require('./FHIRMedicationStatementE
1818
const { FHIRObservationExtractor } = require('./FHIRObservationExtractor');
1919
const { FHIRPatientExtractor } = require('./FHIRPatientExtractor');
2020
const { FHIRProcedureExtractor } = require('./FHIRProcedureExtractor');
21+
const { MCODERadiationProcedureExtractor } = require('./MCODERadiationProcedureExtractor');
2122
const { MCODESurgicalProcedureExtractor } = require('./MCODESurgicalProcedureExtractor');
2223

2324
module.exports = {
@@ -41,5 +42,6 @@ module.exports = {
4142
FHIRObservationExtractor,
4243
FHIRPatientExtractor,
4344
FHIRProcedureExtractor,
45+
MCODERadiationProcedureExtractor,
4446
MCODESurgicalProcedureExtractor,
4547
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"resourceType": "ValueSet",
3+
"id": "mcode-radiation-procedure-vs",
4+
"text": {
5+
"status": "generated",
6+
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><h2>Radiation Procedure Value Set</h2><div><p>Codes describing radiation therapy procedures. The value set includes a limited set of radiation modality codes from SNOMED CT, however, ICD-10-PCS code from Section D (Radiation Therapy) and appropriate CPT radiation procedure codes are also considered compliant. CPT codes are not explicitly included due to licensing restrictions. ICD-10-PCS codes are not included explicitly because they are not currently supported by the FHIR IG Publishing tool.</p>\n<p>Conformance note: If an ICD-10-PCS code is used, and a semantically equivalent SNOMED CT or CPT code is available, the resulting Procedure instance will not be compliant with <a href=\"http://hl7.org/fhir/us/core/index.html\">US Core Profiles</a>.</p>\n</div><ul><li>Include these codes as defined in <a href=\"http://www.snomed.org/\"><code>http://snomed.info/sct</code></a><table class=\"none\"><tr><td style=\"white-space:nowrap\"><b>Code</b></td><td><b>Display</b></td></tr><tr><td><a href=\"http://browser.ihtsdotools.org/?perspective=full&amp;conceptId1=448385000\">448385000</a></td><td>Megavoltage radiation therapy using photons (procedure)</td><td/></tr><tr><td><a href=\"http://browser.ihtsdotools.org/?perspective=full&amp;conceptId1=45643008\">45643008</a></td><td>Teleradiotherapy using electrons (procedure)</td><td/></tr><tr><td><a href=\"http://browser.ihtsdotools.org/?perspective=full&amp;conceptId1=10611004\">10611004</a></td><td>Teleradiotherapy protons (procedure)</td><td/></tr><tr><td><a href=\"http://browser.ihtsdotools.org/?perspective=full&amp;conceptId1=80347004\">80347004</a></td><td>Teleradiotherapy neutrons (procedure)</td><td/></tr><tr><td><a href=\"http://browser.ihtsdotools.org/?perspective=full&amp;conceptId1=152198000\">152198000</a></td><td>Brachytherapy (procedure)</td><td/></tr></table></li></ul></div>"
7+
},
8+
"url": "http://hl7.org/fhir/us/mcode/ValueSet/mcode-radiation-procedure-vs",
9+
"version": "1.0.0",
10+
"name": "RadiationProcedureVS",
11+
"title": "Radiation Procedure Value Set",
12+
"status": "active",
13+
"date": "2020-03-18T16:05:09+00:00",
14+
"publisher": "HL7 International Clinical Interoperability Council",
15+
"contact": [
16+
{
17+
"name": "HL7 International Clinical Interoperability Council",
18+
"telecom": [
19+
{
20+
"system": "url",
21+
"value": "http://www.hl7.org/Special/committees/cic"
22+
},
23+
{
24+
"system": "email",
25+
"value": "ciclist@lists.HL7.org"
26+
}
27+
]
28+
}
29+
],
30+
"description": "Codes describing radiation therapy procedures. The value set includes a limited set of radiation modality codes from SNOMED CT, however, ICD-10-PCS code from Section D (Radiation Therapy) and appropriate CPT radiation procedure codes are also considered compliant. CPT codes are not explicitly included due to licensing restrictions. ICD-10-PCS codes are not included explicitly because they are not currently supported by the FHIR IG Publishing tool.\r\n\r\nConformance note: If an ICD-10-PCS code is used, and a semantically equivalent SNOMED CT or CPT code is available, the resulting Procedure instance will not be compliant with [US Core Profiles](http://hl7.org/fhir/us/core/index.html).",
31+
"compose": {
32+
"include": [
33+
{
34+
"system": "http://snomed.info/sct",
35+
"concept": [
36+
{
37+
"code": "448385000",
38+
"display": "Megavoltage radiation therapy using photons (procedure)"
39+
},
40+
{
41+
"code": "45643008",
42+
"display": "Teleradiotherapy using electrons (procedure)"
43+
},
44+
{
45+
"code": "10611004",
46+
"display": "Teleradiotherapy protons (procedure)"
47+
},
48+
{
49+
"code": "80347004",
50+
"display": "Teleradiotherapy neutrons (procedure)"
51+
},
52+
{
53+
"code": "152198000",
54+
"display": "Brachytherapy (procedure)"
55+
}
56+
]
57+
}
58+
]
59+
}
60+
}

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const {
2929
FHIRObservationExtractor,
3030
FHIRPatientExtractor,
3131
FHIRProcedureExtractor,
32+
MCODERadiationProcedureExtractor,
3233
MCODESurgicalProcedureExtractor,
3334
} = require('./extractors');
3435
const { BaseFHIRModule, CSVModule } = require('./modules');
@@ -91,6 +92,7 @@ module.exports = {
9192
FHIRObservationExtractor,
9293
FHIRPatientExtractor,
9394
FHIRProcedureExtractor,
95+
MCODERadiationProcedureExtractor,
9496
MCODESurgicalProcedureExtractor,
9597
logger,
9698
MCODEClient,
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
const { when } = require('jest-when');
2+
const rewire = require('rewire');
3+
const { MCODERadiationProcedureExtractor } = require('../../src/extractors/MCODERadiationProcedureExtractor.js');
4+
const exampleProcedureBundle = require('./fixtures/surgical-radiation-procedure-bundle.json');
5+
6+
const radiationProcedureExtractorRewired = rewire('../../src/extractors/MCODERadiationProcedureExtractor.js');
7+
const getMCODERadiationProcedures = radiationProcedureExtractorRewired.__get__('getMCODERadiationProcedures');
8+
9+
const MOCK_URL = 'http://example.com';
10+
const MOCK_HEADERS = {};
11+
const MOCK_PATIENT_MRN = 'EXAMPLE-MRN';
12+
13+
const extractor = new MCODERadiationProcedureExtractor({ baseFhirUrl: MOCK_URL, requestHeaders: MOCK_HEADERS });
14+
const { fhirProcedureExtractor } = extractor;
15+
16+
// Spy on fhirProcedureExtractor.get
17+
const fhirProcedureExtractorSpy = jest.spyOn(fhirProcedureExtractor, 'get');
18+
19+
describe('MCODERadiationProcedureExtractor', () => {
20+
describe('getFHIRProcedures', () => {
21+
// it('should return procedure entries for patient from context', async () => {
22+
// const contextPatient = {
23+
// resourceType: 'Patient',
24+
// id: 'context-patient-id',
25+
// };
26+
// const contextProcedure1 = {
27+
// resourceType: 'Procedure',
28+
// id: 'context-procedure-1-id',
29+
// };
30+
// const contextProcedure2 = {
31+
// resourceType: 'Procedure',
32+
// id: 'context-procedure-2-id',
33+
// };
34+
// const context = {
35+
// resourceType: 'Bundle',
36+
// type: 'collection',
37+
// entry: [
38+
// {
39+
// fullUrl: 'context-patient-url',
40+
// resource: contextPatient,
41+
// },
42+
// {
43+
// fullUrl: 'context-procedure-1-url',
44+
// resource: contextProcedure1,
45+
// },
46+
// {
47+
// fullUrl: 'context-procedure-2-url',
48+
// resource: contextProcedure2,
49+
// },
50+
// ],
51+
// };
52+
// const procedures = await extractor.getFHIRProcedures(MOCK_PATIENT_MRN, context);
53+
// expect(fhirProcedureExtractorSpy).not.toHaveBeenCalled();
54+
// expect(procedures).toHaveLength(2);
55+
// expect(procedures[0].resource.id).toEqual(contextProcedure1.id);
56+
// expect(procedures[1].resource.id).toEqual(contextProcedure2.id);
57+
// });
58+
59+
it('should return procedure entries for patient from FHIR search if no context', async () => {
60+
fhirProcedureExtractorSpy.mockClear();
61+
when(fhirProcedureExtractorSpy).calledWith({ mrn: MOCK_PATIENT_MRN, context: {} }).mockReturnValue(exampleProcedureBundle);
62+
const procedures = await extractor.getFHIRProcedures(MOCK_PATIENT_MRN, {});
63+
expect(fhirProcedureExtractorSpy).toHaveBeenCalled();
64+
expect(procedures).toEqual(exampleProcedureBundle.entry);
65+
});
66+
67+
it('should return no procedures if search is empty', async () => {
68+
const emptyCollectionBundle = {
69+
resourceType: 'Bundle',
70+
type: 'collection',
71+
entry: [],
72+
};
73+
fhirProcedureExtractorSpy.mockClear();
74+
when(fhirProcedureExtractorSpy).calledWith({ mrn: MOCK_PATIENT_MRN, context: {} }).mockReturnValue(emptyCollectionBundle);
75+
const procedures = await extractor.getFHIRProcedures(MOCK_PATIENT_MRN, {});
76+
expect(fhirProcedureExtractorSpy).toHaveBeenCalled();
77+
expect(procedures).toEqual([]);
78+
});
79+
});
80+
81+
describe('getMCODERadiationProcedures', () => {
82+
const radiationProcedureCoding = {
83+
system: 'http://snomed.info/sct',
84+
code: '152198000',
85+
display: 'Brachytherapy (procedure)',
86+
};
87+
const otherProcedureCoding = {
88+
system: 'http://snomed.info/sct',
89+
code: '173170008',
90+
display: 'Bilobectomy of lung',
91+
};
92+
let fhirProcedures;
93+
beforeEach(() => {
94+
fhirProcedures = [
95+
{
96+
fullUrl: 'urn:uuid:abc-123',
97+
resource: {
98+
resourceType: 'Procedure',
99+
id: 'abc-123',
100+
code: {
101+
coding: [otherProcedureCoding],
102+
},
103+
},
104+
},
105+
];
106+
});
107+
test('should return procedure that has single code in radiation procedure VS', () => {
108+
const radiationProcedure = {
109+
fullUrl: 'urn:uuid:xyz-987',
110+
resource: {
111+
resourceType: 'Procedure',
112+
id: 'xyz-987',
113+
code: {
114+
coding: [radiationProcedureCoding],
115+
},
116+
},
117+
};
118+
fhirProcedures.push(radiationProcedure);
119+
const resultingProcedures = getMCODERadiationProcedures(fhirProcedures);
120+
expect(resultingProcedures).toHaveLength(1);
121+
expect(resultingProcedures).toContain(radiationProcedure);
122+
});
123+
124+
test('should return procedure that has any code in radiation procedure VS', () => {
125+
const radiationProcedure = {
126+
fullUrl: 'urn:uuid:xyz-987',
127+
resource: {
128+
resourceType: 'Procedure',
129+
id: 'xyz-987',
130+
code: {
131+
coding: [otherProcedureCoding, radiationProcedureCoding],
132+
},
133+
},
134+
};
135+
fhirProcedures.push(radiationProcedure);
136+
const resultingProcedures = getMCODERadiationProcedures(fhirProcedures);
137+
expect(resultingProcedures).toHaveLength(1);
138+
expect(resultingProcedures).toContain(radiationProcedure);
139+
});
140+
141+
test('should not return procedure that has no code in radiation procedure VS', () => {
142+
const resultingProcedures = getMCODERadiationProcedures(fhirProcedures);
143+
expect(resultingProcedures).toHaveLength(0);
144+
});
145+
146+
test('should not return procedure that has no codes', () => {
147+
const emptyProcedure = {
148+
fullUrl: 'urn:uuid:xyz-987',
149+
resource: {
150+
resourceType: 'Procedure',
151+
id: 'xyz-987',
152+
code: {
153+
coding: [],
154+
},
155+
},
156+
};
157+
fhirProcedures.push(emptyProcedure);
158+
const resultingProcedures = getMCODERadiationProcedures(fhirProcedures);
159+
expect(resultingProcedures).toHaveLength(0);
160+
});
161+
test('should return an empty list when provided an empty list of procedures', () => {
162+
const resultingProcedures = getMCODERadiationProcedures([]);
163+
expect(resultingProcedures).toHaveLength(0);
164+
});
165+
});
166+
167+
describe('get', () => {
168+
test('should return a bundle with only procedures that are MCODE cancer related radiation procedures', async () => {
169+
const bundle = {
170+
resourceType: 'Bundle',
171+
type: 'collection',
172+
entry: exampleProcedureBundle.entry,
173+
};
174+
fhirProcedureExtractorSpy.mockClear();
175+
when(fhirProcedureExtractorSpy).calledWith({ mrn: MOCK_PATIENT_MRN, context: {} }).mockReturnValue(bundle);
176+
const data = await extractor.get({ mrn: MOCK_PATIENT_MRN, context: {} });
177+
expect(data.resourceType).toEqual('Bundle');
178+
expect(data.type).toEqual('collection');
179+
expect(data.entry).toBeDefined();
180+
expect(data.entry).toHaveLength(1);
181+
expect(data.entry[0].resource.code.coding[0].code).toEqual('152198000'); // Brachytherapy (procedure) - is in MCODE Cancer Related Radiation Procedure VS
182+
});
183+
});
184+
});

0 commit comments

Comments
 (0)