From 82ffb80fd25007c0656e91d0dddabefcc3b16034 Mon Sep 17 00:00:00 2001 From: Patrick LaRocque Date: Wed, 20 Mar 2024 15:28:58 -0400 Subject: [PATCH 1/4] ETASU using GuidanceResponse --- src/config.ts | 13 ++ src/fhir/guidanceResponseUtilities.ts | 139 ++++++++++++++++++ src/lib/etasu.ts | 2 +- src/lib/schemas/models/Annotation.ts | 20 +++ src/lib/schemas/resources/GuidanceResponse.ts | 87 +++++++++++ src/services/guidanceresponse.service.ts | 63 ++++++++ 6 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/fhir/guidanceResponseUtilities.ts create mode 100644 src/lib/schemas/models/Annotation.ts create mode 100644 src/lib/schemas/resources/GuidanceResponse.ts create mode 100644 src/services/guidanceresponse.service.ts diff --git a/src/config.ts b/src/config.ts index 868644d5..595d3fc8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -115,6 +115,19 @@ export default { valueset: { service: './src/services/valueset.service.ts', versions: [fhirConstants.VERSIONS['4_0_0']] + }, + guidanceresponse: { + service: './src/services/guidanceresponse.service.ts', + versions: [fhirConstants.VERSIONS['4_0_0']], + operation: [ + { + name: 'rems-etasu', + route: '/$rems-etasu', + method: 'POST', + reference: + 'https://build.fhir.org/ig/HL7/fhir-medication-rems-ig/OperationDefinition-REMS-ETASU.html' + } + ] } } } diff --git a/src/fhir/guidanceResponseUtilities.ts b/src/fhir/guidanceResponseUtilities.ts new file mode 100644 index 00000000..8450b1e9 --- /dev/null +++ b/src/fhir/guidanceResponseUtilities.ts @@ -0,0 +1,139 @@ +import { Parameters, Patient, GuidanceResponse } from 'fhir/r4'; +import container from '../lib/winston'; +import { RemsCase } from './models'; + +const MODULE_URI = 'https://build.fhir.org/ig/HL7/fhir-medication-rems-ig/'; + +export class GuidanceResponseUtilities { + static logger = container.get('application'); + + static translateStatus(etasuStatus: string | undefined) { + // translate the status + var remsStatus: + | 'success' + | 'data-requested' + | 'data-required' + | 'in-progress' + | 'failure' + | 'entered-in-error' = 'failure'; + if (etasuStatus === 'Pending') { + remsStatus = 'data-required'; + } else if (etasuStatus === 'Approved') { + remsStatus = 'success'; + } + return remsStatus; + } + + static processEtasuRequirements( + etasu: Pick< + RemsCase, + | 'drugName' + | 'status' + | 'drugCode' + | 'patientFirstName' + | 'patientLastName' + | 'patientDOB' + | 'metRequirements' + > | null + ) { + // create the output parameters + var addedRequirementCount = 0; + var outputParameters: Parameters = { + resourceType: 'Parameters', + id: 'etasuOutputParameters' + }; + + // create the Parameters for the individual ETASU + etasu?.metRequirements?.forEach(metRequirement => { + // create a GuidanceResponse to embed with the individual requirement for the ETASU + var etasuGuidanceResponse: GuidanceResponse = { + resourceType: 'GuidanceResponse', + status: metRequirement?.completed ? 'success' : 'data-required', + moduleUri: MODULE_URI, + subject: { + reference: metRequirement?.stakeholderId + }, + note: [ + { + text: metRequirement?.requirementName ? metRequirement?.requirementName : 'unknown' + } + ] + }; + + addedRequirementCount++; + + const parameter = { + //TODO: remove spaces from name? + name: metRequirement?.requirementName + ? metRequirement?.requirementName + : 'requirement' + addedRequirementCount, + resource: etasuGuidanceResponse + }; + + // add the ETASU requirement GuidanceResponse to the outputParameters + if (!outputParameters?.parameter) { + outputParameters.parameter = [parameter]; + } else { + outputParameters.parameter?.push(parameter); + } + }); + + return outputParameters; + } + + static createEtasuGuidanceResponse( + etasu: Pick< + RemsCase, + | 'drugName' + | 'status' + | 'drugCode' + | 'patientFirstName' + | 'patientLastName' + | 'patientDOB' + | 'metRequirements' + > | null, + patient: Patient | undefined + ) { + const remsStatus = this.translateStatus(etasu?.status); + + // create a GuidanceResponse representing the rems etasu status + var guidanceResponse: GuidanceResponse = { + resourceType: 'GuidanceResponse', + status: remsStatus, + moduleUri: MODULE_URI + }; + + // optionally add the patient as the subject if the ID is available + if (patient?.id) { + guidanceResponse.subject = { + reference: 'Patient/' + patient?.id + }; + } + + // process and add the etasu requirements as output parameters + const outputParameters = this.processEtasuRequirements(etasu); + + if (outputParameters?.parameter) { + // set the output parameters + guidanceResponse.outputParameters = { + reference: '#' + outputParameters.id + }; + + // add the contained parameters + guidanceResponse.contained = [outputParameters]; + } + + // create the return Parameters containing the GuidanceResponse for the ETASU + var returnParameters: Parameters = { + resourceType: 'Parameters', + parameter: [ + { + name: 'rems-etasu', + resource: guidanceResponse + } + ] + }; + + return returnParameters; + } +} diff --git a/src/lib/etasu.ts b/src/lib/etasu.ts index 149b27f1..b74c5a06 100644 --- a/src/lib/etasu.ts +++ b/src/lib/etasu.ts @@ -37,7 +37,7 @@ router.get('/met/:caseId', async (req: Request, res: Response) => { res.send(await remsCaseCollection.findOne({ case_number: req.params.caseId })); }); -const getCaseInfo = async ( +export const getCaseInfo = async ( remsCaseSearchDict: FilterQuery, medicationSearchDict: FilterQuery ): Promise( + { + authorReference: { + type: Reference, + default: void 0 + }, + time: { + type: Date, + default: void 0 + }, + text: { + type: String, + default: void 0 + } + }, + { _id: false } +); diff --git a/src/lib/schemas/resources/GuidanceResponse.ts b/src/lib/schemas/resources/GuidanceResponse.ts new file mode 100644 index 00000000..1ecbeb8b --- /dev/null +++ b/src/lib/schemas/resources/GuidanceResponse.ts @@ -0,0 +1,87 @@ +import mongoose, { model } from 'mongoose'; +import { GuidanceResponse } from 'fhir/r4'; +import Identifier from '../models/Identifier'; +import Reference from '../models/Reference'; +import CodeableConcept from '../models/CodeableConcept'; +import DomainResource from './DomainResource'; +import Library from './Library'; +import ValueSet from './ValueSet'; +import Annotation from '../models/Annotation'; +import DataRequirement from '../models/DataRequirement'; + +function GuidanceResponseSchema() { + const GuidanceResponseInterface = { + ...DomainResource, + requestIdentifier: { + type: Identifier, + default: void 0 + }, + identifier: { + type: [Identifier], + default: void 0 + }, + moduleUri: { + type: String, + default: void 0 + }, + status: { + type: String, + default: void 0 + }, + subject: { + type: Reference, + default: void 0 + }, + encounter: { + type: Reference, + default: void 0 + }, + occurenceDateTime: { + type: Date, + default: void 0 + }, + performer: { + type: Reference, + default: void 0 + }, + reasonCode: { + type: [CodeableConcept], + default: void 0 + }, + reasonReference: { + type: [Reference], + default: void 0 + }, + note: { + type: [Annotation], + default: void 0 + }, + evaluationMessage: { + type: [Reference], + default: void 0 + }, + outputParameters: { + type: Reference, + default: void 0 + }, + result: { + type: Reference, + default: void 0 + }, + dataRequirement: { + type: [DataRequirement], + default: void 0 + } + }; + return new mongoose.Schema(GuidanceResponseInterface, { versionKey: false }); +} + +const qSchema = GuidanceResponseSchema(); +qSchema.add({ + contained: { + type: [qSchema, Library, ValueSet], + default: void 0 + } +}); +const GuidanceResponseModel = model('GuidanceResponse', qSchema); +export default GuidanceResponseModel; diff --git a/src/services/guidanceresponse.service.ts b/src/services/guidanceresponse.service.ts new file mode 100644 index 00000000..6fc712e1 --- /dev/null +++ b/src/services/guidanceresponse.service.ts @@ -0,0 +1,63 @@ +import { FhirUtilities } from '../fhir/utilities'; +import { GuidanceResponseUtilities } from '../fhir/guidanceResponseUtilities'; +import GuidanceResponseModel from '../lib/schemas/resources/GuidanceResponse'; +import { Parameters, Medication, Patient } from 'fhir/r4'; +import { getCaseInfo } from '../lib/etasu'; + +module.exports.searchById = async (args: any) => { + const { id } = args; + console.log('GuidanceResponse >>> searchById: -- ' + id); + return await GuidanceResponseModel.findOne({ id: id.toString() }, { _id: 0 }).exec(); +}; + +module.exports.create = async (args: any, req: any) => { + console.log('GuidanceResponse >>> create'); + const resource = req.req.body; + const { base_version } = args; + return await FhirUtilities.store(resource, GuidanceResponseModel, base_version); +}; + +const getMedicationCode = (medication: Medication | undefined) => { + // grab the medication drug code from the Medication resource + var drugCode = null; + medication?.code?.coding?.forEach(medCode => { + if (medCode?.system?.endsWith('rxnorm')) { + drugCode = medCode?.code; + } + }); + return drugCode; +}; + +module.exports.remsEtasu = async (args: any, context: any, logger: any) => { + console.log('Running GuidanceResponse rems-etasu check /$rems-etasu'); + + var parameters: Parameters = args?.resource; + var patient: Patient | undefined; + var medication: Medication | undefined; + + parameters?.parameter?.forEach(param => { + if (param?.name === 'patient' && param?.resource?.resourceType === 'Patient') { + patient = param.resource; + } else if (param?.name === 'medication' && param?.resource?.resourceType === 'Medication') { + medication = param.resource; + } + }); + + var drugCode = getMedicationCode(medication); + + // grab the patient demographics from the Patient resource in the parameters + const remsCaseSearchDict = { + patientFirstName: patient?.name?.[0]?.given, + patientLastName: patient?.name?.[0]?.family, + patientDOB: patient?.birthDate, + drugCode: drugCode + }; + + const medicationSearchDict = { + code: drugCode + }; + + const etasu = await getCaseInfo(remsCaseSearchDict, medicationSearchDict); + + return GuidanceResponseUtilities.createEtasuGuidanceResponse(etasu, patient); +}; From 390eaadf37026c7e85200b1752678721cae928f6 Mon Sep 17 00:00:00 2001 From: Patrick LaRocque Date: Wed, 20 Mar 2024 16:26:59 -0400 Subject: [PATCH 2/4] lint errors --- src/fhir/guidanceResponseUtilities.ts | 12 ++++++------ src/services/guidanceresponse.service.ts | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/fhir/guidanceResponseUtilities.ts b/src/fhir/guidanceResponseUtilities.ts index 8450b1e9..e0233543 100644 --- a/src/fhir/guidanceResponseUtilities.ts +++ b/src/fhir/guidanceResponseUtilities.ts @@ -9,7 +9,7 @@ export class GuidanceResponseUtilities { static translateStatus(etasuStatus: string | undefined) { // translate the status - var remsStatus: + let remsStatus: | 'success' | 'data-requested' | 'data-required' @@ -37,8 +37,8 @@ export class GuidanceResponseUtilities { > | null ) { // create the output parameters - var addedRequirementCount = 0; - var outputParameters: Parameters = { + let addedRequirementCount = 0; + let outputParameters: Parameters = { resourceType: 'Parameters', id: 'etasuOutputParameters' }; @@ -46,7 +46,7 @@ export class GuidanceResponseUtilities { // create the Parameters for the individual ETASU etasu?.metRequirements?.forEach(metRequirement => { // create a GuidanceResponse to embed with the individual requirement for the ETASU - var etasuGuidanceResponse: GuidanceResponse = { + let etasuGuidanceResponse: GuidanceResponse = { resourceType: 'GuidanceResponse', status: metRequirement?.completed ? 'success' : 'data-required', moduleUri: MODULE_URI, @@ -97,7 +97,7 @@ export class GuidanceResponseUtilities { const remsStatus = this.translateStatus(etasu?.status); // create a GuidanceResponse representing the rems etasu status - var guidanceResponse: GuidanceResponse = { + let guidanceResponse: GuidanceResponse = { resourceType: 'GuidanceResponse', status: remsStatus, moduleUri: MODULE_URI @@ -124,7 +124,7 @@ export class GuidanceResponseUtilities { } // create the return Parameters containing the GuidanceResponse for the ETASU - var returnParameters: Parameters = { + let returnParameters: Parameters = { resourceType: 'Parameters', parameter: [ { diff --git a/src/services/guidanceresponse.service.ts b/src/services/guidanceresponse.service.ts index 6fc712e1..9540f29f 100644 --- a/src/services/guidanceresponse.service.ts +++ b/src/services/guidanceresponse.service.ts @@ -19,7 +19,7 @@ module.exports.create = async (args: any, req: any) => { const getMedicationCode = (medication: Medication | undefined) => { // grab the medication drug code from the Medication resource - var drugCode = null; + let drugCode = null; medication?.code?.coding?.forEach(medCode => { if (medCode?.system?.endsWith('rxnorm')) { drugCode = medCode?.code; @@ -31,9 +31,9 @@ const getMedicationCode = (medication: Medication | undefined) => { module.exports.remsEtasu = async (args: any, context: any, logger: any) => { console.log('Running GuidanceResponse rems-etasu check /$rems-etasu'); - var parameters: Parameters = args?.resource; - var patient: Patient | undefined; - var medication: Medication | undefined; + let parameters: Parameters = args?.resource; + let patient: Patient | undefined; + let medication: Medication | undefined; parameters?.parameter?.forEach(param => { if (param?.name === 'patient' && param?.resource?.resourceType === 'Patient') { @@ -43,7 +43,7 @@ module.exports.remsEtasu = async (args: any, context: any, logger: any) => { } }); - var drugCode = getMedicationCode(medication); + let drugCode = getMedicationCode(medication); // grab the patient demographics from the Patient resource in the parameters const remsCaseSearchDict = { From c32a9b5deba64511348f8105c25745495e6a71de Mon Sep 17 00:00:00 2001 From: Patrick LaRocque Date: Thu, 21 Mar 2024 00:37:59 -0400 Subject: [PATCH 3/4] lint fixes --- src/fhir/guidanceResponseUtilities.ts | 8 ++++---- src/hooks/rems.patientview.ts | 7 ------- src/services/guidanceresponse.service.ts | 6 +++--- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/fhir/guidanceResponseUtilities.ts b/src/fhir/guidanceResponseUtilities.ts index e0233543..1743ead3 100644 --- a/src/fhir/guidanceResponseUtilities.ts +++ b/src/fhir/guidanceResponseUtilities.ts @@ -38,7 +38,7 @@ export class GuidanceResponseUtilities { ) { // create the output parameters let addedRequirementCount = 0; - let outputParameters: Parameters = { + const outputParameters: Parameters = { resourceType: 'Parameters', id: 'etasuOutputParameters' }; @@ -46,7 +46,7 @@ export class GuidanceResponseUtilities { // create the Parameters for the individual ETASU etasu?.metRequirements?.forEach(metRequirement => { // create a GuidanceResponse to embed with the individual requirement for the ETASU - let etasuGuidanceResponse: GuidanceResponse = { + const etasuGuidanceResponse: GuidanceResponse = { resourceType: 'GuidanceResponse', status: metRequirement?.completed ? 'success' : 'data-required', moduleUri: MODULE_URI, @@ -97,7 +97,7 @@ export class GuidanceResponseUtilities { const remsStatus = this.translateStatus(etasu?.status); // create a GuidanceResponse representing the rems etasu status - let guidanceResponse: GuidanceResponse = { + const guidanceResponse: GuidanceResponse = { resourceType: 'GuidanceResponse', status: remsStatus, moduleUri: MODULE_URI @@ -124,7 +124,7 @@ export class GuidanceResponseUtilities { } // create the return Parameters containing the GuidanceResponse for the ETASU - let returnParameters: Parameters = { + const returnParameters: Parameters = { resourceType: 'Parameters', parameter: [ { diff --git a/src/hooks/rems.patientview.ts b/src/hooks/rems.patientview.ts index da33871e..57f278af 100644 --- a/src/hooks/rems.patientview.ts +++ b/src/hooks/rems.patientview.ts @@ -37,13 +37,6 @@ const source = { label: 'MCODE REMS Administrator Prototype', url: new URL('https://github.com/mcode/rems-admin') }; -function buildErrorCard(reason: string) { - const errorCard = new Card('Bad Request', reason, source, 'warning'); - const cards = { - cards: [errorCard.card] - }; - return cards; -} const handler = (req: TypedRequestBody, res: any) => { console.log('REMS patient-view hook'); diff --git a/src/services/guidanceresponse.service.ts b/src/services/guidanceresponse.service.ts index 9540f29f..d16f372b 100644 --- a/src/services/guidanceresponse.service.ts +++ b/src/services/guidanceresponse.service.ts @@ -29,9 +29,9 @@ const getMedicationCode = (medication: Medication | undefined) => { }; module.exports.remsEtasu = async (args: any, context: any, logger: any) => { - console.log('Running GuidanceResponse rems-etasu check /$rems-etasu'); + logger.info('Running GuidanceResponse rems-etasu check /$rems-etasu'); - let parameters: Parameters = args?.resource; + const parameters: Parameters = args?.resource; let patient: Patient | undefined; let medication: Medication | undefined; @@ -43,7 +43,7 @@ module.exports.remsEtasu = async (args: any, context: any, logger: any) => { } }); - let drugCode = getMedicationCode(medication); + const drugCode = getMedicationCode(medication); // grab the patient demographics from the Patient resource in the parameters const remsCaseSearchDict = { From 73751ee70e2884854bf5999c1c51c2bc66bd45df Mon Sep 17 00:00:00 2001 From: Patrick LaRocque Date: Thu, 21 Mar 2024 00:40:43 -0400 Subject: [PATCH 4/4] rename a variable --- src/hooks/hookResources.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hooks/hookResources.ts b/src/hooks/hookResources.ts index 1c08efa3..8d828a64 100644 --- a/src/hooks/hookResources.ts +++ b/src/hooks/hookResources.ts @@ -373,7 +373,7 @@ export async function handleCardOrder( } }); - let smartLinkCountAdded = 0; + let unmetRequirementSmartLinkCount = 0; let smartLinkCount = 0; // process the smart links from the medicationCollection @@ -396,7 +396,7 @@ export async function handleCardOrder( if (patient && patient.resourceType === 'Patient') { createQuestionnaireSuggestion(card, requirement, patient, contextRequest); } - smartLinkCountAdded++; + unmetRequirementSmartLinkCount++; } } } @@ -407,7 +407,7 @@ export async function handleCardOrder( if (patient && patient.resourceType === 'Patient') { createQuestionnaireSuggestion(card, requirement, patient, contextRequest); } - smartLinkCountAdded++; + unmetRequirementSmartLinkCount++; } } else { // add all the required to dispense links if no etasu to check @@ -418,7 +418,7 @@ export async function handleCardOrder( if (patient && patient.resourceType === 'Patient') { createQuestionnaireSuggestion(card, requirement, patient, contextRequest); } - smartLinkCountAdded++; + unmetRequirementSmartLinkCount++; } } } @@ -427,7 +427,7 @@ export async function handleCardOrder( // only add the card if there are smart links to needed forms // allow information only cards to be returned as well - if (smartLinkCountAdded > 0 || smartLinkCount === 0) { + if (unmetRequirementSmartLinkCount > 0 || smartLinkCount === 0) { cardArray.push(card); } }