Skip to content

Commit e5b5453

Browse files
committed
port of surgical procedure to radiation procedure
1 parent 7ae1ae0 commit e5b5453

File tree

5 files changed

+302
-0
lines changed

5 files changed

+302
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
const proceduresInContext = getBundleEntriesByResourceType(context, 'Procedure', {});
29+
if (proceduresInContext.length !== 0) {
30+
logger.debug('Procedure resources found in context.');
31+
return proceduresInContext;
32+
}
33+
34+
logger.debug('Getting procedures available for patient');
35+
const procedureBundle = await this.fhirProcedureExtractor.get({ mrn, context });
36+
37+
logger.debug(`Found ${procedureBundle.entry.length} result(s) in Procedure search`);
38+
return procedureBundle.entry;
39+
}
40+
41+
async get({ mrn, context }) {
42+
const fhirProcedures = await this.getFHIRProcedures(mrn, context);
43+
44+
// Filter to only include procedures that are from MCODE radiation procedure VS
45+
const radiationProcedures = getMCODERadiationProcedures(fhirProcedures);
46+
47+
return {
48+
resourceType: 'Bundle',
49+
type: 'collection',
50+
entry: radiationProcedures,
51+
};
52+
}
53+
}
54+
55+
module.exports = {
56+
MCODERadiationProcedureExtractor,
57+
};

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: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
const { when } = require('jest-when');
2+
const rewire = require('rewire');
3+
const { MCODERadiationProcedureExtractor } = require('../../src/extractors/MCODERadiationProcedureExtractor.js');
4+
const exampleProcedureBundle = require('./fixtures/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 otherProcedureCoding = {
83+
system: 'http://snomed.info/sct',
84+
code: '152198000',
85+
display: 'Brachytherapy (procedure)',
86+
};
87+
const radiationProcedureCoding = {
88+
system: 'http://snomed.info/sct',
89+
code: '173170008',
90+
};
91+
let fhirProcedures;
92+
beforeEach(() => {
93+
fhirProcedures = [
94+
{
95+
fullUrl: 'urn:uuid:abc-123',
96+
resource: {
97+
resourceType: 'Procedure',
98+
id: 'abc-123',
99+
code: {
100+
coding: [otherProcedureCoding],
101+
},
102+
},
103+
},
104+
];
105+
});
106+
test('should return procedure that has single code in radiation procedure VS', () => {
107+
const radiationProcedure = {
108+
fullUrl: 'urn:uuid:xyz-987',
109+
resource: {
110+
resourceType: 'Procedure',
111+
id: 'xyz-987',
112+
code: {
113+
coding: [radiationProcedureCoding],
114+
},
115+
},
116+
};
117+
fhirProcedures.push(radiationProcedure);
118+
const resultingProcedures = getMCODERadiationProcedures(fhirProcedures);
119+
expect(resultingProcedures).toHaveLength(1);
120+
expect(resultingProcedures).toContain(radiationProcedure);
121+
});
122+
123+
test('should return procedure that has any code in radiation procedure VS', () => {
124+
const radiationProcedure = {
125+
fullUrl: 'urn:uuid:xyz-987',
126+
resource: {
127+
resourceType: 'Procedure',
128+
id: 'xyz-987',
129+
code: {
130+
coding: [otherProcedureCoding, radiationProcedureCoding],
131+
},
132+
},
133+
};
134+
fhirProcedures.push(radiationProcedure);
135+
const resultingProcedures = getMCODERadiationProcedures(fhirProcedures);
136+
expect(resultingProcedures).toHaveLength(1);
137+
expect(resultingProcedures).toContain(radiationProcedure);
138+
});
139+
140+
test('should not return procedure that has no code in radiation procedure VS', () => {
141+
const resultingProcedures = getMCODERadiationProcedures(fhirProcedures);
142+
expect(resultingProcedures).toHaveLength(0);
143+
});
144+
145+
test('should not return procedure that has no codes', () => {
146+
const emptyProcedure = {
147+
fullUrl: 'urn:uuid:xyz-987',
148+
resource: {
149+
resourceType: 'Procedure',
150+
id: 'xyz-987',
151+
code: {
152+
coding: [],
153+
},
154+
},
155+
};
156+
fhirProcedures.push(emptyProcedure);
157+
const resultingProcedures = getMCODERadiationProcedures(fhirProcedures);
158+
expect(resultingProcedures).toHaveLength(0);
159+
});
160+
test('should return an empty list when provided an empty list of procedures', () => {
161+
const resultingProcedures = getMCODERadiationProcedures([]);
162+
expect(resultingProcedures).toHaveLength(0);
163+
});
164+
});
165+
166+
describe('get', () => {
167+
test('should return a bundle with only procedures that are MCODE cancer related radiation procedures', async () => {
168+
const context = {
169+
resourceType: 'Bundle',
170+
type: 'collection',
171+
entry: exampleProcedureBundle.entry,
172+
};
173+
const data = await extractor.get({ mrn: MOCK_PATIENT_MRN, context });
174+
expect(data.resourceType).toEqual('Bundle');
175+
expect(data.type).toEqual('collection');
176+
expect(data.entry).toBeDefined();
177+
expect(data.entry).toHaveLength(1);
178+
expect(data.entry[0].resource.code.coding[0].code).toEqual('173170008'); // Bilobectomy of lung - is in MCODE Cancer Related Radiation Procedure VS
179+
});
180+
});
181+
});

0 commit comments

Comments
 (0)