vintasend 0.4.0 → 0.4.7
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.
- package/README.md +5 -0
- package/dist/examples/vintasend-medplum-example/.env.example +11 -0
- package/dist/examples/vintasend-medplum-example/IMPLEMENTATION_PLAN_FILE_ATTACHMENTS.md +597 -0
- package/dist/examples/vintasend-medplum-example/README.md +190 -0
- package/dist/examples/vintasend-medplum-example/TUTORIAL_EMAIL_NOTIFICATIONS.md +2596 -0
- package/dist/examples/vintasend-medplum-example/bots/handlers/send-pending-notifications-bot.ts +39 -0
- package/dist/examples/vintasend-medplum-example/bots/handlers/send-task-assignment-email.ts +41 -0
- package/dist/examples/vintasend-medplum-example/bots/handlers/task-due-soon-notification-bot.ts +86 -0
- package/dist/examples/vintasend-medplum-example/bots/index.ts +53 -0
- package/dist/examples/vintasend-medplum-example/bots/services/emails/schedule-task-due-soon-email.ts +84 -0
- package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.test.ts +388 -0
- package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.ts +113 -0
- package/dist/examples/vintasend-medplum-example/bots/shared/task-due-soon-helpers.ts +115 -0
- package/dist/examples/vintasend-medplum-example/bots/task-assignment-bot.ts +41 -0
- package/dist/examples/vintasend-medplum-example/compiled-notification-templates.json +6 -0
- package/dist/examples/vintasend-medplum-example/esbuild-script.mjs +71 -0
- package/dist/examples/vintasend-medplum-example/index.html +14 -0
- package/dist/examples/vintasend-medplum-example/lib/constants.ts +32 -0
- package/dist/examples/vintasend-medplum-example/lib/extensions.ts +1 -0
- package/dist/examples/vintasend-medplum-example/lib/file-upload.test.ts +389 -0
- package/dist/examples/vintasend-medplum-example/lib/file-upload.ts +222 -0
- package/dist/examples/vintasend-medplum-example/lib/medplum-singleton.ts +18 -0
- package/dist/examples/vintasend-medplum-example/lib/notification-service.test.ts +293 -0
- package/dist/examples/vintasend-medplum-example/lib/notification-service.ts +284 -0
- package/dist/examples/vintasend-medplum-example/lib/patients.ts +20 -0
- package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/body.html.pug +37 -0
- package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/subject.txt.pug +4 -0
- package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/body.html.pug +34 -0
- package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/subject.txt.pug +4 -0
- package/dist/examples/vintasend-medplum-example/package.json +75 -0
- package/dist/examples/vintasend-medplum-example/plugins/gql-plugin.mjs +31 -0
- package/dist/examples/vintasend-medplum-example/postcss.config.mjs +21 -0
- package/dist/examples/vintasend-medplum-example/public/favicon.ico +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/acuity.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/auth0.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/azure.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/calcom.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/candid.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/claude.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/datadog.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/deepseek.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/entra.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/epic.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/google.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/healthgorilla.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/healthie.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/labcorp.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/okta.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/openai.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/particle.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/quest.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/recaptcha.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/snowflake.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/stedi.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/stripe.png +0 -0
- package/dist/examples/vintasend-medplum-example/public/img/integrations/sumo.png +0 -0
- package/dist/examples/vintasend-medplum-example/scripts/README.md +162 -0
- package/dist/examples/vintasend-medplum-example/scripts/client.ts +18 -0
- package/dist/examples/vintasend-medplum-example/scripts/deploy-bots.ts +171 -0
- package/dist/examples/vintasend-medplum-example/src/App.tsx +185 -0
- package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.test.tsx +350 -0
- package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.tsx +241 -0
- package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.test.tsx +616 -0
- package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.tsx +138 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.test.tsx +92 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.tsx +47 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.test.tsx +464 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.tsx +186 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.test.tsx +80 -0
- package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.tsx +82 -0
- package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.test.tsx +100 -0
- package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.tsx +20 -0
- package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.module.css +3 -0
- package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.tsx +62 -0
- package/dist/examples/vintasend-medplum-example/src/components/MessageWithLinks.tsx +47 -0
- package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.test.tsx +299 -0
- package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.tsx +52 -0
- package/dist/examples/vintasend-medplum-example/src/components/ResourceFormWithRequiredProfile.tsx +82 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.test.tsx +1016 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.tsx +298 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.test.tsx +732 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.tsx +282 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.test.tsx +268 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.tsx +224 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.test.tsx +255 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.tsx +212 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.test.tsx +120 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.tsx +57 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.test.tsx +224 -0
- package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.tsx +100 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.test.tsx +431 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.tsx +130 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.module.css +31 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.test.tsx +234 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.tsx +143 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.module.css +11 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.test.tsx +875 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.tsx +943 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.test.tsx +413 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.tsx +203 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/LabSelectEmpty.tsx +22 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/README.md +104 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.test.tsx +318 -0
- package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.tsx +87 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.test.tsx +126 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.tsx +38 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.module.css +23 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.test.tsx +167 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.tsx +53 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.test.tsx +94 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.tsx +165 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.module.css +8 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.test.tsx +523 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.tsx +230 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.module.css +23 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.test.tsx +567 -0
- package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.tsx +358 -0
- package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.module.css +40 -0
- package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.tsx +257 -0
- package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.module.css +7 -0
- package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.test.tsx +279 -0
- package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.tsx +156 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.module.css +45 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.test.tsx +90 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.tsx +84 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.module.css +26 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.tsx +90 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.test.tsx +305 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.tsx +46 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.module.css +262 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.test.tsx +622 -0
- package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.tsx +286 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/NewTaskModal.tsx +275 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskAttachmentList.tsx +132 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.module.css +45 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.test.tsx +749 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.tsx +416 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.test.tsx +278 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.tsx +133 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.module.css +16 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.test.tsx +255 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.tsx +203 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFileUpload.tsx +129 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.test.tsx +156 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.tsx +142 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.utils.ts +28 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.test.tsx +134 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.tsx +250 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.module.css +23 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.test.tsx +149 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.tsx +53 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.test.tsx +68 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.tsx +46 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.test.tsx +555 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.tsx +170 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.test.tsx +32 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.tsx +34 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.test.tsx +47 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.tsx +29 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.test.tsx +285 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.tsx +129 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.test.tsx +455 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.tsx +167 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.test.tsx +435 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.tsx +116 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.module.css +38 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.test.tsx +200 -0
- package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.tsx +84 -0
- package/dist/examples/vintasend-medplum-example/src/components/utils.test.ts +176 -0
- package/dist/examples/vintasend-medplum-example/src/components/utils.ts +17 -0
- package/dist/examples/vintasend-medplum-example/src/config/constants.ts +3 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.test.tsx +166 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.ts +28 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.test.tsx +94 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.ts +11 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.test.tsx +477 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.ts +191 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.test.tsx +100 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.ts +18 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.test.tsx +379 -0
- package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.ts +194 -0
- package/dist/examples/vintasend-medplum-example/src/index.css +8 -0
- package/dist/examples/vintasend-medplum-example/src/main.tsx +57 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.module.css +6 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.test.tsx +295 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.tsx +124 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.test.tsx +77 -0
- package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.tsx +22 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.test.tsx +87 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.tsx +27 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.module.css +16 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.test.tsx +287 -0
- package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.tsx +151 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.test.tsx +519 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.tsx +179 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/FavoriteMedicationsTable.tsx +76 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.test.tsx +234 -0
- package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.tsx +222 -0
- package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.test.tsx +356 -0
- package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.tsx +275 -0
- package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.module.css +8 -0
- package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.test.tsx +103 -0
- package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.tsx +78 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.test.tsx +84 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.tsx +82 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.test.tsx +364 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.tsx +149 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.test.tsx +159 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.tsx +37 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.test.tsx +140 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.tsx +72 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.test.tsx +57 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.tsx +14 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.test.tsx +241 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.tsx +710 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.module.css +37 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.test.tsx +428 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.tsx +334 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.module.css +24 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.test.tsx +154 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.tsx +115 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.test.ts +223 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.ts +89 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.test.tsx +147 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.tsx +79 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientTabsNavigation.tsx +35 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.test.tsx +185 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.tsx +115 -0
- package/dist/examples/vintasend-medplum-example/src/pages/patient/TimelineTab.tsx +14 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.test.tsx +170 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.tsx +117 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceDetailPage.tsx +28 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.test.tsx +131 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.tsx +65 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.test.tsx +108 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.tsx +16 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.module.css +7 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.test.tsx +37 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.tsx +44 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/useResourceType.ts +44 -0
- package/dist/examples/vintasend-medplum-example/src/pages/resource/utils.ts +9 -0
- package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.test.tsx +302 -0
- package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.tsx +416 -0
- package/dist/examples/vintasend-medplum-example/src/pages/spaces/ChatInput.tsx +91 -0
- package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.module.css +6 -0
- package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.test.tsx +102 -0
- package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.tsx +44 -0
- package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.module.css +7 -0
- package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.test.tsx +133 -0
- package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.tsx +91 -0
- package/dist/examples/vintasend-medplum-example/src/test-utils/render.tsx +20 -0
- package/dist/examples/vintasend-medplum-example/src/test.setup.ts +49 -0
- package/dist/examples/vintasend-medplum-example/src/types/encounter.ts +8 -0
- package/dist/examples/vintasend-medplum-example/src/types/spaces.ts +10 -0
- package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.test.ts +141 -0
- package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.ts +59 -0
- package/dist/examples/vintasend-medplum-example/src/utils/claims.test.ts +153 -0
- package/dist/examples/vintasend-medplum-example/src/utils/claims.ts +65 -0
- package/dist/examples/vintasend-medplum-example/src/utils/communication-search.ts +47 -0
- package/dist/examples/vintasend-medplum-example/src/utils/coverage.test.ts +48 -0
- package/dist/examples/vintasend-medplum-example/src/utils/coverage.ts +33 -0
- package/dist/examples/vintasend-medplum-example/src/utils/documentReference.test.ts +102 -0
- package/dist/examples/vintasend-medplum-example/src/utils/documentReference.ts +55 -0
- package/dist/examples/vintasend-medplum-example/src/utils/encounter.test.ts +169 -0
- package/dist/examples/vintasend-medplum-example/src/utils/encounter.ts +261 -0
- package/dist/examples/vintasend-medplum-example/src/utils/intake-form.test.ts +154 -0
- package/dist/examples/vintasend-medplum-example/src/utils/intake-form.ts +272 -0
- package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.test.ts +1137 -0
- package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.ts +827 -0
- package/dist/examples/vintasend-medplum-example/src/utils/notifications.test.ts +27 -0
- package/dist/examples/vintasend-medplum-example/src/utils/notifications.ts +15 -0
- package/dist/examples/vintasend-medplum-example/src/utils/spaceMessaging.ts +249 -0
- package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.test.ts +450 -0
- package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.ts +147 -0
- package/dist/examples/vintasend-medplum-example/src/utils/task-search.ts +63 -0
- package/dist/examples/vintasend-medplum-example/src/vite-env.d.ts +3 -0
- package/dist/examples/vintasend-medplum-example/tsconfig.bots.json +4 -0
- package/dist/examples/vintasend-medplum-example/tsconfig.json +19 -0
- package/dist/examples/vintasend-medplum-example/vercel.json +3 -0
- package/dist/examples/vintasend-medplum-example/vite.config.ts +44 -0
- package/dist/services/notification-backends/base-notification-backend.d.ts +5 -0
- package/dist/services/notification-service.js +11 -1
- package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import type { MedplumClient } from '@medplum/core';
|
|
4
|
+
import {
|
|
5
|
+
addProfileToResource,
|
|
6
|
+
createReference,
|
|
7
|
+
getReferenceString,
|
|
8
|
+
HTTP_HL7_ORG,
|
|
9
|
+
HTTP_TERMINOLOGY_HL7_ORG,
|
|
10
|
+
LOINC,
|
|
11
|
+
SNOMED,
|
|
12
|
+
} from '@medplum/core';
|
|
13
|
+
import type {
|
|
14
|
+
Address,
|
|
15
|
+
CodeableConcept,
|
|
16
|
+
Coding,
|
|
17
|
+
Consent,
|
|
18
|
+
HumanName,
|
|
19
|
+
Observation,
|
|
20
|
+
Organization,
|
|
21
|
+
Patient,
|
|
22
|
+
Questionnaire,
|
|
23
|
+
QuestionnaireItem,
|
|
24
|
+
QuestionnaireResponse,
|
|
25
|
+
QuestionnaireResponseItem,
|
|
26
|
+
QuestionnaireResponseItemAnswer,
|
|
27
|
+
Reference,
|
|
28
|
+
} from '@medplum/fhirtypes';
|
|
29
|
+
|
|
30
|
+
export const PROFILE_URLS: Record<string, string> = {
|
|
31
|
+
AllergyIntolerance: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-allergyintolerance`,
|
|
32
|
+
CareTeam: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-careteam`,
|
|
33
|
+
Coverage: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-coverage`,
|
|
34
|
+
Immunization: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-immunization`,
|
|
35
|
+
MedicationRequest: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-medicationrequest`,
|
|
36
|
+
Patient: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-patient`,
|
|
37
|
+
ObservationSexualOrientation: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-observation-sexual-orientation`,
|
|
38
|
+
ObservationSmokingStatus: `${HTTP_HL7_ORG}/fhir/us/core/StructureDefinition/us-core-smokingstatus`,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const extensionURLMapping: Record<string, string> = {
|
|
42
|
+
race: HTTP_HL7_ORG + '/fhir/us/core/StructureDefinition/us-core-race',
|
|
43
|
+
ethnicity: HTTP_HL7_ORG + '/fhir/us/core/StructureDefinition/us-core-ethnicity',
|
|
44
|
+
veteran: HTTP_HL7_ORG + '/fhir/us/military-service/StructureDefinition/military-service-veteran-status',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const observationCodeMapping: Record<string, CodeableConcept> = {
|
|
48
|
+
housingStatus: { coding: [{ code: '71802-3', system: LOINC, display: 'Housing status' }] },
|
|
49
|
+
educationLevel: { coding: [{ code: '82589-3', system: LOINC, display: 'Highest Level of Education' }] },
|
|
50
|
+
smokingStatus: { coding: [{ code: '72166-2', system: LOINC, display: 'Tobacco smoking status' }] },
|
|
51
|
+
sexualOrientation: { coding: [{ code: '76690-7', system: LOINC, display: 'Sexual orientation' }] },
|
|
52
|
+
pregnancyStatus: { coding: [{ code: '82810-3', system: LOINC, display: 'Pregnancy status' }] },
|
|
53
|
+
estimatedDeliveryDate: { coding: [{ code: '11778-8', system: LOINC, display: 'Estimated date of delivery' }] },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const observationCategoryMapping: Record<string, CodeableConcept> = {
|
|
57
|
+
socialHistory: {
|
|
58
|
+
coding: [
|
|
59
|
+
{
|
|
60
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/observation-category',
|
|
61
|
+
code: 'social-history',
|
|
62
|
+
display: 'Social History',
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
sdoh: {
|
|
67
|
+
coding: [
|
|
68
|
+
{
|
|
69
|
+
system: HTTP_HL7_ORG + '/fhir/us/core/CodeSystem/us-core-tags',
|
|
70
|
+
code: 'sdoh',
|
|
71
|
+
display: 'SDOH',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const consentScopeMapping: Record<string, CodeableConcept> = {
|
|
78
|
+
adr: {
|
|
79
|
+
coding: [
|
|
80
|
+
{
|
|
81
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/consentscope',
|
|
82
|
+
code: 'adr',
|
|
83
|
+
display: 'Advanced Care Directive',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
patientPrivacy: {
|
|
88
|
+
coding: [
|
|
89
|
+
{
|
|
90
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/consentscope',
|
|
91
|
+
code: 'patient-privacy',
|
|
92
|
+
display: 'Patient Privacy',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
treatment: {
|
|
97
|
+
coding: [
|
|
98
|
+
{
|
|
99
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/consentscope',
|
|
100
|
+
code: 'treatment',
|
|
101
|
+
display: 'Treatment',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const consentCategoryMapping: Record<string, CodeableConcept> = {
|
|
108
|
+
acd: {
|
|
109
|
+
coding: [
|
|
110
|
+
{
|
|
111
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/consentcategorycodes',
|
|
112
|
+
code: 'acd',
|
|
113
|
+
display: 'Advanced Care Directive',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
nopp: {
|
|
118
|
+
coding: [
|
|
119
|
+
{
|
|
120
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/v3-ActCode',
|
|
121
|
+
code: 'nopp',
|
|
122
|
+
display: 'Notice of Privacy Practices',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
pay: {
|
|
127
|
+
coding: [
|
|
128
|
+
{
|
|
129
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/v3-ActCode',
|
|
130
|
+
code: 'pay',
|
|
131
|
+
display: 'Payment',
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
med: {
|
|
136
|
+
coding: [
|
|
137
|
+
{
|
|
138
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/v3-ActCode',
|
|
139
|
+
code: 'med',
|
|
140
|
+
display: 'Medical',
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const consentPolicyRuleMapping: Record<string, CodeableConcept> = {
|
|
147
|
+
hipaaNpp: {
|
|
148
|
+
coding: [
|
|
149
|
+
{
|
|
150
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/consentpolicycodes',
|
|
151
|
+
code: 'hipaa-npp',
|
|
152
|
+
display: 'HIPAA Notice of Privacy Practices',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
hipaaSelfPay: {
|
|
157
|
+
coding: [
|
|
158
|
+
{
|
|
159
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/consentpolicycodes',
|
|
160
|
+
code: 'hipaa-self-pay',
|
|
161
|
+
display: 'HIPAA Self-Pay Restriction',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
cric: {
|
|
166
|
+
coding: [
|
|
167
|
+
{
|
|
168
|
+
system: HTTP_TERMINOLOGY_HL7_ORG + '/CodeSystem/consentpolicycodes',
|
|
169
|
+
code: 'cric',
|
|
170
|
+
display: 'Common Rule Informed Consent',
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
adr: {
|
|
175
|
+
coding: [
|
|
176
|
+
{
|
|
177
|
+
system: 'http://medplum.com',
|
|
178
|
+
code: 'BasicADR',
|
|
179
|
+
display: 'Advanced Care Directive',
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
type ObservationQuestionnaireItemType = 'valueCodeableConcept' | 'valueDateTime';
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* This function takes data about an Observation and creates or updates an existing
|
|
189
|
+
* resource with the same patient and code.
|
|
190
|
+
*
|
|
191
|
+
* @param medplum - A Medplum client
|
|
192
|
+
* @param patient - A Patient resource that will be stored as the subject
|
|
193
|
+
* @param code - A code for the observation
|
|
194
|
+
* @param category - A category for the observation
|
|
195
|
+
* @param answerType - The value[x] field where the answer should be stored
|
|
196
|
+
* @param value - The value to be stored in the observation
|
|
197
|
+
* @param profileUrl - An optional profile URL to be added to the resource
|
|
198
|
+
*/
|
|
199
|
+
export async function upsertObservation(
|
|
200
|
+
medplum: MedplumClient,
|
|
201
|
+
patient: Patient,
|
|
202
|
+
code: CodeableConcept,
|
|
203
|
+
category: CodeableConcept,
|
|
204
|
+
answerType: ObservationQuestionnaireItemType,
|
|
205
|
+
value: QuestionnaireResponseItemAnswer | undefined,
|
|
206
|
+
profileUrl?: string
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
if (!value || !code) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let observation: Observation = {
|
|
213
|
+
resourceType: 'Observation',
|
|
214
|
+
status: 'final',
|
|
215
|
+
subject: createReference(patient),
|
|
216
|
+
code: code,
|
|
217
|
+
category: [category],
|
|
218
|
+
effectiveDateTime: new Date().toISOString(),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (profileUrl) {
|
|
222
|
+
observation = addProfileToResource(observation, profileUrl);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (answerType === 'valueCodeableConcept') {
|
|
226
|
+
observation.valueCodeableConcept = {
|
|
227
|
+
coding: [value],
|
|
228
|
+
};
|
|
229
|
+
} else if (answerType === 'valueDateTime') {
|
|
230
|
+
observation.valueDateTime = value.valueDateTime;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const coding = code.coding?.[0] as Coding;
|
|
234
|
+
await medplum.upsertResource(observation, {
|
|
235
|
+
code: `${coding.system}|${coding.code}`,
|
|
236
|
+
subject: getReferenceString(patient),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
type ExtensionQuestionnaireItemType = 'valueCoding' | 'valueBoolean';
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Add an extension to a patient
|
|
244
|
+
*
|
|
245
|
+
* @param patient - A patient resource
|
|
246
|
+
* @param url - An URL that identifies the extension
|
|
247
|
+
* @param answerType - The value[x] field where the answer should be stored
|
|
248
|
+
* @param answer - The value to be stored in the extension
|
|
249
|
+
* @param subExtensionKey - A key to identify a sub-extension
|
|
250
|
+
*/
|
|
251
|
+
export function addExtension(
|
|
252
|
+
patient: Patient,
|
|
253
|
+
url: string,
|
|
254
|
+
answerType: ExtensionQuestionnaireItemType,
|
|
255
|
+
answer: QuestionnaireResponseItemAnswer | undefined,
|
|
256
|
+
subExtensionKey?: string
|
|
257
|
+
): void {
|
|
258
|
+
let value = answer?.[answerType];
|
|
259
|
+
|
|
260
|
+
// Answer to boolean Questionnaire fields will be set as `undefined` if the check mark is not ticked
|
|
261
|
+
// so in this case we should interpret it as `false`.
|
|
262
|
+
if (answerType === 'valueBoolean') {
|
|
263
|
+
value = Boolean(value);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (value === undefined) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
patient.extension ||= [];
|
|
271
|
+
|
|
272
|
+
if (subExtensionKey) {
|
|
273
|
+
const subExtensions = [
|
|
274
|
+
{
|
|
275
|
+
url: subExtensionKey,
|
|
276
|
+
[answerType]: value,
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
if (answerType === 'valueCoding' && (value as Coding).display) {
|
|
280
|
+
subExtensions.push({ url: 'text', valueString: (value as Coding).display as string });
|
|
281
|
+
}
|
|
282
|
+
patient.extension.push({
|
|
283
|
+
url,
|
|
284
|
+
extension: subExtensions,
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
patient.extension.push({
|
|
288
|
+
url,
|
|
289
|
+
[answerType]: value,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Add a language to patient's communication attribute or set an existing one as preferred
|
|
296
|
+
*
|
|
297
|
+
* @param patient - The patient
|
|
298
|
+
* @param valueCoding - A Coding with the language data
|
|
299
|
+
* @param preferred - Whether this language should be set as preferred
|
|
300
|
+
*/
|
|
301
|
+
export function addLanguage(patient: Patient, valueCoding: Coding | undefined, preferred: boolean = false): void {
|
|
302
|
+
if (!valueCoding) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const patientCommunications = patient.communication ?? [];
|
|
307
|
+
|
|
308
|
+
// Checks if the patient already has the language in their list of communications
|
|
309
|
+
let language = patientCommunications.find(
|
|
310
|
+
(communication) => communication.language.coding?.[0].code === valueCoding?.code
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Add the language in case it's not set yet
|
|
314
|
+
if (!language) {
|
|
315
|
+
language = {
|
|
316
|
+
language: {
|
|
317
|
+
coding: [valueCoding],
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
patientCommunications.push(language);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (preferred) {
|
|
324
|
+
language.preferred = preferred;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
patient.communication = patientCommunications;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Adds an AllergyIntolerance resource
|
|
332
|
+
*
|
|
333
|
+
* @param medplum - The Medplum client
|
|
334
|
+
* @param patient - The patient beneficiary of the allergy
|
|
335
|
+
* @param answers - A list of objects where the keys are the linkIds of the fields used to set up an
|
|
336
|
+
*/
|
|
337
|
+
export async function addAllergy(
|
|
338
|
+
medplum: MedplumClient,
|
|
339
|
+
patient: Patient,
|
|
340
|
+
answers: Record<string, QuestionnaireResponseItemAnswer>
|
|
341
|
+
): Promise<void> {
|
|
342
|
+
const code = answers['allergy-substance']?.valueCoding;
|
|
343
|
+
|
|
344
|
+
if (!code) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const reaction = answers['allergy-reaction']?.valueString;
|
|
349
|
+
const onsetDateTime = answers['allergy-onset']?.valueDateTime;
|
|
350
|
+
|
|
351
|
+
await medplum.upsertResource(
|
|
352
|
+
{
|
|
353
|
+
resourceType: 'AllergyIntolerance',
|
|
354
|
+
meta: {
|
|
355
|
+
profile: [PROFILE_URLS.AllergyIntolerance],
|
|
356
|
+
},
|
|
357
|
+
clinicalStatus: {
|
|
358
|
+
text: 'Active',
|
|
359
|
+
coding: [
|
|
360
|
+
{
|
|
361
|
+
system: 'http://hl7.org/fhir/ValueSet/allergyintolerance-clinical',
|
|
362
|
+
code: 'active',
|
|
363
|
+
display: 'Active',
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
verificationStatus: {
|
|
368
|
+
text: 'Unconfirmed',
|
|
369
|
+
coding: [
|
|
370
|
+
{
|
|
371
|
+
system: 'http://hl7.org/fhir/ValueSet/allergyintolerance-verification',
|
|
372
|
+
code: 'unconfirmed',
|
|
373
|
+
display: 'Unconfirmed',
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
patient: createReference(patient),
|
|
378
|
+
code: { coding: [code] },
|
|
379
|
+
reaction: reaction ? [{ manifestation: [{ text: reaction }] }] : undefined,
|
|
380
|
+
onsetDateTime: onsetDateTime,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
patient: getReferenceString(patient),
|
|
384
|
+
code: `${code.system}|${code.code}`,
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Adds a MedicationRequest resource
|
|
391
|
+
*
|
|
392
|
+
* @param medplum - The Medplum client
|
|
393
|
+
* @param patient - The patient beneficiary of the medication
|
|
394
|
+
* @param answers - A list of objects where the keys are the linkIds of the fields used to set up a
|
|
395
|
+
* medication (see getGroupRepeatedAnswers)
|
|
396
|
+
*/
|
|
397
|
+
export async function addMedication(
|
|
398
|
+
medplum: MedplumClient,
|
|
399
|
+
patient: Patient,
|
|
400
|
+
answers: Record<string, QuestionnaireResponseItemAnswer>
|
|
401
|
+
): Promise<void> {
|
|
402
|
+
const code = answers['medication-code']?.valueCoding;
|
|
403
|
+
|
|
404
|
+
if (!code) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const note = answers['medication-note']?.valueString;
|
|
409
|
+
|
|
410
|
+
await medplum.upsertResource(
|
|
411
|
+
{
|
|
412
|
+
resourceType: 'MedicationRequest',
|
|
413
|
+
meta: {
|
|
414
|
+
profile: [PROFILE_URLS.MedicationRequest],
|
|
415
|
+
},
|
|
416
|
+
subject: createReference(patient),
|
|
417
|
+
status: 'active',
|
|
418
|
+
intent: 'order',
|
|
419
|
+
requester: createReference(patient),
|
|
420
|
+
medicationCodeableConcept: { coding: [code] },
|
|
421
|
+
note: note ? [{ text: note }] : undefined,
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
subject: getReferenceString(patient),
|
|
425
|
+
code: `${code.system}|${code.code}`,
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Adds a Condition resource
|
|
432
|
+
*
|
|
433
|
+
* @param medplum - The Medplum client
|
|
434
|
+
* @param patient - The patient beneficiary of the condition
|
|
435
|
+
* @param answers - A list of objects where the keys are the linkIds of the fields used to set up a
|
|
436
|
+
* condition (see getGroupRepeatedAnswers)
|
|
437
|
+
*/
|
|
438
|
+
export async function addCondition(
|
|
439
|
+
medplum: MedplumClient,
|
|
440
|
+
patient: Patient,
|
|
441
|
+
answers: Record<string, QuestionnaireResponseItemAnswer>
|
|
442
|
+
): Promise<void> {
|
|
443
|
+
const code = answers['medical-history-problem']?.valueCoding;
|
|
444
|
+
|
|
445
|
+
if (!code) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const clinicalStatus = answers['medical-history-clinical-status']?.valueCoding;
|
|
450
|
+
const onsetDateTime = answers['medical-history-onset']?.valueDateTime;
|
|
451
|
+
|
|
452
|
+
await medplum.upsertResource(
|
|
453
|
+
{
|
|
454
|
+
resourceType: 'Condition',
|
|
455
|
+
subject: createReference(patient),
|
|
456
|
+
code: { coding: [code] },
|
|
457
|
+
clinicalStatus: clinicalStatus ? { coding: [clinicalStatus] } : undefined,
|
|
458
|
+
onsetDateTime: onsetDateTime,
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
subject: getReferenceString(patient),
|
|
462
|
+
code: `${code?.system}|${code?.code}`,
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Adds a FamilyMemberHistory resource
|
|
469
|
+
*
|
|
470
|
+
* @param medplum - The Medplum client
|
|
471
|
+
* @param patient - The patient beneficiary of the family member history
|
|
472
|
+
* @param answers - A list of objects where the keys are the linkIds of the fields used to set up a
|
|
473
|
+
* family member history (see getGroupRepeatedAnswers)
|
|
474
|
+
*/
|
|
475
|
+
export async function addFamilyMemberHistory(
|
|
476
|
+
medplum: MedplumClient,
|
|
477
|
+
patient: Patient,
|
|
478
|
+
answers: Record<string, QuestionnaireResponseItemAnswer>
|
|
479
|
+
): Promise<void> {
|
|
480
|
+
const condition = answers['family-member-history-problem']?.valueCoding;
|
|
481
|
+
const relationship = answers['family-member-history-relationship']?.valueCoding;
|
|
482
|
+
|
|
483
|
+
if (!condition || !relationship) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const deceased = answers['family-member-history-deceased']?.valueBoolean;
|
|
488
|
+
|
|
489
|
+
await medplum.upsertResource(
|
|
490
|
+
{
|
|
491
|
+
resourceType: 'FamilyMemberHistory',
|
|
492
|
+
patient: createReference(patient),
|
|
493
|
+
status: 'completed',
|
|
494
|
+
relationship: { coding: [relationship] },
|
|
495
|
+
condition: [{ code: { coding: [condition] } }],
|
|
496
|
+
deceasedBoolean: deceased,
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
patient: getReferenceString(patient),
|
|
500
|
+
code: `${condition.system}|${condition.code}`,
|
|
501
|
+
relationship: `${relationship.system}|${relationship.code}`,
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
*
|
|
508
|
+
* @param medplum - The Medplum client
|
|
509
|
+
* @param patient - The patient beneficiary of the immunization
|
|
510
|
+
* @param answers - A list of objects where the keys are the linkIds of the fields used to set up an
|
|
511
|
+
* immunization (see getGroupRepeatedAnswers)
|
|
512
|
+
*/
|
|
513
|
+
export async function addImmunization(
|
|
514
|
+
medplum: MedplumClient,
|
|
515
|
+
patient: Patient,
|
|
516
|
+
answers: Record<string, QuestionnaireResponseItemAnswer>
|
|
517
|
+
): Promise<void> {
|
|
518
|
+
const code = answers['immunization-vaccine']?.valueCoding;
|
|
519
|
+
const occurrenceDateTime = answers['immunization-date']?.valueDateTime;
|
|
520
|
+
|
|
521
|
+
if (!code || !occurrenceDateTime) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
await medplum.upsertResource(
|
|
526
|
+
{
|
|
527
|
+
resourceType: 'Immunization',
|
|
528
|
+
meta: {
|
|
529
|
+
profile: [PROFILE_URLS.Immunization],
|
|
530
|
+
},
|
|
531
|
+
status: 'completed',
|
|
532
|
+
vaccineCode: { coding: [code] },
|
|
533
|
+
patient: createReference(patient),
|
|
534
|
+
occurrenceDateTime: occurrenceDateTime,
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
status: 'completed',
|
|
538
|
+
'vaccine-code': `${code.system}|${code.code}`,
|
|
539
|
+
patient: getReferenceString(patient),
|
|
540
|
+
date: occurrenceDateTime,
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Adds a CareTeam resource associating the patient with a pharmacy
|
|
547
|
+
*
|
|
548
|
+
* @param medplum - The Medplum client
|
|
549
|
+
* @param patient - The patient beneficiary of the care team
|
|
550
|
+
* @param pharmacy - The pharmacy to be added to the care team
|
|
551
|
+
*/
|
|
552
|
+
export async function addPharmacy(
|
|
553
|
+
medplum: MedplumClient,
|
|
554
|
+
patient: Patient,
|
|
555
|
+
pharmacy: Reference<Organization>
|
|
556
|
+
): Promise<void> {
|
|
557
|
+
await medplum.upsertResource(
|
|
558
|
+
{
|
|
559
|
+
resourceType: 'CareTeam',
|
|
560
|
+
meta: {
|
|
561
|
+
profile: [PROFILE_URLS.CareTeam],
|
|
562
|
+
},
|
|
563
|
+
status: 'proposed',
|
|
564
|
+
name: 'Patient Preferred Pharmacy',
|
|
565
|
+
subject: createReference(patient),
|
|
566
|
+
participant: [
|
|
567
|
+
{
|
|
568
|
+
member: pharmacy,
|
|
569
|
+
role: [{ coding: [{ system: SNOMED, code: '76166008', display: 'Practical aid (pharmacy) (occupation)' }] }],
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: 'Patient Preferred Pharmacy',
|
|
575
|
+
subject: getReferenceString(patient),
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Adds a Coverage resource
|
|
582
|
+
*
|
|
583
|
+
* @param medplum - The Medplum client
|
|
584
|
+
* @param patient - The patient beneficiary of the coverage
|
|
585
|
+
* @param answers - A list of objects where the keys are the linkIds of the fields used to set up a
|
|
586
|
+
* coverage (see getGroupRepeatedAnswers)
|
|
587
|
+
*/
|
|
588
|
+
export async function addCoverage(
|
|
589
|
+
medplum: MedplumClient,
|
|
590
|
+
patient: Patient,
|
|
591
|
+
answers: Record<string, QuestionnaireResponseItemAnswer>
|
|
592
|
+
): Promise<void> {
|
|
593
|
+
const payor = answers['insurance-provider'].valueReference as Reference<Organization>;
|
|
594
|
+
const subscriberId = answers['subscriber-id'].valueString;
|
|
595
|
+
const relationshipToSubscriber = answers['relationship-to-subscriber'].valueCoding as Coding;
|
|
596
|
+
|
|
597
|
+
// Create RelatedPerson resource depending on the relationship to the subscriber
|
|
598
|
+
const relatedPersonAnswers = answers['related-person'] as Record<string, QuestionnaireResponseItemAnswer>;
|
|
599
|
+
if (
|
|
600
|
+
relationshipToSubscriber.code &&
|
|
601
|
+
!['other', 'self', 'injured'].includes(relationshipToSubscriber.code) &&
|
|
602
|
+
relatedPersonAnswers
|
|
603
|
+
) {
|
|
604
|
+
const name = getHumanName(relatedPersonAnswers, 'related-person-');
|
|
605
|
+
const relatedPersonRelationship = getRelatedPersonRelationshipFromCoverage(relationshipToSubscriber);
|
|
606
|
+
|
|
607
|
+
await medplum.createResource({
|
|
608
|
+
resourceType: 'RelatedPerson',
|
|
609
|
+
patient: createReference(patient),
|
|
610
|
+
relationship: relatedPersonRelationship ? [{ coding: [relatedPersonRelationship] }] : undefined,
|
|
611
|
+
name: name ? [name] : undefined,
|
|
612
|
+
birthDate: relatedPersonAnswers['related-person-dob']?.valueDate,
|
|
613
|
+
gender:
|
|
614
|
+
(relatedPersonAnswers['related-person-gender-identity']?.valueCoding?.code as Patient['gender']) ?? undefined,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
await medplum.upsertResource(
|
|
619
|
+
{
|
|
620
|
+
resourceType: 'Coverage',
|
|
621
|
+
meta: {
|
|
622
|
+
profile: [PROFILE_URLS.Coverage],
|
|
623
|
+
},
|
|
624
|
+
status: 'active',
|
|
625
|
+
beneficiary: createReference(patient),
|
|
626
|
+
subscriberId: subscriberId,
|
|
627
|
+
relationship: { coding: [relationshipToSubscriber] },
|
|
628
|
+
payor: [payor],
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
beneficiary: getReferenceString(patient),
|
|
632
|
+
payor: getReferenceString(payor),
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export async function addConsent(
|
|
638
|
+
medplum: MedplumClient,
|
|
639
|
+
patient: Patient,
|
|
640
|
+
consentGiven: boolean,
|
|
641
|
+
scope: CodeableConcept,
|
|
642
|
+
category: CodeableConcept,
|
|
643
|
+
policyRule: CodeableConcept | undefined,
|
|
644
|
+
date: Consent['dateTime']
|
|
645
|
+
): Promise<void> {
|
|
646
|
+
await medplum.createResource({
|
|
647
|
+
resourceType: 'Consent',
|
|
648
|
+
patient: createReference(patient),
|
|
649
|
+
status: consentGiven ? 'active' : 'rejected',
|
|
650
|
+
scope: scope,
|
|
651
|
+
category: [category],
|
|
652
|
+
policyRule: policyRule,
|
|
653
|
+
dateTime: date,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Finds a QuestionnaireItem or QuestionnaireResponseItem with the given linkId
|
|
659
|
+
*
|
|
660
|
+
* @param items - The array of objects present in the `.item` attribute of a Questionnaire or QuestionnaireResponse
|
|
661
|
+
* @param linkId - The id to be found
|
|
662
|
+
* @returns - The found item or undefined in case it's not found
|
|
663
|
+
*/
|
|
664
|
+
export function findQuestionnaireItem<T extends QuestionnaireItem | QuestionnaireResponseItem>(
|
|
665
|
+
items: T[] | undefined,
|
|
666
|
+
linkId: string
|
|
667
|
+
): T | undefined {
|
|
668
|
+
if (!items) {
|
|
669
|
+
return undefined;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return items.reduce((foundItem: T | undefined, currentItem: T | undefined) => {
|
|
673
|
+
// If currentItem is undefined or the item was already found just return it
|
|
674
|
+
if (foundItem || !currentItem) {
|
|
675
|
+
return foundItem;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (currentItem.linkId === linkId) {
|
|
679
|
+
return currentItem;
|
|
680
|
+
} else if (currentItem.item) {
|
|
681
|
+
// This enables traversing nested structures
|
|
682
|
+
return findQuestionnaireItem(currentItem.item as T[], linkId);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return undefined;
|
|
686
|
+
}, undefined);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Finds the answers to a group of items that can be repeated.
|
|
691
|
+
*
|
|
692
|
+
* @param questionnaire - The Questionnaire resource, used to find the list of linkIds that will compose each group
|
|
693
|
+
* @param response - The QuestionnaireResponse resource where the answers will be extracted
|
|
694
|
+
* @param groupLinkId - The linkId of the group that can be repeated in the questionnaire
|
|
695
|
+
* @returns - An array of objects each containing a set of grouped answers
|
|
696
|
+
*/
|
|
697
|
+
export function getGroupRepeatedAnswers(
|
|
698
|
+
questionnaire: Questionnaire,
|
|
699
|
+
response: QuestionnaireResponse,
|
|
700
|
+
groupLinkId: string
|
|
701
|
+
): Record<string, QuestionnaireResponseItemAnswer>[] {
|
|
702
|
+
// Find the questionnaire item based on groupLinkId
|
|
703
|
+
const questionnaireItem = findQuestionnaireItem(questionnaire.item, groupLinkId) as QuestionnaireItem;
|
|
704
|
+
|
|
705
|
+
if (questionnaireItem.type !== 'group' || !questionnaireItem?.item) {
|
|
706
|
+
return [];
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Get all response items corresponding to the groupLinkId
|
|
710
|
+
const responseGroups = response.item?.filter((item) => item.linkId === groupLinkId);
|
|
711
|
+
|
|
712
|
+
if (!responseGroups || responseGroups?.length === 0) {
|
|
713
|
+
return [];
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// It expects that responses will be organized in separate groups with the same groupLinkId. Eg.:
|
|
717
|
+
// [
|
|
718
|
+
// {group-linkId-1: [{answer-with-linkId-1}, {answer-with-linkId-2}, {answer-with-linkId-3}]},
|
|
719
|
+
// {group-linkId-1: [{answer-with-linkId-1}, {answer-with-linkId-2}, {answer-with-linkId-3}]}
|
|
720
|
+
// ]
|
|
721
|
+
// This function handles scenarios where each group can have some fields filled and some not, while keeping the order intact. Eg.:
|
|
722
|
+
// [
|
|
723
|
+
// {group-linkId-1: [{answer-with-linkId-1}, {answer-with-linkId-3}]},
|
|
724
|
+
// {group-linkId-1: [{answer-with-linkId-1}, {answer-with-linkId-2}, {answer-with-linkId-3}]}
|
|
725
|
+
// ]
|
|
726
|
+
|
|
727
|
+
const groupAnswers = responseGroups.map((responseItem) => {
|
|
728
|
+
const answers: Record<string, any> = {};
|
|
729
|
+
|
|
730
|
+
const extractAnswers = (items: QuestionnaireResponseItem[]): void => {
|
|
731
|
+
items.forEach(({ linkId, answer, item }) => {
|
|
732
|
+
if (item) {
|
|
733
|
+
const subGroupAnswers: Record<string, any> = {};
|
|
734
|
+
item.forEach((subItem) => {
|
|
735
|
+
if (subItem.answer) {
|
|
736
|
+
subGroupAnswers[subItem.linkId] = subItem.answer?.[0] ?? {};
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
answers[linkId] = subGroupAnswers;
|
|
740
|
+
} else {
|
|
741
|
+
answers[linkId] = answer?.[0] ?? {};
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
extractAnswers(responseItem.item || []);
|
|
747
|
+
return answers;
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
return groupAnswers;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export function convertDateToDateTime(date: string | undefined): string | undefined {
|
|
754
|
+
if (!date) {
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
return new Date(date).toISOString();
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
export function getHumanName(
|
|
761
|
+
answers: Record<string, QuestionnaireResponseItemAnswer>,
|
|
762
|
+
prefix: string = ''
|
|
763
|
+
): HumanName | undefined {
|
|
764
|
+
const patientName: HumanName = {};
|
|
765
|
+
|
|
766
|
+
const givenName = [];
|
|
767
|
+
if (answers[`${prefix}first-name`]?.valueString) {
|
|
768
|
+
givenName.push(answers[`${prefix}first-name`].valueString as string);
|
|
769
|
+
}
|
|
770
|
+
if (answers[`${prefix}middle-name`]?.valueString) {
|
|
771
|
+
givenName.push(answers[`${prefix}middle-name`].valueString as string);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (givenName.length > 0) {
|
|
775
|
+
patientName.given = givenName;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (answers[`${prefix}last-name`]?.valueString) {
|
|
779
|
+
patientName.family = answers[`${prefix}last-name`].valueString;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return Object.keys(patientName).length > 0 ? patientName : undefined;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export function getPatientAddress(answers: Record<string, QuestionnaireResponseItemAnswer>): Address | undefined {
|
|
786
|
+
const patientAddress: Address = {};
|
|
787
|
+
|
|
788
|
+
if (answers['street']?.valueString) {
|
|
789
|
+
patientAddress.line = [answers['street'].valueString];
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
if (answers['city']?.valueString) {
|
|
793
|
+
patientAddress.city = answers['city'].valueString;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (answers['state']?.valueCoding?.code) {
|
|
797
|
+
patientAddress.state = answers['state'].valueCoding.code;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (answers['zip']?.valueString) {
|
|
801
|
+
patientAddress.postalCode = answers['zip'].valueString;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// To simplify the demo, we're assuming the address is always a home address
|
|
805
|
+
return Object.keys(patientAddress).length > 0 ? { use: 'home', type: 'physical', ...patientAddress } : undefined;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function getRelatedPersonRelationshipFromCoverage(coverageRelationship: Coding): Coding | undefined {
|
|
809
|
+
// Basic relationship mapping between Coverage and RelatedPerson
|
|
810
|
+
// Coverage.relationship: http://hl7.org/fhir/ValueSet/subscriber-relationship
|
|
811
|
+
// RelatedPerson.relationship: http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype
|
|
812
|
+
switch (coverageRelationship.code) {
|
|
813
|
+
case 'child':
|
|
814
|
+
return {
|
|
815
|
+
system: 'http://terminology.hl7.org/CodeSystem/v3-RoleCode',
|
|
816
|
+
code: 'PRN',
|
|
817
|
+
display: 'parent',
|
|
818
|
+
};
|
|
819
|
+
case 'parent':
|
|
820
|
+
return { system: 'http://terminology.hl7.org/CodeSystem/v3-RoleCode', code: 'CHILD', display: 'child' };
|
|
821
|
+
case 'spouse':
|
|
822
|
+
case 'common':
|
|
823
|
+
return { system: 'http://terminology.hl7.org/CodeSystem/v3-RoleCode', code: 'SPS', display: 'spouse' };
|
|
824
|
+
default:
|
|
825
|
+
return undefined;
|
|
826
|
+
}
|
|
827
|
+
}
|