Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
]
}
}
}
Expand Down
139 changes: 139 additions & 0 deletions src/fhir/guidanceResponseUtilities.ts
Original file line number Diff line number Diff line change
@@ -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
let 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
let addedRequirementCount = 0;
const 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
const 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
const 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
const returnParameters: Parameters = {
resourceType: 'Parameters',
parameter: [
{
name: 'rems-etasu',
resource: guidanceResponse
}
]
};

return returnParameters;
}
}
10 changes: 5 additions & 5 deletions src/hooks/hookResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ export async function handleCardOrder(
}
});

let smartLinkCountAdded = 0;
let unmetRequirementSmartLinkCount = 0;
let smartLinkCount = 0;

// process the smart links from the medicationCollection
Expand All @@ -396,7 +396,7 @@ export async function handleCardOrder(
if (patient && patient.resourceType === 'Patient') {
createQuestionnaireSuggestion(card, requirement, patient, contextRequest);
}
smartLinkCountAdded++;
unmetRequirementSmartLinkCount++;
}
}
}
Expand All @@ -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
Expand All @@ -418,7 +418,7 @@ export async function handleCardOrder(
if (patient && patient.resourceType === 'Patient') {
createQuestionnaireSuggestion(card, requirement, patient, contextRequest);
}
smartLinkCountAdded++;
unmetRequirementSmartLinkCount++;
}
}
}
Expand All @@ -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);
}
}
Expand Down
7 changes: 0 additions & 7 deletions src/hooks/rems.patientview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion src/lib/etasu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RemsCase>,
medicationSearchDict: FilterQuery<Medication>
): Promise<Pick<
Expand Down
20 changes: 20 additions & 0 deletions src/lib/schemas/models/Annotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import mongoose from 'mongoose';
import { Annotation } from 'fhir/r4';
import Reference from '../models/Reference';
export default new mongoose.Schema<Annotation>(
{
authorReference: {
type: Reference,
default: void 0
},
time: {
type: Date,
default: void 0
},
text: {
type: String,
default: void 0
}
},
{ _id: false }
);
87 changes: 87 additions & 0 deletions src/lib/schemas/resources/GuidanceResponse.ts
Original file line number Diff line number Diff line change
@@ -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<GuidanceResponse>(GuidanceResponseInterface, { versionKey: false });
}

const qSchema = GuidanceResponseSchema();
qSchema.add({
contained: {
type: [qSchema, Library, ValueSet],
default: void 0
}
});
const GuidanceResponseModel = model<GuidanceResponse>('GuidanceResponse', qSchema);
export default GuidanceResponseModel;
Loading