diff --git a/README.md b/README.md index 777d9ad..336c911 100644 --- a/README.md +++ b/README.md @@ -118,4 +118,35 @@ Following are a list of modifiable paths: | WHITELIST | `http://localhost, http://localhost:3005` | List of valid URLs for CORS. Should include any URLs the server accesses for resources. | | SERVER_NAME | `CodeX REMS Administrator Prototype` | Name of the server that is returned in the card source. | | FULL_RESOURCE_IN_APP_CONTEXT | 'false' | If true, the entire order resource will be included in the appContext, otherwise only a reference will be. | -| FRONTEND_PORT | `9080` | Port that the frontend server should run on, change if there are conflicts with port usage. | \ No newline at end of file +| FRONTEND_PORT | `9080` | Port that the frontend server should run on, change if there are conflicts with port usage. | + +# Data Rights + +
+NOTICE +
+ +This (software/technical data) was produced for the U. S. Government under Contract Number 75FCMC18D0047/75FCMC23D0004, and is subject to Federal Acquisition Regulation Clause 52.227-14, Rights in Data-General. + + +No other use other than that granted to the U. S. Government, or to those acting on behalf of the U. S. Government under that Clause is authorized without the express written permission of The MITRE Corporation. + + +For further information, please contact The MITRE Corporation, Contracts Management Office, 7515 Colshire Drive, McLean, VA 22102-7539, (703) 983-6000. + +
+©2025 The MITRE Corporation. +
+ +
+ +Licensed under the Apache License, Version 2.0 (the "License"); use of this repository is permitted in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 693052e..1a91407 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ base: '', plugins: [react()], preview: { - allowedHosts: ['.mitre.org', '.us-east-1.elb.amazonaws.com'] + allowedHosts: ['.mitre.org', '.elb.us-east-1.amazonaws.com'] }, define: { 'process.env': process.env @@ -18,6 +18,6 @@ export default defineConfig({ port: parseInt(process.env.FRONTEND_PORT!), open: false, host: true, - allowedHosts: ['.mitre.org', '.us-east-1.elb.amazonaws.com'] + allowedHosts: ['.mitre.org', '.elb.us-east-1.amazonaws.com'] } }); diff --git a/src/fhir/utilities.ts b/src/fhir/utilities.ts index de0cde9..72e66b3 100644 --- a/src/fhir/utilities.ts +++ b/src/fhir/utilities.ts @@ -311,7 +311,7 @@ export class FhirUtilities { const medicationRequirements = [ { - stakeholderId: 'Organization/pharm0111', + stakeholderId: 'HealthcareService/pharm0111', completed: true, requirementName: 'Pharmacist Enrollment', drugName: 'Turalio', @@ -319,7 +319,7 @@ export class FhirUtilities { case_numbers: [] }, { - stakeholderId: 'Organization/pharm0111', + stakeholderId: 'HealthcareService/pharm0111', completed: true, requirementName: 'Pharmacist Enrollment', drugName: 'TIRF', @@ -327,7 +327,7 @@ export class FhirUtilities { case_numbers: [] }, { - stakeholderId: 'Organization/pharm0111', + stakeholderId: 'HealthcareService/pharm0111', completed: true, requirementName: 'Pharmacist Knowledge Assessment', drugName: 'TIRF', @@ -335,7 +335,7 @@ export class FhirUtilities { case_numbers: [] }, { - stakeholderId: 'Organization/pharm0111', + stakeholderId: 'HealthcareService/pharm0111', completed: true, requirementName: 'Pharmacist Enrollment', drugName: 'Isotretinoin', diff --git a/src/hooks/hookResources.ts b/src/hooks/hookResources.ts index deaccad..7646a44 100644 --- a/src/hooks/hookResources.ts +++ b/src/hooks/hookResources.ts @@ -6,7 +6,8 @@ import { Patient, Bundle, Medication, - BundleEntry + BundleEntry, + HealthcareService } from 'fhir/r4'; import Card, { Link, Suggestion, Action } from '../cards/Card'; import { HookPrefetch, TypedRequestBody } from '../rems-cds-hooks/resources/HookTypes'; @@ -16,7 +17,8 @@ import { Requirement, medicationCollection, remsCaseCollection, - Medication as MongooseMedication + Medication as MongooseMedication, + metRequirementsCollection } from '../fhir/models'; import axios from 'axios'; @@ -368,6 +370,12 @@ export const handleCardOrder = async ( ): Promise => { const patient = resource?.resourceType === 'Patient' ? resource : undefined; + console.log('hydratedPrefetch: ' + JSON.stringify(hydratedPrefetch)); + + const pharmacy = hydratedPrefetch?.pharmacy as HealthcareService; + + console.log(' Pharmacy: ' + pharmacy); + const errorCard = getErrorCard(hydratedPrefetch, contextRequest); if (errorCard) { res.json(errorCard); @@ -397,11 +405,56 @@ export const handleCardOrder = async ( const codeRule = (code && codeMap[code]) || []; - const cards: Card[] = codeRule - .map(getCardOrEmptyArrayFromRules(display, drug, remsCase, request, patient)) - .flat(); + const cardPromises = codeRule.map( + getCardOrEmptyArrayFromRules(display, drug, remsCase, request, patient) + ); - res.json({ cards }); + const remsCards: Card[] = (await Promise.all(cardPromises)).flat(); + + // Create pharmacy status card once (if pharmacy exists) + const allCards: Card[] = []; + if (pharmacy) { + const pharmacyStatusCard = await createPharmacyStatusCard(pharmacy, drug, display); + if (pharmacyStatusCard) { + allCards.push(pharmacyStatusCard); + } + } + + // Add all REMS cards after the pharmacy card + allCards.push(...remsCards); + + res.json({ cards: allCards }); +}; + +const createPharmacyStatusCard = async ( + pharmacy: HealthcareService, + drug: MongooseMedication | null, + display: string | undefined +): Promise => { + if (!pharmacy) { + return null; + } + + const isCertified = await checkPharmacyCertification(pharmacy, drug?.code); + const pharmacyName = pharmacy.name || 'Selected pharmacy'; + const locationInfo = pharmacy.location?.[0]?.display; + const fullPharmacyName = `${pharmacyName} (${locationInfo})`; + + const statusText = `${fullPharmacyName} **is ${ + isCertified ? 'certified' : 'not yet certified' + }** for ${display || 'this medication'} REMS dispensing. This medication **${ + isCertified ? 'can' : 'cannot yet' + }** be dispensed at this location.`; + + const pharmacyStatusCard = new Card( + 'Pharmacy Certification Status', + statusText, + source, + isCertified ? 'info' : 'warning' + ); + + // No links or suggestions for this card - it's informational only + return pharmacyStatusCard; }; const getCardOrEmptyArrayFromRules = @@ -412,7 +465,7 @@ const getCardOrEmptyArrayFromRules = request: MedicationRequest, patient: Patient | undefined ) => - (rule: CardRule): Card | never[] => { + async (rule: CardRule): Promise => { const card = new Card( rule.summary || display || 'Rems', rule.cardDetails || CARD_DETAILS, @@ -462,6 +515,53 @@ const getCardOrEmptyArrayFromRules = return []; }; +const checkPharmacyCertification = async ( + pharmacy: HealthcareService | undefined, + drugCode: string | undefined +) => { + if (!pharmacy?.id || !drugCode) { + return false; + } + + const drug = await medicationCollection + .findOne({ + code: drugCode, + codeSystem: 'http://www.nlm.nih.gov/research/umls/rxnorm' + }) + .exec(); + + if (!drug) { + return false; + } + + const requiredPharmacistRequirements = drug.requirements.filter( + requirement => requirement.stakeholderType === 'pharmacist' && requirement.requiredToDispense + ); + + if (requiredPharmacistRequirements.length === 0) { + return true; + } + + const pharmacyId = `HealthcareService/${pharmacy.id}`; + + for (const requirement of requiredPharmacistRequirements) { + const metRequirement = await metRequirementsCollection + .findOne({ + stakeholderId: pharmacyId, + requirementName: requirement.name, + drugName: drug.name, + completed: true + }) + .exec(); + + if (!metRequirement) { + return false; + } + } + + return true; +}; + const getSmartLinks = ( requirements: Requirement[], request: MedicationRequest,