Skip to content
Merged

Dev #179

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
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
| FRONTEND_PORT | `9080` | Port that the frontend server should run on, change if there are conflicts with port usage. |

# Data Rights

<div style="text-align:center">
<b>NOTICE</b>
</div>

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.

<div style="text-align:center">
<b>&copy;2025 The MITRE Corporation.</b>
</div>

<br />

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.
4 changes: 2 additions & 2 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
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
},
server: {
port: parseInt(process.env.FRONTEND_PORT!),

Check warning on line 18 in frontend/vite.config.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Forbidden non-null assertion

Check warning on line 18 in frontend/vite.config.ts

View workflow job for this annotation

GitHub Actions / Check tsc, lint, and prettier

Forbidden non-null assertion
open: false,
host: true,
allowedHosts: ['.mitre.org', '.us-east-1.elb.amazonaws.com']
allowedHosts: ['.mitre.org', '.elb.us-east-1.amazonaws.com']
}
});
8 changes: 4 additions & 4 deletions src/fhir/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,31 +311,31 @@ export class FhirUtilities {

const medicationRequirements = [
{
stakeholderId: 'Organization/pharm0111',
stakeholderId: 'HealthcareService/pharm0111',
completed: true,
requirementName: 'Pharmacist Enrollment',
drugName: 'Turalio',
completedQuestionnaire: null,
case_numbers: []
},
{
stakeholderId: 'Organization/pharm0111',
stakeholderId: 'HealthcareService/pharm0111',
completed: true,
requirementName: 'Pharmacist Enrollment',
drugName: 'TIRF',
completedQuestionnaire: null,
case_numbers: []
},
{
stakeholderId: 'Organization/pharm0111',
stakeholderId: 'HealthcareService/pharm0111',
completed: true,
requirementName: 'Pharmacist Knowledge Assessment',
drugName: 'TIRF',
completedQuestionnaire: null,
case_numbers: []
},
{
stakeholderId: 'Organization/pharm0111',
stakeholderId: 'HealthcareService/pharm0111',
completed: true,
requirementName: 'Pharmacist Enrollment',
drugName: 'Isotretinoin',
Expand Down
114 changes: 107 additions & 7 deletions src/hooks/hookResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -16,7 +17,8 @@ import {
Requirement,
medicationCollection,
remsCaseCollection,
Medication as MongooseMedication
Medication as MongooseMedication,
metRequirementsCollection
} from '../fhir/models';

import axios from 'axios';
Expand Down Expand Up @@ -368,6 +370,12 @@ export const handleCardOrder = async (
): Promise<void> => {
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);
Expand Down Expand Up @@ -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<Card | null> => {
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 =
Expand All @@ -412,7 +465,7 @@ const getCardOrEmptyArrayFromRules =
request: MedicationRequest,
patient: Patient | undefined
) =>
(rule: CardRule): Card | never[] => {
async (rule: CardRule): Promise<Card | never[]> => {
const card = new Card(
rule.summary || display || 'Rems',
rule.cardDetails || CARD_DETAILS,
Expand Down Expand Up @@ -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,
Expand Down
Loading