@medplum/health-gorilla-react 5.1.1 → 5.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var index_exports={};__export(index_exports,{HealthGorillaLabOrderContext:()=>HealthGorillaLabOrderContext,HealthGorillaLabOrderProvider:()=>HealthGorillaLabOrderProvider,getAutocompleteSearchFunction:()=>getAutocompleteSearchFunction,useHealthGorillaLabOrder:()=>useHealthGorillaLabOrder,useHealthGorillaLabOrderContext:()=>useHealthGorillaLabOrderContext});module.exports=__toCommonJS(index_exports);var import_core=require("@medplum/core");function getAutocompleteSearchFunction(medplum,autocompleteBot){return async params=>{let botResponse=await medplum.executeBot(autocompleteBot,params,import_core.ContentType.JSON);if(botResponse.type==="error")throw new Error("Error executing autocomplete bot",{cause:botResponse});let{result}=botResponse;switch(botResponse.type){case"lab":{if(Array.isArray(result)&&result.every(item=>(0,import_core.isResource)(item,"Organization")))return{type:"lab",result};break}case"test":{let{result:result2}=botResponse;if(Array.isArray(result2)&&result2.every(item=>(0,import_core.isCoding)(item)))return{type:"test",result:result2};break}case"aoe":{let{result:result2}=botResponse;if(result2===void 0||result2.resourceType==="Questionnaire")return{type:"aoe",result:result2};break}default:}throw new Error(`Invalid bot response for ${JSON.stringify(params)}`)}}var import_react=require("react"),HealthGorillaLabOrderContext=(0,import_react.createContext)(void 0);var import_jsx_runtime=require("react/jsx-runtime");function HealthGorillaLabOrderProvider(props){let{children,...labOrderReturn}=props;return(0,import_jsx_runtime.jsx)(HealthGorillaLabOrderContext.Provider,{value:labOrderReturn,children})}var import_core2=require("@medplum/core"),import_health_gorilla_core=require("@medplum/health-gorilla-core"),import_react2=require("@medplum/react"),import_react3=require("react");var EDITABLE_TEST_METADATA_KEYS=["priority","notes","aoeResponses"],INITIAL_TESTS=[],INITIAL_DIAGNOSES=[],INITIAL_TEST_METADATA={},INITIAL_BILLING_INFORMATION={};function testsReducer(prev,action){switch(action.type){case"add":return addTest(prev,action);case"remove":return removeTest(prev,action);case"set":return setTests(prev,action);case"updateMetadata":return updateMetadata(prev,action);case"aoeLoaded":return aoeLoaded(prev,action);case"aoeError":return aoeError(prev,action);case"specimenCollectionDateChange":return specimenCollectionDateChange(prev,action);default:return prev}}function addTest(prev,action){return prev.selectedTests.some(test=>test.code===action.test.code)?prev:{...prev,selectedTests:[...prev.selectedTests,action.test],testMetadata:{...prev.testMetadata,[action.test.code]:{aoeStatus:"loading"}}}}function removeTest(prev,action){return{...prev,selectedTests:prev.selectedTests.filter(test=>test.code!==action.test.code),testMetadata:Object.fromEntries(Object.entries(prev.testMetadata).filter(([code])=>code!==action.test.code))}}function setTests(prev,action){return{...prev,selectedTests:action.tests,testMetadata:Object.fromEntries(action.tests.map(test=>[test.code,prev.testMetadata[test.code]??{aoeStatus:"loading"}]))}}function updateMetadata(prev,action){let{test,partialMetadata}=action,prevTestMetadata=prev.testMetadata[test.code];if(!prev.selectedTests.some(t=>t.code===test.code)||!prevTestMetadata)return console.warn(`Cannot update metadata for test ${test.code} since it is not a selected tests`),prev;let allowedUpdates=partialMetadata,restrictedKeys=Object.keys(partialMetadata).filter(k=>!EDITABLE_TEST_METADATA_KEYS.includes(k));return restrictedKeys.length>0&&(console.warn(`Cannot update test metadata fields: ${restrictedKeys.join(", ")}`),allowedUpdates=Object.fromEntries(Object.entries(partialMetadata).filter(([k])=>!restrictedKeys.includes(k)))),{...prev,testMetadata:{...prev.testMetadata,[test.code]:{...prevTestMetadata,...allowedUpdates}}}}function aoeLoaded(prev,action){let newTestMetadata;for(let[test,aoeQuestionnaire]of action.testAoes){let prevMetadata=prev.testMetadata[test.code];prevMetadata&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevMetadata,aoeStatus:aoeQuestionnaire?"loaded":"none",aoeQuestionnaire:aoeQuestionnaire&&(updateAoeQuestionnaireRequiredItems(aoeQuestionnaire,prev.specimenCollectedDateTime)??aoeQuestionnaire)})}return newTestMetadata?{...prev,testMetadata:newTestMetadata}:prev}function aoeError(prev,action){let newTestMetadata;for(let test of action.tests){let prevMetadata=prev.testMetadata[test.code];!prevMetadata||prevMetadata.aoeStatus!=="loading"||(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevMetadata,aoeStatus:"error"})}return newTestMetadata?{...prev,testMetadata:newTestMetadata}:prev}function specimenCollectionDateChange(prev,action){let newTestMetadata;for(let test of prev.selectedTests){let prevTestMetadata=prev.testMetadata[test.code];if(prevTestMetadata&&prevTestMetadata.aoeQuestionnaire){let newAoeQ=updateAoeQuestionnaireRequiredItems(prevTestMetadata.aoeQuestionnaire,action.newDate);newAoeQ&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevTestMetadata,aoeQuestionnaire:newAoeQ})}}return{...prev,testMetadata:newTestMetadata??prev.testMetadata,specimenCollectedDateTime:action.newDate}}function useHealthGorillaLabOrder(opts){let medplum=(0,import_react2.useMedplum)(),[performingLab,privateSetPerformingLab]=(0,import_react3.useState)(),[performingLabAccountNumber,privateSetPerformingLabAccountNumber]=(0,import_react3.useState)(),[testsAndMetadata,dispatchTests]=(0,import_react3.useReducer)(testsReducer,{specimenCollectedDateTime:void 0,selectedTests:INITIAL_TESTS,testMetadata:INITIAL_TEST_METADATA}),[diagnoses,privateSetDiagnoses]=(0,import_react3.useState)(INITIAL_DIAGNOSES),[billingInformation,setBillingInformation]=(0,import_react3.useState)(INITIAL_BILLING_INFORMATION),[orderNotes,privateSetOrderNotes]=(0,import_react3.useState)(),healthGorillaAutocomplete=(0,import_react3.useMemo)(()=>getAutocompleteSearchFunction(medplum,{system:"https://www.medplum.com/integrations/bot-identifier",value:"health-gorilla-labs/autocomplete"}),[medplum]),fetchAoeQuestionnaire=(0,import_react3.useCallback)(async testCode=>{let response=await healthGorillaAutocomplete({type:"aoe",testCode}),questionnaire=response.result;return questionnaire&&(0,import_health_gorilla_core.normalizeAoeQuestionnaire)(questionnaire),response.result},[healthGorillaAutocomplete]),lastSelectedTests=(0,import_react3.useRef)([]);(0,import_react3.useEffect)(()=>{if(lastSelectedTests.current===testsAndMetadata.selectedTests)return;lastSelectedTests.current=testsAndMetadata.selectedTests;let testCodingAoesBeingFetched=[];async function fetchNeededAoeQuestions(){let promises=[];for(let test of testsAndMetadata.selectedTests)testsAndMetadata.testMetadata[test.code]?.aoeStatus==="loading"&&(testCodingAoesBeingFetched.push(test),promises.push(new Promise((resolve,reject)=>{fetchAoeQuestionnaire(test).then(questionnaire=>{resolve([test,questionnaire])}).catch(reject)})));return Promise.all(promises)}fetchNeededAoeQuestions().then(results=>{results.length!==0&&dispatchTests({type:"aoeLoaded",testAoes:results})}).catch(err=>{console.error("Error fetching AOE questions",err),dispatchTests({type:"aoeError",tests:testCodingAoesBeingFetched})})},[fetchAoeQuestionnaire,testsAndMetadata]);let patientRef=(0,import_react3.useMemo)(()=>(0,import_health_gorilla_core.isReferenceOfType)("Patient",opts.patient)?opts.patient:(0,import_core2.isResource)(opts.patient)?(0,import_core2.createReference)(opts.patient):void 0,[opts.patient]),requesterRef=(0,import_react3.useMemo)(()=>{if((0,import_health_gorilla_core.isReferenceOfType)("Practitioner",opts.requester))return opts.requester;if((0,import_core2.isResource)(opts.requester))return(0,import_core2.createReference)(opts.requester);opts.requester},[opts.requester]),requestingLocationRef=(0,import_react3.useMemo)(()=>(0,import_health_gorilla_core.isReferenceOfType)("Location",opts.requestingLocation)||(0,import_health_gorilla_core.isReferenceOfType)("Organization",opts.requestingLocation)?opts.requestingLocation:(0,import_core2.isResource)(opts.requestingLocation)?(0,import_core2.createReference)(opts.requestingLocation):void 0,[opts.requestingLocation]),state=(0,import_react3.useMemo)(()=>{let cloned=(0,import_core2.deepClone)({performingLab,performingLabAccountNumber,selectedTests:testsAndMetadata.selectedTests,testMetadata:testsAndMetadata.testMetadata,diagnoses,billingInformation,specimenCollectedDateTime:testsAndMetadata.specimenCollectedDateTime,orderNotes});return cloned.specimenCollectedDateTime&&(cloned.specimenCollectedDateTime=new Date(cloned.specimenCollectedDateTime)),cloned},[performingLab,performingLabAccountNumber,testsAndMetadata.selectedTests,testsAndMetadata.testMetadata,testsAndMetadata.specimenCollectedDateTime,diagnoses,billingInformation,orderNotes]),getActivePatientCoverages=(0,import_react3.useCallback)(async()=>{if(!patientRef)return Object.assign([],{bundle:{resourceType:"Bundle",type:"searchset",entry:[],total:0}});let coverageSearch=new URLSearchParams({patient:patientRef.reference,status:"active"});return(await medplum.searchResources("Coverage",coverageSearch)).sort((a,b)=>(a.order??Number.POSITIVE_INFINITY)-(b.order??Number.POSITIVE_INFINITY))},[medplum,patientRef]),updateBillingInformation=(0,import_react3.useCallback)(billingInfo=>{setBillingInformation(prev=>({...prev,...billingInfo}))},[]);return(0,import_react3.useMemo)(()=>({state,searchAvailableLabs:async query=>(await healthGorillaAutocomplete({type:"lab",query})).result,searchAvailableTests:async query=>{if(!performingLab)return[];let hgLabId=(0,import_core2.getIdentifier)(performingLab,import_health_gorilla_core.HEALTH_GORILLA_SYSTEM);if(!hgLabId)throw new Error("No Health Gorilla identifier found for performing lab");return query.length===0?[]:(await healthGorillaAutocomplete({type:"test",query,labId:hgLabId})).result},setPerformingLab:newLab=>{privateSetPerformingLab(newLab)},setPerformingLabAccountNumber:newAccountNumber=>{privateSetPerformingLabAccountNumber(newAccountNumber)},addTest:test=>{dispatchTests({type:"add",test})},removeTest:test=>{dispatchTests({type:"remove",test})},setTests:newTests=>{dispatchTests({type:"set",tests:newTests})},updateTestMetadata:(test,partialMetadata)=>{dispatchTests({type:"updateMetadata",test,partialMetadata})},addDiagnosis(diagnosis){privateSetDiagnoses(prev=>prev.some(item=>toDiagnosisKey(item)===toDiagnosisKey(diagnosis))?prev:[...prev,diagnosis])},removeDiagnosis(diagnosis){privateSetDiagnoses(prev=>{let idx=prev.findIndex(item=>toDiagnosisKey(item)===toDiagnosisKey(diagnosis));if(idx===-1)return prev;let next=[...prev];return next.splice(idx,1),next})},setDiagnoses:newDiagnoses=>{privateSetDiagnoses(newDiagnoses)},getActivePatientCoverages,updateBillingInformation,setSpecimenCollectedDateTime:newDate=>{dispatchTests({type:"specimenCollectionDateChange",newDate})},setOrderNotes:newOrderNotes=>{privateSetOrderNotes(newOrderNotes||void 0)},validateOrder:()=>(0,import_health_gorilla_core.validateLabOrderInputs)({patient:patientRef,requester:requesterRef,performingLab:state.performingLab,performingLabAccountNumber:state.performingLabAccountNumber,selectedTests:state.selectedTests,testMetadata:state.testMetadata,diagnoses:state.diagnoses,billingInformation:state.billingInformation,specimenCollectedDateTime:state.specimenCollectedDateTime,orderNotes:state.orderNotes}),createOrderBundle:async()=>{let txn=(0,import_health_gorilla_core.createLabOrderBundle)({patient:patientRef,requester:requesterRef,requestingLocation:requestingLocationRef,performingLab:state.performingLab,performingLabAccountNumber:state.performingLabAccountNumber,selectedTests:state.selectedTests,testMetadata:state.testMetadata,diagnoses:state.diagnoses,billingInformation:state.billingInformation,specimenCollectedDateTime:state.specimenCollectedDateTime,orderNotes:state.orderNotes}),transactionResponse=await medplum.executeBatch(txn),labOrderServiceRequest;for(let entry of transactionResponse?.entry??[]){if(!entry.response?.status.startsWith("2"))throw new Error("Error creating lab order: Non-2XX status code in response entry",{cause:transactionResponse});entry.resource?.meta?.profile?.includes(import_health_gorilla_core.MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE)&&(labOrderServiceRequest=entry.resource)}if(!labOrderServiceRequest)throw new Error("Error creating lab order: Lab Order Service Request not found in response entries",{cause:transactionResponse});return{transactionResponse,serviceRequest:labOrderServiceRequest}}}),[getActivePatientCoverages,healthGorillaAutocomplete,medplum,patientRef,performingLab,requestingLocationRef,requesterRef,state,updateBillingInformation])}function toDiagnosisKey(diagnosis){return diagnosis.coding[0].code}var REQUIRED_WHEN_SPECIMEN="https://www.healthgorilla.com/fhir/StructureDefinition/questionnaire-requiredwhenspecimen";function updateAoeQuestionnaireRequiredItems(questionnaire,specimenCollectedDateTime){let newQuestionnaire,requiredWhenSpecimen=!!specimenCollectedDateTime;for(let item of(0,import_health_gorilla_core.questionnaireItemIterator)(questionnaire.item))if((0,import_core2.getExtensionValue)(item,REQUIRED_WHEN_SPECIMEN)===!0&&!!item.required!==requiredWhenSpecimen){newQuestionnaire=(0,import_core2.deepClone)(questionnaire);break}if(newQuestionnaire){for(let item of(0,import_health_gorilla_core.questionnaireItemIterator)(newQuestionnaire.item))(0,import_core2.getExtensionValue)(item,REQUIRED_WHEN_SPECIMEN)===!0&&!!item.required!==requiredWhenSpecimen&&(item.required=requiredWhenSpecimen);return newQuestionnaire}}var import_react4=require("react");function useHealthGorillaLabOrderContext(){let context=(0,import_react4.useContext)(HealthGorillaLabOrderContext);if(context===void 0)throw new Error("useHealthGorillaLabOrderContext must be used within a HealthGorillaLabOrderProvider");return context}
1
+ "use strict";var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var index_exports={};__export(index_exports,{HealthGorillaLabOrderContext:()=>HealthGorillaLabOrderContext,HealthGorillaLabOrderProvider:()=>HealthGorillaLabOrderProvider,getAutocompleteSearchFunction:()=>getAutocompleteSearchFunction,useHealthGorillaLabOrder:()=>useHealthGorillaLabOrder,useHealthGorillaLabOrderContext:()=>useHealthGorillaLabOrderContext});module.exports=__toCommonJS(index_exports);var import_core=require("@medplum/core");function getAutocompleteSearchFunction(medplum,autocompleteBot){return async params=>{let botResponse=await medplum.executeBot(autocompleteBot,params,import_core.ContentType.JSON);if(botResponse.type==="error")throw new Error("Error executing autocomplete bot",{cause:botResponse});let{result}=botResponse;switch(botResponse.type){case"lab":{if(Array.isArray(result)&&result.every(item=>(0,import_core.isResource)(item,"Organization")))return{type:"lab",result};break}case"test":{let{result:result2}=botResponse;if(Array.isArray(result2)&&result2.every(item=>(0,import_core.isCoding)(item)))return{type:"test",result:result2};break}case"aoe":{let{result:result2}=botResponse;if(result2===void 0||result2.resourceType==="Questionnaire")return{type:"aoe",result:result2};break}default:}throw new Error(`Invalid bot response for ${JSON.stringify(params)}`)}}var import_react=require("react"),HealthGorillaLabOrderContext=(0,import_react.createContext)(void 0);var import_jsx_runtime=require("react/jsx-runtime");function HealthGorillaLabOrderProvider(props){let{children,...labOrderReturn}=props;return(0,import_jsx_runtime.jsx)(HealthGorillaLabOrderContext.Provider,{value:labOrderReturn,children})}var import_core2=require("@medplum/core"),import_health_gorilla_core=require("@medplum/health-gorilla-core"),import_react2=require("@medplum/react"),import_react3=require("react");var EDITABLE_TEST_METADATA_KEYS=["priority","notes","aoeResponses"],INITIAL_TESTS=[],INITIAL_DIAGNOSES=[],INITIAL_TEST_METADATA={},INITIAL_BILLING_INFORMATION={};function testsReducer(prev,action){switch(action.type){case"add":return addTest(prev,action);case"remove":return removeTest(prev,action);case"set":return setTests(prev,action);case"updateMetadata":return updateMetadata(prev,action);case"aoeLoaded":return aoeLoaded(prev,action);case"aoeError":return aoeError(prev,action);case"specimenCollectionDateChange":return specimenCollectionDateChange(prev,action);default:return prev}}function addTest(prev,action){return prev.selectedTests.some(test=>test.code===action.test.code)?prev:{...prev,selectedTests:[...prev.selectedTests,action.test],testMetadata:{...prev.testMetadata,[action.test.code]:{aoeStatus:"loading"}}}}function removeTest(prev,action){return{...prev,selectedTests:prev.selectedTests.filter(test=>test.code!==action.test.code),testMetadata:Object.fromEntries(Object.entries(prev.testMetadata).filter(([code])=>code!==action.test.code))}}function setTests(prev,action){return{...prev,selectedTests:action.tests,testMetadata:Object.fromEntries(action.tests.map(test=>[test.code,prev.testMetadata[test.code]??{aoeStatus:"loading"}]))}}function updateMetadata(prev,action){let{test,partialMetadata}=action,prevTestMetadata=prev.testMetadata[test.code];if(!prev.selectedTests.some(t=>t.code===test.code)||!prevTestMetadata)return console.warn(`Cannot update metadata for test ${test.code} since it is not a selected tests`),prev;let allowedUpdates=partialMetadata,restrictedKeys=Object.keys(partialMetadata).filter(k=>!EDITABLE_TEST_METADATA_KEYS.includes(k));return restrictedKeys.length>0&&(console.warn(`Cannot update test metadata fields: ${restrictedKeys.join(", ")}`),allowedUpdates=Object.fromEntries(Object.entries(partialMetadata).filter(([k])=>!restrictedKeys.includes(k)))),{...prev,testMetadata:{...prev.testMetadata,[test.code]:{...prevTestMetadata,...allowedUpdates}}}}function aoeLoaded(prev,action){let newTestMetadata;for(let[test,aoeQuestionnaire]of action.testAoes){let prevMetadata=prev.testMetadata[test.code];prevMetadata&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevMetadata,aoeStatus:aoeQuestionnaire?"loaded":"none",aoeQuestionnaire:aoeQuestionnaire&&(updateAoeQuestionnaireRequiredItems(aoeQuestionnaire,prev.specimenCollectedDateTime)??aoeQuestionnaire)})}return newTestMetadata?{...prev,testMetadata:newTestMetadata}:prev}function aoeError(prev,action){let newTestMetadata;for(let test of action.tests){let prevMetadata=prev.testMetadata[test.code];prevMetadata?.aoeStatus==="loading"&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevMetadata,aoeStatus:"error"})}return newTestMetadata?{...prev,testMetadata:newTestMetadata}:prev}function specimenCollectionDateChange(prev,action){let newTestMetadata;for(let test of prev.selectedTests){let prevTestMetadata=prev.testMetadata[test.code];if(prevTestMetadata&&prevTestMetadata.aoeQuestionnaire){let newAoeQ=updateAoeQuestionnaireRequiredItems(prevTestMetadata.aoeQuestionnaire,action.newDate);newAoeQ&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevTestMetadata,aoeQuestionnaire:newAoeQ})}}return{...prev,testMetadata:newTestMetadata??prev.testMetadata,specimenCollectedDateTime:action.newDate}}function useHealthGorillaLabOrder(opts){let medplum=(0,import_react2.useMedplum)(),[performingLab,privateSetPerformingLab]=(0,import_react3.useState)(),[performingLabAccountNumber,privateSetPerformingLabAccountNumber]=(0,import_react3.useState)(),[testsAndMetadata,dispatchTests]=(0,import_react3.useReducer)(testsReducer,{specimenCollectedDateTime:void 0,selectedTests:INITIAL_TESTS,testMetadata:INITIAL_TEST_METADATA}),[diagnoses,privateSetDiagnoses]=(0,import_react3.useState)(INITIAL_DIAGNOSES),[billingInformation,setBillingInformation]=(0,import_react3.useState)(INITIAL_BILLING_INFORMATION),[orderNotes,privateSetOrderNotes]=(0,import_react3.useState)(),healthGorillaAutocomplete=(0,import_react3.useMemo)(()=>getAutocompleteSearchFunction(medplum,{system:"https://www.medplum.com/integrations/bot-identifier",value:"health-gorilla-labs/autocomplete"}),[medplum]),fetchAoeQuestionnaire=(0,import_react3.useCallback)(async testCode=>{let response=await healthGorillaAutocomplete({type:"aoe",testCode}),questionnaire=response.result;return questionnaire&&(0,import_health_gorilla_core.normalizeAoeQuestionnaire)(questionnaire),response.result},[healthGorillaAutocomplete]),lastSelectedTests=(0,import_react3.useRef)([]);(0,import_react3.useEffect)(()=>{if(lastSelectedTests.current===testsAndMetadata.selectedTests)return;lastSelectedTests.current=testsAndMetadata.selectedTests;let testCodingAoesBeingFetched=[];async function fetchNeededAoeQuestions(){let promises=[];for(let test of testsAndMetadata.selectedTests)testsAndMetadata.testMetadata[test.code]?.aoeStatus==="loading"&&(testCodingAoesBeingFetched.push(test),promises.push(new Promise((resolve,reject)=>{fetchAoeQuestionnaire(test).then(questionnaire=>{resolve([test,questionnaire])}).catch(reject)})));return Promise.all(promises)}fetchNeededAoeQuestions().then(results=>{results.length!==0&&dispatchTests({type:"aoeLoaded",testAoes:results})}).catch(err=>{console.error("Error fetching AOE questions",err),dispatchTests({type:"aoeError",tests:testCodingAoesBeingFetched})})},[fetchAoeQuestionnaire,testsAndMetadata]);let patientRef=(0,import_react3.useMemo)(()=>(0,import_health_gorilla_core.isReferenceOfType)("Patient",opts.patient)?opts.patient:(0,import_core2.isResource)(opts.patient)?(0,import_core2.createReference)(opts.patient):void 0,[opts.patient]),requesterRef=(0,import_react3.useMemo)(()=>{if((0,import_health_gorilla_core.isReferenceOfType)("Practitioner",opts.requester))return opts.requester;if((0,import_core2.isResource)(opts.requester))return(0,import_core2.createReference)(opts.requester);opts.requester},[opts.requester]),requestingLocationRef=(0,import_react3.useMemo)(()=>(0,import_health_gorilla_core.isReferenceOfType)("Location",opts.requestingLocation)||(0,import_health_gorilla_core.isReferenceOfType)("Organization",opts.requestingLocation)?opts.requestingLocation:(0,import_core2.isResource)(opts.requestingLocation)?(0,import_core2.createReference)(opts.requestingLocation):void 0,[opts.requestingLocation]),state=(0,import_react3.useMemo)(()=>{let cloned=(0,import_core2.deepClone)({performingLab,performingLabAccountNumber,selectedTests:testsAndMetadata.selectedTests,testMetadata:testsAndMetadata.testMetadata,diagnoses,billingInformation,specimenCollectedDateTime:testsAndMetadata.specimenCollectedDateTime,orderNotes});return cloned.specimenCollectedDateTime&&(cloned.specimenCollectedDateTime=new Date(cloned.specimenCollectedDateTime)),cloned},[performingLab,performingLabAccountNumber,testsAndMetadata.selectedTests,testsAndMetadata.testMetadata,testsAndMetadata.specimenCollectedDateTime,diagnoses,billingInformation,orderNotes]),getActivePatientCoverages=(0,import_react3.useCallback)(async()=>{if(!patientRef)return Object.assign([],{bundle:{resourceType:"Bundle",type:"searchset",entry:[],total:0}});let coverageSearch=new URLSearchParams({patient:patientRef.reference,status:"active"});return(await medplum.searchResources("Coverage",coverageSearch)).sort((a,b)=>(a.order??Number.POSITIVE_INFINITY)-(b.order??Number.POSITIVE_INFINITY))},[medplum,patientRef]),updateBillingInformation=(0,import_react3.useCallback)(billingInfo=>{setBillingInformation(prev=>({...prev,...billingInfo}))},[]);return(0,import_react3.useMemo)(()=>({state,searchAvailableLabs:async query=>(await healthGorillaAutocomplete({type:"lab",query})).result,searchAvailableTests:async query=>{if(!performingLab)return[];let hgLabId=(0,import_core2.getIdentifier)(performingLab,import_health_gorilla_core.HEALTH_GORILLA_SYSTEM);if(!hgLabId)throw new Error("No Health Gorilla identifier found for performing lab");return query.length===0?[]:(await healthGorillaAutocomplete({type:"test",query,labId:hgLabId})).result},setPerformingLab:newLab=>{privateSetPerformingLab(newLab)},setPerformingLabAccountNumber:newAccountNumber=>{privateSetPerformingLabAccountNumber(newAccountNumber)},addTest:test=>{dispatchTests({type:"add",test})},removeTest:test=>{dispatchTests({type:"remove",test})},setTests:newTests=>{dispatchTests({type:"set",tests:newTests})},updateTestMetadata:(test,partialMetadata)=>{dispatchTests({type:"updateMetadata",test,partialMetadata})},addDiagnosis(diagnosis){privateSetDiagnoses(prev=>prev.some(item=>toDiagnosisKey(item)===toDiagnosisKey(diagnosis))?prev:[...prev,diagnosis])},removeDiagnosis(diagnosis){privateSetDiagnoses(prev=>{let idx=prev.findIndex(item=>toDiagnosisKey(item)===toDiagnosisKey(diagnosis));if(idx===-1)return prev;let next=[...prev];return next.splice(idx,1),next})},setDiagnoses:newDiagnoses=>{privateSetDiagnoses(newDiagnoses)},getActivePatientCoverages,updateBillingInformation,setSpecimenCollectedDateTime:newDate=>{dispatchTests({type:"specimenCollectionDateChange",newDate})},setOrderNotes:newOrderNotes=>{privateSetOrderNotes(newOrderNotes||void 0)},validateOrder:()=>(0,import_health_gorilla_core.validateLabOrderInputs)({patient:patientRef,requester:requesterRef,performingLab:state.performingLab,performingLabAccountNumber:state.performingLabAccountNumber,selectedTests:state.selectedTests,testMetadata:state.testMetadata,diagnoses:state.diagnoses,billingInformation:state.billingInformation,specimenCollectedDateTime:state.specimenCollectedDateTime,orderNotes:state.orderNotes}),createOrderBundle:async()=>{let txn=(0,import_health_gorilla_core.createLabOrderBundle)({patient:patientRef,requester:requesterRef,requestingLocation:requestingLocationRef,performingLab:state.performingLab,performingLabAccountNumber:state.performingLabAccountNumber,selectedTests:state.selectedTests,testMetadata:state.testMetadata,diagnoses:state.diagnoses,billingInformation:state.billingInformation,specimenCollectedDateTime:state.specimenCollectedDateTime,orderNotes:state.orderNotes}),transactionResponse=await medplum.executeBatch(txn),labOrderServiceRequest;for(let entry of transactionResponse?.entry??[]){if(!entry.response?.status.startsWith("2"))throw new Error("Error creating lab order: Non-2XX status code in response entry",{cause:transactionResponse});entry.resource?.meta?.profile?.includes(import_health_gorilla_core.MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE)&&(labOrderServiceRequest=entry.resource)}if(!labOrderServiceRequest)throw new Error("Error creating lab order: Lab Order Service Request not found in response entries",{cause:transactionResponse});return{transactionResponse,serviceRequest:labOrderServiceRequest}}}),[getActivePatientCoverages,healthGorillaAutocomplete,medplum,patientRef,performingLab,requestingLocationRef,requesterRef,state,updateBillingInformation])}function toDiagnosisKey(diagnosis){return diagnosis.coding[0].code}var REQUIRED_WHEN_SPECIMEN="https://www.healthgorilla.com/fhir/StructureDefinition/questionnaire-requiredwhenspecimen";function updateAoeQuestionnaireRequiredItems(questionnaire,specimenCollectedDateTime){let newQuestionnaire,requiredWhenSpecimen=!!specimenCollectedDateTime;for(let item of(0,import_health_gorilla_core.questionnaireItemIterator)(questionnaire.item))if((0,import_core2.getExtensionValue)(item,REQUIRED_WHEN_SPECIMEN)===!0&&!!item.required!==requiredWhenSpecimen){newQuestionnaire=(0,import_core2.deepClone)(questionnaire);break}if(newQuestionnaire){for(let item of(0,import_health_gorilla_core.questionnaireItemIterator)(newQuestionnaire.item))(0,import_core2.getExtensionValue)(item,REQUIRED_WHEN_SPECIMEN)===!0&&!!item.required!==requiredWhenSpecimen&&(item.required=requiredWhenSpecimen);return newQuestionnaire}}var import_react4=require("react");function useHealthGorillaLabOrderContext(){let context=(0,import_react4.useContext)(HealthGorillaLabOrderContext);if(context===void 0)throw new Error("useHealthGorillaLabOrderContext must be used within a HealthGorillaLabOrderProvider");return context}
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts", "../../src/autocomplete-endpoint.ts", "../../src/HealthGorillaLabOrderContext.ts", "../../src/HealthGorillaLabOrderProvider.tsx", "../../src/useHealthGorillaLabOrder.ts", "../../src/useHealthGorillaLabOrderContext.ts"],
4
- "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nexport * from './autocomplete-endpoint';\nexport * from './HealthGorillaLabOrderContext';\nexport * from './HealthGorillaLabOrderProvider';\nexport * from './useHealthGorillaLabOrder';\nexport * from './useHealthGorillaLabOrderContext';\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient } from '@medplum/core';\nimport { ContentType, isCoding, isResource } from '@medplum/core';\nimport type { Identifier, Questionnaire } from '@medplum/fhirtypes';\nimport type { HGAutocompleteBotResponse, LabOrganization, TestCoding } from '@medplum/health-gorilla-core';\n\nexport type LabSearchParams = { type: 'lab'; query: string };\nexport type TestSearchParams = { type: 'test'; query: string; labId: string };\nexport type AOESearchParams = { type: 'aoe'; testCode: TestCoding };\n\nexport type LabSearchResult = { type: 'lab'; result: LabOrganization[] };\nexport type TestSearchResult = { type: 'test'; result: TestCoding[] };\nexport type AOESearchResult = { type: 'aoe'; result: Questionnaire | undefined };\n\nexport type LabSearch = { params: LabSearchParams; results: LabSearchResult };\nexport type TestSearch = { params: TestSearchParams; results: TestSearchResult };\nexport type AOESearch = { params: AOESearchParams; results: AOESearchResult };\nexport type HGSearch = LabSearch | TestSearch | AOESearch;\n\nexport type HGSearchFunction = <T extends HGSearch>(params: T['params']) => Promise<T['results']>;\n\nexport function getAutocompleteSearchFunction(\n medplum: MedplumClient,\n autocompleteBot: string | Identifier\n): HGSearchFunction {\n return async (params) => {\n const botResponse: HGAutocompleteBotResponse = await medplum.executeBot(autocompleteBot, params, ContentType.JSON);\n if (botResponse.type === 'error') {\n throw new Error('Error executing autocomplete bot', { cause: botResponse });\n }\n\n const { result } = botResponse;\n switch (botResponse.type) {\n case 'lab': {\n if (Array.isArray(result) && result.every((item) => isResource(item, 'Organization'))) {\n return { type: 'lab', result } as LabSearchResult;\n }\n break;\n }\n case 'test': {\n const { result } = botResponse;\n if (Array.isArray(result) && result.every((item) => isCoding(item))) {\n return { type: 'test', result } as TestSearchResult;\n }\n break;\n }\n case 'aoe': {\n const { result } = botResponse;\n if (result === undefined || result.resourceType === 'Questionnaire') {\n return { type: 'aoe', result } as AOESearchResult;\n }\n break;\n }\n default: {\n botResponse satisfies never;\n }\n }\n throw new Error(`Invalid bot response for ${JSON.stringify(params)}`);\n };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Bundle, Coverage, Questionnaire, ServiceRequest } from '@medplum/fhirtypes';\nimport type {\n BillingInformation,\n DiagnosisCodeableConcept,\n LabOrderInputErrors,\n LabOrderTestMetadata,\n LabOrganization,\n TestCoding,\n} from '@medplum/health-gorilla-core';\nimport { createContext } from 'react';\n\nexport type TestMetadata = LabOrderTestMetadata & {\n aoeStatus: 'loading' | 'loaded' | 'none' | 'error';\n /** The AOE `Questionnaire`, if any, containing Ask at Order Entry (AOE) questions for the test. */\n aoeQuestionnaire?: Questionnaire;\n};\n\nexport type HealthGorillaLabOrderState = {\n performingLab: LabOrganization | undefined;\n performingLabAccountNumber: string | undefined;\n selectedTests: TestCoding[];\n testMetadata: Record<string, TestMetadata | undefined>;\n diagnoses: DiagnosisCodeableConcept[];\n billingInformation: BillingInformation;\n specimenCollectedDateTime: Date | undefined;\n orderNotes: string | undefined;\n};\n\nexport type UseHealthGorillaLabOrderReturn = {\n state: HealthGorillaLabOrderState;\n\n searchAvailableLabs: (query: string) => Promise<LabOrganization[]>;\n /**\n * Set the desired performer for doing the diagnostic testing. Compatible with\n * results from the `searchAvailableLabs` function.\n */\n setPerformingLab: (lab: LabOrganization | undefined) => void;\n /**\n * Sets the account number for the performing lab. If not provided, the\n * `HEALTH_GORILLA_SUBTENANT_ACCOUNT_NUMBER` `Project.secret` is used.\n */\n setPerformingLabAccountNumber: (accountNumber: string | undefined) => void;\n\n searchAvailableTests: (query: string) => Promise<TestCoding[]>;\n addTest: (test: TestCoding) => void;\n removeTest: (test: TestCoding) => void;\n setTests: (tests: TestCoding[]) => void;\n\n updateTestMetadata: (test: TestCoding, metadata: Partial<LabOrderTestMetadata>) => void;\n\n addDiagnosis: (diagnosis: DiagnosisCodeableConcept) => void;\n removeDiagnosis: (diagnosis: DiagnosisCodeableConcept) => void;\n setDiagnoses: (diagnoses: DiagnosisCodeableConcept[]) => void;\n\n getActivePatientCoverages: () => Promise<Coverage[]>;\n updateBillingInformation: (billingInfo: Partial<BillingInformation>) => void;\n\n setSpecimenCollectedDateTime: (date: Date | undefined) => void;\n\n setOrderNotes: (orderNotes: string | undefined) => void;\n\n validateOrder: () => LabOrderInputErrors | undefined;\n createOrderBundle: () => Promise<{ transactionResponse: Bundle; serviceRequest: ServiceRequest }>;\n};\n\nexport const HealthGorillaLabOrderContext = createContext<UseHealthGorillaLabOrderReturn | undefined>(undefined);\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { JSX, PropsWithChildren } from 'react';\nimport type { UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext';\nimport { HealthGorillaLabOrderContext } from './HealthGorillaLabOrderContext';\n\n/**\n * A provider component that propagates the `useHealthGorillaLabOrder` return value to all children components via [React Context](https://reactjs.org/docs/context.html) API. To be used with {@link useHealthGorillaLabOrderContext}.\n *\n * @param props - entire response of `useHealthGorillaLabOrder`\n * @returns a React context provider component.\n *\n * @example\n * ```tsx\n * function App() {\n * const labOrderReturn = useHealthGorillaLabOrder({...});\n * return (\n * <HealthGorillaLabOrderProvider {...methods} >\n * <form onSubmit={labOrderReturn.createOrderBundle()}>\n * <PerformingLabInput />\n * <input type=\"submit\" />\n * </form>\n * </HealthGorillaLabOrderProvider>\n * );\n * }\n *\n * function PerformingLabInput() {\n * const { searchAvailableLabs, setPerformingLab } = useHealthGorillaLabOrderContext();\n * return (\n * <AsyncAutocomplete<LabOrganization>\n * maxValues={1}\n * loadOptions={searchAvailableLabs}\n * onChange={(item) => {\n * if (item.length > 0) {\n * setPerformingLab(item[0]);\n * } else {\n * setPerformingLab(undefined);\n * }\n * }}\n * />\n * );\n * }\n * ```\n */\nexport function HealthGorillaLabOrderProvider(props: PropsWithChildren<UseHealthGorillaLabOrderReturn>): JSX.Element {\n const { children, ...labOrderReturn } = props;\n return (\n <HealthGorillaLabOrderContext.Provider value={labOrderReturn}>{children}</HealthGorillaLabOrderContext.Provider>\n );\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { ResourceArray } from '@medplum/core';\nimport { createReference, deepClone, getExtensionValue, getIdentifier, isResource } from '@medplum/core';\nimport type {\n Bundle,\n Coverage,\n Location,\n Organization,\n Patient,\n Practitioner,\n Questionnaire,\n Reference,\n ServiceRequest,\n} from '@medplum/fhirtypes';\nimport type {\n BillingInformation,\n DiagnosisCodeableConcept,\n LabOrderServiceRequest,\n LabOrderTestMetadata,\n LabOrganization,\n TestCoding,\n} from '@medplum/health-gorilla-core';\nimport {\n HEALTH_GORILLA_SYSTEM,\n MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE,\n createLabOrderBundle,\n isReferenceOfType,\n normalizeAoeQuestionnaire,\n questionnaireItemIterator,\n validateLabOrderInputs,\n} from '@medplum/health-gorilla-core';\nimport { useMedplum } from '@medplum/react';\nimport { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\nimport type { AOESearch, LabSearch, TestSearch } from './autocomplete-endpoint';\nimport { getAutocompleteSearchFunction } from './autocomplete-endpoint';\nimport type {\n HealthGorillaLabOrderState,\n TestMetadata,\n UseHealthGorillaLabOrderReturn,\n} from './HealthGorillaLabOrderContext';\n\nexport type UseHealthGorillaLabOrderOptions = {\n /** Patient the tests are ordered for. */\n patient: Patient | (Reference<Patient> & { reference: string }) | undefined;\n /**\n * The physician who requests diagnostic procedures for the patient and responsible for the order.\n * The Practitioner MUST have an NPI identifier with the system http://hl7.org/fhir/sid/us-npi.\n */\n requester: Practitioner | (Reference<Practitioner> & { reference: string }) | undefined;\n\n /** For multi-location practices, optionally specify the location from which the order is being placed */\n requestingLocation?: Location | Organization | (Reference<Location | Organization> & { reference: string });\n};\n\nexport type EditableLabOrderTestMetadata = Pick<LabOrderTestMetadata, 'priority' | 'notes' | 'aoeResponses'>;\nconst EDITABLE_TEST_METADATA_KEYS: (keyof LabOrderTestMetadata)[] = ['priority', 'notes', 'aoeResponses'];\n\nconst INITIAL_TESTS: TestCoding[] = [];\nconst INITIAL_DIAGNOSES: DiagnosisCodeableConcept[] = [];\nconst INITIAL_TEST_METADATA = {};\nconst INITIAL_BILLING_INFORMATION: BillingInformation = {};\n\ntype TestsReducerState = {\n // Don't use any optional fields here, e.g. `selectedTests?: TestCoding[]` since that would cause\n // some loss of type safety in the reducer function by allowing the reducer to potentially return\n // incomplete state if a field is forgotten about. Instead, always use explicity `| undefined` for\n // fields that may legitimately be undefined in the state.\n\n selectedTests: TestCoding[];\n testMetadata: Record<string, TestMetadata | undefined>;\n specimenCollectedDateTime: Date | undefined;\n};\n\ntype AddTestAction = { type: 'add'; test: TestCoding };\ntype RemoveTestAction = { type: 'remove'; test: TestCoding };\ntype SetTestsAction = { type: 'set'; tests: TestCoding[] };\ntype UpdateMetadataAction = {\n type: 'updateMetadata';\n test: TestCoding;\n partialMetadata: Partial<EditableLabOrderTestMetadata>;\n};\ntype AoeLoadedAction = { type: 'aoeLoaded'; testAoes: [TestCoding, Questionnaire | undefined][] };\ntype SpecimentCollectionDateChangeAction = { type: 'specimenCollectionDateChange'; newDate: Date | undefined };\ntype AoeErrorAction = { type: 'aoeError'; tests: TestCoding[] };\n\ntype TestsAction =\n | AddTestAction\n | RemoveTestAction\n | SetTestsAction\n | UpdateMetadataAction\n | AoeLoadedAction\n | SpecimentCollectionDateChangeAction\n | AoeErrorAction;\n\nfunction testsReducer(prev: TestsReducerState, action: TestsAction): TestsReducerState {\n switch (action.type) {\n case 'add':\n return addTest(prev, action);\n case 'remove':\n return removeTest(prev, action);\n case 'set':\n return setTests(prev, action);\n case 'updateMetadata':\n return updateMetadata(prev, action);\n case 'aoeLoaded':\n return aoeLoaded(prev, action);\n case 'aoeError':\n return aoeError(prev, action);\n case 'specimenCollectionDateChange':\n return specimenCollectionDateChange(prev, action);\n default:\n action satisfies never;\n return prev;\n }\n}\n\nfunction addTest(prev: TestsReducerState, action: AddTestAction): TestsReducerState {\n if (prev.selectedTests.some((test) => test.code === action.test.code)) {\n return prev;\n }\n\n return {\n ...prev,\n selectedTests: [...prev.selectedTests, action.test],\n testMetadata: {\n ...prev.testMetadata,\n [action.test.code]: { aoeStatus: 'loading' },\n },\n };\n}\n\nfunction removeTest(prev: TestsReducerState, action: RemoveTestAction): TestsReducerState {\n return {\n ...prev,\n selectedTests: prev.selectedTests.filter((test) => test.code !== action.test.code),\n testMetadata: Object.fromEntries(Object.entries(prev.testMetadata).filter(([code]) => code !== action.test.code)),\n };\n}\n\nfunction setTests(prev: TestsReducerState, action: SetTestsAction): TestsReducerState {\n return {\n ...prev,\n selectedTests: action.tests,\n testMetadata: Object.fromEntries(\n action.tests.map((test) => {\n return [test.code, prev.testMetadata[test.code] ?? { aoeStatus: 'loading' }];\n })\n ),\n };\n}\n\nfunction updateMetadata(prev: TestsReducerState, action: UpdateMetadataAction): TestsReducerState {\n const { test, partialMetadata } = action;\n\n const prevTestMetadata = prev.testMetadata[test.code];\n if (!prev.selectedTests.some((t) => t.code === test.code) || !prevTestMetadata) {\n console.warn(`Cannot update metadata for test ${test.code} since it is not a selected tests`);\n return prev;\n }\n\n let allowedUpdates = partialMetadata;\n const restrictedKeys = Object.keys(partialMetadata).filter((k) => !EDITABLE_TEST_METADATA_KEYS.includes(k as any));\n\n if (restrictedKeys.length > 0) {\n console.warn(`Cannot update test metadata fields: ${restrictedKeys.join(', ')}`);\n\n allowedUpdates = Object.fromEntries(Object.entries(partialMetadata).filter(([k]) => !restrictedKeys.includes(k)));\n }\n\n return {\n ...prev,\n testMetadata: {\n ...prev.testMetadata,\n [test.code]: { ...prevTestMetadata, ...allowedUpdates },\n },\n };\n}\n\nfunction aoeLoaded(prev: TestsReducerState, action: AoeLoadedAction): TestsReducerState {\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const [test, aoeQuestionnaire] of action.testAoes) {\n const prevMetadata = prev.testMetadata[test.code];\n if (!prevMetadata) {\n continue;\n }\n\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = {\n ...prevMetadata,\n aoeStatus: aoeQuestionnaire ? 'loaded' : 'none',\n aoeQuestionnaire:\n aoeQuestionnaire &&\n (updateAoeQuestionnaireRequiredItems(aoeQuestionnaire, prev.specimenCollectedDateTime) ?? aoeQuestionnaire),\n };\n }\n\n if (!newTestMetadata) {\n return prev;\n }\n\n return {\n ...prev,\n testMetadata: newTestMetadata,\n };\n}\n\nfunction aoeError(prev: TestsReducerState, action: AoeErrorAction): TestsReducerState {\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const test of action.tests) {\n const prevMetadata = prev.testMetadata[test.code];\n if (!prevMetadata || prevMetadata.aoeStatus !== 'loading') {\n continue;\n }\n\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = {\n ...prevMetadata,\n aoeStatus: 'error',\n };\n }\n\n if (!newTestMetadata) {\n return prev;\n }\n\n return {\n ...prev,\n testMetadata: newTestMetadata,\n };\n}\n\nfunction specimenCollectionDateChange(\n prev: TestsReducerState,\n action: SpecimentCollectionDateChangeAction\n): TestsReducerState {\n // check if we can reuse the previous testMetadata object to avoid unnecessary downstream re-renders\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const test of prev.selectedTests) {\n const prevTestMetadata = prev.testMetadata[test.code];\n if (!prevTestMetadata) {\n continue;\n }\n\n if (prevTestMetadata.aoeQuestionnaire) {\n const newAoeQ = updateAoeQuestionnaireRequiredItems(prevTestMetadata.aoeQuestionnaire, action.newDate);\n if (newAoeQ) {\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = { ...prevTestMetadata, aoeQuestionnaire: newAoeQ };\n }\n }\n }\n\n return {\n ...prev,\n // If no changes to AOE questionnaire item.required, reuse the previous object\n testMetadata: newTestMetadata ?? prev.testMetadata,\n specimenCollectedDateTime: action.newDate,\n };\n}\n\nexport function useHealthGorillaLabOrder(opts: UseHealthGorillaLabOrderOptions): UseHealthGorillaLabOrderReturn {\n const medplum = useMedplum();\n const [performingLab, privateSetPerformingLab] = useState<LabOrganization | undefined>();\n const [performingLabAccountNumber, privateSetPerformingLabAccountNumber] = useState<string | undefined>();\n const [testsAndMetadata, dispatchTests] = useReducer(testsReducer, {\n specimenCollectedDateTime: undefined,\n selectedTests: INITIAL_TESTS,\n testMetadata: INITIAL_TEST_METADATA,\n });\n const [diagnoses, privateSetDiagnoses] = useState<DiagnosisCodeableConcept[]>(INITIAL_DIAGNOSES);\n const [billingInformation, setBillingInformation] = useState<BillingInformation>(INITIAL_BILLING_INFORMATION);\n const [orderNotes, privateSetOrderNotes] = useState<string | undefined>();\n\n const healthGorillaAutocomplete = useMemo(() => {\n return getAutocompleteSearchFunction(medplum, {\n system: 'https://www.medplum.com/integrations/bot-identifier',\n value: 'health-gorilla-labs/autocomplete',\n });\n }, [medplum]);\n\n const fetchAoeQuestionnaire = useCallback(\n async (testCode: TestCoding): Promise<Questionnaire | undefined> => {\n const response = await healthGorillaAutocomplete<AOESearch>({ type: 'aoe', testCode });\n const questionnaire = response.result;\n if (questionnaire) {\n normalizeAoeQuestionnaire(questionnaire);\n }\n return response.result;\n },\n [healthGorillaAutocomplete]\n );\n\n const lastSelectedTests = useRef<TestCoding[]>([]);\n useEffect(() => {\n // effect only runs when selectedTests changes\n if (lastSelectedTests.current === testsAndMetadata.selectedTests) {\n return;\n }\n lastSelectedTests.current = testsAndMetadata.selectedTests;\n\n const testCodingAoesBeingFetched: TestCoding[] = [];\n async function fetchNeededAoeQuestions(): Promise<[TestCoding, Questionnaire | undefined][]> {\n const promises: Promise<[TestCoding, Questionnaire | undefined]>[] = [];\n for (const test of testsAndMetadata.selectedTests) {\n const metadata = testsAndMetadata.testMetadata[test.code];\n if (metadata?.aoeStatus !== 'loading') {\n continue;\n }\n\n testCodingAoesBeingFetched.push(test);\n promises.push(\n new Promise((resolve, reject) => {\n fetchAoeQuestionnaire(test)\n .then((questionnaire) => {\n resolve([test, questionnaire]);\n })\n .catch(reject);\n })\n );\n }\n return Promise.all(promises);\n }\n\n fetchNeededAoeQuestions()\n .then((results) => {\n if (results.length === 0) {\n return;\n }\n\n dispatchTests({ type: 'aoeLoaded', testAoes: results });\n })\n .catch((err) => {\n console.error('Error fetching AOE questions', err);\n dispatchTests({ type: 'aoeError', tests: testCodingAoesBeingFetched });\n });\n }, [fetchAoeQuestionnaire, testsAndMetadata]);\n\n const patientRef = useMemo<(Reference<Patient> & { reference: string }) | undefined>(() => {\n if (isReferenceOfType('Patient', opts.patient)) {\n return opts.patient;\n } else if (isResource(opts.patient)) {\n return createReference(opts.patient);\n } else {\n return undefined;\n }\n }, [opts.patient]);\n\n const requesterRef = useMemo<(Reference<Practitioner> & { reference: string }) | undefined>(() => {\n if (isReferenceOfType('Practitioner', opts.requester)) {\n return opts.requester;\n } else if (isResource(opts.requester)) {\n return createReference(opts.requester) as Reference<Practitioner> & { reference: string };\n } else {\n opts.requester satisfies undefined;\n return undefined;\n }\n }, [opts.requester]);\n\n const requestingLocationRef = useMemo<\n (Reference<Location | Organization> & { reference: string }) | undefined\n >(() => {\n if (\n isReferenceOfType('Location', opts.requestingLocation) ||\n isReferenceOfType('Organization', opts.requestingLocation)\n ) {\n return opts.requestingLocation;\n } else if (isResource(opts.requestingLocation)) {\n return createReference(opts.requestingLocation);\n } else {\n return undefined;\n }\n }, [opts.requestingLocation]);\n\n const state: HealthGorillaLabOrderState = useMemo(() => {\n const cloned = deepClone({\n performingLab,\n performingLabAccountNumber,\n selectedTests: testsAndMetadata.selectedTests,\n testMetadata: testsAndMetadata.testMetadata,\n diagnoses,\n billingInformation,\n specimenCollectedDateTime: testsAndMetadata.specimenCollectedDateTime,\n orderNotes,\n });\n\n // specimenCollectedDateTime is a Date object which is serialized in deepClone\n if (cloned.specimenCollectedDateTime) {\n cloned.specimenCollectedDateTime = new Date(cloned.specimenCollectedDateTime);\n }\n\n return cloned;\n }, [\n performingLab,\n performingLabAccountNumber,\n testsAndMetadata.selectedTests,\n testsAndMetadata.testMetadata,\n testsAndMetadata.specimenCollectedDateTime,\n diagnoses,\n billingInformation,\n orderNotes,\n ]);\n\n const getActivePatientCoverages = useCallback(async (): Promise<ResourceArray<Coverage>> => {\n if (!patientRef) {\n const emptyResult: Coverage[] = [];\n return Object.assign(emptyResult, {\n bundle: { resourceType: 'Bundle', type: 'searchset', entry: [], total: 0 } as Bundle<Coverage>,\n });\n }\n\n const coverageSearch = new URLSearchParams({\n patient: patientRef.reference,\n status: 'active',\n });\n const coverages = (await medplum.searchResources('Coverage', coverageSearch)).sort((a, b) => {\n return (a.order ?? Number.POSITIVE_INFINITY) - (b.order ?? Number.POSITIVE_INFINITY);\n });\n\n return coverages;\n }, [medplum, patientRef]);\n\n const updateBillingInformation = useCallback((billingInfo: Partial<BillingInformation>): void => {\n setBillingInformation((prev) => {\n return { ...prev, ...billingInfo };\n });\n }, []);\n\n const result: UseHealthGorillaLabOrderReturn = useMemo(() => {\n return {\n state,\n searchAvailableLabs: async (query: string) => {\n const response = await healthGorillaAutocomplete<LabSearch>({ type: 'lab', query });\n // consider filtering to labs that have the https://www.healthgorilla.com/fhir/StructureDefinition/provider-compendium extension?\n return response.result;\n },\n searchAvailableTests: async (query: string): Promise<TestCoding[]> => {\n if (!performingLab) {\n return [];\n }\n\n const hgLabId = getIdentifier(performingLab, HEALTH_GORILLA_SYSTEM);\n if (!hgLabId) {\n throw new Error('No Health Gorilla identifier found for performing lab');\n }\n\n if (query.length === 0) {\n return [];\n }\n const response = await healthGorillaAutocomplete<TestSearch>({ type: 'test', query, labId: hgLabId });\n\n const tests = response.result;\n return tests;\n },\n setPerformingLab: (newLab: LabOrganization | undefined) => {\n privateSetPerformingLab(newLab);\n },\n setPerformingLabAccountNumber: (newAccountNumber: string | undefined) => {\n privateSetPerformingLabAccountNumber(newAccountNumber);\n },\n addTest: (test: TestCoding) => {\n dispatchTests({ type: 'add', test });\n },\n removeTest: (test: TestCoding) => {\n dispatchTests({ type: 'remove', test });\n },\n setTests: (newTests: TestCoding[]) => {\n dispatchTests({ type: 'set', tests: newTests });\n },\n\n updateTestMetadata: (test: TestCoding, partialMetadata: Partial<LabOrderTestMetadata>) => {\n dispatchTests({ type: 'updateMetadata', test, partialMetadata });\n },\n\n addDiagnosis(diagnosis: DiagnosisCodeableConcept) {\n privateSetDiagnoses((prev) => {\n if (prev.some((item) => toDiagnosisKey(item) === toDiagnosisKey(diagnosis))) {\n return prev;\n }\n return [...prev, diagnosis];\n });\n },\n removeDiagnosis(diagnosis: DiagnosisCodeableConcept) {\n privateSetDiagnoses((prev) => {\n const idx = prev.findIndex((item) => toDiagnosisKey(item) === toDiagnosisKey(diagnosis));\n if (idx === -1) {\n return prev;\n }\n const next = [...prev];\n next.splice(idx, 1);\n return next;\n });\n },\n setDiagnoses: (newDiagnoses: DiagnosisCodeableConcept[]) => {\n privateSetDiagnoses(newDiagnoses);\n },\n\n getActivePatientCoverages,\n updateBillingInformation,\n\n setSpecimenCollectedDateTime: (newDate: Date | undefined) => {\n dispatchTests({ type: 'specimenCollectionDateChange', newDate });\n },\n\n setOrderNotes: (newOrderNotes: string | undefined) => {\n // || instead of ?? so that empty string becomes undefined\n privateSetOrderNotes(newOrderNotes || undefined);\n },\n\n validateOrder: () => {\n return validateLabOrderInputs({\n patient: patientRef,\n requester: requesterRef,\n performingLab: state.performingLab,\n performingLabAccountNumber: state.performingLabAccountNumber,\n selectedTests: state.selectedTests,\n testMetadata: state.testMetadata,\n diagnoses: state.diagnoses,\n billingInformation: state.billingInformation,\n specimenCollectedDateTime: state.specimenCollectedDateTime,\n orderNotes: state.orderNotes,\n });\n },\n createOrderBundle: async (): Promise<{ transactionResponse: Bundle; serviceRequest: ServiceRequest }> => {\n const txn = createLabOrderBundle({\n patient: patientRef,\n requester: requesterRef,\n requestingLocation: requestingLocationRef,\n performingLab: state.performingLab,\n performingLabAccountNumber: state.performingLabAccountNumber,\n selectedTests: state.selectedTests,\n testMetadata: state.testMetadata,\n diagnoses: state.diagnoses,\n billingInformation: state.billingInformation,\n specimenCollectedDateTime: state.specimenCollectedDateTime,\n orderNotes: state.orderNotes,\n });\n\n const transactionResponse = await medplum.executeBatch(txn);\n\n let labOrderServiceRequest: LabOrderServiceRequest | undefined;\n\n for (const entry of transactionResponse?.entry ?? []) {\n if (!entry.response?.status.startsWith('2')) {\n throw new Error('Error creating lab order: Non-2XX status code in response entry', {\n cause: transactionResponse,\n });\n }\n if (entry.resource?.meta?.profile?.includes(MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE)) {\n labOrderServiceRequest = entry.resource as LabOrderServiceRequest;\n }\n }\n\n if (!labOrderServiceRequest) {\n throw new Error('Error creating lab order: Lab Order Service Request not found in response entries', {\n cause: transactionResponse,\n });\n }\n\n return { transactionResponse, serviceRequest: labOrderServiceRequest };\n },\n };\n }, [\n getActivePatientCoverages,\n healthGorillaAutocomplete,\n medplum,\n patientRef,\n performingLab,\n requestingLocationRef,\n requesterRef,\n state,\n updateBillingInformation,\n ]);\n\n return result;\n}\n\nfunction toDiagnosisKey(diagnosis: DiagnosisCodeableConcept): string {\n return diagnosis.coding[0].code;\n}\n\nconst REQUIRED_WHEN_SPECIMEN =\n 'https://www.healthgorilla.com/fhir/StructureDefinition/questionnaire-requiredwhenspecimen';\n\n/**\n * Updates the required status of items in an AOE questionnaire based on whether a specimen is being collected.\n * @param questionnaire - The Questionnaire to update\n * @param specimenCollectedDateTime - The date and time the specimen was collected, if any\n * @returns A new Questionnaire with the required status of items updated, or undefined if no changes are needed\n */\nfunction updateAoeQuestionnaireRequiredItems(\n questionnaire: Questionnaire,\n specimenCollectedDateTime: Date | undefined\n): Questionnaire | undefined {\n let newQuestionnaire: Questionnaire | undefined;\n const requiredWhenSpecimen = Boolean(specimenCollectedDateTime);\n\n // first, check if the questionnaire needs any changes\n for (const item of questionnaireItemIterator(questionnaire.item)) {\n if (getExtensionValue(item, REQUIRED_WHEN_SPECIMEN) === true && Boolean(item.required) !== requiredWhenSpecimen) {\n // at least one item needs updating, so crete a clone to be updated rather than updating in place\n newQuestionnaire = deepClone(questionnaire);\n break;\n }\n }\n\n // if no changes are needed, return the original Questionnaire\n if (!newQuestionnaire) {\n return undefined;\n }\n\n // otherwise, update the cloned Questionnaire\n for (const item of questionnaireItemIterator(newQuestionnaire.item)) {\n if (getExtensionValue(item, REQUIRED_WHEN_SPECIMEN) === true) {\n if (Boolean(item.required) !== requiredWhenSpecimen) {\n item.required = requiredWhenSpecimen;\n }\n }\n }\n\n return newQuestionnaire;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { useContext } from 'react';\nimport type { UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext';\nimport { HealthGorillaLabOrderContext } from './HealthGorillaLabOrderContext';\n\nexport function useHealthGorillaLabOrderContext(): UseHealthGorillaLabOrderReturn {\n const context = useContext(HealthGorillaLabOrderContext);\n if (context === undefined) {\n throw new Error('useHealthGorillaLabOrderContext must be used within a HealthGorillaLabOrderProvider');\n }\n return context;\n}\n"],
5
- "mappings": "wpBAAA,kZCGA,gBAAkD,yBAmB3C,SAAS,8BACd,QACA,gBACkB,CAClB,MAAO,OAAO,QAAW,CACvB,IAAM,YAAyC,MAAM,QAAQ,WAAW,gBAAiB,OAAQ,wBAAY,IAAI,EACjH,GAAI,YAAY,OAAS,QACvB,MAAM,IAAI,MAAM,mCAAoC,CAAE,MAAO,WAAY,CAAC,EAG5E,GAAM,CAAE,MAAO,EAAI,YACnB,OAAQ,YAAY,KAAM,CACxB,IAAK,MAAO,CACV,GAAI,MAAM,QAAQ,MAAM,GAAK,OAAO,MAAO,SAAS,wBAAW,KAAM,cAAc,CAAC,EAClF,MAAO,CAAE,KAAM,MAAO,MAAO,EAE/B,KACF,CACA,IAAK,OAAQ,CACX,GAAM,CAAE,OAAAA,OAAO,EAAI,YACnB,GAAI,MAAM,QAAQA,OAAM,GAAKA,QAAO,MAAO,SAAS,sBAAS,IAAI,CAAC,EAChE,MAAO,CAAE,KAAM,OAAQ,OAAAA,OAAO,EAEhC,KACF,CACA,IAAK,MAAO,CACV,GAAM,CAAE,OAAAA,OAAO,EAAI,YACnB,GAAIA,UAAW,QAAaA,QAAO,eAAiB,gBAClD,MAAO,CAAE,KAAM,MAAO,OAAAA,OAAO,EAE/B,KACF,CACA,QAGF,CACA,MAAM,IAAI,MAAM,4BAA4B,KAAK,UAAU,MAAM,CAAC,EAAE,CACtE,CACF,CCjDA,iBAA8B,iBAwDjB,gCAA+B,4BAA0D,MAAS,ECpB3G,oDAHG,SAAS,8BAA8B,MAAuE,CACnH,GAAM,CAAE,SAAU,GAAG,cAAe,EAAI,MACxC,SACE,wBAAC,6BAA6B,SAA7B,CAAsC,MAAO,eAAiB,SAAS,CAE5E,CC9CA,IAAAC,aAAyF,yBAoBzF,2BAQO,wCACPC,cAA2B,0BAC3BA,cAA8E,iBAuB9E,IAAM,4BAA8D,CAAC,WAAY,QAAS,cAAc,EAElG,cAA8B,CAAC,EAC/B,kBAAgD,CAAC,EACjD,sBAAwB,CAAC,EACzB,4BAAkD,CAAC,EAkCzD,SAAS,aAAa,KAAyB,OAAwC,CACrF,OAAQ,OAAO,KAAM,CACnB,IAAK,MACH,OAAO,QAAQ,KAAM,MAAM,EAC7B,IAAK,SACH,OAAO,WAAW,KAAM,MAAM,EAChC,IAAK,MACH,OAAO,SAAS,KAAM,MAAM,EAC9B,IAAK,iBACH,OAAO,eAAe,KAAM,MAAM,EACpC,IAAK,YACH,OAAO,UAAU,KAAM,MAAM,EAC/B,IAAK,WACH,OAAO,SAAS,KAAM,MAAM,EAC9B,IAAK,+BACH,OAAO,6BAA6B,KAAM,MAAM,EAClD,QAEE,OAAO,IACX,CACF,CAEA,SAAS,QAAQ,KAAyB,OAA0C,CAClF,OAAI,KAAK,cAAc,KAAM,MAAS,KAAK,OAAS,OAAO,KAAK,IAAI,EAC3D,KAGF,CACL,GAAG,KACH,cAAe,CAAC,GAAG,KAAK,cAAe,OAAO,IAAI,EAClD,aAAc,CACZ,GAAG,KAAK,aACR,CAAC,OAAO,KAAK,IAAI,EAAG,CAAE,UAAW,SAAU,CAC7C,CACF,CACF,CAEA,SAAS,WAAW,KAAyB,OAA6C,CACxF,MAAO,CACL,GAAG,KACH,cAAe,KAAK,cAAc,OAAQ,MAAS,KAAK,OAAS,OAAO,KAAK,IAAI,EACjF,aAAc,OAAO,YAAY,OAAO,QAAQ,KAAK,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,IAAM,OAAS,OAAO,KAAK,IAAI,CAAC,CAClH,CACF,CAEA,SAAS,SAAS,KAAyB,OAA2C,CACpF,MAAO,CACL,GAAG,KACH,cAAe,OAAO,MACtB,aAAc,OAAO,YACnB,OAAO,MAAM,IAAK,MACT,CAAC,KAAK,KAAM,KAAK,aAAa,KAAK,IAAI,GAAK,CAAE,UAAW,SAAU,CAAC,CAC5E,CACH,CACF,CACF,CAEA,SAAS,eAAe,KAAyB,OAAiD,CAChG,GAAM,CAAE,KAAM,eAAgB,EAAI,OAE5B,iBAAmB,KAAK,aAAa,KAAK,IAAI,EACpD,GAAI,CAAC,KAAK,cAAc,KAAM,GAAM,EAAE,OAAS,KAAK,IAAI,GAAK,CAAC,iBAC5D,eAAQ,KAAK,mCAAmC,KAAK,IAAI,mCAAmC,EACrF,KAGT,IAAI,eAAiB,gBACf,eAAiB,OAAO,KAAK,eAAe,EAAE,OAAQ,GAAM,CAAC,4BAA4B,SAAS,CAAQ,CAAC,EAEjH,OAAI,eAAe,OAAS,IAC1B,QAAQ,KAAK,uCAAuC,eAAe,KAAK,IAAI,CAAC,EAAE,EAE/E,eAAiB,OAAO,YAAY,OAAO,QAAQ,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC,IAAM,CAAC,eAAe,SAAS,CAAC,CAAC,CAAC,GAG3G,CACL,GAAG,KACH,aAAc,CACZ,GAAG,KAAK,aACR,CAAC,KAAK,IAAI,EAAG,CAAE,GAAG,iBAAkB,GAAG,cAAe,CACxD,CACF,CACF,CAEA,SAAS,UAAU,KAAyB,OAA4C,CACtF,IAAI,gBACJ,OAAW,CAAC,KAAM,gBAAgB,IAAK,OAAO,SAAU,CACtD,IAAM,aAAe,KAAK,aAAa,KAAK,IAAI,EAC3C,eAIL,kBAAoB,CAAE,GAAG,KAAK,YAAa,EAC3C,gBAAgB,KAAK,IAAI,EAAI,CAC3B,GAAG,aACH,UAAW,iBAAmB,SAAW,OACzC,iBACE,mBACC,oCAAoC,iBAAkB,KAAK,yBAAyB,GAAK,iBAC9F,EACF,CAEA,OAAK,gBAIE,CACL,GAAG,KACH,aAAc,eAChB,EANS,IAOX,CAEA,SAAS,SAAS,KAAyB,OAA2C,CACpF,IAAI,gBACJ,QAAW,QAAQ,OAAO,MAAO,CAC/B,IAAM,aAAe,KAAK,aAAa,KAAK,IAAI,EAC5C,CAAC,cAAgB,aAAa,YAAc,YAIhD,kBAAoB,CAAE,GAAG,KAAK,YAAa,EAC3C,gBAAgB,KAAK,IAAI,EAAI,CAC3B,GAAG,aACH,UAAW,OACb,EACF,CAEA,OAAK,gBAIE,CACL,GAAG,KACH,aAAc,eAChB,EANS,IAOX,CAEA,SAAS,6BACP,KACA,OACmB,CAEnB,IAAI,gBACJ,QAAW,QAAQ,KAAK,cAAe,CACrC,IAAM,iBAAmB,KAAK,aAAa,KAAK,IAAI,EACpD,GAAK,kBAID,iBAAiB,iBAAkB,CACrC,IAAM,QAAU,oCAAoC,iBAAiB,iBAAkB,OAAO,OAAO,EACjG,UACF,kBAAoB,CAAE,GAAG,KAAK,YAAa,EAC3C,gBAAgB,KAAK,IAAI,EAAI,CAAE,GAAG,iBAAkB,iBAAkB,OAAQ,EAElF,CACF,CAEA,MAAO,CACL,GAAG,KAEH,aAAc,iBAAmB,KAAK,aACtC,0BAA2B,OAAO,OACpC,CACF,CAEO,SAAS,yBAAyB,KAAuE,CAC9G,IAAM,WAAU,0BAAW,EACrB,CAAC,cAAe,uBAAuB,KAAI,wBAAsC,EACjF,CAAC,2BAA4B,oCAAoC,KAAI,wBAA6B,EAClG,CAAC,iBAAkB,aAAa,KAAI,0BAAW,aAAc,CACjE,0BAA2B,OAC3B,cAAe,cACf,aAAc,qBAChB,CAAC,EACK,CAAC,UAAW,mBAAmB,KAAI,wBAAqC,iBAAiB,EACzF,CAAC,mBAAoB,qBAAqB,KAAI,wBAA6B,2BAA2B,EACtG,CAAC,WAAY,oBAAoB,KAAI,wBAA6B,EAElE,6BAA4B,uBAAQ,IACjC,8BAA8B,QAAS,CAC5C,OAAQ,sDACR,MAAO,kCACT,CAAC,EACA,CAAC,OAAO,CAAC,EAEN,yBAAwB,2BAC5B,MAAO,UAA6D,CAClE,IAAM,SAAW,MAAM,0BAAqC,CAAE,KAAM,MAAO,QAAS,CAAC,EAC/E,cAAgB,SAAS,OAC/B,OAAI,kBACF,sDAA0B,aAAa,EAElC,SAAS,MAClB,EACA,CAAC,yBAAyB,CAC5B,EAEM,qBAAoB,sBAAqB,CAAC,CAAC,KACjD,yBAAU,IAAM,CAEd,GAAI,kBAAkB,UAAY,iBAAiB,cACjD,OAEF,kBAAkB,QAAU,iBAAiB,cAE7C,IAAM,2BAA2C,CAAC,EAClD,eAAe,yBAA8E,CAC3F,IAAM,SAA+D,CAAC,EACtE,QAAW,QAAQ,iBAAiB,cACjB,iBAAiB,aAAa,KAAK,IAAI,GAC1C,YAAc,YAI5B,2BAA2B,KAAK,IAAI,EACpC,SAAS,KACP,IAAI,QAAQ,CAAC,QAAS,SAAW,CAC/B,sBAAsB,IAAI,EACvB,KAAM,eAAkB,CACvB,QAAQ,CAAC,KAAM,aAAa,CAAC,CAC/B,CAAC,EACA,MAAM,MAAM,CACjB,CAAC,CACH,GAEF,OAAO,QAAQ,IAAI,QAAQ,CAC7B,CAEA,wBAAwB,EACrB,KAAM,SAAY,CACb,QAAQ,SAAW,GAIvB,cAAc,CAAE,KAAM,YAAa,SAAU,OAAQ,CAAC,CACxD,CAAC,EACA,MAAO,KAAQ,CACd,QAAQ,MAAM,+BAAgC,GAAG,EACjD,cAAc,CAAE,KAAM,WAAY,MAAO,0BAA2B,CAAC,CACvE,CAAC,CACL,EAAG,CAAC,sBAAuB,gBAAgB,CAAC,EAE5C,IAAM,cAAa,uBAAkE,OAC/E,8CAAkB,UAAW,KAAK,OAAO,EACpC,KAAK,WACH,yBAAW,KAAK,OAAO,KACzB,8BAAgB,KAAK,OAAO,EAEnC,OAED,CAAC,KAAK,OAAO,CAAC,EAEX,gBAAe,uBAAuE,IAAM,CAChG,MAAI,8CAAkB,eAAgB,KAAK,SAAS,EAClD,OAAO,KAAK,UACP,MAAI,yBAAW,KAAK,SAAS,EAClC,SAAO,8BAAgB,KAAK,SAAS,EAErC,KAAK,SAGT,EAAG,CAAC,KAAK,SAAS,CAAC,EAEb,yBAAwB,uBAE5B,OAEE,8CAAkB,WAAY,KAAK,kBAAkB,MACrD,8CAAkB,eAAgB,KAAK,kBAAkB,EAElD,KAAK,sBACH,yBAAW,KAAK,kBAAkB,KACpC,8BAAgB,KAAK,kBAAkB,EAE9C,OAED,CAAC,KAAK,kBAAkB,CAAC,EAEtB,SAAoC,uBAAQ,IAAM,CACtD,IAAM,UAAS,wBAAU,CACvB,cACA,2BACA,cAAe,iBAAiB,cAChC,aAAc,iBAAiB,aAC/B,UACA,mBACA,0BAA2B,iBAAiB,0BAC5C,UACF,CAAC,EAGD,OAAI,OAAO,4BACT,OAAO,0BAA4B,IAAI,KAAK,OAAO,yBAAyB,GAGvE,MACT,EAAG,CACD,cACA,2BACA,iBAAiB,cACjB,iBAAiB,aACjB,iBAAiB,0BACjB,UACA,mBACA,UACF,CAAC,EAEK,6BAA4B,2BAAY,SAA8C,CAC1F,GAAI,CAAC,WAEH,OAAO,OAAO,OADkB,CAAC,EACC,CAChC,OAAQ,CAAE,aAAc,SAAU,KAAM,YAAa,MAAO,CAAC,EAAG,MAAO,CAAE,CAC3E,CAAC,EAGH,IAAM,eAAiB,IAAI,gBAAgB,CACzC,QAAS,WAAW,UACpB,OAAQ,QACV,CAAC,EAKD,OAJmB,MAAM,QAAQ,gBAAgB,WAAY,cAAc,GAAG,KAAK,CAAC,EAAG,KAC7E,EAAE,OAAS,OAAO,oBAAsB,EAAE,OAAS,OAAO,kBACnE,CAGH,EAAG,CAAC,QAAS,UAAU,CAAC,EAElB,4BAA2B,2BAAa,aAAmD,CAC/F,sBAAuB,OACd,CAAE,GAAG,KAAM,GAAG,WAAY,EAClC,CACH,EAAG,CAAC,CAAC,EAoJL,SAlJ+C,uBAAQ,KAC9C,CACL,MACA,oBAAqB,MAAO,QACT,MAAM,0BAAqC,CAAE,KAAM,MAAO,KAAM,CAAC,GAElE,OAElB,qBAAsB,MAAO,OAAyC,CACpE,GAAI,CAAC,cACH,MAAO,CAAC,EAGV,IAAM,WAAU,4BAAc,cAAe,gDAAqB,EAClE,GAAI,CAAC,QACH,MAAM,IAAI,MAAM,uDAAuD,EAGzE,OAAI,MAAM,SAAW,EACZ,CAAC,GAEO,MAAM,0BAAsC,CAAE,KAAM,OAAQ,MAAO,MAAO,OAAQ,CAAC,GAE7E,MAEzB,EACA,iBAAmB,QAAwC,CACzD,wBAAwB,MAAM,CAChC,EACA,8BAAgC,kBAAyC,CACvE,qCAAqC,gBAAgB,CACvD,EACA,QAAU,MAAqB,CAC7B,cAAc,CAAE,KAAM,MAAO,IAAK,CAAC,CACrC,EACA,WAAa,MAAqB,CAChC,cAAc,CAAE,KAAM,SAAU,IAAK,CAAC,CACxC,EACA,SAAW,UAA2B,CACpC,cAAc,CAAE,KAAM,MAAO,MAAO,QAAS,CAAC,CAChD,EAEA,mBAAoB,CAAC,KAAkB,kBAAmD,CACxF,cAAc,CAAE,KAAM,iBAAkB,KAAM,eAAgB,CAAC,CACjE,EAEA,aAAa,UAAqC,CAChD,oBAAqB,MACf,KAAK,KAAM,MAAS,eAAe,IAAI,IAAM,eAAe,SAAS,CAAC,EACjE,KAEF,CAAC,GAAG,KAAM,SAAS,CAC3B,CACH,EACA,gBAAgB,UAAqC,CACnD,oBAAqB,MAAS,CAC5B,IAAM,IAAM,KAAK,UAAW,MAAS,eAAe,IAAI,IAAM,eAAe,SAAS,CAAC,EACvF,GAAI,MAAQ,GACV,OAAO,KAET,IAAM,KAAO,CAAC,GAAG,IAAI,EACrB,YAAK,OAAO,IAAK,CAAC,EACX,IACT,CAAC,CACH,EACA,aAAe,cAA6C,CAC1D,oBAAoB,YAAY,CAClC,EAEA,0BACA,yBAEA,6BAA+B,SAA8B,CAC3D,cAAc,CAAE,KAAM,+BAAgC,OAAQ,CAAC,CACjE,EAEA,cAAgB,eAAsC,CAEpD,qBAAqB,eAAiB,MAAS,CACjD,EAEA,cAAe,OACN,mDAAuB,CAC5B,QAAS,WACT,UAAW,aACX,cAAe,MAAM,cACrB,2BAA4B,MAAM,2BAClC,cAAe,MAAM,cACrB,aAAc,MAAM,aACpB,UAAW,MAAM,UACjB,mBAAoB,MAAM,mBAC1B,0BAA2B,MAAM,0BACjC,WAAY,MAAM,UACpB,CAAC,EAEH,kBAAmB,SAAsF,CACvG,IAAM,OAAM,iDAAqB,CAC/B,QAAS,WACT,UAAW,aACX,mBAAoB,sBACpB,cAAe,MAAM,cACrB,2BAA4B,MAAM,2BAClC,cAAe,MAAM,cACrB,aAAc,MAAM,aACpB,UAAW,MAAM,UACjB,mBAAoB,MAAM,mBAC1B,0BAA2B,MAAM,0BACjC,WAAY,MAAM,UACpB,CAAC,EAEK,oBAAsB,MAAM,QAAQ,aAAa,GAAG,EAEtD,uBAEJ,QAAW,SAAS,qBAAqB,OAAS,CAAC,EAAG,CACpD,GAAI,CAAC,MAAM,UAAU,OAAO,WAAW,GAAG,EACxC,MAAM,IAAI,MAAM,kEAAmE,CACjF,MAAO,mBACT,CAAC,EAEC,MAAM,UAAU,MAAM,SAAS,SAAS,mEAAwC,IAClF,uBAAyB,MAAM,SAEnC,CAEA,GAAI,CAAC,uBACH,MAAM,IAAI,MAAM,oFAAqF,CACnG,MAAO,mBACT,CAAC,EAGH,MAAO,CAAE,oBAAqB,eAAgB,sBAAuB,CACvE,CACF,GACC,CACD,0BACA,0BACA,QACA,WACA,cACA,sBACA,aACA,MACA,wBACF,CAAC,CAGH,CAEA,SAAS,eAAe,UAA6C,CACnE,OAAO,UAAU,OAAO,CAAC,EAAE,IAC7B,CAEA,IAAM,uBACJ,4FAQF,SAAS,oCACP,cACA,0BAC2B,CAC3B,IAAI,iBACE,qBAAuB,EAAQ,0BAGrC,QAAW,UAAQ,sDAA0B,cAAc,IAAI,EAC7D,MAAI,gCAAkB,KAAM,sBAAsB,IAAM,IAAQ,EAAQ,KAAK,WAAc,qBAAsB,CAE/G,oBAAmB,wBAAU,aAAa,EAC1C,KACF,CAIF,GAAK,iBAKL,SAAW,UAAQ,sDAA0B,iBAAiB,IAAI,KAC5D,gCAAkB,KAAM,sBAAsB,IAAM,IAClD,EAAQ,KAAK,WAAc,uBAC7B,KAAK,SAAW,sBAKtB,OAAO,iBACT,CC3mBA,IAAAC,cAA2B,iBAIpB,SAAS,iCAAkE,CAChF,IAAM,WAAU,0BAAW,4BAA4B,EACvD,GAAI,UAAY,OACd,MAAM,IAAI,MAAM,qFAAqF,EAEvG,OAAO,OACT",
4
+ "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nexport * from './autocomplete-endpoint';\nexport * from './HealthGorillaLabOrderContext';\nexport * from './HealthGorillaLabOrderProvider';\nexport * from './useHealthGorillaLabOrder';\nexport * from './useHealthGorillaLabOrderContext';\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient } from '@medplum/core';\nimport { ContentType, isCoding, isResource } from '@medplum/core';\nimport type { Identifier, Questionnaire } from '@medplum/fhirtypes';\nimport type { HGAutocompleteBotResponse, LabOrganization, TestCoding } from '@medplum/health-gorilla-core';\n\nexport type LabSearchParams = { type: 'lab'; query: string };\nexport type TestSearchParams = { type: 'test'; query: string; labId: string };\nexport type AOESearchParams = { type: 'aoe'; testCode: TestCoding };\n\nexport type LabSearchResult = { type: 'lab'; result: LabOrganization[] };\nexport type TestSearchResult = { type: 'test'; result: TestCoding[] };\nexport type AOESearchResult = { type: 'aoe'; result: Questionnaire | undefined };\n\nexport type LabSearch = { params: LabSearchParams; results: LabSearchResult };\nexport type TestSearch = { params: TestSearchParams; results: TestSearchResult };\nexport type AOESearch = { params: AOESearchParams; results: AOESearchResult };\nexport type HGSearch = LabSearch | TestSearch | AOESearch;\n\nexport type HGSearchFunction = <T extends HGSearch>(params: T['params']) => Promise<T['results']>;\n\nexport function getAutocompleteSearchFunction(\n medplum: MedplumClient,\n autocompleteBot: string | Identifier\n): HGSearchFunction {\n return async (params) => {\n const botResponse: HGAutocompleteBotResponse = await medplum.executeBot(autocompleteBot, params, ContentType.JSON);\n if (botResponse.type === 'error') {\n throw new Error('Error executing autocomplete bot', { cause: botResponse });\n }\n\n const { result } = botResponse;\n switch (botResponse.type) {\n case 'lab': {\n if (Array.isArray(result) && result.every((item) => isResource(item, 'Organization'))) {\n return { type: 'lab', result } as LabSearchResult;\n }\n break;\n }\n case 'test': {\n const { result } = botResponse;\n if (Array.isArray(result) && result.every((item) => isCoding(item))) {\n return { type: 'test', result } as TestSearchResult;\n }\n break;\n }\n case 'aoe': {\n const { result } = botResponse;\n if (result === undefined || result.resourceType === 'Questionnaire') {\n return { type: 'aoe', result } as AOESearchResult;\n }\n break;\n }\n default: {\n botResponse satisfies never;\n }\n }\n throw new Error(`Invalid bot response for ${JSON.stringify(params)}`);\n };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Bundle, Coverage, Questionnaire, ServiceRequest } from '@medplum/fhirtypes';\nimport type {\n BillingInformation,\n DiagnosisCodeableConcept,\n LabOrderInputErrors,\n LabOrderTestMetadata,\n LabOrganization,\n TestCoding,\n} from '@medplum/health-gorilla-core';\nimport { createContext } from 'react';\n\nexport type TestMetadata = LabOrderTestMetadata & {\n aoeStatus: 'loading' | 'loaded' | 'none' | 'error';\n /** The AOE `Questionnaire`, if any, containing Ask at Order Entry (AOE) questions for the test. */\n aoeQuestionnaire?: Questionnaire;\n};\n\nexport type HealthGorillaLabOrderState = {\n performingLab: LabOrganization | undefined;\n performingLabAccountNumber: string | undefined;\n selectedTests: TestCoding[];\n testMetadata: Record<string, TestMetadata | undefined>;\n diagnoses: DiagnosisCodeableConcept[];\n billingInformation: BillingInformation;\n specimenCollectedDateTime: Date | undefined;\n orderNotes: string | undefined;\n};\n\nexport type UseHealthGorillaLabOrderReturn = {\n state: HealthGorillaLabOrderState;\n\n searchAvailableLabs: (query: string) => Promise<LabOrganization[]>;\n /**\n * Set the desired performer for doing the diagnostic testing. Compatible with\n * results from the `searchAvailableLabs` function.\n */\n setPerformingLab: (lab: LabOrganization | undefined) => void;\n /**\n * Sets the account number for the performing lab. If not provided, the\n * `HEALTH_GORILLA_SUBTENANT_ACCOUNT_NUMBER` `Project.secret` is used.\n */\n setPerformingLabAccountNumber: (accountNumber: string | undefined) => void;\n\n searchAvailableTests: (query: string) => Promise<TestCoding[]>;\n addTest: (test: TestCoding) => void;\n removeTest: (test: TestCoding) => void;\n setTests: (tests: TestCoding[]) => void;\n\n updateTestMetadata: (test: TestCoding, metadata: Partial<LabOrderTestMetadata>) => void;\n\n addDiagnosis: (diagnosis: DiagnosisCodeableConcept) => void;\n removeDiagnosis: (diagnosis: DiagnosisCodeableConcept) => void;\n setDiagnoses: (diagnoses: DiagnosisCodeableConcept[]) => void;\n\n getActivePatientCoverages: () => Promise<Coverage[]>;\n updateBillingInformation: (billingInfo: Partial<BillingInformation>) => void;\n\n setSpecimenCollectedDateTime: (date: Date | undefined) => void;\n\n setOrderNotes: (orderNotes: string | undefined) => void;\n\n validateOrder: () => LabOrderInputErrors | undefined;\n createOrderBundle: () => Promise<{ transactionResponse: Bundle; serviceRequest: ServiceRequest }>;\n};\n\nexport const HealthGorillaLabOrderContext = createContext<UseHealthGorillaLabOrderReturn | undefined>(undefined);\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { JSX, PropsWithChildren } from 'react';\nimport type { UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext';\nimport { HealthGorillaLabOrderContext } from './HealthGorillaLabOrderContext';\n\n/**\n * A provider component that propagates the `useHealthGorillaLabOrder` return value to all children components via [React Context](https://reactjs.org/docs/context.html) API. To be used with {@link useHealthGorillaLabOrderContext}.\n *\n * @param props - entire response of `useHealthGorillaLabOrder`\n * @returns a React context provider component.\n *\n * @example\n * ```tsx\n * function App() {\n * const labOrderReturn = useHealthGorillaLabOrder({...});\n * return (\n * <HealthGorillaLabOrderProvider {...methods} >\n * <form onSubmit={labOrderReturn.createOrderBundle()}>\n * <PerformingLabInput />\n * <input type=\"submit\" />\n * </form>\n * </HealthGorillaLabOrderProvider>\n * );\n * }\n *\n * function PerformingLabInput() {\n * const { searchAvailableLabs, setPerformingLab } = useHealthGorillaLabOrderContext();\n * return (\n * <AsyncAutocomplete<LabOrganization>\n * maxValues={1}\n * loadOptions={searchAvailableLabs}\n * onChange={(item) => {\n * if (item.length > 0) {\n * setPerformingLab(item[0]);\n * } else {\n * setPerformingLab(undefined);\n * }\n * }}\n * />\n * );\n * }\n * ```\n */\nexport function HealthGorillaLabOrderProvider(props: PropsWithChildren<UseHealthGorillaLabOrderReturn>): JSX.Element {\n const { children, ...labOrderReturn } = props;\n return (\n <HealthGorillaLabOrderContext.Provider value={labOrderReturn}>{children}</HealthGorillaLabOrderContext.Provider>\n );\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { ResourceArray } from '@medplum/core';\nimport { createReference, deepClone, getExtensionValue, getIdentifier, isResource } from '@medplum/core';\nimport type {\n Bundle,\n Coverage,\n Location,\n Organization,\n Patient,\n Practitioner,\n Questionnaire,\n Reference,\n ServiceRequest,\n} from '@medplum/fhirtypes';\nimport type {\n BillingInformation,\n DiagnosisCodeableConcept,\n LabOrderServiceRequest,\n LabOrderTestMetadata,\n LabOrganization,\n TestCoding,\n} from '@medplum/health-gorilla-core';\nimport {\n HEALTH_GORILLA_SYSTEM,\n MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE,\n createLabOrderBundle,\n isReferenceOfType,\n normalizeAoeQuestionnaire,\n questionnaireItemIterator,\n validateLabOrderInputs,\n} from '@medplum/health-gorilla-core';\nimport { useMedplum } from '@medplum/react';\nimport { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\nimport type { AOESearch, LabSearch, TestSearch } from './autocomplete-endpoint';\nimport { getAutocompleteSearchFunction } from './autocomplete-endpoint';\nimport type {\n HealthGorillaLabOrderState,\n TestMetadata,\n UseHealthGorillaLabOrderReturn,\n} from './HealthGorillaLabOrderContext';\n\nexport type UseHealthGorillaLabOrderOptions = {\n /** Patient the tests are ordered for. */\n patient: Patient | (Reference<Patient> & { reference: string }) | undefined;\n /**\n * The physician who requests diagnostic procedures for the patient and responsible for the order.\n * The Practitioner MUST have an NPI identifier with the system http://hl7.org/fhir/sid/us-npi.\n */\n requester: Practitioner | (Reference<Practitioner> & { reference: string }) | undefined;\n\n /** For multi-location practices, optionally specify the location from which the order is being placed */\n requestingLocation?: Location | Organization | (Reference<Location | Organization> & { reference: string });\n};\n\nexport type EditableLabOrderTestMetadata = Pick<LabOrderTestMetadata, 'priority' | 'notes' | 'aoeResponses'>;\nconst EDITABLE_TEST_METADATA_KEYS: (keyof LabOrderTestMetadata)[] = ['priority', 'notes', 'aoeResponses'];\n\nconst INITIAL_TESTS: TestCoding[] = [];\nconst INITIAL_DIAGNOSES: DiagnosisCodeableConcept[] = [];\nconst INITIAL_TEST_METADATA = {};\nconst INITIAL_BILLING_INFORMATION: BillingInformation = {};\n\ntype TestsReducerState = {\n // Don't use any optional fields here, e.g. `selectedTests?: TestCoding[]` since that would cause\n // some loss of type safety in the reducer function by allowing the reducer to potentially return\n // incomplete state if a field is forgotten about. Instead, always use explicity `| undefined` for\n // fields that may legitimately be undefined in the state.\n\n selectedTests: TestCoding[];\n testMetadata: Record<string, TestMetadata | undefined>;\n specimenCollectedDateTime: Date | undefined;\n};\n\ntype AddTestAction = { type: 'add'; test: TestCoding };\ntype RemoveTestAction = { type: 'remove'; test: TestCoding };\ntype SetTestsAction = { type: 'set'; tests: TestCoding[] };\ntype UpdateMetadataAction = {\n type: 'updateMetadata';\n test: TestCoding;\n partialMetadata: Partial<EditableLabOrderTestMetadata>;\n};\ntype AoeLoadedAction = { type: 'aoeLoaded'; testAoes: [TestCoding, Questionnaire | undefined][] };\ntype SpecimentCollectionDateChangeAction = { type: 'specimenCollectionDateChange'; newDate: Date | undefined };\ntype AoeErrorAction = { type: 'aoeError'; tests: TestCoding[] };\n\ntype TestsAction =\n | AddTestAction\n | RemoveTestAction\n | SetTestsAction\n | UpdateMetadataAction\n | AoeLoadedAction\n | SpecimentCollectionDateChangeAction\n | AoeErrorAction;\n\nfunction testsReducer(prev: TestsReducerState, action: TestsAction): TestsReducerState {\n switch (action.type) {\n case 'add':\n return addTest(prev, action);\n case 'remove':\n return removeTest(prev, action);\n case 'set':\n return setTests(prev, action);\n case 'updateMetadata':\n return updateMetadata(prev, action);\n case 'aoeLoaded':\n return aoeLoaded(prev, action);\n case 'aoeError':\n return aoeError(prev, action);\n case 'specimenCollectionDateChange':\n return specimenCollectionDateChange(prev, action);\n default:\n action satisfies never;\n return prev;\n }\n}\n\nfunction addTest(prev: TestsReducerState, action: AddTestAction): TestsReducerState {\n if (prev.selectedTests.some((test) => test.code === action.test.code)) {\n return prev;\n }\n\n return {\n ...prev,\n selectedTests: [...prev.selectedTests, action.test],\n testMetadata: {\n ...prev.testMetadata,\n [action.test.code]: { aoeStatus: 'loading' },\n },\n };\n}\n\nfunction removeTest(prev: TestsReducerState, action: RemoveTestAction): TestsReducerState {\n return {\n ...prev,\n selectedTests: prev.selectedTests.filter((test) => test.code !== action.test.code),\n testMetadata: Object.fromEntries(Object.entries(prev.testMetadata).filter(([code]) => code !== action.test.code)),\n };\n}\n\nfunction setTests(prev: TestsReducerState, action: SetTestsAction): TestsReducerState {\n return {\n ...prev,\n selectedTests: action.tests,\n testMetadata: Object.fromEntries(\n action.tests.map((test) => {\n return [test.code, prev.testMetadata[test.code] ?? { aoeStatus: 'loading' }];\n })\n ),\n };\n}\n\nfunction updateMetadata(prev: TestsReducerState, action: UpdateMetadataAction): TestsReducerState {\n const { test, partialMetadata } = action;\n\n const prevTestMetadata = prev.testMetadata[test.code];\n if (!prev.selectedTests.some((t) => t.code === test.code) || !prevTestMetadata) {\n console.warn(`Cannot update metadata for test ${test.code} since it is not a selected tests`);\n return prev;\n }\n\n let allowedUpdates = partialMetadata;\n const restrictedKeys = Object.keys(partialMetadata).filter((k) => !EDITABLE_TEST_METADATA_KEYS.includes(k as any));\n\n if (restrictedKeys.length > 0) {\n console.warn(`Cannot update test metadata fields: ${restrictedKeys.join(', ')}`);\n\n allowedUpdates = Object.fromEntries(Object.entries(partialMetadata).filter(([k]) => !restrictedKeys.includes(k)));\n }\n\n return {\n ...prev,\n testMetadata: {\n ...prev.testMetadata,\n [test.code]: { ...prevTestMetadata, ...allowedUpdates },\n },\n };\n}\n\nfunction aoeLoaded(prev: TestsReducerState, action: AoeLoadedAction): TestsReducerState {\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const [test, aoeQuestionnaire] of action.testAoes) {\n const prevMetadata = prev.testMetadata[test.code];\n if (!prevMetadata) {\n continue;\n }\n\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = {\n ...prevMetadata,\n aoeStatus: aoeQuestionnaire ? 'loaded' : 'none',\n aoeQuestionnaire:\n aoeQuestionnaire &&\n (updateAoeQuestionnaireRequiredItems(aoeQuestionnaire, prev.specimenCollectedDateTime) ?? aoeQuestionnaire),\n };\n }\n\n if (!newTestMetadata) {\n return prev;\n }\n\n return {\n ...prev,\n testMetadata: newTestMetadata,\n };\n}\n\nfunction aoeError(prev: TestsReducerState, action: AoeErrorAction): TestsReducerState {\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const test of action.tests) {\n const prevMetadata = prev.testMetadata[test.code];\n if (prevMetadata?.aoeStatus !== 'loading') {\n continue;\n }\n\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = {\n ...prevMetadata,\n aoeStatus: 'error',\n };\n }\n\n if (!newTestMetadata) {\n return prev;\n }\n\n return {\n ...prev,\n testMetadata: newTestMetadata,\n };\n}\n\nfunction specimenCollectionDateChange(\n prev: TestsReducerState,\n action: SpecimentCollectionDateChangeAction\n): TestsReducerState {\n // check if we can reuse the previous testMetadata object to avoid unnecessary downstream re-renders\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const test of prev.selectedTests) {\n const prevTestMetadata = prev.testMetadata[test.code];\n if (!prevTestMetadata) {\n continue;\n }\n\n if (prevTestMetadata.aoeQuestionnaire) {\n const newAoeQ = updateAoeQuestionnaireRequiredItems(prevTestMetadata.aoeQuestionnaire, action.newDate);\n if (newAoeQ) {\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = { ...prevTestMetadata, aoeQuestionnaire: newAoeQ };\n }\n }\n }\n\n return {\n ...prev,\n // If no changes to AOE questionnaire item.required, reuse the previous object\n testMetadata: newTestMetadata ?? prev.testMetadata,\n specimenCollectedDateTime: action.newDate,\n };\n}\n\nexport function useHealthGorillaLabOrder(opts: UseHealthGorillaLabOrderOptions): UseHealthGorillaLabOrderReturn {\n const medplum = useMedplum();\n const [performingLab, privateSetPerformingLab] = useState<LabOrganization | undefined>();\n const [performingLabAccountNumber, privateSetPerformingLabAccountNumber] = useState<string | undefined>();\n const [testsAndMetadata, dispatchTests] = useReducer(testsReducer, {\n specimenCollectedDateTime: undefined,\n selectedTests: INITIAL_TESTS,\n testMetadata: INITIAL_TEST_METADATA,\n });\n const [diagnoses, privateSetDiagnoses] = useState<DiagnosisCodeableConcept[]>(INITIAL_DIAGNOSES);\n const [billingInformation, setBillingInformation] = useState<BillingInformation>(INITIAL_BILLING_INFORMATION);\n const [orderNotes, privateSetOrderNotes] = useState<string | undefined>();\n\n const healthGorillaAutocomplete = useMemo(() => {\n return getAutocompleteSearchFunction(medplum, {\n system: 'https://www.medplum.com/integrations/bot-identifier',\n value: 'health-gorilla-labs/autocomplete',\n });\n }, [medplum]);\n\n const fetchAoeQuestionnaire = useCallback(\n async (testCode: TestCoding): Promise<Questionnaire | undefined> => {\n const response = await healthGorillaAutocomplete<AOESearch>({ type: 'aoe', testCode });\n const questionnaire = response.result;\n if (questionnaire) {\n normalizeAoeQuestionnaire(questionnaire);\n }\n return response.result;\n },\n [healthGorillaAutocomplete]\n );\n\n const lastSelectedTests = useRef<TestCoding[]>([]);\n useEffect(() => {\n // effect only runs when selectedTests changes\n if (lastSelectedTests.current === testsAndMetadata.selectedTests) {\n return;\n }\n lastSelectedTests.current = testsAndMetadata.selectedTests;\n\n const testCodingAoesBeingFetched: TestCoding[] = [];\n async function fetchNeededAoeQuestions(): Promise<[TestCoding, Questionnaire | undefined][]> {\n const promises: Promise<[TestCoding, Questionnaire | undefined]>[] = [];\n for (const test of testsAndMetadata.selectedTests) {\n const metadata = testsAndMetadata.testMetadata[test.code];\n if (metadata?.aoeStatus !== 'loading') {\n continue;\n }\n\n testCodingAoesBeingFetched.push(test);\n promises.push(\n new Promise((resolve, reject) => {\n fetchAoeQuestionnaire(test)\n .then((questionnaire) => {\n resolve([test, questionnaire]);\n })\n .catch(reject);\n })\n );\n }\n return Promise.all(promises);\n }\n\n fetchNeededAoeQuestions()\n .then((results) => {\n if (results.length === 0) {\n return;\n }\n\n dispatchTests({ type: 'aoeLoaded', testAoes: results });\n })\n .catch((err) => {\n console.error('Error fetching AOE questions', err);\n dispatchTests({ type: 'aoeError', tests: testCodingAoesBeingFetched });\n });\n }, [fetchAoeQuestionnaire, testsAndMetadata]);\n\n const patientRef = useMemo<(Reference<Patient> & { reference: string }) | undefined>(() => {\n if (isReferenceOfType('Patient', opts.patient)) {\n return opts.patient;\n } else if (isResource(opts.patient)) {\n return createReference(opts.patient);\n } else {\n return undefined;\n }\n }, [opts.patient]);\n\n const requesterRef = useMemo<(Reference<Practitioner> & { reference: string }) | undefined>(() => {\n if (isReferenceOfType('Practitioner', opts.requester)) {\n return opts.requester;\n } else if (isResource(opts.requester)) {\n return createReference(opts.requester) as Reference<Practitioner> & { reference: string };\n } else {\n opts.requester satisfies undefined;\n return undefined;\n }\n }, [opts.requester]);\n\n const requestingLocationRef = useMemo<\n (Reference<Location | Organization> & { reference: string }) | undefined\n >(() => {\n if (\n isReferenceOfType('Location', opts.requestingLocation) ||\n isReferenceOfType('Organization', opts.requestingLocation)\n ) {\n return opts.requestingLocation;\n } else if (isResource(opts.requestingLocation)) {\n return createReference(opts.requestingLocation);\n } else {\n return undefined;\n }\n }, [opts.requestingLocation]);\n\n const state: HealthGorillaLabOrderState = useMemo(() => {\n const cloned = deepClone({\n performingLab,\n performingLabAccountNumber,\n selectedTests: testsAndMetadata.selectedTests,\n testMetadata: testsAndMetadata.testMetadata,\n diagnoses,\n billingInformation,\n specimenCollectedDateTime: testsAndMetadata.specimenCollectedDateTime,\n orderNotes,\n });\n\n // specimenCollectedDateTime is a Date object which is serialized in deepClone\n if (cloned.specimenCollectedDateTime) {\n cloned.specimenCollectedDateTime = new Date(cloned.specimenCollectedDateTime);\n }\n\n return cloned;\n }, [\n performingLab,\n performingLabAccountNumber,\n testsAndMetadata.selectedTests,\n testsAndMetadata.testMetadata,\n testsAndMetadata.specimenCollectedDateTime,\n diagnoses,\n billingInformation,\n orderNotes,\n ]);\n\n const getActivePatientCoverages = useCallback(async (): Promise<ResourceArray<Coverage>> => {\n if (!patientRef) {\n const emptyResult: Coverage[] = [];\n return Object.assign(emptyResult, {\n bundle: { resourceType: 'Bundle', type: 'searchset', entry: [], total: 0 } as Bundle<Coverage>,\n });\n }\n\n const coverageSearch = new URLSearchParams({\n patient: patientRef.reference,\n status: 'active',\n });\n const coverages = (await medplum.searchResources('Coverage', coverageSearch)).sort((a, b) => {\n return (a.order ?? Number.POSITIVE_INFINITY) - (b.order ?? Number.POSITIVE_INFINITY);\n });\n\n return coverages;\n }, [medplum, patientRef]);\n\n const updateBillingInformation = useCallback((billingInfo: Partial<BillingInformation>): void => {\n setBillingInformation((prev) => {\n return { ...prev, ...billingInfo };\n });\n }, []);\n\n const result: UseHealthGorillaLabOrderReturn = useMemo(() => {\n return {\n state,\n searchAvailableLabs: async (query: string) => {\n const response = await healthGorillaAutocomplete<LabSearch>({ type: 'lab', query });\n // consider filtering to labs that have the https://www.healthgorilla.com/fhir/StructureDefinition/provider-compendium extension?\n return response.result;\n },\n searchAvailableTests: async (query: string): Promise<TestCoding[]> => {\n if (!performingLab) {\n return [];\n }\n\n const hgLabId = getIdentifier(performingLab, HEALTH_GORILLA_SYSTEM);\n if (!hgLabId) {\n throw new Error('No Health Gorilla identifier found for performing lab');\n }\n\n if (query.length === 0) {\n return [];\n }\n const response = await healthGorillaAutocomplete<TestSearch>({ type: 'test', query, labId: hgLabId });\n\n const tests = response.result;\n return tests;\n },\n setPerformingLab: (newLab: LabOrganization | undefined) => {\n privateSetPerformingLab(newLab);\n },\n setPerformingLabAccountNumber: (newAccountNumber: string | undefined) => {\n privateSetPerformingLabAccountNumber(newAccountNumber);\n },\n addTest: (test: TestCoding) => {\n dispatchTests({ type: 'add', test });\n },\n removeTest: (test: TestCoding) => {\n dispatchTests({ type: 'remove', test });\n },\n setTests: (newTests: TestCoding[]) => {\n dispatchTests({ type: 'set', tests: newTests });\n },\n\n updateTestMetadata: (test: TestCoding, partialMetadata: Partial<LabOrderTestMetadata>) => {\n dispatchTests({ type: 'updateMetadata', test, partialMetadata });\n },\n\n addDiagnosis(diagnosis: DiagnosisCodeableConcept) {\n privateSetDiagnoses((prev) => {\n if (prev.some((item) => toDiagnosisKey(item) === toDiagnosisKey(diagnosis))) {\n return prev;\n }\n return [...prev, diagnosis];\n });\n },\n removeDiagnosis(diagnosis: DiagnosisCodeableConcept) {\n privateSetDiagnoses((prev) => {\n const idx = prev.findIndex((item) => toDiagnosisKey(item) === toDiagnosisKey(diagnosis));\n if (idx === -1) {\n return prev;\n }\n const next = [...prev];\n next.splice(idx, 1);\n return next;\n });\n },\n setDiagnoses: (newDiagnoses: DiagnosisCodeableConcept[]) => {\n privateSetDiagnoses(newDiagnoses);\n },\n\n getActivePatientCoverages,\n updateBillingInformation,\n\n setSpecimenCollectedDateTime: (newDate: Date | undefined) => {\n dispatchTests({ type: 'specimenCollectionDateChange', newDate });\n },\n\n setOrderNotes: (newOrderNotes: string | undefined) => {\n // || instead of ?? so that empty string becomes undefined\n privateSetOrderNotes(newOrderNotes || undefined);\n },\n\n validateOrder: () => {\n return validateLabOrderInputs({\n patient: patientRef,\n requester: requesterRef,\n performingLab: state.performingLab,\n performingLabAccountNumber: state.performingLabAccountNumber,\n selectedTests: state.selectedTests,\n testMetadata: state.testMetadata,\n diagnoses: state.diagnoses,\n billingInformation: state.billingInformation,\n specimenCollectedDateTime: state.specimenCollectedDateTime,\n orderNotes: state.orderNotes,\n });\n },\n createOrderBundle: async (): Promise<{ transactionResponse: Bundle; serviceRequest: ServiceRequest }> => {\n const txn = createLabOrderBundle({\n patient: patientRef,\n requester: requesterRef,\n requestingLocation: requestingLocationRef,\n performingLab: state.performingLab,\n performingLabAccountNumber: state.performingLabAccountNumber,\n selectedTests: state.selectedTests,\n testMetadata: state.testMetadata,\n diagnoses: state.diagnoses,\n billingInformation: state.billingInformation,\n specimenCollectedDateTime: state.specimenCollectedDateTime,\n orderNotes: state.orderNotes,\n });\n\n const transactionResponse = await medplum.executeBatch(txn);\n\n let labOrderServiceRequest: LabOrderServiceRequest | undefined;\n\n for (const entry of transactionResponse?.entry ?? []) {\n if (!entry.response?.status.startsWith('2')) {\n throw new Error('Error creating lab order: Non-2XX status code in response entry', {\n cause: transactionResponse,\n });\n }\n if (entry.resource?.meta?.profile?.includes(MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE)) {\n labOrderServiceRequest = entry.resource as LabOrderServiceRequest;\n }\n }\n\n if (!labOrderServiceRequest) {\n throw new Error('Error creating lab order: Lab Order Service Request not found in response entries', {\n cause: transactionResponse,\n });\n }\n\n return { transactionResponse, serviceRequest: labOrderServiceRequest };\n },\n };\n }, [\n getActivePatientCoverages,\n healthGorillaAutocomplete,\n medplum,\n patientRef,\n performingLab,\n requestingLocationRef,\n requesterRef,\n state,\n updateBillingInformation,\n ]);\n\n return result;\n}\n\nfunction toDiagnosisKey(diagnosis: DiagnosisCodeableConcept): string {\n return diagnosis.coding[0].code;\n}\n\nconst REQUIRED_WHEN_SPECIMEN =\n 'https://www.healthgorilla.com/fhir/StructureDefinition/questionnaire-requiredwhenspecimen';\n\n/**\n * Updates the required status of items in an AOE questionnaire based on whether a specimen is being collected.\n * @param questionnaire - The Questionnaire to update\n * @param specimenCollectedDateTime - The date and time the specimen was collected, if any\n * @returns A new Questionnaire with the required status of items updated, or undefined if no changes are needed\n */\nfunction updateAoeQuestionnaireRequiredItems(\n questionnaire: Questionnaire,\n specimenCollectedDateTime: Date | undefined\n): Questionnaire | undefined {\n let newQuestionnaire: Questionnaire | undefined;\n const requiredWhenSpecimen = Boolean(specimenCollectedDateTime);\n\n // first, check if the questionnaire needs any changes\n for (const item of questionnaireItemIterator(questionnaire.item)) {\n if (getExtensionValue(item, REQUIRED_WHEN_SPECIMEN) === true && Boolean(item.required) !== requiredWhenSpecimen) {\n // at least one item needs updating, so crete a clone to be updated rather than updating in place\n newQuestionnaire = deepClone(questionnaire);\n break;\n }\n }\n\n // if no changes are needed, return the original Questionnaire\n if (!newQuestionnaire) {\n return undefined;\n }\n\n // otherwise, update the cloned Questionnaire\n for (const item of questionnaireItemIterator(newQuestionnaire.item)) {\n if (getExtensionValue(item, REQUIRED_WHEN_SPECIMEN) === true) {\n if (Boolean(item.required) !== requiredWhenSpecimen) {\n item.required = requiredWhenSpecimen;\n }\n }\n }\n\n return newQuestionnaire;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { useContext } from 'react';\nimport type { UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext';\nimport { HealthGorillaLabOrderContext } from './HealthGorillaLabOrderContext';\n\nexport function useHealthGorillaLabOrderContext(): UseHealthGorillaLabOrderReturn {\n const context = useContext(HealthGorillaLabOrderContext);\n if (context === undefined) {\n throw new Error('useHealthGorillaLabOrderContext must be used within a HealthGorillaLabOrderProvider');\n }\n return context;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["result", "import_core", "import_react", "import_react"]
7
7
  }
@@ -1,2 +1,2 @@
1
- import{ContentType,isCoding,isResource}from"@medplum/core";function getAutocompleteSearchFunction(medplum,autocompleteBot){return async params=>{let botResponse=await medplum.executeBot(autocompleteBot,params,ContentType.JSON);if(botResponse.type==="error")throw new Error("Error executing autocomplete bot",{cause:botResponse});let{result}=botResponse;switch(botResponse.type){case"lab":{if(Array.isArray(result)&&result.every(item=>isResource(item,"Organization")))return{type:"lab",result};break}case"test":{let{result:result2}=botResponse;if(Array.isArray(result2)&&result2.every(item=>isCoding(item)))return{type:"test",result:result2};break}case"aoe":{let{result:result2}=botResponse;if(result2===void 0||result2.resourceType==="Questionnaire")return{type:"aoe",result:result2};break}default:}throw new Error(`Invalid bot response for ${JSON.stringify(params)}`)}}import{createContext}from"react";var HealthGorillaLabOrderContext=createContext(void 0);import{jsx}from"react/jsx-runtime";function HealthGorillaLabOrderProvider(props){let{children,...labOrderReturn}=props;return jsx(HealthGorillaLabOrderContext.Provider,{value:labOrderReturn,children})}import{createReference,deepClone,getExtensionValue,getIdentifier,isResource as isResource2}from"@medplum/core";import{HEALTH_GORILLA_SYSTEM,MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE,createLabOrderBundle,isReferenceOfType,normalizeAoeQuestionnaire,questionnaireItemIterator,validateLabOrderInputs}from"@medplum/health-gorilla-core";import{useMedplum}from"@medplum/react";import{useCallback,useEffect,useMemo,useReducer,useRef,useState}from"react";var EDITABLE_TEST_METADATA_KEYS=["priority","notes","aoeResponses"],INITIAL_TESTS=[],INITIAL_DIAGNOSES=[],INITIAL_TEST_METADATA={},INITIAL_BILLING_INFORMATION={};function testsReducer(prev,action){switch(action.type){case"add":return addTest(prev,action);case"remove":return removeTest(prev,action);case"set":return setTests(prev,action);case"updateMetadata":return updateMetadata(prev,action);case"aoeLoaded":return aoeLoaded(prev,action);case"aoeError":return aoeError(prev,action);case"specimenCollectionDateChange":return specimenCollectionDateChange(prev,action);default:return prev}}function addTest(prev,action){return prev.selectedTests.some(test=>test.code===action.test.code)?prev:{...prev,selectedTests:[...prev.selectedTests,action.test],testMetadata:{...prev.testMetadata,[action.test.code]:{aoeStatus:"loading"}}}}function removeTest(prev,action){return{...prev,selectedTests:prev.selectedTests.filter(test=>test.code!==action.test.code),testMetadata:Object.fromEntries(Object.entries(prev.testMetadata).filter(([code])=>code!==action.test.code))}}function setTests(prev,action){return{...prev,selectedTests:action.tests,testMetadata:Object.fromEntries(action.tests.map(test=>[test.code,prev.testMetadata[test.code]??{aoeStatus:"loading"}]))}}function updateMetadata(prev,action){let{test,partialMetadata}=action,prevTestMetadata=prev.testMetadata[test.code];if(!prev.selectedTests.some(t=>t.code===test.code)||!prevTestMetadata)return console.warn(`Cannot update metadata for test ${test.code} since it is not a selected tests`),prev;let allowedUpdates=partialMetadata,restrictedKeys=Object.keys(partialMetadata).filter(k=>!EDITABLE_TEST_METADATA_KEYS.includes(k));return restrictedKeys.length>0&&(console.warn(`Cannot update test metadata fields: ${restrictedKeys.join(", ")}`),allowedUpdates=Object.fromEntries(Object.entries(partialMetadata).filter(([k])=>!restrictedKeys.includes(k)))),{...prev,testMetadata:{...prev.testMetadata,[test.code]:{...prevTestMetadata,...allowedUpdates}}}}function aoeLoaded(prev,action){let newTestMetadata;for(let[test,aoeQuestionnaire]of action.testAoes){let prevMetadata=prev.testMetadata[test.code];prevMetadata&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevMetadata,aoeStatus:aoeQuestionnaire?"loaded":"none",aoeQuestionnaire:aoeQuestionnaire&&(updateAoeQuestionnaireRequiredItems(aoeQuestionnaire,prev.specimenCollectedDateTime)??aoeQuestionnaire)})}return newTestMetadata?{...prev,testMetadata:newTestMetadata}:prev}function aoeError(prev,action){let newTestMetadata;for(let test of action.tests){let prevMetadata=prev.testMetadata[test.code];!prevMetadata||prevMetadata.aoeStatus!=="loading"||(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevMetadata,aoeStatus:"error"})}return newTestMetadata?{...prev,testMetadata:newTestMetadata}:prev}function specimenCollectionDateChange(prev,action){let newTestMetadata;for(let test of prev.selectedTests){let prevTestMetadata=prev.testMetadata[test.code];if(prevTestMetadata&&prevTestMetadata.aoeQuestionnaire){let newAoeQ=updateAoeQuestionnaireRequiredItems(prevTestMetadata.aoeQuestionnaire,action.newDate);newAoeQ&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevTestMetadata,aoeQuestionnaire:newAoeQ})}}return{...prev,testMetadata:newTestMetadata??prev.testMetadata,specimenCollectedDateTime:action.newDate}}function useHealthGorillaLabOrder(opts){let medplum=useMedplum(),[performingLab,privateSetPerformingLab]=useState(),[performingLabAccountNumber,privateSetPerformingLabAccountNumber]=useState(),[testsAndMetadata,dispatchTests]=useReducer(testsReducer,{specimenCollectedDateTime:void 0,selectedTests:INITIAL_TESTS,testMetadata:INITIAL_TEST_METADATA}),[diagnoses,privateSetDiagnoses]=useState(INITIAL_DIAGNOSES),[billingInformation,setBillingInformation]=useState(INITIAL_BILLING_INFORMATION),[orderNotes,privateSetOrderNotes]=useState(),healthGorillaAutocomplete=useMemo(()=>getAutocompleteSearchFunction(medplum,{system:"https://www.medplum.com/integrations/bot-identifier",value:"health-gorilla-labs/autocomplete"}),[medplum]),fetchAoeQuestionnaire=useCallback(async testCode=>{let response=await healthGorillaAutocomplete({type:"aoe",testCode}),questionnaire=response.result;return questionnaire&&normalizeAoeQuestionnaire(questionnaire),response.result},[healthGorillaAutocomplete]),lastSelectedTests=useRef([]);useEffect(()=>{if(lastSelectedTests.current===testsAndMetadata.selectedTests)return;lastSelectedTests.current=testsAndMetadata.selectedTests;let testCodingAoesBeingFetched=[];async function fetchNeededAoeQuestions(){let promises=[];for(let test of testsAndMetadata.selectedTests)testsAndMetadata.testMetadata[test.code]?.aoeStatus==="loading"&&(testCodingAoesBeingFetched.push(test),promises.push(new Promise((resolve,reject)=>{fetchAoeQuestionnaire(test).then(questionnaire=>{resolve([test,questionnaire])}).catch(reject)})));return Promise.all(promises)}fetchNeededAoeQuestions().then(results=>{results.length!==0&&dispatchTests({type:"aoeLoaded",testAoes:results})}).catch(err=>{console.error("Error fetching AOE questions",err),dispatchTests({type:"aoeError",tests:testCodingAoesBeingFetched})})},[fetchAoeQuestionnaire,testsAndMetadata]);let patientRef=useMemo(()=>isReferenceOfType("Patient",opts.patient)?opts.patient:isResource2(opts.patient)?createReference(opts.patient):void 0,[opts.patient]),requesterRef=useMemo(()=>{if(isReferenceOfType("Practitioner",opts.requester))return opts.requester;if(isResource2(opts.requester))return createReference(opts.requester);opts.requester},[opts.requester]),requestingLocationRef=useMemo(()=>isReferenceOfType("Location",opts.requestingLocation)||isReferenceOfType("Organization",opts.requestingLocation)?opts.requestingLocation:isResource2(opts.requestingLocation)?createReference(opts.requestingLocation):void 0,[opts.requestingLocation]),state=useMemo(()=>{let cloned=deepClone({performingLab,performingLabAccountNumber,selectedTests:testsAndMetadata.selectedTests,testMetadata:testsAndMetadata.testMetadata,diagnoses,billingInformation,specimenCollectedDateTime:testsAndMetadata.specimenCollectedDateTime,orderNotes});return cloned.specimenCollectedDateTime&&(cloned.specimenCollectedDateTime=new Date(cloned.specimenCollectedDateTime)),cloned},[performingLab,performingLabAccountNumber,testsAndMetadata.selectedTests,testsAndMetadata.testMetadata,testsAndMetadata.specimenCollectedDateTime,diagnoses,billingInformation,orderNotes]),getActivePatientCoverages=useCallback(async()=>{if(!patientRef)return Object.assign([],{bundle:{resourceType:"Bundle",type:"searchset",entry:[],total:0}});let coverageSearch=new URLSearchParams({patient:patientRef.reference,status:"active"});return(await medplum.searchResources("Coverage",coverageSearch)).sort((a,b)=>(a.order??Number.POSITIVE_INFINITY)-(b.order??Number.POSITIVE_INFINITY))},[medplum,patientRef]),updateBillingInformation=useCallback(billingInfo=>{setBillingInformation(prev=>({...prev,...billingInfo}))},[]);return useMemo(()=>({state,searchAvailableLabs:async query=>(await healthGorillaAutocomplete({type:"lab",query})).result,searchAvailableTests:async query=>{if(!performingLab)return[];let hgLabId=getIdentifier(performingLab,HEALTH_GORILLA_SYSTEM);if(!hgLabId)throw new Error("No Health Gorilla identifier found for performing lab");return query.length===0?[]:(await healthGorillaAutocomplete({type:"test",query,labId:hgLabId})).result},setPerformingLab:newLab=>{privateSetPerformingLab(newLab)},setPerformingLabAccountNumber:newAccountNumber=>{privateSetPerformingLabAccountNumber(newAccountNumber)},addTest:test=>{dispatchTests({type:"add",test})},removeTest:test=>{dispatchTests({type:"remove",test})},setTests:newTests=>{dispatchTests({type:"set",tests:newTests})},updateTestMetadata:(test,partialMetadata)=>{dispatchTests({type:"updateMetadata",test,partialMetadata})},addDiagnosis(diagnosis){privateSetDiagnoses(prev=>prev.some(item=>toDiagnosisKey(item)===toDiagnosisKey(diagnosis))?prev:[...prev,diagnosis])},removeDiagnosis(diagnosis){privateSetDiagnoses(prev=>{let idx=prev.findIndex(item=>toDiagnosisKey(item)===toDiagnosisKey(diagnosis));if(idx===-1)return prev;let next=[...prev];return next.splice(idx,1),next})},setDiagnoses:newDiagnoses=>{privateSetDiagnoses(newDiagnoses)},getActivePatientCoverages,updateBillingInformation,setSpecimenCollectedDateTime:newDate=>{dispatchTests({type:"specimenCollectionDateChange",newDate})},setOrderNotes:newOrderNotes=>{privateSetOrderNotes(newOrderNotes||void 0)},validateOrder:()=>validateLabOrderInputs({patient:patientRef,requester:requesterRef,performingLab:state.performingLab,performingLabAccountNumber:state.performingLabAccountNumber,selectedTests:state.selectedTests,testMetadata:state.testMetadata,diagnoses:state.diagnoses,billingInformation:state.billingInformation,specimenCollectedDateTime:state.specimenCollectedDateTime,orderNotes:state.orderNotes}),createOrderBundle:async()=>{let txn=createLabOrderBundle({patient:patientRef,requester:requesterRef,requestingLocation:requestingLocationRef,performingLab:state.performingLab,performingLabAccountNumber:state.performingLabAccountNumber,selectedTests:state.selectedTests,testMetadata:state.testMetadata,diagnoses:state.diagnoses,billingInformation:state.billingInformation,specimenCollectedDateTime:state.specimenCollectedDateTime,orderNotes:state.orderNotes}),transactionResponse=await medplum.executeBatch(txn),labOrderServiceRequest;for(let entry of transactionResponse?.entry??[]){if(!entry.response?.status.startsWith("2"))throw new Error("Error creating lab order: Non-2XX status code in response entry",{cause:transactionResponse});entry.resource?.meta?.profile?.includes(MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE)&&(labOrderServiceRequest=entry.resource)}if(!labOrderServiceRequest)throw new Error("Error creating lab order: Lab Order Service Request not found in response entries",{cause:transactionResponse});return{transactionResponse,serviceRequest:labOrderServiceRequest}}}),[getActivePatientCoverages,healthGorillaAutocomplete,medplum,patientRef,performingLab,requestingLocationRef,requesterRef,state,updateBillingInformation])}function toDiagnosisKey(diagnosis){return diagnosis.coding[0].code}var REQUIRED_WHEN_SPECIMEN="https://www.healthgorilla.com/fhir/StructureDefinition/questionnaire-requiredwhenspecimen";function updateAoeQuestionnaireRequiredItems(questionnaire,specimenCollectedDateTime){let newQuestionnaire,requiredWhenSpecimen=!!specimenCollectedDateTime;for(let item of questionnaireItemIterator(questionnaire.item))if(getExtensionValue(item,REQUIRED_WHEN_SPECIMEN)===!0&&!!item.required!==requiredWhenSpecimen){newQuestionnaire=deepClone(questionnaire);break}if(newQuestionnaire){for(let item of questionnaireItemIterator(newQuestionnaire.item))getExtensionValue(item,REQUIRED_WHEN_SPECIMEN)===!0&&!!item.required!==requiredWhenSpecimen&&(item.required=requiredWhenSpecimen);return newQuestionnaire}}import{useContext}from"react";function useHealthGorillaLabOrderContext(){let context=useContext(HealthGorillaLabOrderContext);if(context===void 0)throw new Error("useHealthGorillaLabOrderContext must be used within a HealthGorillaLabOrderProvider");return context}export{HealthGorillaLabOrderContext,HealthGorillaLabOrderProvider,getAutocompleteSearchFunction,useHealthGorillaLabOrder,useHealthGorillaLabOrderContext};
1
+ import{ContentType,isCoding,isResource}from"@medplum/core";function getAutocompleteSearchFunction(medplum,autocompleteBot){return async params=>{let botResponse=await medplum.executeBot(autocompleteBot,params,ContentType.JSON);if(botResponse.type==="error")throw new Error("Error executing autocomplete bot",{cause:botResponse});let{result}=botResponse;switch(botResponse.type){case"lab":{if(Array.isArray(result)&&result.every(item=>isResource(item,"Organization")))return{type:"lab",result};break}case"test":{let{result:result2}=botResponse;if(Array.isArray(result2)&&result2.every(item=>isCoding(item)))return{type:"test",result:result2};break}case"aoe":{let{result:result2}=botResponse;if(result2===void 0||result2.resourceType==="Questionnaire")return{type:"aoe",result:result2};break}default:}throw new Error(`Invalid bot response for ${JSON.stringify(params)}`)}}import{createContext}from"react";var HealthGorillaLabOrderContext=createContext(void 0);import{jsx}from"react/jsx-runtime";function HealthGorillaLabOrderProvider(props){let{children,...labOrderReturn}=props;return jsx(HealthGorillaLabOrderContext.Provider,{value:labOrderReturn,children})}import{createReference,deepClone,getExtensionValue,getIdentifier,isResource as isResource2}from"@medplum/core";import{HEALTH_GORILLA_SYSTEM,MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE,createLabOrderBundle,isReferenceOfType,normalizeAoeQuestionnaire,questionnaireItemIterator,validateLabOrderInputs}from"@medplum/health-gorilla-core";import{useMedplum}from"@medplum/react";import{useCallback,useEffect,useMemo,useReducer,useRef,useState}from"react";var EDITABLE_TEST_METADATA_KEYS=["priority","notes","aoeResponses"],INITIAL_TESTS=[],INITIAL_DIAGNOSES=[],INITIAL_TEST_METADATA={},INITIAL_BILLING_INFORMATION={};function testsReducer(prev,action){switch(action.type){case"add":return addTest(prev,action);case"remove":return removeTest(prev,action);case"set":return setTests(prev,action);case"updateMetadata":return updateMetadata(prev,action);case"aoeLoaded":return aoeLoaded(prev,action);case"aoeError":return aoeError(prev,action);case"specimenCollectionDateChange":return specimenCollectionDateChange(prev,action);default:return prev}}function addTest(prev,action){return prev.selectedTests.some(test=>test.code===action.test.code)?prev:{...prev,selectedTests:[...prev.selectedTests,action.test],testMetadata:{...prev.testMetadata,[action.test.code]:{aoeStatus:"loading"}}}}function removeTest(prev,action){return{...prev,selectedTests:prev.selectedTests.filter(test=>test.code!==action.test.code),testMetadata:Object.fromEntries(Object.entries(prev.testMetadata).filter(([code])=>code!==action.test.code))}}function setTests(prev,action){return{...prev,selectedTests:action.tests,testMetadata:Object.fromEntries(action.tests.map(test=>[test.code,prev.testMetadata[test.code]??{aoeStatus:"loading"}]))}}function updateMetadata(prev,action){let{test,partialMetadata}=action,prevTestMetadata=prev.testMetadata[test.code];if(!prev.selectedTests.some(t=>t.code===test.code)||!prevTestMetadata)return console.warn(`Cannot update metadata for test ${test.code} since it is not a selected tests`),prev;let allowedUpdates=partialMetadata,restrictedKeys=Object.keys(partialMetadata).filter(k=>!EDITABLE_TEST_METADATA_KEYS.includes(k));return restrictedKeys.length>0&&(console.warn(`Cannot update test metadata fields: ${restrictedKeys.join(", ")}`),allowedUpdates=Object.fromEntries(Object.entries(partialMetadata).filter(([k])=>!restrictedKeys.includes(k)))),{...prev,testMetadata:{...prev.testMetadata,[test.code]:{...prevTestMetadata,...allowedUpdates}}}}function aoeLoaded(prev,action){let newTestMetadata;for(let[test,aoeQuestionnaire]of action.testAoes){let prevMetadata=prev.testMetadata[test.code];prevMetadata&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevMetadata,aoeStatus:aoeQuestionnaire?"loaded":"none",aoeQuestionnaire:aoeQuestionnaire&&(updateAoeQuestionnaireRequiredItems(aoeQuestionnaire,prev.specimenCollectedDateTime)??aoeQuestionnaire)})}return newTestMetadata?{...prev,testMetadata:newTestMetadata}:prev}function aoeError(prev,action){let newTestMetadata;for(let test of action.tests){let prevMetadata=prev.testMetadata[test.code];prevMetadata?.aoeStatus==="loading"&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevMetadata,aoeStatus:"error"})}return newTestMetadata?{...prev,testMetadata:newTestMetadata}:prev}function specimenCollectionDateChange(prev,action){let newTestMetadata;for(let test of prev.selectedTests){let prevTestMetadata=prev.testMetadata[test.code];if(prevTestMetadata&&prevTestMetadata.aoeQuestionnaire){let newAoeQ=updateAoeQuestionnaireRequiredItems(prevTestMetadata.aoeQuestionnaire,action.newDate);newAoeQ&&(newTestMetadata??={...prev.testMetadata},newTestMetadata[test.code]={...prevTestMetadata,aoeQuestionnaire:newAoeQ})}}return{...prev,testMetadata:newTestMetadata??prev.testMetadata,specimenCollectedDateTime:action.newDate}}function useHealthGorillaLabOrder(opts){let medplum=useMedplum(),[performingLab,privateSetPerformingLab]=useState(),[performingLabAccountNumber,privateSetPerformingLabAccountNumber]=useState(),[testsAndMetadata,dispatchTests]=useReducer(testsReducer,{specimenCollectedDateTime:void 0,selectedTests:INITIAL_TESTS,testMetadata:INITIAL_TEST_METADATA}),[diagnoses,privateSetDiagnoses]=useState(INITIAL_DIAGNOSES),[billingInformation,setBillingInformation]=useState(INITIAL_BILLING_INFORMATION),[orderNotes,privateSetOrderNotes]=useState(),healthGorillaAutocomplete=useMemo(()=>getAutocompleteSearchFunction(medplum,{system:"https://www.medplum.com/integrations/bot-identifier",value:"health-gorilla-labs/autocomplete"}),[medplum]),fetchAoeQuestionnaire=useCallback(async testCode=>{let response=await healthGorillaAutocomplete({type:"aoe",testCode}),questionnaire=response.result;return questionnaire&&normalizeAoeQuestionnaire(questionnaire),response.result},[healthGorillaAutocomplete]),lastSelectedTests=useRef([]);useEffect(()=>{if(lastSelectedTests.current===testsAndMetadata.selectedTests)return;lastSelectedTests.current=testsAndMetadata.selectedTests;let testCodingAoesBeingFetched=[];async function fetchNeededAoeQuestions(){let promises=[];for(let test of testsAndMetadata.selectedTests)testsAndMetadata.testMetadata[test.code]?.aoeStatus==="loading"&&(testCodingAoesBeingFetched.push(test),promises.push(new Promise((resolve,reject)=>{fetchAoeQuestionnaire(test).then(questionnaire=>{resolve([test,questionnaire])}).catch(reject)})));return Promise.all(promises)}fetchNeededAoeQuestions().then(results=>{results.length!==0&&dispatchTests({type:"aoeLoaded",testAoes:results})}).catch(err=>{console.error("Error fetching AOE questions",err),dispatchTests({type:"aoeError",tests:testCodingAoesBeingFetched})})},[fetchAoeQuestionnaire,testsAndMetadata]);let patientRef=useMemo(()=>isReferenceOfType("Patient",opts.patient)?opts.patient:isResource2(opts.patient)?createReference(opts.patient):void 0,[opts.patient]),requesterRef=useMemo(()=>{if(isReferenceOfType("Practitioner",opts.requester))return opts.requester;if(isResource2(opts.requester))return createReference(opts.requester);opts.requester},[opts.requester]),requestingLocationRef=useMemo(()=>isReferenceOfType("Location",opts.requestingLocation)||isReferenceOfType("Organization",opts.requestingLocation)?opts.requestingLocation:isResource2(opts.requestingLocation)?createReference(opts.requestingLocation):void 0,[opts.requestingLocation]),state=useMemo(()=>{let cloned=deepClone({performingLab,performingLabAccountNumber,selectedTests:testsAndMetadata.selectedTests,testMetadata:testsAndMetadata.testMetadata,diagnoses,billingInformation,specimenCollectedDateTime:testsAndMetadata.specimenCollectedDateTime,orderNotes});return cloned.specimenCollectedDateTime&&(cloned.specimenCollectedDateTime=new Date(cloned.specimenCollectedDateTime)),cloned},[performingLab,performingLabAccountNumber,testsAndMetadata.selectedTests,testsAndMetadata.testMetadata,testsAndMetadata.specimenCollectedDateTime,diagnoses,billingInformation,orderNotes]),getActivePatientCoverages=useCallback(async()=>{if(!patientRef)return Object.assign([],{bundle:{resourceType:"Bundle",type:"searchset",entry:[],total:0}});let coverageSearch=new URLSearchParams({patient:patientRef.reference,status:"active"});return(await medplum.searchResources("Coverage",coverageSearch)).sort((a,b)=>(a.order??Number.POSITIVE_INFINITY)-(b.order??Number.POSITIVE_INFINITY))},[medplum,patientRef]),updateBillingInformation=useCallback(billingInfo=>{setBillingInformation(prev=>({...prev,...billingInfo}))},[]);return useMemo(()=>({state,searchAvailableLabs:async query=>(await healthGorillaAutocomplete({type:"lab",query})).result,searchAvailableTests:async query=>{if(!performingLab)return[];let hgLabId=getIdentifier(performingLab,HEALTH_GORILLA_SYSTEM);if(!hgLabId)throw new Error("No Health Gorilla identifier found for performing lab");return query.length===0?[]:(await healthGorillaAutocomplete({type:"test",query,labId:hgLabId})).result},setPerformingLab:newLab=>{privateSetPerformingLab(newLab)},setPerformingLabAccountNumber:newAccountNumber=>{privateSetPerformingLabAccountNumber(newAccountNumber)},addTest:test=>{dispatchTests({type:"add",test})},removeTest:test=>{dispatchTests({type:"remove",test})},setTests:newTests=>{dispatchTests({type:"set",tests:newTests})},updateTestMetadata:(test,partialMetadata)=>{dispatchTests({type:"updateMetadata",test,partialMetadata})},addDiagnosis(diagnosis){privateSetDiagnoses(prev=>prev.some(item=>toDiagnosisKey(item)===toDiagnosisKey(diagnosis))?prev:[...prev,diagnosis])},removeDiagnosis(diagnosis){privateSetDiagnoses(prev=>{let idx=prev.findIndex(item=>toDiagnosisKey(item)===toDiagnosisKey(diagnosis));if(idx===-1)return prev;let next=[...prev];return next.splice(idx,1),next})},setDiagnoses:newDiagnoses=>{privateSetDiagnoses(newDiagnoses)},getActivePatientCoverages,updateBillingInformation,setSpecimenCollectedDateTime:newDate=>{dispatchTests({type:"specimenCollectionDateChange",newDate})},setOrderNotes:newOrderNotes=>{privateSetOrderNotes(newOrderNotes||void 0)},validateOrder:()=>validateLabOrderInputs({patient:patientRef,requester:requesterRef,performingLab:state.performingLab,performingLabAccountNumber:state.performingLabAccountNumber,selectedTests:state.selectedTests,testMetadata:state.testMetadata,diagnoses:state.diagnoses,billingInformation:state.billingInformation,specimenCollectedDateTime:state.specimenCollectedDateTime,orderNotes:state.orderNotes}),createOrderBundle:async()=>{let txn=createLabOrderBundle({patient:patientRef,requester:requesterRef,requestingLocation:requestingLocationRef,performingLab:state.performingLab,performingLabAccountNumber:state.performingLabAccountNumber,selectedTests:state.selectedTests,testMetadata:state.testMetadata,diagnoses:state.diagnoses,billingInformation:state.billingInformation,specimenCollectedDateTime:state.specimenCollectedDateTime,orderNotes:state.orderNotes}),transactionResponse=await medplum.executeBatch(txn),labOrderServiceRequest;for(let entry of transactionResponse?.entry??[]){if(!entry.response?.status.startsWith("2"))throw new Error("Error creating lab order: Non-2XX status code in response entry",{cause:transactionResponse});entry.resource?.meta?.profile?.includes(MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE)&&(labOrderServiceRequest=entry.resource)}if(!labOrderServiceRequest)throw new Error("Error creating lab order: Lab Order Service Request not found in response entries",{cause:transactionResponse});return{transactionResponse,serviceRequest:labOrderServiceRequest}}}),[getActivePatientCoverages,healthGorillaAutocomplete,medplum,patientRef,performingLab,requestingLocationRef,requesterRef,state,updateBillingInformation])}function toDiagnosisKey(diagnosis){return diagnosis.coding[0].code}var REQUIRED_WHEN_SPECIMEN="https://www.healthgorilla.com/fhir/StructureDefinition/questionnaire-requiredwhenspecimen";function updateAoeQuestionnaireRequiredItems(questionnaire,specimenCollectedDateTime){let newQuestionnaire,requiredWhenSpecimen=!!specimenCollectedDateTime;for(let item of questionnaireItemIterator(questionnaire.item))if(getExtensionValue(item,REQUIRED_WHEN_SPECIMEN)===!0&&!!item.required!==requiredWhenSpecimen){newQuestionnaire=deepClone(questionnaire);break}if(newQuestionnaire){for(let item of questionnaireItemIterator(newQuestionnaire.item))getExtensionValue(item,REQUIRED_WHEN_SPECIMEN)===!0&&!!item.required!==requiredWhenSpecimen&&(item.required=requiredWhenSpecimen);return newQuestionnaire}}import{useContext}from"react";function useHealthGorillaLabOrderContext(){let context=useContext(HealthGorillaLabOrderContext);if(context===void 0)throw new Error("useHealthGorillaLabOrderContext must be used within a HealthGorillaLabOrderProvider");return context}export{HealthGorillaLabOrderContext,HealthGorillaLabOrderProvider,getAutocompleteSearchFunction,useHealthGorillaLabOrder,useHealthGorillaLabOrderContext};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/autocomplete-endpoint.ts", "../../src/HealthGorillaLabOrderContext.ts", "../../src/HealthGorillaLabOrderProvider.tsx", "../../src/useHealthGorillaLabOrder.ts", "../../src/useHealthGorillaLabOrderContext.ts"],
4
- "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient } from '@medplum/core';\nimport { ContentType, isCoding, isResource } from '@medplum/core';\nimport type { Identifier, Questionnaire } from '@medplum/fhirtypes';\nimport type { HGAutocompleteBotResponse, LabOrganization, TestCoding } from '@medplum/health-gorilla-core';\n\nexport type LabSearchParams = { type: 'lab'; query: string };\nexport type TestSearchParams = { type: 'test'; query: string; labId: string };\nexport type AOESearchParams = { type: 'aoe'; testCode: TestCoding };\n\nexport type LabSearchResult = { type: 'lab'; result: LabOrganization[] };\nexport type TestSearchResult = { type: 'test'; result: TestCoding[] };\nexport type AOESearchResult = { type: 'aoe'; result: Questionnaire | undefined };\n\nexport type LabSearch = { params: LabSearchParams; results: LabSearchResult };\nexport type TestSearch = { params: TestSearchParams; results: TestSearchResult };\nexport type AOESearch = { params: AOESearchParams; results: AOESearchResult };\nexport type HGSearch = LabSearch | TestSearch | AOESearch;\n\nexport type HGSearchFunction = <T extends HGSearch>(params: T['params']) => Promise<T['results']>;\n\nexport function getAutocompleteSearchFunction(\n medplum: MedplumClient,\n autocompleteBot: string | Identifier\n): HGSearchFunction {\n return async (params) => {\n const botResponse: HGAutocompleteBotResponse = await medplum.executeBot(autocompleteBot, params, ContentType.JSON);\n if (botResponse.type === 'error') {\n throw new Error('Error executing autocomplete bot', { cause: botResponse });\n }\n\n const { result } = botResponse;\n switch (botResponse.type) {\n case 'lab': {\n if (Array.isArray(result) && result.every((item) => isResource(item, 'Organization'))) {\n return { type: 'lab', result } as LabSearchResult;\n }\n break;\n }\n case 'test': {\n const { result } = botResponse;\n if (Array.isArray(result) && result.every((item) => isCoding(item))) {\n return { type: 'test', result } as TestSearchResult;\n }\n break;\n }\n case 'aoe': {\n const { result } = botResponse;\n if (result === undefined || result.resourceType === 'Questionnaire') {\n return { type: 'aoe', result } as AOESearchResult;\n }\n break;\n }\n default: {\n botResponse satisfies never;\n }\n }\n throw new Error(`Invalid bot response for ${JSON.stringify(params)}`);\n };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Bundle, Coverage, Questionnaire, ServiceRequest } from '@medplum/fhirtypes';\nimport type {\n BillingInformation,\n DiagnosisCodeableConcept,\n LabOrderInputErrors,\n LabOrderTestMetadata,\n LabOrganization,\n TestCoding,\n} from '@medplum/health-gorilla-core';\nimport { createContext } from 'react';\n\nexport type TestMetadata = LabOrderTestMetadata & {\n aoeStatus: 'loading' | 'loaded' | 'none' | 'error';\n /** The AOE `Questionnaire`, if any, containing Ask at Order Entry (AOE) questions for the test. */\n aoeQuestionnaire?: Questionnaire;\n};\n\nexport type HealthGorillaLabOrderState = {\n performingLab: LabOrganization | undefined;\n performingLabAccountNumber: string | undefined;\n selectedTests: TestCoding[];\n testMetadata: Record<string, TestMetadata | undefined>;\n diagnoses: DiagnosisCodeableConcept[];\n billingInformation: BillingInformation;\n specimenCollectedDateTime: Date | undefined;\n orderNotes: string | undefined;\n};\n\nexport type UseHealthGorillaLabOrderReturn = {\n state: HealthGorillaLabOrderState;\n\n searchAvailableLabs: (query: string) => Promise<LabOrganization[]>;\n /**\n * Set the desired performer for doing the diagnostic testing. Compatible with\n * results from the `searchAvailableLabs` function.\n */\n setPerformingLab: (lab: LabOrganization | undefined) => void;\n /**\n * Sets the account number for the performing lab. If not provided, the\n * `HEALTH_GORILLA_SUBTENANT_ACCOUNT_NUMBER` `Project.secret` is used.\n */\n setPerformingLabAccountNumber: (accountNumber: string | undefined) => void;\n\n searchAvailableTests: (query: string) => Promise<TestCoding[]>;\n addTest: (test: TestCoding) => void;\n removeTest: (test: TestCoding) => void;\n setTests: (tests: TestCoding[]) => void;\n\n updateTestMetadata: (test: TestCoding, metadata: Partial<LabOrderTestMetadata>) => void;\n\n addDiagnosis: (diagnosis: DiagnosisCodeableConcept) => void;\n removeDiagnosis: (diagnosis: DiagnosisCodeableConcept) => void;\n setDiagnoses: (diagnoses: DiagnosisCodeableConcept[]) => void;\n\n getActivePatientCoverages: () => Promise<Coverage[]>;\n updateBillingInformation: (billingInfo: Partial<BillingInformation>) => void;\n\n setSpecimenCollectedDateTime: (date: Date | undefined) => void;\n\n setOrderNotes: (orderNotes: string | undefined) => void;\n\n validateOrder: () => LabOrderInputErrors | undefined;\n createOrderBundle: () => Promise<{ transactionResponse: Bundle; serviceRequest: ServiceRequest }>;\n};\n\nexport const HealthGorillaLabOrderContext = createContext<UseHealthGorillaLabOrderReturn | undefined>(undefined);\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { JSX, PropsWithChildren } from 'react';\nimport type { UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext';\nimport { HealthGorillaLabOrderContext } from './HealthGorillaLabOrderContext';\n\n/**\n * A provider component that propagates the `useHealthGorillaLabOrder` return value to all children components via [React Context](https://reactjs.org/docs/context.html) API. To be used with {@link useHealthGorillaLabOrderContext}.\n *\n * @param props - entire response of `useHealthGorillaLabOrder`\n * @returns a React context provider component.\n *\n * @example\n * ```tsx\n * function App() {\n * const labOrderReturn = useHealthGorillaLabOrder({...});\n * return (\n * <HealthGorillaLabOrderProvider {...methods} >\n * <form onSubmit={labOrderReturn.createOrderBundle()}>\n * <PerformingLabInput />\n * <input type=\"submit\" />\n * </form>\n * </HealthGorillaLabOrderProvider>\n * );\n * }\n *\n * function PerformingLabInput() {\n * const { searchAvailableLabs, setPerformingLab } = useHealthGorillaLabOrderContext();\n * return (\n * <AsyncAutocomplete<LabOrganization>\n * maxValues={1}\n * loadOptions={searchAvailableLabs}\n * onChange={(item) => {\n * if (item.length > 0) {\n * setPerformingLab(item[0]);\n * } else {\n * setPerformingLab(undefined);\n * }\n * }}\n * />\n * );\n * }\n * ```\n */\nexport function HealthGorillaLabOrderProvider(props: PropsWithChildren<UseHealthGorillaLabOrderReturn>): JSX.Element {\n const { children, ...labOrderReturn } = props;\n return (\n <HealthGorillaLabOrderContext.Provider value={labOrderReturn}>{children}</HealthGorillaLabOrderContext.Provider>\n );\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { ResourceArray } from '@medplum/core';\nimport { createReference, deepClone, getExtensionValue, getIdentifier, isResource } from '@medplum/core';\nimport type {\n Bundle,\n Coverage,\n Location,\n Organization,\n Patient,\n Practitioner,\n Questionnaire,\n Reference,\n ServiceRequest,\n} from '@medplum/fhirtypes';\nimport type {\n BillingInformation,\n DiagnosisCodeableConcept,\n LabOrderServiceRequest,\n LabOrderTestMetadata,\n LabOrganization,\n TestCoding,\n} from '@medplum/health-gorilla-core';\nimport {\n HEALTH_GORILLA_SYSTEM,\n MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE,\n createLabOrderBundle,\n isReferenceOfType,\n normalizeAoeQuestionnaire,\n questionnaireItemIterator,\n validateLabOrderInputs,\n} from '@medplum/health-gorilla-core';\nimport { useMedplum } from '@medplum/react';\nimport { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\nimport type { AOESearch, LabSearch, TestSearch } from './autocomplete-endpoint';\nimport { getAutocompleteSearchFunction } from './autocomplete-endpoint';\nimport type {\n HealthGorillaLabOrderState,\n TestMetadata,\n UseHealthGorillaLabOrderReturn,\n} from './HealthGorillaLabOrderContext';\n\nexport type UseHealthGorillaLabOrderOptions = {\n /** Patient the tests are ordered for. */\n patient: Patient | (Reference<Patient> & { reference: string }) | undefined;\n /**\n * The physician who requests diagnostic procedures for the patient and responsible for the order.\n * The Practitioner MUST have an NPI identifier with the system http://hl7.org/fhir/sid/us-npi.\n */\n requester: Practitioner | (Reference<Practitioner> & { reference: string }) | undefined;\n\n /** For multi-location practices, optionally specify the location from which the order is being placed */\n requestingLocation?: Location | Organization | (Reference<Location | Organization> & { reference: string });\n};\n\nexport type EditableLabOrderTestMetadata = Pick<LabOrderTestMetadata, 'priority' | 'notes' | 'aoeResponses'>;\nconst EDITABLE_TEST_METADATA_KEYS: (keyof LabOrderTestMetadata)[] = ['priority', 'notes', 'aoeResponses'];\n\nconst INITIAL_TESTS: TestCoding[] = [];\nconst INITIAL_DIAGNOSES: DiagnosisCodeableConcept[] = [];\nconst INITIAL_TEST_METADATA = {};\nconst INITIAL_BILLING_INFORMATION: BillingInformation = {};\n\ntype TestsReducerState = {\n // Don't use any optional fields here, e.g. `selectedTests?: TestCoding[]` since that would cause\n // some loss of type safety in the reducer function by allowing the reducer to potentially return\n // incomplete state if a field is forgotten about. Instead, always use explicity `| undefined` for\n // fields that may legitimately be undefined in the state.\n\n selectedTests: TestCoding[];\n testMetadata: Record<string, TestMetadata | undefined>;\n specimenCollectedDateTime: Date | undefined;\n};\n\ntype AddTestAction = { type: 'add'; test: TestCoding };\ntype RemoveTestAction = { type: 'remove'; test: TestCoding };\ntype SetTestsAction = { type: 'set'; tests: TestCoding[] };\ntype UpdateMetadataAction = {\n type: 'updateMetadata';\n test: TestCoding;\n partialMetadata: Partial<EditableLabOrderTestMetadata>;\n};\ntype AoeLoadedAction = { type: 'aoeLoaded'; testAoes: [TestCoding, Questionnaire | undefined][] };\ntype SpecimentCollectionDateChangeAction = { type: 'specimenCollectionDateChange'; newDate: Date | undefined };\ntype AoeErrorAction = { type: 'aoeError'; tests: TestCoding[] };\n\ntype TestsAction =\n | AddTestAction\n | RemoveTestAction\n | SetTestsAction\n | UpdateMetadataAction\n | AoeLoadedAction\n | SpecimentCollectionDateChangeAction\n | AoeErrorAction;\n\nfunction testsReducer(prev: TestsReducerState, action: TestsAction): TestsReducerState {\n switch (action.type) {\n case 'add':\n return addTest(prev, action);\n case 'remove':\n return removeTest(prev, action);\n case 'set':\n return setTests(prev, action);\n case 'updateMetadata':\n return updateMetadata(prev, action);\n case 'aoeLoaded':\n return aoeLoaded(prev, action);\n case 'aoeError':\n return aoeError(prev, action);\n case 'specimenCollectionDateChange':\n return specimenCollectionDateChange(prev, action);\n default:\n action satisfies never;\n return prev;\n }\n}\n\nfunction addTest(prev: TestsReducerState, action: AddTestAction): TestsReducerState {\n if (prev.selectedTests.some((test) => test.code === action.test.code)) {\n return prev;\n }\n\n return {\n ...prev,\n selectedTests: [...prev.selectedTests, action.test],\n testMetadata: {\n ...prev.testMetadata,\n [action.test.code]: { aoeStatus: 'loading' },\n },\n };\n}\n\nfunction removeTest(prev: TestsReducerState, action: RemoveTestAction): TestsReducerState {\n return {\n ...prev,\n selectedTests: prev.selectedTests.filter((test) => test.code !== action.test.code),\n testMetadata: Object.fromEntries(Object.entries(prev.testMetadata).filter(([code]) => code !== action.test.code)),\n };\n}\n\nfunction setTests(prev: TestsReducerState, action: SetTestsAction): TestsReducerState {\n return {\n ...prev,\n selectedTests: action.tests,\n testMetadata: Object.fromEntries(\n action.tests.map((test) => {\n return [test.code, prev.testMetadata[test.code] ?? { aoeStatus: 'loading' }];\n })\n ),\n };\n}\n\nfunction updateMetadata(prev: TestsReducerState, action: UpdateMetadataAction): TestsReducerState {\n const { test, partialMetadata } = action;\n\n const prevTestMetadata = prev.testMetadata[test.code];\n if (!prev.selectedTests.some((t) => t.code === test.code) || !prevTestMetadata) {\n console.warn(`Cannot update metadata for test ${test.code} since it is not a selected tests`);\n return prev;\n }\n\n let allowedUpdates = partialMetadata;\n const restrictedKeys = Object.keys(partialMetadata).filter((k) => !EDITABLE_TEST_METADATA_KEYS.includes(k as any));\n\n if (restrictedKeys.length > 0) {\n console.warn(`Cannot update test metadata fields: ${restrictedKeys.join(', ')}`);\n\n allowedUpdates = Object.fromEntries(Object.entries(partialMetadata).filter(([k]) => !restrictedKeys.includes(k)));\n }\n\n return {\n ...prev,\n testMetadata: {\n ...prev.testMetadata,\n [test.code]: { ...prevTestMetadata, ...allowedUpdates },\n },\n };\n}\n\nfunction aoeLoaded(prev: TestsReducerState, action: AoeLoadedAction): TestsReducerState {\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const [test, aoeQuestionnaire] of action.testAoes) {\n const prevMetadata = prev.testMetadata[test.code];\n if (!prevMetadata) {\n continue;\n }\n\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = {\n ...prevMetadata,\n aoeStatus: aoeQuestionnaire ? 'loaded' : 'none',\n aoeQuestionnaire:\n aoeQuestionnaire &&\n (updateAoeQuestionnaireRequiredItems(aoeQuestionnaire, prev.specimenCollectedDateTime) ?? aoeQuestionnaire),\n };\n }\n\n if (!newTestMetadata) {\n return prev;\n }\n\n return {\n ...prev,\n testMetadata: newTestMetadata,\n };\n}\n\nfunction aoeError(prev: TestsReducerState, action: AoeErrorAction): TestsReducerState {\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const test of action.tests) {\n const prevMetadata = prev.testMetadata[test.code];\n if (!prevMetadata || prevMetadata.aoeStatus !== 'loading') {\n continue;\n }\n\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = {\n ...prevMetadata,\n aoeStatus: 'error',\n };\n }\n\n if (!newTestMetadata) {\n return prev;\n }\n\n return {\n ...prev,\n testMetadata: newTestMetadata,\n };\n}\n\nfunction specimenCollectionDateChange(\n prev: TestsReducerState,\n action: SpecimentCollectionDateChangeAction\n): TestsReducerState {\n // check if we can reuse the previous testMetadata object to avoid unnecessary downstream re-renders\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const test of prev.selectedTests) {\n const prevTestMetadata = prev.testMetadata[test.code];\n if (!prevTestMetadata) {\n continue;\n }\n\n if (prevTestMetadata.aoeQuestionnaire) {\n const newAoeQ = updateAoeQuestionnaireRequiredItems(prevTestMetadata.aoeQuestionnaire, action.newDate);\n if (newAoeQ) {\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = { ...prevTestMetadata, aoeQuestionnaire: newAoeQ };\n }\n }\n }\n\n return {\n ...prev,\n // If no changes to AOE questionnaire item.required, reuse the previous object\n testMetadata: newTestMetadata ?? prev.testMetadata,\n specimenCollectedDateTime: action.newDate,\n };\n}\n\nexport function useHealthGorillaLabOrder(opts: UseHealthGorillaLabOrderOptions): UseHealthGorillaLabOrderReturn {\n const medplum = useMedplum();\n const [performingLab, privateSetPerformingLab] = useState<LabOrganization | undefined>();\n const [performingLabAccountNumber, privateSetPerformingLabAccountNumber] = useState<string | undefined>();\n const [testsAndMetadata, dispatchTests] = useReducer(testsReducer, {\n specimenCollectedDateTime: undefined,\n selectedTests: INITIAL_TESTS,\n testMetadata: INITIAL_TEST_METADATA,\n });\n const [diagnoses, privateSetDiagnoses] = useState<DiagnosisCodeableConcept[]>(INITIAL_DIAGNOSES);\n const [billingInformation, setBillingInformation] = useState<BillingInformation>(INITIAL_BILLING_INFORMATION);\n const [orderNotes, privateSetOrderNotes] = useState<string | undefined>();\n\n const healthGorillaAutocomplete = useMemo(() => {\n return getAutocompleteSearchFunction(medplum, {\n system: 'https://www.medplum.com/integrations/bot-identifier',\n value: 'health-gorilla-labs/autocomplete',\n });\n }, [medplum]);\n\n const fetchAoeQuestionnaire = useCallback(\n async (testCode: TestCoding): Promise<Questionnaire | undefined> => {\n const response = await healthGorillaAutocomplete<AOESearch>({ type: 'aoe', testCode });\n const questionnaire = response.result;\n if (questionnaire) {\n normalizeAoeQuestionnaire(questionnaire);\n }\n return response.result;\n },\n [healthGorillaAutocomplete]\n );\n\n const lastSelectedTests = useRef<TestCoding[]>([]);\n useEffect(() => {\n // effect only runs when selectedTests changes\n if (lastSelectedTests.current === testsAndMetadata.selectedTests) {\n return;\n }\n lastSelectedTests.current = testsAndMetadata.selectedTests;\n\n const testCodingAoesBeingFetched: TestCoding[] = [];\n async function fetchNeededAoeQuestions(): Promise<[TestCoding, Questionnaire | undefined][]> {\n const promises: Promise<[TestCoding, Questionnaire | undefined]>[] = [];\n for (const test of testsAndMetadata.selectedTests) {\n const metadata = testsAndMetadata.testMetadata[test.code];\n if (metadata?.aoeStatus !== 'loading') {\n continue;\n }\n\n testCodingAoesBeingFetched.push(test);\n promises.push(\n new Promise((resolve, reject) => {\n fetchAoeQuestionnaire(test)\n .then((questionnaire) => {\n resolve([test, questionnaire]);\n })\n .catch(reject);\n })\n );\n }\n return Promise.all(promises);\n }\n\n fetchNeededAoeQuestions()\n .then((results) => {\n if (results.length === 0) {\n return;\n }\n\n dispatchTests({ type: 'aoeLoaded', testAoes: results });\n })\n .catch((err) => {\n console.error('Error fetching AOE questions', err);\n dispatchTests({ type: 'aoeError', tests: testCodingAoesBeingFetched });\n });\n }, [fetchAoeQuestionnaire, testsAndMetadata]);\n\n const patientRef = useMemo<(Reference<Patient> & { reference: string }) | undefined>(() => {\n if (isReferenceOfType('Patient', opts.patient)) {\n return opts.patient;\n } else if (isResource(opts.patient)) {\n return createReference(opts.patient);\n } else {\n return undefined;\n }\n }, [opts.patient]);\n\n const requesterRef = useMemo<(Reference<Practitioner> & { reference: string }) | undefined>(() => {\n if (isReferenceOfType('Practitioner', opts.requester)) {\n return opts.requester;\n } else if (isResource(opts.requester)) {\n return createReference(opts.requester) as Reference<Practitioner> & { reference: string };\n } else {\n opts.requester satisfies undefined;\n return undefined;\n }\n }, [opts.requester]);\n\n const requestingLocationRef = useMemo<\n (Reference<Location | Organization> & { reference: string }) | undefined\n >(() => {\n if (\n isReferenceOfType('Location', opts.requestingLocation) ||\n isReferenceOfType('Organization', opts.requestingLocation)\n ) {\n return opts.requestingLocation;\n } else if (isResource(opts.requestingLocation)) {\n return createReference(opts.requestingLocation);\n } else {\n return undefined;\n }\n }, [opts.requestingLocation]);\n\n const state: HealthGorillaLabOrderState = useMemo(() => {\n const cloned = deepClone({\n performingLab,\n performingLabAccountNumber,\n selectedTests: testsAndMetadata.selectedTests,\n testMetadata: testsAndMetadata.testMetadata,\n diagnoses,\n billingInformation,\n specimenCollectedDateTime: testsAndMetadata.specimenCollectedDateTime,\n orderNotes,\n });\n\n // specimenCollectedDateTime is a Date object which is serialized in deepClone\n if (cloned.specimenCollectedDateTime) {\n cloned.specimenCollectedDateTime = new Date(cloned.specimenCollectedDateTime);\n }\n\n return cloned;\n }, [\n performingLab,\n performingLabAccountNumber,\n testsAndMetadata.selectedTests,\n testsAndMetadata.testMetadata,\n testsAndMetadata.specimenCollectedDateTime,\n diagnoses,\n billingInformation,\n orderNotes,\n ]);\n\n const getActivePatientCoverages = useCallback(async (): Promise<ResourceArray<Coverage>> => {\n if (!patientRef) {\n const emptyResult: Coverage[] = [];\n return Object.assign(emptyResult, {\n bundle: { resourceType: 'Bundle', type: 'searchset', entry: [], total: 0 } as Bundle<Coverage>,\n });\n }\n\n const coverageSearch = new URLSearchParams({\n patient: patientRef.reference,\n status: 'active',\n });\n const coverages = (await medplum.searchResources('Coverage', coverageSearch)).sort((a, b) => {\n return (a.order ?? Number.POSITIVE_INFINITY) - (b.order ?? Number.POSITIVE_INFINITY);\n });\n\n return coverages;\n }, [medplum, patientRef]);\n\n const updateBillingInformation = useCallback((billingInfo: Partial<BillingInformation>): void => {\n setBillingInformation((prev) => {\n return { ...prev, ...billingInfo };\n });\n }, []);\n\n const result: UseHealthGorillaLabOrderReturn = useMemo(() => {\n return {\n state,\n searchAvailableLabs: async (query: string) => {\n const response = await healthGorillaAutocomplete<LabSearch>({ type: 'lab', query });\n // consider filtering to labs that have the https://www.healthgorilla.com/fhir/StructureDefinition/provider-compendium extension?\n return response.result;\n },\n searchAvailableTests: async (query: string): Promise<TestCoding[]> => {\n if (!performingLab) {\n return [];\n }\n\n const hgLabId = getIdentifier(performingLab, HEALTH_GORILLA_SYSTEM);\n if (!hgLabId) {\n throw new Error('No Health Gorilla identifier found for performing lab');\n }\n\n if (query.length === 0) {\n return [];\n }\n const response = await healthGorillaAutocomplete<TestSearch>({ type: 'test', query, labId: hgLabId });\n\n const tests = response.result;\n return tests;\n },\n setPerformingLab: (newLab: LabOrganization | undefined) => {\n privateSetPerformingLab(newLab);\n },\n setPerformingLabAccountNumber: (newAccountNumber: string | undefined) => {\n privateSetPerformingLabAccountNumber(newAccountNumber);\n },\n addTest: (test: TestCoding) => {\n dispatchTests({ type: 'add', test });\n },\n removeTest: (test: TestCoding) => {\n dispatchTests({ type: 'remove', test });\n },\n setTests: (newTests: TestCoding[]) => {\n dispatchTests({ type: 'set', tests: newTests });\n },\n\n updateTestMetadata: (test: TestCoding, partialMetadata: Partial<LabOrderTestMetadata>) => {\n dispatchTests({ type: 'updateMetadata', test, partialMetadata });\n },\n\n addDiagnosis(diagnosis: DiagnosisCodeableConcept) {\n privateSetDiagnoses((prev) => {\n if (prev.some((item) => toDiagnosisKey(item) === toDiagnosisKey(diagnosis))) {\n return prev;\n }\n return [...prev, diagnosis];\n });\n },\n removeDiagnosis(diagnosis: DiagnosisCodeableConcept) {\n privateSetDiagnoses((prev) => {\n const idx = prev.findIndex((item) => toDiagnosisKey(item) === toDiagnosisKey(diagnosis));\n if (idx === -1) {\n return prev;\n }\n const next = [...prev];\n next.splice(idx, 1);\n return next;\n });\n },\n setDiagnoses: (newDiagnoses: DiagnosisCodeableConcept[]) => {\n privateSetDiagnoses(newDiagnoses);\n },\n\n getActivePatientCoverages,\n updateBillingInformation,\n\n setSpecimenCollectedDateTime: (newDate: Date | undefined) => {\n dispatchTests({ type: 'specimenCollectionDateChange', newDate });\n },\n\n setOrderNotes: (newOrderNotes: string | undefined) => {\n // || instead of ?? so that empty string becomes undefined\n privateSetOrderNotes(newOrderNotes || undefined);\n },\n\n validateOrder: () => {\n return validateLabOrderInputs({\n patient: patientRef,\n requester: requesterRef,\n performingLab: state.performingLab,\n performingLabAccountNumber: state.performingLabAccountNumber,\n selectedTests: state.selectedTests,\n testMetadata: state.testMetadata,\n diagnoses: state.diagnoses,\n billingInformation: state.billingInformation,\n specimenCollectedDateTime: state.specimenCollectedDateTime,\n orderNotes: state.orderNotes,\n });\n },\n createOrderBundle: async (): Promise<{ transactionResponse: Bundle; serviceRequest: ServiceRequest }> => {\n const txn = createLabOrderBundle({\n patient: patientRef,\n requester: requesterRef,\n requestingLocation: requestingLocationRef,\n performingLab: state.performingLab,\n performingLabAccountNumber: state.performingLabAccountNumber,\n selectedTests: state.selectedTests,\n testMetadata: state.testMetadata,\n diagnoses: state.diagnoses,\n billingInformation: state.billingInformation,\n specimenCollectedDateTime: state.specimenCollectedDateTime,\n orderNotes: state.orderNotes,\n });\n\n const transactionResponse = await medplum.executeBatch(txn);\n\n let labOrderServiceRequest: LabOrderServiceRequest | undefined;\n\n for (const entry of transactionResponse?.entry ?? []) {\n if (!entry.response?.status.startsWith('2')) {\n throw new Error('Error creating lab order: Non-2XX status code in response entry', {\n cause: transactionResponse,\n });\n }\n if (entry.resource?.meta?.profile?.includes(MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE)) {\n labOrderServiceRequest = entry.resource as LabOrderServiceRequest;\n }\n }\n\n if (!labOrderServiceRequest) {\n throw new Error('Error creating lab order: Lab Order Service Request not found in response entries', {\n cause: transactionResponse,\n });\n }\n\n return { transactionResponse, serviceRequest: labOrderServiceRequest };\n },\n };\n }, [\n getActivePatientCoverages,\n healthGorillaAutocomplete,\n medplum,\n patientRef,\n performingLab,\n requestingLocationRef,\n requesterRef,\n state,\n updateBillingInformation,\n ]);\n\n return result;\n}\n\nfunction toDiagnosisKey(diagnosis: DiagnosisCodeableConcept): string {\n return diagnosis.coding[0].code;\n}\n\nconst REQUIRED_WHEN_SPECIMEN =\n 'https://www.healthgorilla.com/fhir/StructureDefinition/questionnaire-requiredwhenspecimen';\n\n/**\n * Updates the required status of items in an AOE questionnaire based on whether a specimen is being collected.\n * @param questionnaire - The Questionnaire to update\n * @param specimenCollectedDateTime - The date and time the specimen was collected, if any\n * @returns A new Questionnaire with the required status of items updated, or undefined if no changes are needed\n */\nfunction updateAoeQuestionnaireRequiredItems(\n questionnaire: Questionnaire,\n specimenCollectedDateTime: Date | undefined\n): Questionnaire | undefined {\n let newQuestionnaire: Questionnaire | undefined;\n const requiredWhenSpecimen = Boolean(specimenCollectedDateTime);\n\n // first, check if the questionnaire needs any changes\n for (const item of questionnaireItemIterator(questionnaire.item)) {\n if (getExtensionValue(item, REQUIRED_WHEN_SPECIMEN) === true && Boolean(item.required) !== requiredWhenSpecimen) {\n // at least one item needs updating, so crete a clone to be updated rather than updating in place\n newQuestionnaire = deepClone(questionnaire);\n break;\n }\n }\n\n // if no changes are needed, return the original Questionnaire\n if (!newQuestionnaire) {\n return undefined;\n }\n\n // otherwise, update the cloned Questionnaire\n for (const item of questionnaireItemIterator(newQuestionnaire.item)) {\n if (getExtensionValue(item, REQUIRED_WHEN_SPECIMEN) === true) {\n if (Boolean(item.required) !== requiredWhenSpecimen) {\n item.required = requiredWhenSpecimen;\n }\n }\n }\n\n return newQuestionnaire;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { useContext } from 'react';\nimport type { UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext';\nimport { HealthGorillaLabOrderContext } from './HealthGorillaLabOrderContext';\n\nexport function useHealthGorillaLabOrderContext(): UseHealthGorillaLabOrderReturn {\n const context = useContext(HealthGorillaLabOrderContext);\n if (context === undefined) {\n throw new Error('useHealthGorillaLabOrderContext must be used within a HealthGorillaLabOrderProvider');\n }\n return context;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { MedplumClient } from '@medplum/core';\nimport { ContentType, isCoding, isResource } from '@medplum/core';\nimport type { Identifier, Questionnaire } from '@medplum/fhirtypes';\nimport type { HGAutocompleteBotResponse, LabOrganization, TestCoding } from '@medplum/health-gorilla-core';\n\nexport type LabSearchParams = { type: 'lab'; query: string };\nexport type TestSearchParams = { type: 'test'; query: string; labId: string };\nexport type AOESearchParams = { type: 'aoe'; testCode: TestCoding };\n\nexport type LabSearchResult = { type: 'lab'; result: LabOrganization[] };\nexport type TestSearchResult = { type: 'test'; result: TestCoding[] };\nexport type AOESearchResult = { type: 'aoe'; result: Questionnaire | undefined };\n\nexport type LabSearch = { params: LabSearchParams; results: LabSearchResult };\nexport type TestSearch = { params: TestSearchParams; results: TestSearchResult };\nexport type AOESearch = { params: AOESearchParams; results: AOESearchResult };\nexport type HGSearch = LabSearch | TestSearch | AOESearch;\n\nexport type HGSearchFunction = <T extends HGSearch>(params: T['params']) => Promise<T['results']>;\n\nexport function getAutocompleteSearchFunction(\n medplum: MedplumClient,\n autocompleteBot: string | Identifier\n): HGSearchFunction {\n return async (params) => {\n const botResponse: HGAutocompleteBotResponse = await medplum.executeBot(autocompleteBot, params, ContentType.JSON);\n if (botResponse.type === 'error') {\n throw new Error('Error executing autocomplete bot', { cause: botResponse });\n }\n\n const { result } = botResponse;\n switch (botResponse.type) {\n case 'lab': {\n if (Array.isArray(result) && result.every((item) => isResource(item, 'Organization'))) {\n return { type: 'lab', result } as LabSearchResult;\n }\n break;\n }\n case 'test': {\n const { result } = botResponse;\n if (Array.isArray(result) && result.every((item) => isCoding(item))) {\n return { type: 'test', result } as TestSearchResult;\n }\n break;\n }\n case 'aoe': {\n const { result } = botResponse;\n if (result === undefined || result.resourceType === 'Questionnaire') {\n return { type: 'aoe', result } as AOESearchResult;\n }\n break;\n }\n default: {\n botResponse satisfies never;\n }\n }\n throw new Error(`Invalid bot response for ${JSON.stringify(params)}`);\n };\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { Bundle, Coverage, Questionnaire, ServiceRequest } from '@medplum/fhirtypes';\nimport type {\n BillingInformation,\n DiagnosisCodeableConcept,\n LabOrderInputErrors,\n LabOrderTestMetadata,\n LabOrganization,\n TestCoding,\n} from '@medplum/health-gorilla-core';\nimport { createContext } from 'react';\n\nexport type TestMetadata = LabOrderTestMetadata & {\n aoeStatus: 'loading' | 'loaded' | 'none' | 'error';\n /** The AOE `Questionnaire`, if any, containing Ask at Order Entry (AOE) questions for the test. */\n aoeQuestionnaire?: Questionnaire;\n};\n\nexport type HealthGorillaLabOrderState = {\n performingLab: LabOrganization | undefined;\n performingLabAccountNumber: string | undefined;\n selectedTests: TestCoding[];\n testMetadata: Record<string, TestMetadata | undefined>;\n diagnoses: DiagnosisCodeableConcept[];\n billingInformation: BillingInformation;\n specimenCollectedDateTime: Date | undefined;\n orderNotes: string | undefined;\n};\n\nexport type UseHealthGorillaLabOrderReturn = {\n state: HealthGorillaLabOrderState;\n\n searchAvailableLabs: (query: string) => Promise<LabOrganization[]>;\n /**\n * Set the desired performer for doing the diagnostic testing. Compatible with\n * results from the `searchAvailableLabs` function.\n */\n setPerformingLab: (lab: LabOrganization | undefined) => void;\n /**\n * Sets the account number for the performing lab. If not provided, the\n * `HEALTH_GORILLA_SUBTENANT_ACCOUNT_NUMBER` `Project.secret` is used.\n */\n setPerformingLabAccountNumber: (accountNumber: string | undefined) => void;\n\n searchAvailableTests: (query: string) => Promise<TestCoding[]>;\n addTest: (test: TestCoding) => void;\n removeTest: (test: TestCoding) => void;\n setTests: (tests: TestCoding[]) => void;\n\n updateTestMetadata: (test: TestCoding, metadata: Partial<LabOrderTestMetadata>) => void;\n\n addDiagnosis: (diagnosis: DiagnosisCodeableConcept) => void;\n removeDiagnosis: (diagnosis: DiagnosisCodeableConcept) => void;\n setDiagnoses: (diagnoses: DiagnosisCodeableConcept[]) => void;\n\n getActivePatientCoverages: () => Promise<Coverage[]>;\n updateBillingInformation: (billingInfo: Partial<BillingInformation>) => void;\n\n setSpecimenCollectedDateTime: (date: Date | undefined) => void;\n\n setOrderNotes: (orderNotes: string | undefined) => void;\n\n validateOrder: () => LabOrderInputErrors | undefined;\n createOrderBundle: () => Promise<{ transactionResponse: Bundle; serviceRequest: ServiceRequest }>;\n};\n\nexport const HealthGorillaLabOrderContext = createContext<UseHealthGorillaLabOrderReturn | undefined>(undefined);\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { JSX, PropsWithChildren } from 'react';\nimport type { UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext';\nimport { HealthGorillaLabOrderContext } from './HealthGorillaLabOrderContext';\n\n/**\n * A provider component that propagates the `useHealthGorillaLabOrder` return value to all children components via [React Context](https://reactjs.org/docs/context.html) API. To be used with {@link useHealthGorillaLabOrderContext}.\n *\n * @param props - entire response of `useHealthGorillaLabOrder`\n * @returns a React context provider component.\n *\n * @example\n * ```tsx\n * function App() {\n * const labOrderReturn = useHealthGorillaLabOrder({...});\n * return (\n * <HealthGorillaLabOrderProvider {...methods} >\n * <form onSubmit={labOrderReturn.createOrderBundle()}>\n * <PerformingLabInput />\n * <input type=\"submit\" />\n * </form>\n * </HealthGorillaLabOrderProvider>\n * );\n * }\n *\n * function PerformingLabInput() {\n * const { searchAvailableLabs, setPerformingLab } = useHealthGorillaLabOrderContext();\n * return (\n * <AsyncAutocomplete<LabOrganization>\n * maxValues={1}\n * loadOptions={searchAvailableLabs}\n * onChange={(item) => {\n * if (item.length > 0) {\n * setPerformingLab(item[0]);\n * } else {\n * setPerformingLab(undefined);\n * }\n * }}\n * />\n * );\n * }\n * ```\n */\nexport function HealthGorillaLabOrderProvider(props: PropsWithChildren<UseHealthGorillaLabOrderReturn>): JSX.Element {\n const { children, ...labOrderReturn } = props;\n return (\n <HealthGorillaLabOrderContext.Provider value={labOrderReturn}>{children}</HealthGorillaLabOrderContext.Provider>\n );\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport type { ResourceArray } from '@medplum/core';\nimport { createReference, deepClone, getExtensionValue, getIdentifier, isResource } from '@medplum/core';\nimport type {\n Bundle,\n Coverage,\n Location,\n Organization,\n Patient,\n Practitioner,\n Questionnaire,\n Reference,\n ServiceRequest,\n} from '@medplum/fhirtypes';\nimport type {\n BillingInformation,\n DiagnosisCodeableConcept,\n LabOrderServiceRequest,\n LabOrderTestMetadata,\n LabOrganization,\n TestCoding,\n} from '@medplum/health-gorilla-core';\nimport {\n HEALTH_GORILLA_SYSTEM,\n MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE,\n createLabOrderBundle,\n isReferenceOfType,\n normalizeAoeQuestionnaire,\n questionnaireItemIterator,\n validateLabOrderInputs,\n} from '@medplum/health-gorilla-core';\nimport { useMedplum } from '@medplum/react';\nimport { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';\nimport type { AOESearch, LabSearch, TestSearch } from './autocomplete-endpoint';\nimport { getAutocompleteSearchFunction } from './autocomplete-endpoint';\nimport type {\n HealthGorillaLabOrderState,\n TestMetadata,\n UseHealthGorillaLabOrderReturn,\n} from './HealthGorillaLabOrderContext';\n\nexport type UseHealthGorillaLabOrderOptions = {\n /** Patient the tests are ordered for. */\n patient: Patient | (Reference<Patient> & { reference: string }) | undefined;\n /**\n * The physician who requests diagnostic procedures for the patient and responsible for the order.\n * The Practitioner MUST have an NPI identifier with the system http://hl7.org/fhir/sid/us-npi.\n */\n requester: Practitioner | (Reference<Practitioner> & { reference: string }) | undefined;\n\n /** For multi-location practices, optionally specify the location from which the order is being placed */\n requestingLocation?: Location | Organization | (Reference<Location | Organization> & { reference: string });\n};\n\nexport type EditableLabOrderTestMetadata = Pick<LabOrderTestMetadata, 'priority' | 'notes' | 'aoeResponses'>;\nconst EDITABLE_TEST_METADATA_KEYS: (keyof LabOrderTestMetadata)[] = ['priority', 'notes', 'aoeResponses'];\n\nconst INITIAL_TESTS: TestCoding[] = [];\nconst INITIAL_DIAGNOSES: DiagnosisCodeableConcept[] = [];\nconst INITIAL_TEST_METADATA = {};\nconst INITIAL_BILLING_INFORMATION: BillingInformation = {};\n\ntype TestsReducerState = {\n // Don't use any optional fields here, e.g. `selectedTests?: TestCoding[]` since that would cause\n // some loss of type safety in the reducer function by allowing the reducer to potentially return\n // incomplete state if a field is forgotten about. Instead, always use explicity `| undefined` for\n // fields that may legitimately be undefined in the state.\n\n selectedTests: TestCoding[];\n testMetadata: Record<string, TestMetadata | undefined>;\n specimenCollectedDateTime: Date | undefined;\n};\n\ntype AddTestAction = { type: 'add'; test: TestCoding };\ntype RemoveTestAction = { type: 'remove'; test: TestCoding };\ntype SetTestsAction = { type: 'set'; tests: TestCoding[] };\ntype UpdateMetadataAction = {\n type: 'updateMetadata';\n test: TestCoding;\n partialMetadata: Partial<EditableLabOrderTestMetadata>;\n};\ntype AoeLoadedAction = { type: 'aoeLoaded'; testAoes: [TestCoding, Questionnaire | undefined][] };\ntype SpecimentCollectionDateChangeAction = { type: 'specimenCollectionDateChange'; newDate: Date | undefined };\ntype AoeErrorAction = { type: 'aoeError'; tests: TestCoding[] };\n\ntype TestsAction =\n | AddTestAction\n | RemoveTestAction\n | SetTestsAction\n | UpdateMetadataAction\n | AoeLoadedAction\n | SpecimentCollectionDateChangeAction\n | AoeErrorAction;\n\nfunction testsReducer(prev: TestsReducerState, action: TestsAction): TestsReducerState {\n switch (action.type) {\n case 'add':\n return addTest(prev, action);\n case 'remove':\n return removeTest(prev, action);\n case 'set':\n return setTests(prev, action);\n case 'updateMetadata':\n return updateMetadata(prev, action);\n case 'aoeLoaded':\n return aoeLoaded(prev, action);\n case 'aoeError':\n return aoeError(prev, action);\n case 'specimenCollectionDateChange':\n return specimenCollectionDateChange(prev, action);\n default:\n action satisfies never;\n return prev;\n }\n}\n\nfunction addTest(prev: TestsReducerState, action: AddTestAction): TestsReducerState {\n if (prev.selectedTests.some((test) => test.code === action.test.code)) {\n return prev;\n }\n\n return {\n ...prev,\n selectedTests: [...prev.selectedTests, action.test],\n testMetadata: {\n ...prev.testMetadata,\n [action.test.code]: { aoeStatus: 'loading' },\n },\n };\n}\n\nfunction removeTest(prev: TestsReducerState, action: RemoveTestAction): TestsReducerState {\n return {\n ...prev,\n selectedTests: prev.selectedTests.filter((test) => test.code !== action.test.code),\n testMetadata: Object.fromEntries(Object.entries(prev.testMetadata).filter(([code]) => code !== action.test.code)),\n };\n}\n\nfunction setTests(prev: TestsReducerState, action: SetTestsAction): TestsReducerState {\n return {\n ...prev,\n selectedTests: action.tests,\n testMetadata: Object.fromEntries(\n action.tests.map((test) => {\n return [test.code, prev.testMetadata[test.code] ?? { aoeStatus: 'loading' }];\n })\n ),\n };\n}\n\nfunction updateMetadata(prev: TestsReducerState, action: UpdateMetadataAction): TestsReducerState {\n const { test, partialMetadata } = action;\n\n const prevTestMetadata = prev.testMetadata[test.code];\n if (!prev.selectedTests.some((t) => t.code === test.code) || !prevTestMetadata) {\n console.warn(`Cannot update metadata for test ${test.code} since it is not a selected tests`);\n return prev;\n }\n\n let allowedUpdates = partialMetadata;\n const restrictedKeys = Object.keys(partialMetadata).filter((k) => !EDITABLE_TEST_METADATA_KEYS.includes(k as any));\n\n if (restrictedKeys.length > 0) {\n console.warn(`Cannot update test metadata fields: ${restrictedKeys.join(', ')}`);\n\n allowedUpdates = Object.fromEntries(Object.entries(partialMetadata).filter(([k]) => !restrictedKeys.includes(k)));\n }\n\n return {\n ...prev,\n testMetadata: {\n ...prev.testMetadata,\n [test.code]: { ...prevTestMetadata, ...allowedUpdates },\n },\n };\n}\n\nfunction aoeLoaded(prev: TestsReducerState, action: AoeLoadedAction): TestsReducerState {\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const [test, aoeQuestionnaire] of action.testAoes) {\n const prevMetadata = prev.testMetadata[test.code];\n if (!prevMetadata) {\n continue;\n }\n\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = {\n ...prevMetadata,\n aoeStatus: aoeQuestionnaire ? 'loaded' : 'none',\n aoeQuestionnaire:\n aoeQuestionnaire &&\n (updateAoeQuestionnaireRequiredItems(aoeQuestionnaire, prev.specimenCollectedDateTime) ?? aoeQuestionnaire),\n };\n }\n\n if (!newTestMetadata) {\n return prev;\n }\n\n return {\n ...prev,\n testMetadata: newTestMetadata,\n };\n}\n\nfunction aoeError(prev: TestsReducerState, action: AoeErrorAction): TestsReducerState {\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const test of action.tests) {\n const prevMetadata = prev.testMetadata[test.code];\n if (prevMetadata?.aoeStatus !== 'loading') {\n continue;\n }\n\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = {\n ...prevMetadata,\n aoeStatus: 'error',\n };\n }\n\n if (!newTestMetadata) {\n return prev;\n }\n\n return {\n ...prev,\n testMetadata: newTestMetadata,\n };\n}\n\nfunction specimenCollectionDateChange(\n prev: TestsReducerState,\n action: SpecimentCollectionDateChangeAction\n): TestsReducerState {\n // check if we can reuse the previous testMetadata object to avoid unnecessary downstream re-renders\n let newTestMetadata: TestsReducerState['testMetadata'] | undefined;\n for (const test of prev.selectedTests) {\n const prevTestMetadata = prev.testMetadata[test.code];\n if (!prevTestMetadata) {\n continue;\n }\n\n if (prevTestMetadata.aoeQuestionnaire) {\n const newAoeQ = updateAoeQuestionnaireRequiredItems(prevTestMetadata.aoeQuestionnaire, action.newDate);\n if (newAoeQ) {\n newTestMetadata ??= { ...prev.testMetadata };\n newTestMetadata[test.code] = { ...prevTestMetadata, aoeQuestionnaire: newAoeQ };\n }\n }\n }\n\n return {\n ...prev,\n // If no changes to AOE questionnaire item.required, reuse the previous object\n testMetadata: newTestMetadata ?? prev.testMetadata,\n specimenCollectedDateTime: action.newDate,\n };\n}\n\nexport function useHealthGorillaLabOrder(opts: UseHealthGorillaLabOrderOptions): UseHealthGorillaLabOrderReturn {\n const medplum = useMedplum();\n const [performingLab, privateSetPerformingLab] = useState<LabOrganization | undefined>();\n const [performingLabAccountNumber, privateSetPerformingLabAccountNumber] = useState<string | undefined>();\n const [testsAndMetadata, dispatchTests] = useReducer(testsReducer, {\n specimenCollectedDateTime: undefined,\n selectedTests: INITIAL_TESTS,\n testMetadata: INITIAL_TEST_METADATA,\n });\n const [diagnoses, privateSetDiagnoses] = useState<DiagnosisCodeableConcept[]>(INITIAL_DIAGNOSES);\n const [billingInformation, setBillingInformation] = useState<BillingInformation>(INITIAL_BILLING_INFORMATION);\n const [orderNotes, privateSetOrderNotes] = useState<string | undefined>();\n\n const healthGorillaAutocomplete = useMemo(() => {\n return getAutocompleteSearchFunction(medplum, {\n system: 'https://www.medplum.com/integrations/bot-identifier',\n value: 'health-gorilla-labs/autocomplete',\n });\n }, [medplum]);\n\n const fetchAoeQuestionnaire = useCallback(\n async (testCode: TestCoding): Promise<Questionnaire | undefined> => {\n const response = await healthGorillaAutocomplete<AOESearch>({ type: 'aoe', testCode });\n const questionnaire = response.result;\n if (questionnaire) {\n normalizeAoeQuestionnaire(questionnaire);\n }\n return response.result;\n },\n [healthGorillaAutocomplete]\n );\n\n const lastSelectedTests = useRef<TestCoding[]>([]);\n useEffect(() => {\n // effect only runs when selectedTests changes\n if (lastSelectedTests.current === testsAndMetadata.selectedTests) {\n return;\n }\n lastSelectedTests.current = testsAndMetadata.selectedTests;\n\n const testCodingAoesBeingFetched: TestCoding[] = [];\n async function fetchNeededAoeQuestions(): Promise<[TestCoding, Questionnaire | undefined][]> {\n const promises: Promise<[TestCoding, Questionnaire | undefined]>[] = [];\n for (const test of testsAndMetadata.selectedTests) {\n const metadata = testsAndMetadata.testMetadata[test.code];\n if (metadata?.aoeStatus !== 'loading') {\n continue;\n }\n\n testCodingAoesBeingFetched.push(test);\n promises.push(\n new Promise((resolve, reject) => {\n fetchAoeQuestionnaire(test)\n .then((questionnaire) => {\n resolve([test, questionnaire]);\n })\n .catch(reject);\n })\n );\n }\n return Promise.all(promises);\n }\n\n fetchNeededAoeQuestions()\n .then((results) => {\n if (results.length === 0) {\n return;\n }\n\n dispatchTests({ type: 'aoeLoaded', testAoes: results });\n })\n .catch((err) => {\n console.error('Error fetching AOE questions', err);\n dispatchTests({ type: 'aoeError', tests: testCodingAoesBeingFetched });\n });\n }, [fetchAoeQuestionnaire, testsAndMetadata]);\n\n const patientRef = useMemo<(Reference<Patient> & { reference: string }) | undefined>(() => {\n if (isReferenceOfType('Patient', opts.patient)) {\n return opts.patient;\n } else if (isResource(opts.patient)) {\n return createReference(opts.patient);\n } else {\n return undefined;\n }\n }, [opts.patient]);\n\n const requesterRef = useMemo<(Reference<Practitioner> & { reference: string }) | undefined>(() => {\n if (isReferenceOfType('Practitioner', opts.requester)) {\n return opts.requester;\n } else if (isResource(opts.requester)) {\n return createReference(opts.requester) as Reference<Practitioner> & { reference: string };\n } else {\n opts.requester satisfies undefined;\n return undefined;\n }\n }, [opts.requester]);\n\n const requestingLocationRef = useMemo<\n (Reference<Location | Organization> & { reference: string }) | undefined\n >(() => {\n if (\n isReferenceOfType('Location', opts.requestingLocation) ||\n isReferenceOfType('Organization', opts.requestingLocation)\n ) {\n return opts.requestingLocation;\n } else if (isResource(opts.requestingLocation)) {\n return createReference(opts.requestingLocation);\n } else {\n return undefined;\n }\n }, [opts.requestingLocation]);\n\n const state: HealthGorillaLabOrderState = useMemo(() => {\n const cloned = deepClone({\n performingLab,\n performingLabAccountNumber,\n selectedTests: testsAndMetadata.selectedTests,\n testMetadata: testsAndMetadata.testMetadata,\n diagnoses,\n billingInformation,\n specimenCollectedDateTime: testsAndMetadata.specimenCollectedDateTime,\n orderNotes,\n });\n\n // specimenCollectedDateTime is a Date object which is serialized in deepClone\n if (cloned.specimenCollectedDateTime) {\n cloned.specimenCollectedDateTime = new Date(cloned.specimenCollectedDateTime);\n }\n\n return cloned;\n }, [\n performingLab,\n performingLabAccountNumber,\n testsAndMetadata.selectedTests,\n testsAndMetadata.testMetadata,\n testsAndMetadata.specimenCollectedDateTime,\n diagnoses,\n billingInformation,\n orderNotes,\n ]);\n\n const getActivePatientCoverages = useCallback(async (): Promise<ResourceArray<Coverage>> => {\n if (!patientRef) {\n const emptyResult: Coverage[] = [];\n return Object.assign(emptyResult, {\n bundle: { resourceType: 'Bundle', type: 'searchset', entry: [], total: 0 } as Bundle<Coverage>,\n });\n }\n\n const coverageSearch = new URLSearchParams({\n patient: patientRef.reference,\n status: 'active',\n });\n const coverages = (await medplum.searchResources('Coverage', coverageSearch)).sort((a, b) => {\n return (a.order ?? Number.POSITIVE_INFINITY) - (b.order ?? Number.POSITIVE_INFINITY);\n });\n\n return coverages;\n }, [medplum, patientRef]);\n\n const updateBillingInformation = useCallback((billingInfo: Partial<BillingInformation>): void => {\n setBillingInformation((prev) => {\n return { ...prev, ...billingInfo };\n });\n }, []);\n\n const result: UseHealthGorillaLabOrderReturn = useMemo(() => {\n return {\n state,\n searchAvailableLabs: async (query: string) => {\n const response = await healthGorillaAutocomplete<LabSearch>({ type: 'lab', query });\n // consider filtering to labs that have the https://www.healthgorilla.com/fhir/StructureDefinition/provider-compendium extension?\n return response.result;\n },\n searchAvailableTests: async (query: string): Promise<TestCoding[]> => {\n if (!performingLab) {\n return [];\n }\n\n const hgLabId = getIdentifier(performingLab, HEALTH_GORILLA_SYSTEM);\n if (!hgLabId) {\n throw new Error('No Health Gorilla identifier found for performing lab');\n }\n\n if (query.length === 0) {\n return [];\n }\n const response = await healthGorillaAutocomplete<TestSearch>({ type: 'test', query, labId: hgLabId });\n\n const tests = response.result;\n return tests;\n },\n setPerformingLab: (newLab: LabOrganization | undefined) => {\n privateSetPerformingLab(newLab);\n },\n setPerformingLabAccountNumber: (newAccountNumber: string | undefined) => {\n privateSetPerformingLabAccountNumber(newAccountNumber);\n },\n addTest: (test: TestCoding) => {\n dispatchTests({ type: 'add', test });\n },\n removeTest: (test: TestCoding) => {\n dispatchTests({ type: 'remove', test });\n },\n setTests: (newTests: TestCoding[]) => {\n dispatchTests({ type: 'set', tests: newTests });\n },\n\n updateTestMetadata: (test: TestCoding, partialMetadata: Partial<LabOrderTestMetadata>) => {\n dispatchTests({ type: 'updateMetadata', test, partialMetadata });\n },\n\n addDiagnosis(diagnosis: DiagnosisCodeableConcept) {\n privateSetDiagnoses((prev) => {\n if (prev.some((item) => toDiagnosisKey(item) === toDiagnosisKey(diagnosis))) {\n return prev;\n }\n return [...prev, diagnosis];\n });\n },\n removeDiagnosis(diagnosis: DiagnosisCodeableConcept) {\n privateSetDiagnoses((prev) => {\n const idx = prev.findIndex((item) => toDiagnosisKey(item) === toDiagnosisKey(diagnosis));\n if (idx === -1) {\n return prev;\n }\n const next = [...prev];\n next.splice(idx, 1);\n return next;\n });\n },\n setDiagnoses: (newDiagnoses: DiagnosisCodeableConcept[]) => {\n privateSetDiagnoses(newDiagnoses);\n },\n\n getActivePatientCoverages,\n updateBillingInformation,\n\n setSpecimenCollectedDateTime: (newDate: Date | undefined) => {\n dispatchTests({ type: 'specimenCollectionDateChange', newDate });\n },\n\n setOrderNotes: (newOrderNotes: string | undefined) => {\n // || instead of ?? so that empty string becomes undefined\n privateSetOrderNotes(newOrderNotes || undefined);\n },\n\n validateOrder: () => {\n return validateLabOrderInputs({\n patient: patientRef,\n requester: requesterRef,\n performingLab: state.performingLab,\n performingLabAccountNumber: state.performingLabAccountNumber,\n selectedTests: state.selectedTests,\n testMetadata: state.testMetadata,\n diagnoses: state.diagnoses,\n billingInformation: state.billingInformation,\n specimenCollectedDateTime: state.specimenCollectedDateTime,\n orderNotes: state.orderNotes,\n });\n },\n createOrderBundle: async (): Promise<{ transactionResponse: Bundle; serviceRequest: ServiceRequest }> => {\n const txn = createLabOrderBundle({\n patient: patientRef,\n requester: requesterRef,\n requestingLocation: requestingLocationRef,\n performingLab: state.performingLab,\n performingLabAccountNumber: state.performingLabAccountNumber,\n selectedTests: state.selectedTests,\n testMetadata: state.testMetadata,\n diagnoses: state.diagnoses,\n billingInformation: state.billingInformation,\n specimenCollectedDateTime: state.specimenCollectedDateTime,\n orderNotes: state.orderNotes,\n });\n\n const transactionResponse = await medplum.executeBatch(txn);\n\n let labOrderServiceRequest: LabOrderServiceRequest | undefined;\n\n for (const entry of transactionResponse?.entry ?? []) {\n if (!entry.response?.status.startsWith('2')) {\n throw new Error('Error creating lab order: Non-2XX status code in response entry', {\n cause: transactionResponse,\n });\n }\n if (entry.resource?.meta?.profile?.includes(MEDPLUM_HEALTH_GORILLA_LAB_ORDER_PROFILE)) {\n labOrderServiceRequest = entry.resource as LabOrderServiceRequest;\n }\n }\n\n if (!labOrderServiceRequest) {\n throw new Error('Error creating lab order: Lab Order Service Request not found in response entries', {\n cause: transactionResponse,\n });\n }\n\n return { transactionResponse, serviceRequest: labOrderServiceRequest };\n },\n };\n }, [\n getActivePatientCoverages,\n healthGorillaAutocomplete,\n medplum,\n patientRef,\n performingLab,\n requestingLocationRef,\n requesterRef,\n state,\n updateBillingInformation,\n ]);\n\n return result;\n}\n\nfunction toDiagnosisKey(diagnosis: DiagnosisCodeableConcept): string {\n return diagnosis.coding[0].code;\n}\n\nconst REQUIRED_WHEN_SPECIMEN =\n 'https://www.healthgorilla.com/fhir/StructureDefinition/questionnaire-requiredwhenspecimen';\n\n/**\n * Updates the required status of items in an AOE questionnaire based on whether a specimen is being collected.\n * @param questionnaire - The Questionnaire to update\n * @param specimenCollectedDateTime - The date and time the specimen was collected, if any\n * @returns A new Questionnaire with the required status of items updated, or undefined if no changes are needed\n */\nfunction updateAoeQuestionnaireRequiredItems(\n questionnaire: Questionnaire,\n specimenCollectedDateTime: Date | undefined\n): Questionnaire | undefined {\n let newQuestionnaire: Questionnaire | undefined;\n const requiredWhenSpecimen = Boolean(specimenCollectedDateTime);\n\n // first, check if the questionnaire needs any changes\n for (const item of questionnaireItemIterator(questionnaire.item)) {\n if (getExtensionValue(item, REQUIRED_WHEN_SPECIMEN) === true && Boolean(item.required) !== requiredWhenSpecimen) {\n // at least one item needs updating, so crete a clone to be updated rather than updating in place\n newQuestionnaire = deepClone(questionnaire);\n break;\n }\n }\n\n // if no changes are needed, return the original Questionnaire\n if (!newQuestionnaire) {\n return undefined;\n }\n\n // otherwise, update the cloned Questionnaire\n for (const item of questionnaireItemIterator(newQuestionnaire.item)) {\n if (getExtensionValue(item, REQUIRED_WHEN_SPECIMEN) === true) {\n if (Boolean(item.required) !== requiredWhenSpecimen) {\n item.required = requiredWhenSpecimen;\n }\n }\n }\n\n return newQuestionnaire;\n}\n", "// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors\n// SPDX-License-Identifier: Apache-2.0\nimport { useContext } from 'react';\nimport type { UseHealthGorillaLabOrderReturn } from './HealthGorillaLabOrderContext';\nimport { HealthGorillaLabOrderContext } from './HealthGorillaLabOrderContext';\n\nexport function useHealthGorillaLabOrderContext(): UseHealthGorillaLabOrderReturn {\n const context = useContext(HealthGorillaLabOrderContext);\n if (context === undefined) {\n throw new Error('useHealthGorillaLabOrderContext must be used within a HealthGorillaLabOrderProvider');\n }\n return context;\n}\n"],
5
+ "mappings": "AAGA,OAAS,YAAa,SAAU,eAAkB,gBAmB3C,SAAS,8BACd,QACA,gBACkB,CAClB,MAAO,OAAO,QAAW,CACvB,IAAM,YAAyC,MAAM,QAAQ,WAAW,gBAAiB,OAAQ,YAAY,IAAI,EACjH,GAAI,YAAY,OAAS,QACvB,MAAM,IAAI,MAAM,mCAAoC,CAAE,MAAO,WAAY,CAAC,EAG5E,GAAM,CAAE,MAAO,EAAI,YACnB,OAAQ,YAAY,KAAM,CACxB,IAAK,MAAO,CACV,GAAI,MAAM,QAAQ,MAAM,GAAK,OAAO,MAAO,MAAS,WAAW,KAAM,cAAc,CAAC,EAClF,MAAO,CAAE,KAAM,MAAO,MAAO,EAE/B,KACF,CACA,IAAK,OAAQ,CACX,GAAM,CAAE,OAAAA,OAAO,EAAI,YACnB,GAAI,MAAM,QAAQA,OAAM,GAAKA,QAAO,MAAO,MAAS,SAAS,IAAI,CAAC,EAChE,MAAO,CAAE,KAAM,OAAQ,OAAAA,OAAO,EAEhC,KACF,CACA,IAAK,MAAO,CACV,GAAM,CAAE,OAAAA,OAAO,EAAI,YACnB,GAAIA,UAAW,QAAaA,QAAO,eAAiB,gBAClD,MAAO,CAAE,KAAM,MAAO,OAAAA,OAAO,EAE/B,KACF,CACA,QAGF,CACA,MAAM,IAAI,MAAM,4BAA4B,KAAK,UAAU,MAAM,CAAC,EAAE,CACtE,CACF,CCjDA,OAAS,kBAAqB,QAwDvB,IAAM,6BAA+B,cAA0D,MAAS,ECpB3G,mCAHG,SAAS,8BAA8B,MAAuE,CACnH,GAAM,CAAE,SAAU,GAAG,cAAe,EAAI,MACxC,OACE,IAAC,6BAA6B,SAA7B,CAAsC,MAAO,eAAiB,SAAS,CAE5E,CC9CA,OAAS,gBAAiB,UAAW,kBAAmB,cAAe,cAAAC,gBAAkB,gBAoBzF,OACE,sBACA,yCACA,qBACA,kBACA,0BACA,0BACA,2BACK,+BACP,OAAS,eAAkB,iBAC3B,OAAS,YAAa,UAAW,QAAS,WAAY,OAAQ,aAAgB,QAuB9E,IAAM,4BAA8D,CAAC,WAAY,QAAS,cAAc,EAElG,cAA8B,CAAC,EAC/B,kBAAgD,CAAC,EACjD,sBAAwB,CAAC,EACzB,4BAAkD,CAAC,EAkCzD,SAAS,aAAa,KAAyB,OAAwC,CACrF,OAAQ,OAAO,KAAM,CACnB,IAAK,MACH,OAAO,QAAQ,KAAM,MAAM,EAC7B,IAAK,SACH,OAAO,WAAW,KAAM,MAAM,EAChC,IAAK,MACH,OAAO,SAAS,KAAM,MAAM,EAC9B,IAAK,iBACH,OAAO,eAAe,KAAM,MAAM,EACpC,IAAK,YACH,OAAO,UAAU,KAAM,MAAM,EAC/B,IAAK,WACH,OAAO,SAAS,KAAM,MAAM,EAC9B,IAAK,+BACH,OAAO,6BAA6B,KAAM,MAAM,EAClD,QAEE,OAAO,IACX,CACF,CAEA,SAAS,QAAQ,KAAyB,OAA0C,CAClF,OAAI,KAAK,cAAc,KAAM,MAAS,KAAK,OAAS,OAAO,KAAK,IAAI,EAC3D,KAGF,CACL,GAAG,KACH,cAAe,CAAC,GAAG,KAAK,cAAe,OAAO,IAAI,EAClD,aAAc,CACZ,GAAG,KAAK,aACR,CAAC,OAAO,KAAK,IAAI,EAAG,CAAE,UAAW,SAAU,CAC7C,CACF,CACF,CAEA,SAAS,WAAW,KAAyB,OAA6C,CACxF,MAAO,CACL,GAAG,KACH,cAAe,KAAK,cAAc,OAAQ,MAAS,KAAK,OAAS,OAAO,KAAK,IAAI,EACjF,aAAc,OAAO,YAAY,OAAO,QAAQ,KAAK,YAAY,EAAE,OAAO,CAAC,CAAC,IAAI,IAAM,OAAS,OAAO,KAAK,IAAI,CAAC,CAClH,CACF,CAEA,SAAS,SAAS,KAAyB,OAA2C,CACpF,MAAO,CACL,GAAG,KACH,cAAe,OAAO,MACtB,aAAc,OAAO,YACnB,OAAO,MAAM,IAAK,MACT,CAAC,KAAK,KAAM,KAAK,aAAa,KAAK,IAAI,GAAK,CAAE,UAAW,SAAU,CAAC,CAC5E,CACH,CACF,CACF,CAEA,SAAS,eAAe,KAAyB,OAAiD,CAChG,GAAM,CAAE,KAAM,eAAgB,EAAI,OAE5B,iBAAmB,KAAK,aAAa,KAAK,IAAI,EACpD,GAAI,CAAC,KAAK,cAAc,KAAM,GAAM,EAAE,OAAS,KAAK,IAAI,GAAK,CAAC,iBAC5D,eAAQ,KAAK,mCAAmC,KAAK,IAAI,mCAAmC,EACrF,KAGT,IAAI,eAAiB,gBACf,eAAiB,OAAO,KAAK,eAAe,EAAE,OAAQ,GAAM,CAAC,4BAA4B,SAAS,CAAQ,CAAC,EAEjH,OAAI,eAAe,OAAS,IAC1B,QAAQ,KAAK,uCAAuC,eAAe,KAAK,IAAI,CAAC,EAAE,EAE/E,eAAiB,OAAO,YAAY,OAAO,QAAQ,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC,IAAM,CAAC,eAAe,SAAS,CAAC,CAAC,CAAC,GAG3G,CACL,GAAG,KACH,aAAc,CACZ,GAAG,KAAK,aACR,CAAC,KAAK,IAAI,EAAG,CAAE,GAAG,iBAAkB,GAAG,cAAe,CACxD,CACF,CACF,CAEA,SAAS,UAAU,KAAyB,OAA4C,CACtF,IAAI,gBACJ,OAAW,CAAC,KAAM,gBAAgB,IAAK,OAAO,SAAU,CACtD,IAAM,aAAe,KAAK,aAAa,KAAK,IAAI,EAC3C,eAIL,kBAAoB,CAAE,GAAG,KAAK,YAAa,EAC3C,gBAAgB,KAAK,IAAI,EAAI,CAC3B,GAAG,aACH,UAAW,iBAAmB,SAAW,OACzC,iBACE,mBACC,oCAAoC,iBAAkB,KAAK,yBAAyB,GAAK,iBAC9F,EACF,CAEA,OAAK,gBAIE,CACL,GAAG,KACH,aAAc,eAChB,EANS,IAOX,CAEA,SAAS,SAAS,KAAyB,OAA2C,CACpF,IAAI,gBACJ,QAAW,QAAQ,OAAO,MAAO,CAC/B,IAAM,aAAe,KAAK,aAAa,KAAK,IAAI,EAC5C,cAAc,YAAc,YAIhC,kBAAoB,CAAE,GAAG,KAAK,YAAa,EAC3C,gBAAgB,KAAK,IAAI,EAAI,CAC3B,GAAG,aACH,UAAW,OACb,EACF,CAEA,OAAK,gBAIE,CACL,GAAG,KACH,aAAc,eAChB,EANS,IAOX,CAEA,SAAS,6BACP,KACA,OACmB,CAEnB,IAAI,gBACJ,QAAW,QAAQ,KAAK,cAAe,CACrC,IAAM,iBAAmB,KAAK,aAAa,KAAK,IAAI,EACpD,GAAK,kBAID,iBAAiB,iBAAkB,CACrC,IAAM,QAAU,oCAAoC,iBAAiB,iBAAkB,OAAO,OAAO,EACjG,UACF,kBAAoB,CAAE,GAAG,KAAK,YAAa,EAC3C,gBAAgB,KAAK,IAAI,EAAI,CAAE,GAAG,iBAAkB,iBAAkB,OAAQ,EAElF,CACF,CAEA,MAAO,CACL,GAAG,KAEH,aAAc,iBAAmB,KAAK,aACtC,0BAA2B,OAAO,OACpC,CACF,CAEO,SAAS,yBAAyB,KAAuE,CAC9G,IAAM,QAAU,WAAW,EACrB,CAAC,cAAe,uBAAuB,EAAI,SAAsC,EACjF,CAAC,2BAA4B,oCAAoC,EAAI,SAA6B,EAClG,CAAC,iBAAkB,aAAa,EAAI,WAAW,aAAc,CACjE,0BAA2B,OAC3B,cAAe,cACf,aAAc,qBAChB,CAAC,EACK,CAAC,UAAW,mBAAmB,EAAI,SAAqC,iBAAiB,EACzF,CAAC,mBAAoB,qBAAqB,EAAI,SAA6B,2BAA2B,EACtG,CAAC,WAAY,oBAAoB,EAAI,SAA6B,EAElE,0BAA4B,QAAQ,IACjC,8BAA8B,QAAS,CAC5C,OAAQ,sDACR,MAAO,kCACT,CAAC,EACA,CAAC,OAAO,CAAC,EAEN,sBAAwB,YAC5B,MAAO,UAA6D,CAClE,IAAM,SAAW,MAAM,0BAAqC,CAAE,KAAM,MAAO,QAAS,CAAC,EAC/E,cAAgB,SAAS,OAC/B,OAAI,eACF,0BAA0B,aAAa,EAElC,SAAS,MAClB,EACA,CAAC,yBAAyB,CAC5B,EAEM,kBAAoB,OAAqB,CAAC,CAAC,EACjD,UAAU,IAAM,CAEd,GAAI,kBAAkB,UAAY,iBAAiB,cACjD,OAEF,kBAAkB,QAAU,iBAAiB,cAE7C,IAAM,2BAA2C,CAAC,EAClD,eAAe,yBAA8E,CAC3F,IAAM,SAA+D,CAAC,EACtE,QAAW,QAAQ,iBAAiB,cACjB,iBAAiB,aAAa,KAAK,IAAI,GAC1C,YAAc,YAI5B,2BAA2B,KAAK,IAAI,EACpC,SAAS,KACP,IAAI,QAAQ,CAAC,QAAS,SAAW,CAC/B,sBAAsB,IAAI,EACvB,KAAM,eAAkB,CACvB,QAAQ,CAAC,KAAM,aAAa,CAAC,CAC/B,CAAC,EACA,MAAM,MAAM,CACjB,CAAC,CACH,GAEF,OAAO,QAAQ,IAAI,QAAQ,CAC7B,CAEA,wBAAwB,EACrB,KAAM,SAAY,CACb,QAAQ,SAAW,GAIvB,cAAc,CAAE,KAAM,YAAa,SAAU,OAAQ,CAAC,CACxD,CAAC,EACA,MAAO,KAAQ,CACd,QAAQ,MAAM,+BAAgC,GAAG,EACjD,cAAc,CAAE,KAAM,WAAY,MAAO,0BAA2B,CAAC,CACvE,CAAC,CACL,EAAG,CAAC,sBAAuB,gBAAgB,CAAC,EAE5C,IAAM,WAAa,QAAkE,IAC/E,kBAAkB,UAAW,KAAK,OAAO,EACpC,KAAK,QACHC,YAAW,KAAK,OAAO,EACzB,gBAAgB,KAAK,OAAO,EAEnC,OAED,CAAC,KAAK,OAAO,CAAC,EAEX,aAAe,QAAuE,IAAM,CAChG,GAAI,kBAAkB,eAAgB,KAAK,SAAS,EAClD,OAAO,KAAK,UACP,GAAIA,YAAW,KAAK,SAAS,EAClC,OAAO,gBAAgB,KAAK,SAAS,EAErC,KAAK,SAGT,EAAG,CAAC,KAAK,SAAS,CAAC,EAEb,sBAAwB,QAE5B,IAEE,kBAAkB,WAAY,KAAK,kBAAkB,GACrD,kBAAkB,eAAgB,KAAK,kBAAkB,EAElD,KAAK,mBACHA,YAAW,KAAK,kBAAkB,EACpC,gBAAgB,KAAK,kBAAkB,EAE9C,OAED,CAAC,KAAK,kBAAkB,CAAC,EAEtB,MAAoC,QAAQ,IAAM,CACtD,IAAM,OAAS,UAAU,CACvB,cACA,2BACA,cAAe,iBAAiB,cAChC,aAAc,iBAAiB,aAC/B,UACA,mBACA,0BAA2B,iBAAiB,0BAC5C,UACF,CAAC,EAGD,OAAI,OAAO,4BACT,OAAO,0BAA4B,IAAI,KAAK,OAAO,yBAAyB,GAGvE,MACT,EAAG,CACD,cACA,2BACA,iBAAiB,cACjB,iBAAiB,aACjB,iBAAiB,0BACjB,UACA,mBACA,UACF,CAAC,EAEK,0BAA4B,YAAY,SAA8C,CAC1F,GAAI,CAAC,WAEH,OAAO,OAAO,OADkB,CAAC,EACC,CAChC,OAAQ,CAAE,aAAc,SAAU,KAAM,YAAa,MAAO,CAAC,EAAG,MAAO,CAAE,CAC3E,CAAC,EAGH,IAAM,eAAiB,IAAI,gBAAgB,CACzC,QAAS,WAAW,UACpB,OAAQ,QACV,CAAC,EAKD,OAJmB,MAAM,QAAQ,gBAAgB,WAAY,cAAc,GAAG,KAAK,CAAC,EAAG,KAC7E,EAAE,OAAS,OAAO,oBAAsB,EAAE,OAAS,OAAO,kBACnE,CAGH,EAAG,CAAC,QAAS,UAAU,CAAC,EAElB,yBAA2B,YAAa,aAAmD,CAC/F,sBAAuB,OACd,CAAE,GAAG,KAAM,GAAG,WAAY,EAClC,CACH,EAAG,CAAC,CAAC,EAoJL,OAlJ+C,QAAQ,KAC9C,CACL,MACA,oBAAqB,MAAO,QACT,MAAM,0BAAqC,CAAE,KAAM,MAAO,KAAM,CAAC,GAElE,OAElB,qBAAsB,MAAO,OAAyC,CACpE,GAAI,CAAC,cACH,MAAO,CAAC,EAGV,IAAM,QAAU,cAAc,cAAe,qBAAqB,EAClE,GAAI,CAAC,QACH,MAAM,IAAI,MAAM,uDAAuD,EAGzE,OAAI,MAAM,SAAW,EACZ,CAAC,GAEO,MAAM,0BAAsC,CAAE,KAAM,OAAQ,MAAO,MAAO,OAAQ,CAAC,GAE7E,MAEzB,EACA,iBAAmB,QAAwC,CACzD,wBAAwB,MAAM,CAChC,EACA,8BAAgC,kBAAyC,CACvE,qCAAqC,gBAAgB,CACvD,EACA,QAAU,MAAqB,CAC7B,cAAc,CAAE,KAAM,MAAO,IAAK,CAAC,CACrC,EACA,WAAa,MAAqB,CAChC,cAAc,CAAE,KAAM,SAAU,IAAK,CAAC,CACxC,EACA,SAAW,UAA2B,CACpC,cAAc,CAAE,KAAM,MAAO,MAAO,QAAS,CAAC,CAChD,EAEA,mBAAoB,CAAC,KAAkB,kBAAmD,CACxF,cAAc,CAAE,KAAM,iBAAkB,KAAM,eAAgB,CAAC,CACjE,EAEA,aAAa,UAAqC,CAChD,oBAAqB,MACf,KAAK,KAAM,MAAS,eAAe,IAAI,IAAM,eAAe,SAAS,CAAC,EACjE,KAEF,CAAC,GAAG,KAAM,SAAS,CAC3B,CACH,EACA,gBAAgB,UAAqC,CACnD,oBAAqB,MAAS,CAC5B,IAAM,IAAM,KAAK,UAAW,MAAS,eAAe,IAAI,IAAM,eAAe,SAAS,CAAC,EACvF,GAAI,MAAQ,GACV,OAAO,KAET,IAAM,KAAO,CAAC,GAAG,IAAI,EACrB,YAAK,OAAO,IAAK,CAAC,EACX,IACT,CAAC,CACH,EACA,aAAe,cAA6C,CAC1D,oBAAoB,YAAY,CAClC,EAEA,0BACA,yBAEA,6BAA+B,SAA8B,CAC3D,cAAc,CAAE,KAAM,+BAAgC,OAAQ,CAAC,CACjE,EAEA,cAAgB,eAAsC,CAEpD,qBAAqB,eAAiB,MAAS,CACjD,EAEA,cAAe,IACN,uBAAuB,CAC5B,QAAS,WACT,UAAW,aACX,cAAe,MAAM,cACrB,2BAA4B,MAAM,2BAClC,cAAe,MAAM,cACrB,aAAc,MAAM,aACpB,UAAW,MAAM,UACjB,mBAAoB,MAAM,mBAC1B,0BAA2B,MAAM,0BACjC,WAAY,MAAM,UACpB,CAAC,EAEH,kBAAmB,SAAsF,CACvG,IAAM,IAAM,qBAAqB,CAC/B,QAAS,WACT,UAAW,aACX,mBAAoB,sBACpB,cAAe,MAAM,cACrB,2BAA4B,MAAM,2BAClC,cAAe,MAAM,cACrB,aAAc,MAAM,aACpB,UAAW,MAAM,UACjB,mBAAoB,MAAM,mBAC1B,0BAA2B,MAAM,0BACjC,WAAY,MAAM,UACpB,CAAC,EAEK,oBAAsB,MAAM,QAAQ,aAAa,GAAG,EAEtD,uBAEJ,QAAW,SAAS,qBAAqB,OAAS,CAAC,EAAG,CACpD,GAAI,CAAC,MAAM,UAAU,OAAO,WAAW,GAAG,EACxC,MAAM,IAAI,MAAM,kEAAmE,CACjF,MAAO,mBACT,CAAC,EAEC,MAAM,UAAU,MAAM,SAAS,SAAS,wCAAwC,IAClF,uBAAyB,MAAM,SAEnC,CAEA,GAAI,CAAC,uBACH,MAAM,IAAI,MAAM,oFAAqF,CACnG,MAAO,mBACT,CAAC,EAGH,MAAO,CAAE,oBAAqB,eAAgB,sBAAuB,CACvE,CACF,GACC,CACD,0BACA,0BACA,QACA,WACA,cACA,sBACA,aACA,MACA,wBACF,CAAC,CAGH,CAEA,SAAS,eAAe,UAA6C,CACnE,OAAO,UAAU,OAAO,CAAC,EAAE,IAC7B,CAEA,IAAM,uBACJ,4FAQF,SAAS,oCACP,cACA,0BAC2B,CAC3B,IAAI,iBACE,qBAAuB,EAAQ,0BAGrC,QAAW,QAAQ,0BAA0B,cAAc,IAAI,EAC7D,GAAI,kBAAkB,KAAM,sBAAsB,IAAM,IAAQ,EAAQ,KAAK,WAAc,qBAAsB,CAE/G,iBAAmB,UAAU,aAAa,EAC1C,KACF,CAIF,GAAK,iBAKL,SAAW,QAAQ,0BAA0B,iBAAiB,IAAI,EAC5D,kBAAkB,KAAM,sBAAsB,IAAM,IAClD,EAAQ,KAAK,WAAc,uBAC7B,KAAK,SAAW,sBAKtB,OAAO,iBACT,CC3mBA,OAAS,eAAkB,QAIpB,SAAS,iCAAkE,CAChF,IAAM,QAAU,WAAW,4BAA4B,EACvD,GAAI,UAAY,OACd,MAAM,IAAI,MAAM,qFAAqF,EAEvG,OAAO,OACT",
6
6
  "names": ["result", "isResource", "isResource"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medplum/health-gorilla-react",
3
- "version": "5.1.1",
3
+ "version": "5.1.4",
4
4
  "description": "Medplum Health Gorilla React SDK",
5
5
  "keywords": [
6
6
  "medplum",
@@ -60,29 +60,29 @@
60
60
  "test:watch": "vitest watch"
61
61
  },
62
62
  "devDependencies": {
63
- "@medplum/core": "5.1.1",
64
- "@medplum/definitions": "5.1.1",
65
- "@medplum/fhirtypes": "5.1.1",
66
- "@medplum/health-gorilla-core": "5.1.1",
67
- "@medplum/mock": "5.1.1",
68
- "@medplum/react": "5.1.1",
63
+ "@medplum/core": "5.1.4",
64
+ "@medplum/definitions": "5.1.4",
65
+ "@medplum/fhirtypes": "5.1.4",
66
+ "@medplum/health-gorilla-core": "5.1.4",
67
+ "@medplum/mock": "5.1.4",
68
+ "@medplum/react": "5.1.4",
69
69
  "@testing-library/jest-dom": "6.9.1",
70
70
  "@testing-library/react": "16.3.2",
71
- "@types/node": "22.19.11",
71
+ "@types/node": "22.19.15",
72
72
  "@types/react": "19.2.14",
73
73
  "@types/react-dom": "19.2.3",
74
- "@vitejs/plugin-react": "5.1.4",
75
- "jsdom": "28.1.0",
74
+ "@vitejs/plugin-react": "6.0.1",
75
+ "jsdom": "29.0.1",
76
76
  "react": "19.2.4",
77
77
  "react-dom": "19.2.4",
78
- "react-router": "7.13.0",
79
- "vitest": "4.0.18"
78
+ "react-router": "7.13.1",
79
+ "vitest": "4.1.0"
80
80
  },
81
81
  "peerDependencies": {
82
- "@medplum/core": "5.1.1",
83
- "@medplum/fhirtypes": "5.1.1",
84
- "@medplum/health-gorilla-core": "5.1.1",
85
- "@medplum/react": "5.1.1",
82
+ "@medplum/core": "5.1.4",
83
+ "@medplum/fhirtypes": "5.1.4",
84
+ "@medplum/health-gorilla-core": "5.1.4",
85
+ "@medplum/react": "5.1.4",
86
86
  "react": "^18.0.0 || ^19.0.0"
87
87
  },
88
88
  "engines": {