diff --git a/src/PrefetchTemplate.js b/src/PrefetchTemplate.js
index fb485a93..a429730a 100644
--- a/src/PrefetchTemplate.js
+++ b/src/PrefetchTemplate.js
@@ -6,83 +6,33 @@ export class PrefetchTemplate {
const prefetchMap = new Map();
- const COVERAGE_PREFETCH_QUERY = new PrefetchTemplate(
- "Coverage?patient={{context.patientId}}");
+ const PRACTITIONER_PREFETCH = new PrefetchTemplate(
+ "{{context.userId}}");
- const DEVICE_REQUEST_BUNDLE = new PrefetchTemplate(
- "DeviceRequest?_id={{context.draftOrders.DeviceRequest.id}}"
- + "&_include=DeviceRequest:patient"
- + "&_include=DeviceRequest:performer"
- + "&_include=DeviceRequest:requester"
- + "&_include=DeviceRequest:device"
- + "&_include:iterate=PractitionerRole:organization"
- + "&_include:iterate=PractitionerRole:practitioner");
+ const REQUEST_PREFETCH = new PrefetchTemplate(
+ "MedicationRequest/{{context.medications.MedicationRequest.id}}");
+ const PATIENT_PREFETCH = new PrefetchTemplate("{{context.patientId}}");
- const MEDICATION_REQUEST_BUNDLE = new PrefetchTemplate(
- "MedicationRequest?_id={{context.medications.MedicationRequest.id}}"
- + "&_include=MedicationRequest:patient"
- + "&_include=MedicationRequest:intended-dispenser"
- + "&_include=MedicationRequest:requester:PractitionerRole"
- + "&_include=MedicationRequest:medication"
- + "&_include:iterate=PractitionerRole:organization"
- + "&_include:iterate=PractitionerRole:practitioner");
-
- const MEDICATION_DISPENSE_BUNDLE = new PrefetchTemplate(
- "MedicationDispense?_id={{context.medications.MedicationDispense.id}}"
- + "&_include=MedicationDispense:patient"
- + "&_include=MedicationDispense:intended-dispenser"
- + "&_include=MedicationDispense:requester:PractitionerRole"
- + "&_include=MedicationDispense:medication"
- + "&_include:iterate=PractitionerRole:organization"
- + "&_include:iterate=PractitionerRole:practitioner");
-
- const NUTRITION_ORDER_BUNDLE = new PrefetchTemplate(
- "NutritionOrder?_id={{context.draftOrders.NutritionOrder.id}}"
- + "&_include=NutritionOrder:patient"
- + "&_include=NutritionOrder:provider"
- + "&_include=NutritionOrder:requester"
- + "&_include=PractitionerRole:organization"
- + "&_include=PractitionerRole:practitioner"
- + "&_include=NutritionOrder:encounter"
- + "&_include=Encounter:location");
-
- const SERVICE_REQUEST_BUNDLE = new PrefetchTemplate(
- "ServiceRequest?_id={{context.draftOrders.ServiceRequest.id}}"
- + "&_include=ServiceRequest:patient"
- + "&_include=ServiceRequest:performer"
- + "&_include=ServiceRequest:requester"
- + "&_include:iterate=PractitionerRole:organization"
- + "&_include:iterate=PractitionerRole:practitioner");
-
- const APPOINTMENT_BUNDLE = new PrefetchTemplate(
- "appointmentBundle",
- "Appointment?_id={{context.appointments.Appointment.id}}"
- + "&_include=Appointment:patient"
- + "&_include=Appointment:practitioner:PractitionerRole"
- + "&_include:iterate=PractitionerRole:organization"
- + "&_include:iterate=PractitionerRole:practitioner"
- + "&_include=Appointment:location");
-
- const ENCOUNTER_BUNDLE = new PrefetchTemplate(
- "encounterBundle",
- "Encounter?_id={{context.encounterId}}"
- + "&_include=Encounter:patient"
- + "&_include=Encounter:service-provider"
- + "&_include=Encounter:practitioner"
- + "&_include=Encounter:location");
-
- prefetchMap.set("Coverage", COVERAGE_PREFETCH_QUERY);
- prefetchMap.set("DeviceRequest", DEVICE_REQUEST_BUNDLE);
- prefetchMap.set("MedicationRequest", MEDICATION_REQUEST_BUNDLE);
- prefetchMap.set("MedicationDispense", MEDICATION_DISPENSE_BUNDLE);
- prefetchMap.set("ServiceRequest", SERVICE_REQUEST_BUNDLE);
- prefetchMap.set("Encounter", ENCOUNTER_BUNDLE);
+ // prefetchMap.set("Coverage", COVERAGE_PREFETCH_QUERY);
+ prefetchMap.set("request", REQUEST_PREFETCH);
+ prefetchMap.set("practitioner", PRACTITIONER_PREFETCH);
+ prefetchMap.set("patient", PATIENT_PREFETCH);
+ // prefetchMap.set("ServiceRequest", SERVICE_REQUEST_BUNDLE);
+ // prefetchMap.set("Encounter", ENCOUNTER_BUNDLE);
return prefetchMap;
}
static generateParamElementMap() {
const paramElementMap = new Map();
+ // TODO - this should just be inferred based on context. Or rather
+ // the instructions from the hook about what context to fill in
+ // Quite literally, the "context" here refers to the "context" of the
+ // cds-hook, which as of now is just hard-coded in buildRequest.js
+ // Rather than do this, which searches the request resource for information,
+ // the cds-hook should be constructed and then the context used to actually make
+ // the appropriate requests.
+ paramElementMap.set('context.userId', ['requester', 'reference'])
paramElementMap.set('context.draftOrders.DeviceRequest.id', ['id']);
paramElementMap.set('context.medications.MedicationRequest.id', ['id']);
paramElementMap.set('context.medications.MedicationDispense.id', ['id']);
diff --git a/src/components/RequestBox/RequestBox.js b/src/components/RequestBox/RequestBox.js
index 31f21b17..9ddc4f70 100644
--- a/src/components/RequestBox/RequestBox.js
+++ b/src/components/RequestBox/RequestBox.js
@@ -3,8 +3,9 @@ import FHIR from "fhirclient";
import SMARTBox from "../SMARTBox/SMARTBox";
import PatientBox from "../SMARTBox/PatientBox";
import CheckBox from '../Inputs/CheckBox';
-import { defaultValues, shortNameMap } from "../../util/data";
+import { types, defaultValues, shortNameMap } from "../../util/data";
import { getAge } from "../../util/fhir";
+import buildNewRxRequest from '../../util/buildScript.2017071.js';
import _ from "lodash";
import "./request.css";
import { PrefetchTemplate } from "../../PrefetchTemplate";
@@ -57,9 +58,15 @@ export default class RequestBox extends Component {
prepPrefetch() {
const preppedResources = new Map();
Object.keys(this.state.prefetchedResources).forEach((resourceKey) => {
- const resourceList = this.state.prefetchedResources[resourceKey].map((resource) => {
- return resource;
- })
+ let resourceList = []
+ if(Array.isArray(this.state.prefetchedResources[resourceKey])){
+ resourceList = this.state.prefetchedResources[resourceKey].map((resource) => {
+ return resource;
+ })
+ } else {
+ resourceList = this.state.prefetchedResources[resourceKey]
+ }
+
preppedResources.set(resourceKey, resourceList);
});
return preppedResources;
@@ -92,7 +99,7 @@ export default class RequestBox extends Component {
if(!prevState[elementName][key]){
prevState[elementName][key] = [];
}
- return {[elementName]: {...prevState[elementName], [key]: [...prevState[elementName][key], text]}};
+ return {[elementName]: {...prevState[elementName], [key]: text}};
});
};
@@ -212,11 +219,16 @@ export default class RequestBox extends Component {
var renderedPrefetches = new Map();
requestResources.forEach((resourceList, resourceKey) => {
const renderedList = [];
- resourceList.forEach((resource) => {
- console.log("Request resources:" + JSON.stringify(requestResources));
- console.log("Request key:" + resourceKey);
- renderedList.push(this.renderResource(resource))
- });
+ if(Array.isArray(resourceList)){
+ resourceList.forEach((resource) => {
+ console.log("Request resources:" + JSON.stringify(requestResources));
+ console.log("Request key:" + resourceKey);
+ renderedList.push(this.renderResource(resource))
+ });
+ } else {
+ renderedList.push(this.renderResource(resourceList))
+ }
+
renderedPrefetches.set(resourceKey, renderedList);
});
console.log(renderedPrefetches);
@@ -325,6 +337,64 @@ export default class RequestBox extends Component {
});
}
+ /**
+ * Send the NewRxRequestMessage to the Pharmacy Information System (PIMS)
+ */
+ sendRx = (e) => {
+ console.log("sendRx: " + this.props.pimsUrl);
+
+ // build the NewRx Message
+ var newRx = buildNewRxRequest(this.state.prefetchedResources.patient,
+ this.state.prefetchedResources.practitioner,
+ this.state.request);
+ console.log(newRx);
+ const serializer = new XMLSerializer();
+
+ // send the message to the prescriber
+ this.props.consoleLog("Sending Rx to PIMS", types.info);
+ fetch(this.props.pimsUrl, {
+ method: 'POST',
+ //mode: 'no-cors',
+ headers: {
+ 'Accept': 'application/xml',
+ 'Content-Type': 'application/xml'
+ },
+ body: serializer.serializeToString(newRx)
+ })
+ .then(response => {
+ console.log("sendRx response: ");
+ console.log(response);
+ this.props.consoleLog("Successfully sent Rx to PIMS", types.info);
+ })
+ .catch(error => {
+ console.log("sendRx error: ");
+ this.props.consoleLog("Server returned error sending Rx to PIMS: ", types.error);
+ this.props.consoleLog(error.message);
+ console.log(error);
+ });
+
+ }
+
+ resetRemsAdmin = (e) => {
+ console.log("reset rems admin: " + "localhost:8090/etasu/reset");
+
+ fetch("http://localhost:8090/etasu/reset", {
+ method: 'POST',
+ })
+ .then(response => {
+ console.log("Reset rems admin etasu: ");
+ console.log(response);
+ this.props.consoleLog("Successfully reset rems admin etasu", types.info);
+ })
+ .catch(error => {
+ console.log("Reset rems admin error: ");
+ this.props.consoleLog("Server returned error when resetting rems admin etasu: ", types.error);
+ this.props.consoleLog(error.message);
+ console.log(error);
+ });
+
+ }
+
isOrderNotSelected() {
return Object.keys(this.state.request).length === 0;
}
@@ -341,6 +411,7 @@ export default class RequestBox extends Component {
}
const disableSendToCRD = this.isOrderNotSelected() || this.props.loading ;
const disableLaunchDTR = this.isOrderNotSelected() && Object.keys(this.state.response).length === 0;
+ const disableSendRx = this.isOrderNotSelected() || this.props.loading;
return (
@@ -400,11 +471,17 @@ export default class RequestBox extends Component {
+
+
);
diff --git a/src/components/SMARTBox/PatientBox.js b/src/components/SMARTBox/PatientBox.js
index e638ac5d..468e4c26 100644
--- a/src/components/SMARTBox/PatientBox.js
+++ b/src/components/SMARTBox/PatientBox.js
@@ -38,7 +38,7 @@ export default class SMARTBox extends Component {
} else if (request.resourceType === "ServiceRequest") {
code = request.code.coding[0];
} else if (request.resourceType === "MedicationRequest"
- || request.resourceType === "MedicationDispense") {
+ || request.resourceType === "MedicationDispense") {
code = request.medicationCodeableConcept.coding[0];
}
if (code) {
@@ -77,7 +77,7 @@ export default class SMARTBox extends Component {
this.props.callback("patient", patient);
this.props.callback("openPatient", false);
this.props.clearCallback();
- if(this.state.request !== "none" ) {
+ if (this.state.request !== "none") {
const request = JSON.parse(this.state.request);
if (request.resourceType === "DeviceRequest" || request.resourceType === "ServiceRequest" || request.resourceType === "MedicationRequest" || request.resourceType === "MedicationDispense") {
this.updatePrefetchRequest(request);
@@ -85,8 +85,8 @@ export default class SMARTBox extends Component {
this.props.clearCallback();
}
}
-
- if(this.state.response !== "none") {
+
+ if (this.state.response !== "none") {
const response = JSON.parse(this.state.response);
this.updateQRResponse(patient, response);
}
@@ -98,7 +98,7 @@ export default class SMARTBox extends Component {
updatePrefetchRequest(request) {
this.props.callback(request.resourceType, request);
- const queries = this.props.updatePrefetchCallback(request, request.resourceType, "Coverage");
+ const queries = this.props.updatePrefetchCallback(request, "request", "patient", "practitioner");
queries.forEach((query, queryKey) => {
const urlQuery = this.props.ehrUrl + '/' + query;
fetch(urlQuery, {
@@ -106,10 +106,8 @@ export default class SMARTBox extends Component {
}).then((response) => {
const responseJson = response.json()
return responseJson;
- }).then((bundle) => {
- bundle['entry'].forEach((fullResource) => {
- this.props.callbackMap("prefetchedResources", queryKey, fullResource);
- });
+ }).then((resource) => {
+ this.props.callbackMap("prefetchedResources", queryKey, resource);
});
});
this.props.callback("request", request);
@@ -163,7 +161,7 @@ export default class SMARTBox extends Component {
flat: true,
})
.then((result) => {
- this.setState({ medicationDispenses: result});
+ this.setState({ medicationDispenses: result });
});
}
@@ -191,17 +189,17 @@ export default class SMARTBox extends Component {
this.setState({
response: "none"
});
- } else {
+ } else {
this.setState({
response: data.value
});
}
}
-
+
getRequests() {
const client = FHIR.client(
- this.props.params
- );
+ this.props.params
+ );
const patientId = this.props.patient.id;
this.getDeviceRequest(patientId, client);
this.getServiceRequest(patientId, client);
@@ -235,7 +233,7 @@ export default class SMARTBox extends Component {
const display = `${qr.questionnaire}: created at ${qr.authored}`;
let option = {
key: qr.id,
- text: display,
+ text: display,
value: JSON.stringify(qr),
content: (
@@ -270,24 +268,25 @@ export default class SMARTBox extends Component {
});
}
if (this.state.medicationRequests.data) {
- this.state.medicationRequests.data.forEach((e) => {
+ this.state.medicationRequests.data.forEach((e) => {
this.makeOption(e, options);
});
}
if (this.state.medicationDispenses.data) {
this.state.medicationDispenses.data.forEach((e) => {
- this.makeOption(e, options);
- })};
+ this.makeOption(e, options);
+ })
+ };
- if(this.state.questionnaireResponses.data) {
+ if (this.state.questionnaireResponses.data) {
returned = true;
this.state.questionnaireResponses.data.forEach(qr => this.makeQROption(qr, responseOptions));
}
-
+
let noResults = 'No results found.'
- if(!returned) {
- noResults = 'Loading...';
+ if (!returned) {
+ noResults = 'Loading...';
}
return (
@@ -321,10 +320,10 @@ export default class SMARTBox extends Component {
Request:
-
In Progress Form:
- { return isNaN(foo) }),
@@ -107,6 +108,11 @@ export default class RequestBuilder extends Component {
}
+ timeout = (time) => {
+ let controller = new AbortController();
+ setTimeout(()=>controller.abort(), time * 1000);
+ return controller;
+ }
submit_info(prefetch, request, patient, hook, deidentifyRecords) {
this.setState({loading: true});
@@ -128,21 +134,19 @@ export default class RequestBuilder extends Component {
}
let baseUrl = this.state.baseUrl;
const jwt = "Bearer " + createJwt(this.state.keypair, baseUrl, cdsUrl);
- console.log(jwt);
var myHeaders = new Headers({
"Content-Type": "application/json",
"authorization": jwt
});
- this.consoleLog("Fetching response from " + cdsUrl, types.info);
try {
fetch(cdsUrl, {
method: "POST",
headers: myHeaders,
- body: JSON.stringify(json_request)
+ body: JSON.stringify(json_request),
+ signal: this.timeout(10).signal //Timeout set to 10 seconds
}).then(response => {
- this.consoleLog("Received response", types.info);
+ clearTimeout(this.timeout)
response.json().then((fhirResponse) => {
- console.log(fhirResponse);
if (fhirResponse && fhirResponse.status) {
this.consoleLog("Server returned status "
+ fhirResponse.status + ": "
@@ -153,16 +157,17 @@ export default class RequestBuilder extends Component {
}
this.setState({ loading: false });
})
- }).catch(() => this.consoleLog("No response recieved from the server", types.error));
+ }).catch(() => {
+ this.consoleLog("No response received from the server", types.error);
+ this.setState({loading: false});
+ });
} catch (error) {
this.setState({ loading: false });
this.consoleLog("Unexpected error occured", types.error)
- // this.consoleLog(e.,types.error);
if (error instanceof TypeError) {
this.consoleLog(error.name + ": " + error.message, types.error);
}
}
-
}
takeSuggestion(resource) {
@@ -248,6 +253,12 @@ export default class RequestBuilder extends Component {
"value": this.state.responseExpirationDays,
"key": "responseExpirationDays"
},
+ "pimsUrl": {
+ "type": "input",
+ "display": "PIMS Server",
+ "value": this.state.pimsUrl,
+ "key": "pimsUrl"
+ },
"includeConfig": {
"type": "check",
"display": "Include Configuration in CRD Request",
@@ -302,8 +313,10 @@ export default class RequestBuilder extends Component {
patientId={this.state.patient.id}
launchUrl={this.state.launchUrl}
responseExpirationDays={this.state.responseExpirationDays}
+ pimsUrl={this.state.pimsUrl}
ref={this.requestBox}
loading={this.state.loading}
+ consoleLog={this.consoleLog}
/>
diff --git a/src/properties.json b/src/properties.json
index 725130a4..7295b544 100644
--- a/src/properties.json
+++ b/src/properties.json
@@ -5,13 +5,14 @@
"server": "http://localhost:8090",
"ehr_server": "http://localhost:8080/test-ehr/r4",
"ehr_base": "http://localhost:8080/test-ehr/r4",
- "cds_service":"http://localhost:8090/r4/cds-services",
- "order_sign": "order-sign-crd",
- "order_select": "order-select-crd",
+ "cds_service":"http://localhost:8090/cds-services",
+ "order_sign": "rems-order-sign",
+ "order_select": "rems-order-select",
"user": "alice",
"password": "alice",
"public_keys": "http://localhost:3001/public_keys",
"alt_drug": true,
"launch_url": "http://localhost:3005/launch",
- "response_expiration_days": 30
+ "response_expiration_days": 30,
+ "pims_server": "http://localhost:5051/doctorOrders/api/addRx"
}
diff --git a/src/util/buildRequest.js b/src/util/buildRequest.js
index 7fb9fff3..1095d5d6 100644
--- a/src/util/buildRequest.js
+++ b/src/util/buildRequest.js
@@ -44,7 +44,7 @@ export default function buildRequest(request, patient, ehrUrl, token, prefetch,
"subject": "cds-service4"
},
"context": {
- "userId": "Practitioner/example",
+ "userId": request.requester.reference,
"patientId": patient.id,
"encounterId": "enc89284"
}
@@ -85,41 +85,8 @@ export default function buildRequest(request, patient, ehrUrl, token, prefetch,
if(includePrefetch){
r4json.prefetch = {};
-
prefetch.forEach((resource, key) => {
- if (key === 'DeviceRequest') {
- r4json.prefetch.deviceRequestBundle = {
- "resourceType": "Bundle",
- "type": "collection",
- "entry": resource
- };
- } else if (key === 'ServiceRequest') {
- r4json.prefetch.serviceRequestBundle = {
- "resourceType": "Bundle",
- "type": "collection",
- "entry": resource
- };
- } else if(key === 'MedicationRequest') {
- r4json.prefetch.medicationRequestBundle = {
- "resourceType": "Bundle",
- "type": "collection",
- "entry": resource
- };
- } else if (key === 'MedicationDispense') {
- r4json.prefetch.medicationDispenseBundle = {
- "resourceType": "Bundle",
- "type": "collection",
- "entry": resource
- };
- } else if (key === 'Coverage') {
- r4json.prefetch.coverageBundle = {
- "resourceType": "Bundle",
- "type": "collection",
- "entry": resource
- };
- } else {
- console.error("Invalid prefetch key used: " + key + ".");
- }
+ r4json.prefetch[key] = resource
});
}
diff --git a/src/util/buildScript.2017071.js b/src/util/buildScript.2017071.js
new file mode 100644
index 00000000..62ca37df
--- /dev/null
+++ b/src/util/buildScript.2017071.js
@@ -0,0 +1,299 @@
+/* 2017071 NCPDP SCRIPT Support */
+
+ function xmlAddTextNode(xmlDoc, parent, sectionName, value) {
+ var section = xmlDoc.createElement(sectionName);
+ var textNode = xmlDoc.createTextNode(value);
+ section.appendChild(textNode);
+ parent.appendChild(section);
+ }
+
+ function buildNewRxName(doc, nameResource) {
+ var name = doc.createElement("Name");
+ xmlAddTextNode(doc, name, "LastName", nameResource.family);
+ xmlAddTextNode(doc, name, "FirstName", nameResource.given[0]);
+ return name;
+ }
+
+ function buildNewRxAddress(doc, addressResource) {
+ // console.log(addressResource);
+ var address = doc.createElement("Address");
+ xmlAddTextNode(doc, address, "AddressLine1", addressResource.line[0]);
+ xmlAddTextNode(doc, address, "City", addressResource.city);
+ xmlAddTextNode(doc, address, "StateProvince", addressResource.state);
+ xmlAddTextNode(doc, address, "PostalCode", addressResource.postalCode);
+ xmlAddTextNode(doc, address, "Country", "US"); // assume US for now
+ return address;
+ }
+
+ function buildNewRxPatient(doc, patientResource) {
+ // console.log(patientResource);
+ var patient = doc.createElement("Patient");
+ var humanPatient = doc.createElement("HumanPatient");
+
+ // Patient Name
+ const patientNameResource = patientResource.name[0];
+ humanPatient.appendChild(buildNewRxName(doc, patientNameResource));
+
+ // Patient Gender and Sex
+ var gender = "U"; // unknown
+ var patientResourceGender = patientResource.gender.toLowerCase();
+ if (patientResourceGender === "male") {
+ gender = "M"; // male
+ } else if (patientResourceGender === "female") {
+ gender = "F"; // female
+ } else if (patientResourceGender === "other") {
+ gender = "N"; // non-binary
+ }
+ xmlAddTextNode(doc, humanPatient, "Gender", gender);
+
+ // Patient Birth Date
+ var dateOfBirth = doc.createElement("DateOfBirth");
+ xmlAddTextNode(doc, dateOfBirth, "Date", patientResource.birthDate);
+ humanPatient.appendChild(dateOfBirth);
+
+ // Patient Address
+ const patientAddressResource = patientResource.address[0];
+ humanPatient.appendChild(buildNewRxAddress(doc, patientAddressResource));
+
+ patient.appendChild(humanPatient);
+ return patient;
+ }
+
+ function buildNewRxPrescriber(doc, practitionerResource) {
+ // console.log(practitionerResource);
+ var prescriber = doc.createElement("Prescriber");
+ var nonVeterinarian = doc.createElement("NonVeterinarian");
+
+ // Prescriber Identifier
+ for (let i = 0; i < practitionerResource.identifier.length; i++) {
+ let id = practitionerResource.identifier[i];
+ if ((id.system) && (id.system.includes("us-npi"))) {
+ var identification = doc.createElement("Identification");
+ xmlAddTextNode(doc, identification, "NPI", id.value);
+ nonVeterinarian.appendChild(identification);
+ }
+ }
+
+ // Prescriber Name
+ const practitionerNameResource = practitionerResource.name[0];
+ nonVeterinarian.appendChild(buildNewRxName(doc, practitionerNameResource));
+
+ // Prescriber Address
+ const practitionerAddressResource = practitionerResource.address[0];
+ nonVeterinarian.appendChild(buildNewRxAddress(doc, practitionerAddressResource));
+
+ // Prescriber Phone Number and Email
+ var communicationNumbers = doc.createElement("CommunicationNumbers");
+ for (let i = 0; i < practitionerResource.telecom.length; i++) {
+ const telecom = practitionerResource.telecom[i];
+ if (telecom.system === "phone") {
+ var primaryTelephone = doc.createElement("PrimaryTelephone");
+ xmlAddTextNode(doc, primaryTelephone, "Number", telecom.value);
+ communicationNumbers.appendChild(primaryTelephone);
+ } else if (telecom.system === "email") {
+ xmlAddTextNode(doc, communicationNumbers, "ElectronicMail", telecom.value);
+ }
+ }
+ nonVeterinarian.appendChild(communicationNumbers)
+
+ prescriber.appendChild(nonVeterinarian);
+ return prescriber;
+ }
+
+ function quantityUnitOfMeasureFromDrugFormCode(dispenseRequest) {
+ // Switch on Orderable Drug Form codes from:
+ // https://terminology.hl7.org/5.0.0/CodeSystem-v3-orderableDrugForm.html
+ // Return NCPDP QuantityUnitOfMeasure
+ if (dispenseRequest.quantity.system.toLowerCase().endsWith("v3-orderableDrugForm".toLowerCase())) {
+ // is a subset of the codes, not a complete list
+ switch (dispenseRequest.quantity.code.toUpperCase()) {
+ case "APPFUL": // Applicatorful
+ case "FOAMAPL": // Foam with Applicator
+ case "VAGFOAMAPL": // Vaginal Foam with Applicator
+ case "VAGCRMAPL": // Vaginal Cream with Applicator
+ case "OINTAPL": // Ointment with Applicator
+ case "VAGOINTAPL": // Vaginal Ointment with Applicator
+ case "GELAPL": // Gel with Applicator
+ case "VGELAPL": // Vaginal Gel with Applicator
+ return "C62412"; // Applicator
+ //case "":
+ // return "C54564" // Blister
+ case "CAPLET": // Caplet
+ return "C64696"; // Caplet
+ case 'CAP': // Capsule
+ return "C48480"; // Capsule
+ //case "":
+ // return "C64933" // Each
+ //case "":
+ // return "C53499" // Film
+ //case "":
+ // return "C48155" // Gram
+ case "GUM": // Chewing Gum
+ return "C69124"; // Gum
+ //case "":
+ // return "C48499" // Implant
+ //case "":
+ // return "C62276" // Insert
+ //case "":
+ // return "C48504" // Kit
+ //case "":
+ // return "C120263" // Lancet
+ case "ORTROCHE": // Lozenge/Oral Troche
+ return "C48506"; // Lozenge
+ //case "":
+ // return "C28254" // Milliliter
+ //case "":
+ // return "C48521" // Packet
+ case "PAD": // Pad
+ case "MEDPAD": // Medicated Pad
+ return "C65032"; // Pad
+ case "PATCH": // Patch
+ case "TPATCH": // Transdermal Patch
+ case "TPATH16": // 16 Hour Transdermal Patch
+ case "TPATH24": // 24 Hour Transdermal Patch
+ case "TPATH2WK": // Biweekly Transdermal Patch
+ case "TPATH72": // 72 Hour Transdermal Patch
+ case "TPATHWK": // Weekly Hour Transdermal Patch
+ return "C48524"; // Patch
+ //case "":
+ // return "C120216" // Pen Needle
+ //case "":
+ // return "C62609" // Ring
+ // case "":
+ // return "C53502" // Sponge
+ //case "":
+ // return "C53503" // Stick
+ //case "":
+ // return "C48538" // Strip
+ case "SUPP": // Suppository
+ case "RECSUPP": // Rectal Suppository
+ case "URETHSUPP": // Urethral Suppository
+ case "VAGSUPP": // Vaginal Suppository
+ return "C48539"; // Suppository
+ case "SWAB": // Swab
+ case "MEDSWAB": // Medicated Swab
+ return "C53504"; // Swab
+ case "TAB": // Tablet
+ case "ORTAB": // Oral Tablet
+ case "BUCTAB": // Buccal Tablet
+ case "SRBUCTAB": // Sustained Release Buccal Tablet
+ case "CHEWTAB": // Chewable Tablet
+ case "CPTAB": // Coated Particles Tablet
+ case "DISINTTAB": // Disintegrating Tablet
+ case "DRTAB": // Delayed Release Tablet
+ case "ECTAB": // Enteric Coated Tablet
+ case "ERECTTAB": // Extended Release Enteric Coated Tablet
+ case "ERTAB": // Extended Release Tablet
+ case "ERTAB12": // 12 Hour Extended Release Tablet
+ case "ERTAB24": // 24 Hour Extended Release Tablet
+ case "SLTAB": // Sublingual Tablet
+ case "VAGTAB": // Vaginal Tablet
+ return "C48542"; // Tablet
+ //case "":
+ // return "C48548" // Troche
+ case "WAFER": // Wafer
+ return "C48552"; // Wafer
+ default:
+ return "C38046"; // Unspecified
+ }
+ }
+ return "C38046"; // unspecified
+ }
+
+ function buildNewRxMedication(doc, medicationRequestResource) {
+ // console.log(medicationRequestResource);
+ var medicationPrescribed = doc.createElement("MedicationPrescribed");
+
+ // Medication Product
+ var drugCoded = doc.createElement("DrugCoded");
+
+ // loop through the coding values and find the ndc code and the rxnorm code
+ const medicationCodingList = medicationRequestResource.medicationCodeableConcept.coding;
+ for (let i = 0; i < medicationCodingList.length; i++) {
+ const coding = medicationCodingList[i];
+ const system = coding.system.toLowerCase();
+
+ if (system.endsWith("rxnorm")) {
+ // Medication Drug Description
+ xmlAddTextNode(doc, medicationPrescribed, "DrugDescription", coding.display);
+
+ } else if (system.endsWith("ndc")) {
+ // Medication Drug Code
+ var productCode = doc.createElement("ProductCode");
+ xmlAddTextNode(doc, productCode, "Code", coding.code);
+ xmlAddTextNode(doc, productCode, "Qualifier", "ND"); // National Drug Code (NDC)
+ drugCoded.appendChild(productCode);
+ }
+ }
+
+ medicationPrescribed.appendChild(drugCoded);
+
+ // Medication Quantity
+ const dispenseRequest = medicationRequestResource.dispenseRequest;
+ var quantity = doc.createElement("Quantity");
+ xmlAddTextNode(doc, quantity, "Value", dispenseRequest.quantity.value);
+ xmlAddTextNode(doc, quantity, "CodeListQualifier", 38); // Original Quantity
+ var quantityUnitOfMeasure = doc.createElement("QuantityUnitOfMeasure");
+ xmlAddTextNode(doc, quantityUnitOfMeasure, "Code", quantityUnitOfMeasureFromDrugFormCode(dispenseRequest));
+ quantity.appendChild(quantityUnitOfMeasure);
+ medicationPrescribed.appendChild(quantity);
+
+ // Medication Written Date
+ var writtenDate = doc.createElement("WrittenDate");
+ xmlAddTextNode(doc, writtenDate, "Date", medicationRequestResource.authoredOn);
+ medicationPrescribed.appendChild(writtenDate);
+
+ // Medication Substitutions (0 - None)
+ xmlAddTextNode(doc, medicationPrescribed, "Substitutions", 0);
+
+ // Medication NumberOfRefills (0 - None)
+ xmlAddTextNode(doc, medicationPrescribed, "NumberOfRefills", dispenseRequest.numberOfRepeatsAllowed);
+
+ // Medication Sig
+ var sig = doc.createElement("Sig");
+ xmlAddTextNode(doc, sig, "SigText", medicationRequestResource.dosageInstruction[0].text);
+ medicationPrescribed.appendChild(sig);
+
+ // Medication REMS
+ // A - Prescriber has checked REMS and the prescriber's actions have been completed.
+ // B - Prescriber has checked REMS and the prescriber's actions are not yet completed.
+ // N - Prescriber has not checked REMS.
+ xmlAddTextNode(doc, medicationPrescribed, "PrescriberCheckedREMS", "B");
+
+ return medicationPrescribed;
+ }
+
+ export default function buildNewRxRequest(patientResource, practitionerResource, medicationRequestResource) {
+ // console.log(medicationRequestResource);
+ var doc = document.implementation.createDocument("", "", null);
+ var message = doc.createElement("Message");
+
+ // Header
+ var header = doc.createElement("Header");
+ // generate the message id (just get the milliseconds since epoch and use that)
+ const d1 = new Date();
+ const messageIdValue = d1.getTime();
+ // console.log(messageIdValue);
+ xmlAddTextNode(doc, header, "MessageID", messageIdValue);
+ message.appendChild(header);
+
+ // Body
+ var body = doc.createElement("Body");
+ var newRx = doc.createElement("NewRx");
+
+ // Patient
+ newRx.appendChild(buildNewRxPatient(doc, patientResource));
+
+ // Prescriber
+ newRx.appendChild(buildNewRxPrescriber(doc, practitionerResource));
+
+ // Medication
+ newRx.appendChild(buildNewRxMedication(doc, medicationRequestResource));
+
+ body.appendChild(newRx);
+ message.appendChild(body);
+
+ doc.appendChild(message);
+
+ return doc;
+ }
\ No newline at end of file
diff --git a/src/util/buildScript.2022071.js b/src/util/buildScript.2022071.js
new file mode 100644
index 00000000..3ec9c2b5
--- /dev/null
+++ b/src/util/buildScript.2022071.js
@@ -0,0 +1,310 @@
+/* 2022071 NCPDP SCRIPT Support */
+
+ function xmlAddTextNode(xmlDoc, parent, sectionName, value) {
+ var section = xmlDoc.createElement(sectionName);
+ var textNode = xmlDoc.createTextNode(value);
+ section.appendChild(textNode);
+ parent.appendChild(section);
+ }
+
+ function buildNewRxName(doc, nameResource) {
+ var names = doc.createElement("Names");
+ var name = doc.createElement("Name");
+ xmlAddTextNode(doc, name, "LastName", nameResource.family);
+ xmlAddTextNode(doc, name, "FirstName", nameResource.given[0]);
+ names.appendChild(name);
+ return names;
+ }
+
+ function buildNewRxAddress(doc, addressResource) {
+ // console.log(addressResource);
+ var address = doc.createElement("Address");
+ xmlAddTextNode(doc, address, "AddressLine1", addressResource.line[0]);
+ xmlAddTextNode(doc, address, "City", addressResource.city);
+ xmlAddTextNode(doc, address, "StateProvince", addressResource.state);
+ xmlAddTextNode(doc, address, "PostalCode", addressResource.postalCode);
+ xmlAddTextNode(doc, address, "Country", "US"); // assume US for now
+ return address;
+ }
+
+ function buildNewRxPatient(doc, patientResource) {
+ // console.log(patientResource);
+ var patient = doc.createElement("Patient");
+ var humanPatient = doc.createElement("HumanPatient");
+
+ // Patient Name
+ const patientNameResource = patientResource.name[0];
+ humanPatient.appendChild(buildNewRxName(doc, patientNameResource));
+
+ // Patient Gender and Sex
+ var genderAndSex = doc.createElement("GenderAndSex");
+ var gender = "U"; // unknown
+ var patientResourceGender = patientResource.gender.toLowerCase();
+ if (patientResourceGender === "male") {
+ gender = "M"; // male
+ } else if (patientResourceGender === "female") {
+ gender = "F"; // female
+ } else if (patientResourceGender === "other") {
+ gender = "N"; // non-binary
+ }
+ xmlAddTextNode(doc, genderAndSex, "AdministrativeGender", gender);
+ humanPatient.appendChild(genderAndSex);
+
+ // Patient Birth Date
+ var dateOfBirth = doc.createElement("DateOfBirth");
+ xmlAddTextNode(doc, dateOfBirth, "Date", patientResource.birthDate);
+ humanPatient.appendChild(dateOfBirth);
+
+ // Patient Address
+ const patientAddressResource = patientResource.address[0];
+ humanPatient.appendChild(buildNewRxAddress(doc, patientAddressResource));
+
+ patient.appendChild(humanPatient);
+ return patient;
+ }
+
+ function buildNewRxPrescriber(doc, practitionerResource) {
+ // console.log(practitionerResource);
+ var prescriber = doc.createElement("Prescriber");
+ var nonVeterinarian = doc.createElement("NonVeterinarian");
+
+ // Prescriber Identifier
+ for (let i = 0; i < practitionerResource.identifier.length; i++) {
+ let id = practitionerResource.identifier[i];
+ if ((id.system) && (id.system.includes("us-npi"))) {
+ var identification = doc.createElement("Identification");
+ xmlAddTextNode(doc, identification, "NPI", id.value);
+ nonVeterinarian.appendChild(identification);
+ }
+ }
+
+ // Prescriber Name
+ const practitionerNameResource = practitionerResource.name[0];
+ nonVeterinarian.appendChild(buildNewRxName(doc, practitionerNameResource));
+
+ // Prescriber Address
+ const practitionerAddressResource = practitionerResource.address[0];
+ nonVeterinarian.appendChild(buildNewRxAddress(doc, practitionerAddressResource));
+
+ // Prescriber Phone Number and Email
+ var communicationNumbers = doc.createElement("CommunicationNumbers");
+ for (let i = 0; i < practitionerResource.telecom.length; i++) {
+ const telecom = practitionerResource.telecom[i];
+ if (telecom.system === "phone") {
+ var primaryTelephone = doc.createElement("PrimaryTelephone");
+ xmlAddTextNode(doc, primaryTelephone, "Number", telecom.value);
+ communicationNumbers.appendChild(primaryTelephone);
+ } else if (telecom.system === "email") {
+ xmlAddTextNode(doc, communicationNumbers, "ElectronicMail", telecom.value);
+ }
+ }
+ nonVeterinarian.appendChild(communicationNumbers)
+
+ prescriber.appendChild(nonVeterinarian);
+ return prescriber;
+ }
+
+ function quantityUnitOfMeasureFromDrugFormCode(dispenseRequest) {
+ // Switch on Orderable Drug Form codes from:
+ // https://terminology.hl7.org/5.0.0/CodeSystem-v3-orderableDrugForm.html
+ // Return NCPDP QuantityUnitOfMeasure
+ if (dispenseRequest.quantity.system.toLowerCase().endsWith("v3-orderableDrugForm".toLowerCase())) {
+ // is a subset of the codes, not a complete list
+ switch (dispenseRequest.quantity.code.toUpperCase()) {
+ case "APPFUL": // Applicatorful
+ case "FOAMAPL": // Foam with Applicator
+ case "VAGFOAMAPL": // Vaginal Foam with Applicator
+ case "VAGCRMAPL": // Vaginal Cream with Applicator
+ case "OINTAPL": // Ointment with Applicator
+ case "VAGOINTAPL": // Vaginal Ointment with Applicator
+ case "GELAPL": // Gel with Applicator
+ case "VGELAPL": // Vaginal Gel with Applicator
+ return "C62412"; // Applicator
+ //case "":
+ // return "C54564" // Blister
+ case "CAPLET": // Caplet
+ return "C64696"; // Caplet
+ case 'CAP': // Capsule
+ return "C48480"; // Capsule
+ //case "":
+ // return "C64933" // Each
+ //case "":
+ // return "C53499" // Film
+ //case "":
+ // return "C48155" // Gram
+ case "GUM": // Chewing Gum
+ return "C69124"; // Gum
+ //case "":
+ // return "C48499" // Implant
+ //case "":
+ // return "C62276" // Insert
+ //case "":
+ // return "C48504" // Kit
+ //case "":
+ // return "C120263" // Lancet
+ case "ORTROCHE": // Lozenge/Oral Troche
+ return "C48506"; // Lozenge
+ //case "":
+ // return "C28254" // Milliliter
+ //case "":
+ // return "C48521" // Packet
+ case "PAD": // Pad
+ case "MEDPAD": // Medicated Pad
+ return "C65032"; // Pad
+ case "PATCH": // Patch
+ case "TPATCH": // Transdermal Patch
+ case "TPATH16": // 16 Hour Transdermal Patch
+ case "TPATH24": // 24 Hour Transdermal Patch
+ case "TPATH2WK": // Biweekly Transdermal Patch
+ case "TPATH72": // 72 Hour Transdermal Patch
+ case "TPATHWK": // Weekly Hour Transdermal Patch
+ return "C48524"; // Patch
+ //case "":
+ // return "C120216" // Pen Needle
+ //case "":
+ // return "C62609" // Ring
+ // case "":
+ // return "C53502" // Sponge
+ //case "":
+ // return "C53503" // Stick
+ //case "":
+ // return "C48538" // Strip
+ case "SUPP": // Suppository
+ case "RECSUPP": // Rectal Suppository
+ case "URETHSUPP": // Urethral Suppository
+ case "VAGSUPP": // Vaginal Suppository
+ return "C48539"; // Suppository
+ case "SWAB": // Swab
+ case "MEDSWAB": // Medicated Swab
+ return "C53504"; // Swab
+ case "TAB": // Tablet
+ case "ORTAB": // Oral Tablet
+ case "BUCTAB": // Buccal Tablet
+ case "SRBUCTAB": // Sustained Release Buccal Tablet
+ case "CHEWTAB": // Chewable Tablet
+ case "CPTAB": // Coated Particles Tablet
+ case "DISINTTAB": // Disintegrating Tablet
+ case "DRTAB": // Delayed Release Tablet
+ case "ECTAB": // Enteric Coated Tablet
+ case "ERECTTAB": // Extended Release Enteric Coated Tablet
+ case "ERTAB": // Extended Release Tablet
+ case "ERTAB12": // 12 Hour Extended Release Tablet
+ case "ERTAB24": // 24 Hour Extended Release Tablet
+ case "SLTAB": // Sublingual Tablet
+ case "VAGTAB": // Vaginal Tablet
+ return "C48542"; // Tablet
+ //case "":
+ // return "C48548" // Troche
+ case "WAFER": // Wafer
+ return "C48552"; // Wafer
+ default:
+ return "C38046"; // Unspecified
+ }
+ }
+ return "C38046"; // unspecified
+ }
+
+ function buildNewRxMedication(doc, medicationRequestResource) {
+ // console.log(medicationRequestResource);
+ var medicationPrescribed = doc.createElement("MedicationPrescribed");
+
+ // Medication Product
+ var product = doc.createElement("Product");
+ var drugCoded = doc.createElement("DrugCoded");
+
+ // loop through the coding values and find the ndc code and the rxnorm code
+ const medicationCodingList = medicationRequestResource.medicationCodeableConcept.coding;
+ for (let i = 0; i < medicationCodingList.length; i++) {
+ const coding = medicationCodingList[i];
+ const system = coding.system.toLowerCase();
+
+ if (system.endsWith("rxnorm")) {
+ // Medication Drug Description
+ xmlAddTextNode(doc, medicationPrescribed, "DrugDescription", coding.display);
+
+ // Medication Drug Code
+ var productCode = doc.createElement("ProductCode");
+ xmlAddTextNode(doc, productCode, "Code", coding.code);
+ xmlAddTextNode(doc, productCode, "Qualifier", "SBD"); // RxNorm Semantic Branded Drug
+ drugCoded.appendChild(productCode);
+
+ } else if (system.endsWith("ndc")) {
+ // Medication NDC
+ xmlAddTextNode(doc, drugCoded, "NDC", coding.code); // 10-digit number
+ }
+ }
+
+ product.appendChild(drugCoded);
+ medicationPrescribed.appendChild(product);
+
+ // Medication Quantity
+ const dispenseRequest = medicationRequestResource.dispenseRequest;
+ var quantity = doc.createElement("Quantity");
+ xmlAddTextNode(doc, quantity, "Value", dispenseRequest.quantity.value);
+ xmlAddTextNode(doc, quantity, "CodeListQualifier", 38); // Original Quantity
+ var quantityUnitOfMeasure = doc.createElement("QuantityUnitOfMeasure");
+ xmlAddTextNode(doc, quantityUnitOfMeasure, "Code", quantityUnitOfMeasureFromDrugFormCode(dispenseRequest));
+ quantity.appendChild(quantityUnitOfMeasure);
+ medicationPrescribed.appendChild(quantity);
+
+ // Medication Written Date
+ var writtenDate = doc.createElement("WrittenDate");
+ xmlAddTextNode(doc, writtenDate, "Date", medicationRequestResource.authoredOn);
+ medicationPrescribed.appendChild(writtenDate);
+
+ // Medication Substitutions (0 - None)
+ var substitutions = doc.createElement("Substitutions");
+ xmlAddTextNode(doc, substitutions, "Substitutions", 0);
+ medicationPrescribed.appendChild(substitutions);
+
+ // Medication NumberOfRefills (0 - None)
+ xmlAddTextNode(doc, medicationPrescribed, "NumberOfRefills", dispenseRequest.numberOfRepeatsAllowed);
+
+ // Medication Sig
+ var sig = doc.createElement("Sig");
+ xmlAddTextNode(doc, sig, "SigText", medicationRequestResource.dosageInstruction[0].text);
+ medicationPrescribed.appendChild(sig);
+
+ // Medication REMS
+ // A - Prescriber has checked REMS and the prescriber's actions have been completed.
+ // B - Prescriber has checked REMS and the prescriber's actions are not yet completed.
+ // N - Prescriber has not checked REMS.
+ xmlAddTextNode(doc, medicationPrescribed, "PrescriberCheckedREMS", "B");
+
+ return medicationPrescribed;
+ }
+
+ export default function buildNewRxRequest(patientResource, practitionerResource, medicationRequestResource) {
+ // console.log(medicationRequestResource);
+ var doc = document.implementation.createDocument("", "", null);
+ var message = doc.createElement("Message");
+
+ // Header
+ var header = doc.createElement("Header");
+ // generate the message id (just get the milliseconds since epoch and use that)
+ const d1 = new Date();
+ const messageIdValue = d1.getTime();
+ // console.log(messageIdValue);
+ xmlAddTextNode(doc, header, "MessageID", messageIdValue);
+ message.appendChild(header);
+
+ // Body
+ var body = doc.createElement("Body");
+ var newRx = doc.createElement("NewRx");
+
+ // Patient
+ newRx.appendChild(buildNewRxPatient(doc, patientResource));
+
+ // Prescriber
+ newRx.appendChild(buildNewRxPrescriber(doc, practitionerResource));
+
+ // Medication
+ newRx.appendChild(buildNewRxMedication(doc, medicationRequestResource));
+
+ body.appendChild(newRx);
+ message.appendChild(body);
+
+ doc.appendChild(message);
+
+ return doc;
+ }
\ No newline at end of file
diff --git a/src/util/data.js b/src/util/data.js
index 0b967f23..efd2ee0a 100644
--- a/src/util/data.js
+++ b/src/util/data.js
@@ -48,6 +48,11 @@ const headers = {
"display": "In Progress Form Expiration Days",
"value": (process.env.FORM_EXPIRATION_DAYS ? process.env.FORM_EXPIRATION_DAYS : config.response_expiration_days),
"key": "responseExpirationDays"
+ },
+ "pimsUrl" : {
+ "display": "PIMS Server",
+ "value": (process.env.PIMS_URL ? process.env.PIMS_URL : config.pims_server),
+ "key": "pimsUrl"
}
}