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.
Files changed (284) hide show
  1. package/README.md +5 -0
  2. package/dist/examples/vintasend-medplum-example/.env.example +11 -0
  3. package/dist/examples/vintasend-medplum-example/IMPLEMENTATION_PLAN_FILE_ATTACHMENTS.md +597 -0
  4. package/dist/examples/vintasend-medplum-example/README.md +190 -0
  5. package/dist/examples/vintasend-medplum-example/TUTORIAL_EMAIL_NOTIFICATIONS.md +2596 -0
  6. package/dist/examples/vintasend-medplum-example/bots/handlers/send-pending-notifications-bot.ts +39 -0
  7. package/dist/examples/vintasend-medplum-example/bots/handlers/send-task-assignment-email.ts +41 -0
  8. package/dist/examples/vintasend-medplum-example/bots/handlers/task-due-soon-notification-bot.ts +86 -0
  9. package/dist/examples/vintasend-medplum-example/bots/index.ts +53 -0
  10. package/dist/examples/vintasend-medplum-example/bots/services/emails/schedule-task-due-soon-email.ts +84 -0
  11. package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.test.ts +388 -0
  12. package/dist/examples/vintasend-medplum-example/bots/services/emails/send-task-assignment-email.ts +113 -0
  13. package/dist/examples/vintasend-medplum-example/bots/shared/task-due-soon-helpers.ts +115 -0
  14. package/dist/examples/vintasend-medplum-example/bots/task-assignment-bot.ts +41 -0
  15. package/dist/examples/vintasend-medplum-example/compiled-notification-templates.json +6 -0
  16. package/dist/examples/vintasend-medplum-example/esbuild-script.mjs +71 -0
  17. package/dist/examples/vintasend-medplum-example/index.html +14 -0
  18. package/dist/examples/vintasend-medplum-example/lib/constants.ts +32 -0
  19. package/dist/examples/vintasend-medplum-example/lib/extensions.ts +1 -0
  20. package/dist/examples/vintasend-medplum-example/lib/file-upload.test.ts +389 -0
  21. package/dist/examples/vintasend-medplum-example/lib/file-upload.ts +222 -0
  22. package/dist/examples/vintasend-medplum-example/lib/medplum-singleton.ts +18 -0
  23. package/dist/examples/vintasend-medplum-example/lib/notification-service.test.ts +293 -0
  24. package/dist/examples/vintasend-medplum-example/lib/notification-service.ts +284 -0
  25. package/dist/examples/vintasend-medplum-example/lib/patients.ts +20 -0
  26. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/body.html.pug +37 -0
  27. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-assignment/subject.txt.pug +4 -0
  28. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/body.html.pug +34 -0
  29. package/dist/examples/vintasend-medplum-example/notification-templates/emails/task-due-soon/subject.txt.pug +4 -0
  30. package/dist/examples/vintasend-medplum-example/package.json +75 -0
  31. package/dist/examples/vintasend-medplum-example/plugins/gql-plugin.mjs +31 -0
  32. package/dist/examples/vintasend-medplum-example/postcss.config.mjs +21 -0
  33. package/dist/examples/vintasend-medplum-example/public/favicon.ico +0 -0
  34. package/dist/examples/vintasend-medplum-example/public/img/integrations/acuity.png +0 -0
  35. package/dist/examples/vintasend-medplum-example/public/img/integrations/auth0.png +0 -0
  36. package/dist/examples/vintasend-medplum-example/public/img/integrations/azure.png +0 -0
  37. package/dist/examples/vintasend-medplum-example/public/img/integrations/calcom.png +0 -0
  38. package/dist/examples/vintasend-medplum-example/public/img/integrations/candid.png +0 -0
  39. package/dist/examples/vintasend-medplum-example/public/img/integrations/claude.png +0 -0
  40. package/dist/examples/vintasend-medplum-example/public/img/integrations/datadog.png +0 -0
  41. package/dist/examples/vintasend-medplum-example/public/img/integrations/deepseek.png +0 -0
  42. package/dist/examples/vintasend-medplum-example/public/img/integrations/entra.png +0 -0
  43. package/dist/examples/vintasend-medplum-example/public/img/integrations/epic.png +0 -0
  44. package/dist/examples/vintasend-medplum-example/public/img/integrations/google.png +0 -0
  45. package/dist/examples/vintasend-medplum-example/public/img/integrations/healthgorilla.png +0 -0
  46. package/dist/examples/vintasend-medplum-example/public/img/integrations/healthie.png +0 -0
  47. package/dist/examples/vintasend-medplum-example/public/img/integrations/labcorp.png +0 -0
  48. package/dist/examples/vintasend-medplum-example/public/img/integrations/okta.png +0 -0
  49. package/dist/examples/vintasend-medplum-example/public/img/integrations/openai.png +0 -0
  50. package/dist/examples/vintasend-medplum-example/public/img/integrations/particle.png +0 -0
  51. package/dist/examples/vintasend-medplum-example/public/img/integrations/quest.png +0 -0
  52. package/dist/examples/vintasend-medplum-example/public/img/integrations/recaptcha.png +0 -0
  53. package/dist/examples/vintasend-medplum-example/public/img/integrations/snowflake.png +0 -0
  54. package/dist/examples/vintasend-medplum-example/public/img/integrations/stedi.png +0 -0
  55. package/dist/examples/vintasend-medplum-example/public/img/integrations/stripe.png +0 -0
  56. package/dist/examples/vintasend-medplum-example/public/img/integrations/sumo.png +0 -0
  57. package/dist/examples/vintasend-medplum-example/scripts/README.md +162 -0
  58. package/dist/examples/vintasend-medplum-example/scripts/client.ts +18 -0
  59. package/dist/examples/vintasend-medplum-example/scripts/deploy-bots.ts +171 -0
  60. package/dist/examples/vintasend-medplum-example/src/App.tsx +185 -0
  61. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.test.tsx +350 -0
  62. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemList.tsx +241 -0
  63. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.test.tsx +616 -0
  64. package/dist/examples/vintasend-medplum-example/src/components/ChargeItem/ChargeItemPanel.tsx +138 -0
  65. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.test.tsx +92 -0
  66. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionItem.tsx +47 -0
  67. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.test.tsx +464 -0
  68. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionList.tsx +186 -0
  69. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.test.tsx +80 -0
  70. package/dist/examples/vintasend-medplum-example/src/components/Conditions/ConditionModal.tsx +82 -0
  71. package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.test.tsx +100 -0
  72. package/dist/examples/vintasend-medplum-example/src/components/DoseSpotIcon.tsx +20 -0
  73. package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.module.css +3 -0
  74. package/dist/examples/vintasend-medplum-example/src/components/IntegrationCard.tsx +62 -0
  75. package/dist/examples/vintasend-medplum-example/src/components/MessageWithLinks.tsx +47 -0
  76. package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.test.tsx +299 -0
  77. package/dist/examples/vintasend-medplum-example/src/components/PerformingLabInput.tsx +52 -0
  78. package/dist/examples/vintasend-medplum-example/src/components/ResourceFormWithRequiredProfile.tsx +82 -0
  79. package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.test.tsx +1016 -0
  80. package/dist/examples/vintasend-medplum-example/src/components/encounter/BillingTab.tsx +298 -0
  81. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.test.tsx +732 -0
  82. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterChart.tsx +282 -0
  83. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.test.tsx +268 -0
  84. package/dist/examples/vintasend-medplum-example/src/components/encounter/EncounterHeader.tsx +224 -0
  85. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.test.tsx +255 -0
  86. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignAddendum.tsx +212 -0
  87. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.test.tsx +120 -0
  88. package/dist/examples/vintasend-medplum-example/src/components/encounter/SignLockDialog.tsx +57 -0
  89. package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.test.tsx +224 -0
  90. package/dist/examples/vintasend-medplum-example/src/components/encounter/VisitDetailsPanel.tsx +100 -0
  91. package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.test.tsx +431 -0
  92. package/dist/examples/vintasend-medplum-example/src/components/labs/CoverageInput.tsx +130 -0
  93. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.module.css +31 -0
  94. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.test.tsx +234 -0
  95. package/dist/examples/vintasend-medplum-example/src/components/labs/LabListItem.tsx +143 -0
  96. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.module.css +11 -0
  97. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.test.tsx +875 -0
  98. package/dist/examples/vintasend-medplum-example/src/components/labs/LabOrderDetails.tsx +943 -0
  99. package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.test.tsx +413 -0
  100. package/dist/examples/vintasend-medplum-example/src/components/labs/LabResultDetails.tsx +203 -0
  101. package/dist/examples/vintasend-medplum-example/src/components/labs/LabSelectEmpty.tsx +22 -0
  102. package/dist/examples/vintasend-medplum-example/src/components/labs/README.md +104 -0
  103. package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.test.tsx +318 -0
  104. package/dist/examples/vintasend-medplum-example/src/components/labs/TestMetadataCardInput.tsx +87 -0
  105. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.test.tsx +126 -0
  106. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatList.tsx +38 -0
  107. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.module.css +23 -0
  108. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.test.tsx +167 -0
  109. package/dist/examples/vintasend-medplum-example/src/components/messages/ChatListItem.tsx +53 -0
  110. package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.test.tsx +94 -0
  111. package/dist/examples/vintasend-medplum-example/src/components/messages/NewTopicDialog.tsx +165 -0
  112. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.module.css +8 -0
  113. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.test.tsx +523 -0
  114. package/dist/examples/vintasend-medplum-example/src/components/messages/ParticipantFilter.tsx +230 -0
  115. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.module.css +23 -0
  116. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.test.tsx +567 -0
  117. package/dist/examples/vintasend-medplum-example/src/components/messages/ThreadInbox.tsx +358 -0
  118. package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.module.css +40 -0
  119. package/dist/examples/vintasend-medplum-example/src/components/plandefinition/AddPlanDefinition.tsx +257 -0
  120. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.module.css +7 -0
  121. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.test.tsx +279 -0
  122. package/dist/examples/vintasend-medplum-example/src/components/schedule/CreateVisit.tsx +156 -0
  123. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.module.css +45 -0
  124. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.test.tsx +90 -0
  125. package/dist/examples/vintasend-medplum-example/src/components/spaces/HistoryList.tsx +84 -0
  126. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.module.css +26 -0
  127. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourceBox.tsx +90 -0
  128. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.test.tsx +305 -0
  129. package/dist/examples/vintasend-medplum-example/src/components/spaces/ResourcePanel.tsx +46 -0
  130. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.module.css +262 -0
  131. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.test.tsx +622 -0
  132. package/dist/examples/vintasend-medplum-example/src/components/spaces/SpacesInbox.tsx +286 -0
  133. package/dist/examples/vintasend-medplum-example/src/components/tasks/NewTaskModal.tsx +275 -0
  134. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskAttachmentList.tsx +132 -0
  135. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.module.css +45 -0
  136. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.test.tsx +749 -0
  137. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskBoard.tsx +416 -0
  138. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.test.tsx +278 -0
  139. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailPanel.tsx +133 -0
  140. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.module.css +16 -0
  141. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.test.tsx +255 -0
  142. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskDetailsModal.tsx +203 -0
  143. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFileUpload.tsx +129 -0
  144. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.test.tsx +156 -0
  145. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.tsx +142 -0
  146. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskFilterMenu.utils.ts +28 -0
  147. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.test.tsx +134 -0
  148. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskInputNote.tsx +250 -0
  149. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.module.css +23 -0
  150. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.test.tsx +149 -0
  151. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskListItem.tsx +53 -0
  152. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.test.tsx +68 -0
  153. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskNoteItem.tsx +46 -0
  154. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.test.tsx +555 -0
  155. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskProperties.tsx +170 -0
  156. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.test.tsx +32 -0
  157. package/dist/examples/vintasend-medplum-example/src/components/tasks/TaskSelectEmpty.tsx +34 -0
  158. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.test.tsx +47 -0
  159. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/SimpleTask.tsx +29 -0
  160. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.test.tsx +285 -0
  161. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskPanel.tsx +129 -0
  162. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.test.tsx +455 -0
  163. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskQuestionnaireForm.tsx +167 -0
  164. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.test.tsx +435 -0
  165. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskServiceRequest.tsx +116 -0
  166. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.module.css +38 -0
  167. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.test.tsx +200 -0
  168. package/dist/examples/vintasend-medplum-example/src/components/tasks/encounter/TaskStatusPanel.tsx +84 -0
  169. package/dist/examples/vintasend-medplum-example/src/components/utils.test.ts +176 -0
  170. package/dist/examples/vintasend-medplum-example/src/components/utils.ts +17 -0
  171. package/dist/examples/vintasend-medplum-example/src/config/constants.ts +3 -0
  172. package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.test.tsx +166 -0
  173. package/dist/examples/vintasend-medplum-example/src/hooks/useDebouncedUpdateResource.ts +28 -0
  174. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.test.tsx +94 -0
  175. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounter.ts +11 -0
  176. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.test.tsx +477 -0
  177. package/dist/examples/vintasend-medplum-example/src/hooks/useEncounterChart.ts +191 -0
  178. package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.test.tsx +100 -0
  179. package/dist/examples/vintasend-medplum-example/src/hooks/usePatient.ts +18 -0
  180. package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.test.tsx +379 -0
  181. package/dist/examples/vintasend-medplum-example/src/hooks/useThreadInbox.ts +194 -0
  182. package/dist/examples/vintasend-medplum-example/src/index.css +8 -0
  183. package/dist/examples/vintasend-medplum-example/src/main.tsx +57 -0
  184. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.module.css +6 -0
  185. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.test.tsx +295 -0
  186. package/dist/examples/vintasend-medplum-example/src/pages/SearchPage.tsx +124 -0
  187. package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.test.tsx +77 -0
  188. package/dist/examples/vintasend-medplum-example/src/pages/SignInPage.tsx +22 -0
  189. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.test.tsx +87 -0
  190. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterChartPage.tsx +27 -0
  191. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.module.css +16 -0
  192. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.test.tsx +287 -0
  193. package/dist/examples/vintasend-medplum-example/src/pages/encounter/EncounterModal.tsx +151 -0
  194. package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.test.tsx +519 -0
  195. package/dist/examples/vintasend-medplum-example/src/pages/integrations/DoseSpotFavoritesPage.tsx +179 -0
  196. package/dist/examples/vintasend-medplum-example/src/pages/integrations/FavoriteMedicationsTable.tsx +76 -0
  197. package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.test.tsx +234 -0
  198. package/dist/examples/vintasend-medplum-example/src/pages/integrations/IntegrationsPage.tsx +222 -0
  199. package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.test.tsx +356 -0
  200. package/dist/examples/vintasend-medplum-example/src/pages/labs/OrderLabsPage.tsx +275 -0
  201. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.module.css +8 -0
  202. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.test.tsx +103 -0
  203. package/dist/examples/vintasend-medplum-example/src/pages/messages/MessagesPage.tsx +78 -0
  204. package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.test.tsx +84 -0
  205. package/dist/examples/vintasend-medplum-example/src/pages/patient/CommunicationTab.tsx +82 -0
  206. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.test.tsx +364 -0
  207. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotAdvancedOptions.tsx +149 -0
  208. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.test.tsx +159 -0
  209. package/dist/examples/vintasend-medplum-example/src/pages/patient/DoseSpotTab.tsx +37 -0
  210. package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.test.tsx +140 -0
  211. package/dist/examples/vintasend-medplum-example/src/pages/patient/EditTab.tsx +72 -0
  212. package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.test.tsx +57 -0
  213. package/dist/examples/vintasend-medplum-example/src/pages/patient/ExportTab.tsx +14 -0
  214. package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.test.tsx +241 -0
  215. package/dist/examples/vintasend-medplum-example/src/pages/patient/IntakeFormPage.tsx +710 -0
  216. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.module.css +37 -0
  217. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.test.tsx +428 -0
  218. package/dist/examples/vintasend-medplum-example/src/pages/patient/LabsPage.tsx +334 -0
  219. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.module.css +24 -0
  220. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.test.tsx +154 -0
  221. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.tsx +115 -0
  222. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.test.ts +223 -0
  223. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientPage.utils.ts +89 -0
  224. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.test.tsx +147 -0
  225. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientSearchPage.tsx +79 -0
  226. package/dist/examples/vintasend-medplum-example/src/pages/patient/PatientTabsNavigation.tsx +35 -0
  227. package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.test.tsx +185 -0
  228. package/dist/examples/vintasend-medplum-example/src/pages/patient/TasksTab.tsx +115 -0
  229. package/dist/examples/vintasend-medplum-example/src/pages/patient/TimelineTab.tsx +14 -0
  230. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.test.tsx +170 -0
  231. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceCreatePage.tsx +117 -0
  232. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceDetailPage.tsx +28 -0
  233. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.test.tsx +131 -0
  234. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceEditPage.tsx +65 -0
  235. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.test.tsx +108 -0
  236. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourceHistoryPage.tsx +16 -0
  237. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.module.css +7 -0
  238. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.test.tsx +37 -0
  239. package/dist/examples/vintasend-medplum-example/src/pages/resource/ResourcePage.tsx +44 -0
  240. package/dist/examples/vintasend-medplum-example/src/pages/resource/useResourceType.ts +44 -0
  241. package/dist/examples/vintasend-medplum-example/src/pages/resource/utils.ts +9 -0
  242. package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.test.tsx +302 -0
  243. package/dist/examples/vintasend-medplum-example/src/pages/schedule/SchedulePage.tsx +416 -0
  244. package/dist/examples/vintasend-medplum-example/src/pages/spaces/ChatInput.tsx +91 -0
  245. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.module.css +6 -0
  246. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.test.tsx +102 -0
  247. package/dist/examples/vintasend-medplum-example/src/pages/spaces/SpacesPage.tsx +44 -0
  248. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.module.css +7 -0
  249. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.test.tsx +133 -0
  250. package/dist/examples/vintasend-medplum-example/src/pages/tasks/TasksPage.tsx +91 -0
  251. package/dist/examples/vintasend-medplum-example/src/test-utils/render.tsx +20 -0
  252. package/dist/examples/vintasend-medplum-example/src/test.setup.ts +49 -0
  253. package/dist/examples/vintasend-medplum-example/src/types/encounter.ts +8 -0
  254. package/dist/examples/vintasend-medplum-example/src/types/spaces.ts +10 -0
  255. package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.test.ts +141 -0
  256. package/dist/examples/vintasend-medplum-example/src/utils/chargeitems.ts +59 -0
  257. package/dist/examples/vintasend-medplum-example/src/utils/claims.test.ts +153 -0
  258. package/dist/examples/vintasend-medplum-example/src/utils/claims.ts +65 -0
  259. package/dist/examples/vintasend-medplum-example/src/utils/communication-search.ts +47 -0
  260. package/dist/examples/vintasend-medplum-example/src/utils/coverage.test.ts +48 -0
  261. package/dist/examples/vintasend-medplum-example/src/utils/coverage.ts +33 -0
  262. package/dist/examples/vintasend-medplum-example/src/utils/documentReference.test.ts +102 -0
  263. package/dist/examples/vintasend-medplum-example/src/utils/documentReference.ts +55 -0
  264. package/dist/examples/vintasend-medplum-example/src/utils/encounter.test.ts +169 -0
  265. package/dist/examples/vintasend-medplum-example/src/utils/encounter.ts +261 -0
  266. package/dist/examples/vintasend-medplum-example/src/utils/intake-form.test.ts +154 -0
  267. package/dist/examples/vintasend-medplum-example/src/utils/intake-form.ts +272 -0
  268. package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.test.ts +1137 -0
  269. package/dist/examples/vintasend-medplum-example/src/utils/intake-utils.ts +827 -0
  270. package/dist/examples/vintasend-medplum-example/src/utils/notifications.test.ts +27 -0
  271. package/dist/examples/vintasend-medplum-example/src/utils/notifications.ts +15 -0
  272. package/dist/examples/vintasend-medplum-example/src/utils/spaceMessaging.ts +249 -0
  273. package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.test.ts +450 -0
  274. package/dist/examples/vintasend-medplum-example/src/utils/spacePersistence.ts +147 -0
  275. package/dist/examples/vintasend-medplum-example/src/utils/task-search.ts +63 -0
  276. package/dist/examples/vintasend-medplum-example/src/vite-env.d.ts +3 -0
  277. package/dist/examples/vintasend-medplum-example/tsconfig.bots.json +4 -0
  278. package/dist/examples/vintasend-medplum-example/tsconfig.json +19 -0
  279. package/dist/examples/vintasend-medplum-example/vercel.json +3 -0
  280. package/dist/examples/vintasend-medplum-example/vite.config.ts +44 -0
  281. package/dist/services/notification-backends/base-notification-backend.d.ts +5 -0
  282. package/dist/services/notification-service.js +11 -1
  283. package/dist/services/notification-template-renderers/base-email-template-renderer.d.ts +5 -0
  284. 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
+ }