@kenyaemr/esm-ward-app 8.5.4-pre.83 → 8.5.4-pre.9

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 (241) hide show
  1. package/README.md +31 -0
  2. package/package.json +1 -1
  3. package/src/config-schema.ts +21 -1
  4. package/src/in-patient/inpatient-tabs.component.tsx +84 -3
  5. package/src/in-patient/labourCareGuide/alert-rules.ts +155 -41
  6. package/src/in-patient/labourCareGuide/components/lcg-correction-audit-banner.component.tsx +42 -0
  7. package/src/in-patient/labourCareGuide/constants/admission-form.constants.ts +22 -0
  8. package/src/in-patient/labourCareGuide/context/lcg-section-state.context.tsx +252 -0
  9. package/src/in-patient/labourCareGuide/forms/admission-details-form.component.tsx +71 -17
  10. package/src/in-patient/labourCareGuide/forms/baby-form.component.tsx +55 -179
  11. package/src/in-patient/labourCareGuide/forms/labour-care-guide-forms.component.tsx +74 -29
  12. package/src/in-patient/labourCareGuide/forms/labour-progress-form.component.tsx +163 -216
  13. package/src/in-patient/labourCareGuide/forms/lcg-session-correction-wizard.component.tsx +214 -0
  14. package/src/in-patient/labourCareGuide/forms/medication-form.component.tsx +598 -220
  15. package/src/in-patient/labourCareGuide/forms/shared-decision-assessment-field.component.tsx +109 -0
  16. package/src/in-patient/labourCareGuide/forms/shared-decision-making-form.component.tsx +206 -191
  17. package/src/in-patient/labourCareGuide/forms/supportive-care-form.component.tsx +33 -90
  18. package/src/in-patient/labourCareGuide/forms/time-form.component.tsx +12 -4
  19. package/src/in-patient/labourCareGuide/forms/time-picker-dropdown.component.tsx +168 -53
  20. package/src/in-patient/labourCareGuide/forms/woman-form.component.tsx +51 -79
  21. package/src/in-patient/labourCareGuide/global-cds-alerts/global-cds-alert-context.tsx +2 -2
  22. package/src/in-patient/labourCareGuide/graphs/labour-progress.component.tsx +188 -86
  23. package/src/in-patient/labourCareGuide/graphs/medication.component.tsx +15 -23
  24. package/src/in-patient/labourCareGuide/graphs/shared-decision-making.component.tsx +172 -79
  25. package/src/in-patient/labourCareGuide/graphs/supportive-care.component.tsx +4 -1
  26. package/src/in-patient/labourCareGuide/graphs/woman.component.tsx +3 -39
  27. package/src/in-patient/labourCareGuide/hooks/use-lcg-data.ts +556 -205
  28. package/src/in-patient/labourCareGuide/hooks/use-lcg-debounced-value.ts +17 -0
  29. package/src/in-patient/labourCareGuide/hooks/use-lcg-form-clock.ts +36 -0
  30. package/src/in-patient/labourCareGuide/hooks/use-lcg-section-autosave.ts +33 -0
  31. package/src/in-patient/labourCareGuide/labour-care-guide.component.tsx +693 -68
  32. package/src/in-patient/labourCareGuide/labour-care-guide.scss +49 -14
  33. package/src/in-patient/labourCareGuide/labour-care-guide.utils.ts +148 -6
  34. package/src/in-patient/labourCareGuide/lcg-alert-lifecycle.ts +161 -0
  35. package/src/in-patient/labourCareGuide/partography-data-form.scss +149 -3
  36. package/src/in-patient/labourCareGuide/partography.resource.ts +15 -27
  37. package/src/in-patient/labourCareGuide/resources/admission-details.resource.ts +26 -25
  38. package/src/in-patient/labourCareGuide/resources/drug-stock.resource.ts +98 -0
  39. package/src/in-patient/labourCareGuide/resources/labour-progress.resource.ts +34 -2
  40. package/src/in-patient/labourCareGuide/resources/lcg-session-correction.resource.ts +1027 -0
  41. package/src/in-patient/labourCareGuide/resources/medication.resource.ts +241 -37
  42. package/src/in-patient/labourCareGuide/resources/shared-decision-making.resource.ts +37 -3
  43. package/src/in-patient/labourCareGuide/resources/supportive-care.resource.ts +53 -5
  44. package/src/in-patient/labourCareGuide/storage/lcg-local-storage.ts +374 -0
  45. package/src/in-patient/labourCareGuide/time-slot-utils.ts +180 -26
  46. package/src/in-patient/labourCareGuide/types/concept-uuids.constants.ts +21 -0
  47. package/src/in-patient/labourCareGuide/types/index.ts +31 -7
  48. package/src/in-patient/treatmentChart/nursing-cardex-payload-builder.ts +9 -0
  49. package/src/in-patient/treatmentChart/nursing-cardex-plan.resource.ts +22 -6
  50. package/src/in-patient/treatmentChart/nursing-cardex-plan.workspace.scss +7 -1
  51. package/src/in-patient/treatmentChart/nursing-cardex-plan.workspace.tsx +20 -0
  52. package/src/in-patient/treatmentChart/nursing-treatment-sheet.resource.ts +27 -2
  53. package/src/in-patient/treatmentChart/treatment-chart-dashboard.component.tsx +2 -0
  54. package/src/in-patient/treatmentChart/use-nursing-cardex-form-schema.ts +2 -0
  55. package/src/index.ts +17 -0
  56. package/src/routes.json +20 -0
  57. package/src/ward-patients/ward-patients-slots.ts +21 -0
  58. package/src/ward-patients/ward-patients-table.test.tsx +58 -0
  59. package/src/ward-patients/ward-patients-table.tsx +25 -8
  60. package/translations/en.json +358 -26
  61. package/translations/fr.json +359 -26
  62. package/.turbo/turbo-build.log +0 -8
  63. package/dist/1084.js +0 -1
  64. package/dist/1084.js.map +0 -1
  65. package/dist/1189.js +0 -1
  66. package/dist/1189.js.map +0 -1
  67. package/dist/12.js +0 -17
  68. package/dist/12.js.map +0 -1
  69. package/dist/126.js +0 -1
  70. package/dist/1308.js +0 -1
  71. package/dist/1308.js.map +0 -1
  72. package/dist/1374.js +0 -1
  73. package/dist/1374.js.map +0 -1
  74. package/dist/1387.js +0 -1
  75. package/dist/1387.js.map +0 -1
  76. package/dist/15.js +0 -1
  77. package/dist/1564.js +0 -1
  78. package/dist/1567.js +0 -1
  79. package/dist/1567.js.map +0 -1
  80. package/dist/1739.js +0 -1
  81. package/dist/1739.js.map +0 -1
  82. package/dist/1845.js +0 -1
  83. package/dist/1972.js +0 -1
  84. package/dist/1972.js.map +0 -1
  85. package/dist/2016.js +0 -1
  86. package/dist/2016.js.map +0 -1
  87. package/dist/2117.js +0 -1
  88. package/dist/2117.js.map +0 -1
  89. package/dist/215.js +0 -1
  90. package/dist/2178.js +0 -1
  91. package/dist/239.js +0 -1
  92. package/dist/239.js.map +0 -1
  93. package/dist/2486.js +0 -1
  94. package/dist/2486.js.map +0 -1
  95. package/dist/2566.js +0 -1
  96. package/dist/2710.js +0 -1
  97. package/dist/2710.js.map +0 -1
  98. package/dist/2759.js +0 -1
  99. package/dist/2823.js +0 -15
  100. package/dist/2823.js.map +0 -1
  101. package/dist/3089.js +0 -1
  102. package/dist/3089.js.map +0 -1
  103. package/dist/3230.js +0 -1
  104. package/dist/3261.js +0 -1
  105. package/dist/3261.js.map +0 -1
  106. package/dist/3321.js +0 -1
  107. package/dist/3321.js.map +0 -1
  108. package/dist/3350.js +0 -1
  109. package/dist/3399.js +0 -1
  110. package/dist/3399.js.map +0 -1
  111. package/dist/3441.js +0 -1
  112. package/dist/3547.js +0 -1
  113. package/dist/3547.js.map +0 -1
  114. package/dist/3548.js +0 -1
  115. package/dist/3548.js.map +0 -1
  116. package/dist/3565.js +0 -1
  117. package/dist/3571.js +0 -1
  118. package/dist/3571.js.map +0 -1
  119. package/dist/364.js +0 -1
  120. package/dist/364.js.map +0 -1
  121. package/dist/3746.js +0 -1
  122. package/dist/3925.js +0 -1
  123. package/dist/3946.js +0 -1
  124. package/dist/401.js +0 -1
  125. package/dist/401.js.map +0 -1
  126. package/dist/4032.js +0 -1
  127. package/dist/4032.js.map +0 -1
  128. package/dist/4206.js +0 -1
  129. package/dist/4206.js.map +0 -1
  130. package/dist/4480.js +0 -1
  131. package/dist/4480.js.map +0 -1
  132. package/dist/4635.js +0 -1
  133. package/dist/4635.js.map +0 -1
  134. package/dist/4684.js +0 -1
  135. package/dist/4684.js.map +0 -1
  136. package/dist/4735.js +0 -1
  137. package/dist/4735.js.map +0 -1
  138. package/dist/4744.js +0 -1
  139. package/dist/4744.js.map +0 -1
  140. package/dist/4894.js +0 -1
  141. package/dist/4970.js +0 -1
  142. package/dist/4970.js.map +0 -1
  143. package/dist/5069.js +0 -1
  144. package/dist/5069.js.map +0 -1
  145. package/dist/5090.js +0 -1
  146. package/dist/5090.js.map +0 -1
  147. package/dist/5130.js +0 -1
  148. package/dist/5187.js +0 -1
  149. package/dist/5411.js +0 -1
  150. package/dist/5411.js.map +0 -1
  151. package/dist/5441.js +0 -1
  152. package/dist/5441.js.map +0 -1
  153. package/dist/5491.js +0 -1
  154. package/dist/5491.js.map +0 -1
  155. package/dist/5496.js +0 -1
  156. package/dist/5496.js.map +0 -1
  157. package/dist/5595.js +0 -1
  158. package/dist/5659.js +0 -1
  159. package/dist/5659.js.map +0 -1
  160. package/dist/5706.js +0 -1
  161. package/dist/5706.js.map +0 -1
  162. package/dist/5961.js +0 -1
  163. package/dist/6061.js +0 -1
  164. package/dist/6061.js.map +0 -1
  165. package/dist/6133.js +0 -1
  166. package/dist/6180.js +0 -1
  167. package/dist/6180.js.map +0 -1
  168. package/dist/6296.js +0 -1
  169. package/dist/6296.js.map +0 -1
  170. package/dist/6322.js +0 -1
  171. package/dist/6322.js.map +0 -1
  172. package/dist/6381.js +0 -1
  173. package/dist/6381.js.map +0 -1
  174. package/dist/6455.js +0 -1
  175. package/dist/6455.js.map +0 -1
  176. package/dist/6456.js +0 -1
  177. package/dist/6466.js +0 -1
  178. package/dist/6613.js +0 -1
  179. package/dist/6783.js +0 -1
  180. package/dist/6925.js +0 -1
  181. package/dist/6925.js.map +0 -1
  182. package/dist/7091.js +0 -1
  183. package/dist/7091.js.map +0 -1
  184. package/dist/7263.js +0 -1
  185. package/dist/7263.js.map +0 -1
  186. package/dist/7348.js +0 -1
  187. package/dist/7431.js +0 -1
  188. package/dist/7431.js.map +0 -1
  189. package/dist/7543.js +0 -1
  190. package/dist/7607.js +0 -1
  191. package/dist/762.js +0 -1
  192. package/dist/762.js.map +0 -1
  193. package/dist/772.js +0 -1
  194. package/dist/775.js +0 -1
  195. package/dist/775.js.map +0 -1
  196. package/dist/8171.js +0 -1
  197. package/dist/8171.js.map +0 -1
  198. package/dist/8488.js +0 -1
  199. package/dist/8488.js.map +0 -1
  200. package/dist/8727.js +0 -1
  201. package/dist/8736.js +0 -1
  202. package/dist/8736.js.map +0 -1
  203. package/dist/8847.js +0 -1
  204. package/dist/9009.js +0 -1
  205. package/dist/9009.js.map +0 -1
  206. package/dist/9015.js +0 -1
  207. package/dist/906.js +0 -1
  208. package/dist/9065.js +0 -1
  209. package/dist/9182.js +0 -1
  210. package/dist/9314.js +0 -1
  211. package/dist/9314.js.map +0 -1
  212. package/dist/9339.js +0 -1
  213. package/dist/9361.js +0 -1
  214. package/dist/9361.js.map +0 -1
  215. package/dist/9453.js +0 -1
  216. package/dist/9465.js +0 -1
  217. package/dist/9465.js.map +0 -1
  218. package/dist/9535.js +0 -22
  219. package/dist/9535.js.map +0 -1
  220. package/dist/9610.js +0 -1
  221. package/dist/9610.js.map +0 -1
  222. package/dist/9668.js +0 -1
  223. package/dist/9668.js.map +0 -1
  224. package/dist/9727.js +0 -1
  225. package/dist/9727.js.map +0 -1
  226. package/dist/9734.js +0 -1
  227. package/dist/9734.js.map +0 -1
  228. package/dist/9833.js +0 -1
  229. package/dist/9833.js.map +0 -1
  230. package/dist/9848.js +0 -1
  231. package/dist/9848.js.map +0 -1
  232. package/dist/9876.js +0 -1
  233. package/dist/9876.js.map +0 -1
  234. package/dist/9920.js +0 -1
  235. package/dist/9938.js +0 -1
  236. package/dist/kenyaemr-esm-ward-app.js +0 -5
  237. package/dist/kenyaemr-esm-ward-app.js.buildmanifest.json +0 -2723
  238. package/dist/kenyaemr-esm-ward-app.js.map +0 -1
  239. package/dist/main.js +0 -5
  240. package/dist/main.js.map +0 -1
  241. package/dist/routes.json +0 -1
package/README.md CHANGED
@@ -22,6 +22,37 @@ Please see the [EMR API README](https://github.com/openmrs/openmrs-module-emrapi
22
22
 
23
23
  See the [config-schema](./src/config-schema.ts) for details on the configuration options available to the ward app.
24
24
 
25
+ ## Ward Patient Table Tab Extension Slots
26
+
27
+ The default ward table view renders four fixed tabs (Awaiting Admission, Admitted, Discharge In, Discharged). Each tab panel is backed by an extension slot so implementers can replace the default table UI without forking the ward app.
28
+
29
+ | Tab | Slot name | Default extension name |
30
+ |-----|-----------|------------------------|
31
+ | Awaiting Admission | `ward-patients-awaiting-admission-slot` | `ward-patients-awaiting-admission` |
32
+ | Admitted | `ward-patients-admitted-slot` | `ward-patients-admitted` |
33
+ | Discharge In | `ward-patients-discharge-in-slot` | `ward-patients-discharge-in` |
34
+ | Discharged | `ward-patients-discharged-slot` | `ward-patients-discharged` |
35
+
36
+ ### Custom component requirements
37
+
38
+ - Render under `DefaultWardView` so `useAppContext('ward-view-context')` provides ward patient data.
39
+ - Reuse types from [`src/types/index.ts`](./src/types/index.ts): `WardViewContext`, `WardPatient`, etc.
40
+ - Optional shared UI helpers: `HyperLinkPatientCell`, `EmptyState`, `ErrorState`, `getOpenmrsId`.
41
+
42
+ ### Replacing a default table
43
+
44
+ In your module's `routes.json`, register the replacement extension:
45
+
46
+ ```json
47
+ {
48
+ "name": "my-custom-awaiting-admission",
49
+ "component": "myAwaitingAdmissionTable",
50
+ "slot": "ward-patients-awaiting-admission-slot"
51
+ }
52
+ ```
53
+
54
+ The default table components are exported from this module (`wardPatientsAwaitingAdmissionTable`, `wardPatientsAdmittedTable`, `wardPatientsDischargeInTable`, `wardPatientsDischargedTable`) if you need to wrap or extend them.
55
+
25
56
  ## Bed Management
26
57
 
27
58
  The ward app is designed to support patient bed assignments and to be used in conjunction with the bedmanagement module and the bed-management-app ESM. However, bed management is entirely optional. Patients will appear on a ward if they:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-ward-app",
3
- "version": "8.5.4-pre.83",
3
+ "version": "8.5.4-pre.9",
4
4
  "description": "Ward and bed management module for O3",
5
5
  "browser": "dist/kenyaemr-esm-ward-app.js",
6
6
  "main": "src/index.ts",
@@ -45,6 +45,7 @@ export const labourCareGuideConceptUuids = {
45
45
  UUID_URINE_ONE_PLUS: '1362AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
46
46
  UUID_URINE_TWO_PLUS: '1363AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
47
47
  UUID_URINE_THREE_PLUS: '1364AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
48
+ UUID_URINE_NOT_DONE: '1118AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
48
49
  UUID_URINE_TEST_TIME_SLOT: 'bb3724c9-fbcc-49c5-9702-6cde0be325ca',
49
50
  UUID_PARTOGRAPHY_ENCOUNTER: '022d62af-e2a5-4282-953b-52dd5cba3296',
50
51
  UUID_MEDICATION_OXYTOCIN_ADMINISTERED: '82580974-ff46-41c5-b218-4bea7f53bf7e',
@@ -83,6 +84,19 @@ export const labourCareGuideConceptUuids = {
83
84
  UUID_URGE_TO_PUSH: '9ad5b56d-597c-4a40-ade6-387802daa44a',
84
85
  UUID_EMERGENCY_OVERRIDE: '1670AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
85
86
  UUID_EMERGENCY_REASON: '163761AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
87
+ UUID_SHARED_DECISION_TERMINATE_ONGOING_PROGRESS: '737fa73a-509f-40d8-9e84-91dc71db7f4c',
88
+ UUID_LCG_TERMINATION_REASON_TYPE: '6098AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
89
+ UUID_LCG_TERMINATION_REASON_WRONG_PATIENT: '164144AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
90
+ UUID_LCG_TERMINATION_REASON_OTHER: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
91
+ UUID_LCG_CORRECTION_ENCOUNTER_TYPE: 'b2c4d5e6-7f8a-4e9b-8c1d-2e3f8e4a3b8f',
92
+ UUID_LCG_CORRECTION_TYPE: '4b3eb27b-43c0-4862-bd83-be06beb3d699',
93
+ UUID_LCG_CORRECTION_TYPE_WRONG_PATIENT_TRANSFER: '1286AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
94
+ UUID_LCG_CORRECTION_SOURCE_PATIENT_UUID: '162720AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
95
+ UUID_LCG_CORRECTION_SOURCE_TERMINATE_ENCOUNTER_UUID: '1671AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
96
+ UUID_LCG_CORRECTION_TERMINATION_REASON: '162704AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
97
+ UUID_LCG_CORRECTION_SOURCE_PATIENT_DISPLAY: '850a960b-e8b5-4775-ba74-aaddb2abbf51',
98
+ UUID_LCG_CORRECTION_TERMINATED_AT: '163286AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
99
+ UUID_LCG_CORRECTION_TRANSFER_NOTES: '1056dc03-ce4d-45cb-a1eb-87707162e6ee',
86
100
  FETAL_HEART_RATE_CONCEPT: '1440AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
87
101
  FETAL_HEART_RATE_TIME_CONCEPT: 'bb3724c9-fbcc-49c5-9702-6cde0be325ca',
88
102
  FETAL_HEART_RATE_HOUR_CONCEPT: '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
@@ -98,7 +112,7 @@ export const labourCareGuideConceptUuids = {
98
112
  CONTRACTION_LEVEL_MODERATE_CONCEPT: '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
99
113
  CONTRACTION_LEVEL_STRONG_CONCEPT: '166788AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
100
114
  UTERINE_CONTRACTION_FREQUENCY_CONCEPT: 'd266dee4-bbe8-4736-b94d-c33300f55bca',
101
- UTERINE_CONTRACTION_DURATION_CONCEPT: 'd266dee4-bbe8-4736-b94d-c33300f55bca',
115
+ UTERINE_CONTRACTION_DURATION_CONCEPT: '159368AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
102
116
  OXYTOCIN_TIME_CONCEPT: 'bb3724c9-fbcc-49c5-9702-6cde0be325ca',
103
117
  OXYTOCIN_DROPS_PER_MINUTE_CONCEPT: '1d109b10-7b30-4bfa-8a7c-6ecb73357fc2',
104
118
  OXYTOCIN_DOSE_CONCEPT: '81369AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
@@ -920,6 +934,11 @@ export const configSchema: ConfigSchema = {
920
934
  _description: 'Labour care guide partography module configuration',
921
935
  _default: labourCareGuidePartographyConfig,
922
936
  },
937
+ lcgSessionCorrectionPrivilege: {
938
+ _type: Type.String,
939
+ _description: 'OpenMRS privilege required to open the LCG wrong-patient correction wizard',
940
+ _default: 'Correct LCG Wrong Patient',
941
+ },
923
942
  } as const;
924
943
 
925
944
  export interface WardConfigObject {
@@ -1030,6 +1049,7 @@ export interface WardConfigObject {
1030
1049
  treatmentChartNameConceptUuid: string;
1031
1050
  treatmentChartTimeConceptUuid: string;
1032
1051
  partography: LabourCareGuidePartographyConfig;
1052
+ lcgSessionCorrectionPrivilege: string;
1033
1053
  }
1034
1054
  export interface PendingItemsElementConfig {
1035
1055
  id: string;
@@ -6,6 +6,7 @@ import {
6
6
  ExtensionSlot,
7
7
  type FHIRResource,
8
8
  useAssignedExtensions,
9
+ useConfig,
9
10
  usePatient,
10
11
  useRenderableExtensions,
11
12
  } from '@openmrs/esm-framework';
@@ -14,14 +15,71 @@ import { useTranslation } from 'react-i18next';
14
15
  import { ComponentContext, translateFrom } from '@openmrs/esm-framework/src/internal';
15
16
  import styles from './inpatient.scss';
16
17
  import { moduleName } from '../constant';
18
+ import useCurrentPatientAdmissionEncounter from '../hooks/useCurrentPatientAdmissionEncounter';
19
+ import { useAdmissionLocationTags } from './in-patient.resource';
20
+ import type { WardConfigObject } from '../config-schema';
17
21
 
18
22
  type InpatientTabsProps = {
19
23
  patientUuid: string;
20
24
  };
21
25
  const INPATIENT_TABS_EXTENSION_SLOT = 'inpatient-dashboard-tabs-slot';
26
+ const LABOUR_CARE_GUIDE_EXTENSION_NAME = 'maternal-and-child-health-labour-care-guide';
27
+
28
+ const getPatientGenderValue = (patient: any): string => {
29
+ const gender = patient?.gender;
30
+ if (typeof gender === 'string') {
31
+ return gender;
32
+ }
33
+
34
+ if (gender && typeof gender === 'object') {
35
+ return String(gender.display ?? gender.name ?? gender.value ?? '');
36
+ }
37
+
38
+ return '';
39
+ };
40
+
41
+ const isFemalePatient = (patient: any): boolean => {
42
+ const gender = getPatientGenderValue(patient).trim().toLowerCase();
43
+ return gender === 'f' || gender === 'female' || gender.startsWith('female ');
44
+ };
45
+
46
+ const getPatientAge = (patient: any): number => {
47
+ const ageFromPatient = Number(patient?.age);
48
+ if (Number.isFinite(ageFromPatient)) {
49
+ return ageFromPatient;
50
+ }
51
+
52
+ const birthDate = patient?.birthDate;
53
+ if (!birthDate) {
54
+ return NaN;
55
+ }
56
+
57
+ const birth = new Date(birthDate);
58
+ if (Number.isNaN(birth.getTime())) {
59
+ return NaN;
60
+ }
61
+
62
+ const now = new Date();
63
+ let age = now.getFullYear() - birth.getFullYear();
64
+ const hasNotHadBirthdayYet =
65
+ now.getMonth() < birth.getMonth() || (now.getMonth() === birth.getMonth() && now.getDate() < birth.getDate());
66
+ if (hasNotHadBirthdayYet) {
67
+ age -= 1;
68
+ }
69
+
70
+ return age;
71
+ };
72
+
22
73
  const InpatientTabs: FC<InpatientTabsProps> = ({ patientUuid }) => {
23
74
  const { t } = useTranslation();
24
75
  const { patient, isLoading, error } = usePatient(patientUuid);
76
+ const { inpatientLocationTags } = useConfig<WardConfigObject>();
77
+ const {
78
+ admissionEncounter,
79
+ isLoading: isLoadingAdmission,
80
+ isPatientAdmitted,
81
+ } = useCurrentPatientAdmissionEncounter(patientUuid);
82
+ const { tags, isLoading: isLoadingTags } = useAdmissionLocationTags(admissionEncounter?.location?.uuid);
25
83
  const state = useMemo(() => ({ patientUuid, patient }), [patientUuid, patient]);
26
84
  const assignedExtensions = useAssignedExtensions(INPATIENT_TABS_EXTENSION_SLOT);
27
85
  const extensions = useRenderableExtensions(INPATIENT_TABS_EXTENSION_SLOT);
@@ -32,7 +90,30 @@ const InpatientTabs: FC<InpatientTabsProps> = ({ patientUuid }) => {
32
90
  (a.order ?? Infinity) - (b.order ?? Infinity),
33
91
  );
34
92
  }, [assignedExtensions]);
35
- if (isLoading) {
93
+
94
+ const isAdmittedToAllowedWard = useMemo(() => {
95
+ const allowedWardTags = [
96
+ inpatientLocationTags?.labourWard,
97
+ inpatientLocationTags?.antenatalWard,
98
+ inpatientLocationTags?.maternityWard,
99
+ ].filter(Boolean);
100
+ return allowedWardTags.some((tagUuid) => tags.some((tag) => tag.uuid === tagUuid));
101
+ }, [inpatientLocationTags, tags]);
102
+
103
+ const canShowLabourCareGuideTab = useMemo(() => {
104
+ const age = getPatientAge(patient);
105
+ return isFemalePatient(patient) && Number.isFinite(age) && age > 9 && isPatientAdmitted && isAdmittedToAllowedWard;
106
+ }, [patient, isPatientAdmitted, isAdmittedToAllowedWard]);
107
+
108
+ const visibleExtensions = useMemo(
109
+ () =>
110
+ sortedExtensions.filter(
111
+ (extension) => extension.name !== LABOUR_CARE_GUIDE_EXTENSION_NAME || canShowLabourCareGuideTab,
112
+ ),
113
+ [sortedExtensions, canShowLabourCareGuideTab],
114
+ );
115
+
116
+ if (isLoading || isLoadingAdmission || isLoadingTags) {
36
117
  return <TabsSkeleton />;
37
118
  }
38
119
 
@@ -43,14 +124,14 @@ const InpatientTabs: FC<InpatientTabsProps> = ({ patientUuid }) => {
43
124
  <Layer>
44
125
  <Tabs>
45
126
  <TabList contained>
46
- {sortedExtensions.map((ext) => (
127
+ {visibleExtensions.map((ext) => (
47
128
  <Tab key={ext.name}>{translateFrom(ext.moduleName, ext.meta.title, ext.meta.title)}</Tab>
48
129
  ))}
49
130
  </TabList>
50
131
  <TabPanels
51
132
  data-extension-slot-name={INPATIENT_TABS_EXTENSION_SLOT}
52
133
  data-extension-slot-module-name={moduleName}>
53
- {sortedExtensions.map((ext) => (
134
+ {visibleExtensions.map((ext) => (
54
135
  <TabPanel key={ext.name}>
55
136
  <ComponentContext.Provider
56
137
  key={ext.id}
@@ -29,13 +29,15 @@ export const isSupportiveAlertValue = (rowKey: SupportiveAlertKey, rawValue: str
29
29
  return false;
30
30
  }
31
31
 
32
+ const isNoValue = value === 'N' || value === 'NO' || value.includes('1066');
33
+
32
34
  switch (rowKey) {
33
35
  case 'posture':
34
- return value === 'SP';
36
+ return value === 'SP' || value.includes('SUPINE');
35
37
  case 'companion':
36
38
  case 'painRelief':
37
39
  case 'oralFluid':
38
- return value === 'N';
40
+ return isNoValue;
39
41
  default:
40
42
  return false;
41
43
  }
@@ -46,22 +48,22 @@ export const getSupportiveAlertGuidance = (t: TFunction, rowKey: SupportiveAlert
46
48
  case 'companion':
47
49
  return t(
48
50
  'supportiveCompanionAlertGuidance',
49
- 'Companion N: Placeholder guidance. No companion documented; add supportive-care action.',
51
+ "Companion N: Immediately facilitate the woman's chosen companion to be present and oriented in the room. If unavailable, assign a dedicated midwife, nurse, or doula for continuous one-on-one support.",
50
52
  );
51
53
  case 'painRelief':
52
54
  return t(
53
55
  'supportivePainReliefAlertGuidance',
54
- 'Pain relief N: Placeholder guidance. Pain-relief action not documented; add action plan.',
56
+ 'Pain relief N: Assess pain using a standard scale, offer non-pharmacological options first (breathing, massage, warm compress, position changes), and give prescribed pharmacological analgesia if needed per protocol.',
55
57
  );
56
58
  case 'oralFluid':
57
59
  return t(
58
60
  'supportiveOralFluidAlertGuidance',
59
- 'Oral fluid N: Placeholder guidance. Fluid support not documented; add supportive-care action.',
61
+ 'Oral fluid N: Encourage oral hydration and energy fluids (water, clear juice, isotonic drinks) unless there is a clear documented contraindication such as imminent general anesthesia.',
60
62
  );
61
63
  case 'posture':
62
64
  return t(
63
65
  'supportivePostureAlertGuidance',
64
- "Posture, Supine, SP: Encourage the woman to walk around freely during first stage of labour. Support the woman's choice of position (left lateral, squatting, kneeling, standing supported by companion).",
66
+ 'Posture SP: Encourage walking or upright mobility; if in bed, support lateral, upright, or semi-recumbent positions. Avoid prolonged supine position to reduce aortocaval compression risk.',
65
67
  );
66
68
  default:
67
69
  return '';
@@ -94,32 +96,32 @@ export const getSupportiveAlertTokenGuidance = (t: TFunction, token: string) =>
94
96
  if (normalized.includes('COMPANION')) {
95
97
  return t(
96
98
  'supportiveCompanionTokenHoverGuidance',
97
- 'Companion N: Placeholder guidance. No companion documented; add supportive-care action.',
99
+ "Companion N: Immediately facilitate the woman's chosen companion to be present and oriented in the room. If unavailable, assign a dedicated midwife, nurse, or doula for continuous one-on-one support.",
98
100
  );
99
101
  }
100
102
 
101
103
  if (normalized.includes('PAIN RELIEF')) {
102
104
  return t(
103
105
  'supportivePainReliefTokenHoverGuidance',
104
- 'Pain relief N: Placeholder guidance. Pain-relief action not documented; add action plan.',
106
+ 'Pain relief N: Assess pain using a standard scale, offer non-pharmacological options first (breathing, massage, warm compress, position changes), and give prescribed pharmacological analgesia if needed per protocol.',
105
107
  );
106
108
  }
107
109
 
108
110
  if (normalized.includes('ORAL FLUID')) {
109
111
  return t(
110
112
  'supportiveOralFluidTokenHoverGuidance',
111
- 'Oral fluid N: Placeholder guidance. Fluid support not documented; add supportive-care action.',
113
+ 'Oral fluid N: Encourage oral hydration and energy fluids (water, clear juice, isotonic drinks) unless there is a clear documented contraindication such as imminent general anesthesia.',
112
114
  );
113
115
  }
114
116
 
115
117
  if (normalized.startsWith('SP')) {
116
118
  return t(
117
119
  'supportivePostureTokenHoverGuidance',
118
- "Posture, Supine, SP: Encourage the woman to walk around freely during first stage of labour. Support the woman's choice of position (left lateral, squatting, kneeling, standing supported by companion).",
120
+ 'Posture SP: Encourage walking or upright mobility; if in bed, support lateral, upright, or semi-recumbent positions. Avoid prolonged supine position to reduce aortocaval compression risk.',
119
121
  );
120
122
  }
121
123
 
122
- return t('supportiveTokenHoverPlaceholder', 'Placeholder: supportive-care guidance to be added.');
124
+ return t('supportiveTokenHoverPlaceholder', 'Supportive-care alert: correct the identified care gap immediately.');
123
125
  };
124
126
 
125
127
  export const getBabyAlertTokenGuidance = (t: TFunction, _token: string) => {
@@ -131,40 +133,49 @@ export const getBabyAlertTokenGuidance = (t: TFunction, _token: string) => {
131
133
  if (normalized.includes('BASELINE FHR')) {
132
134
  return t(
133
135
  'babyBaselineFhrTokenHoverGuidance',
134
- `Baseline FHR ${code}: Placeholder guidance. Value is <110 or >=160; review and act.`,
136
+ `Baseline FHR ${code}: Reposition mother to left lateral, give IV fluids if hypotensive, and reduce/stop oxytocin if hyperstimulation is present. Reassess FHR promptly; if persistent, start continuous fetal monitoring and prepare for expedited delivery if distress is confirmed.`,
135
137
  );
136
138
  }
137
139
 
138
140
  if (normalized.includes('FHR DECELERATION')) {
139
- return t('babyFhrDecelerationTokenHoverGuidance', 'FHR deceleration L: Placeholder guidance. Review and act.');
141
+ return t(
142
+ 'babyFhrDecelerationTokenHoverGuidance',
143
+ 'FHR deceleration: optimize maternal position and perfusion, reassess immediately, and escalate to continuous monitoring. Prepare expedited delivery if abnormal tracing persists.',
144
+ );
140
145
  }
141
146
 
142
147
  if (normalized.includes('AMNIOTIC FLUID')) {
143
148
  return t(
144
149
  'babyAmnioticFluidTokenHoverGuidance',
145
- `Amniotic fluid ${code}: Placeholder guidance. Alert values are M+++ or B; review and act.`,
150
+ `Amniotic fluid ${code}: For thick meconium, increase FHR surveillance or initiate continuous CTG. For foul-smelling liquor, start broad-spectrum IV antibiotics for suspected chorioamnionitis, monitor maternal temperature closely, and prepare neonatal resuscitation support.`,
146
151
  );
147
152
  }
148
153
 
149
154
  if (normalized.includes('FETAL POSITION')) {
150
155
  return t(
151
156
  'babyFetalPositionTokenHoverGuidance',
152
- `Fetal position ${code}: Placeholder guidance. Alert values are P or T; review and act.`,
157
+ `Fetal position ${code}: reassess position and descent trend, support corrective maternal positioning, and escalate obstetric review if persistent malposition is associated with poor progress.`,
153
158
  );
154
159
  }
155
160
 
156
161
  if (normalized.includes('CAPUT')) {
157
- return t('babyCaputTokenHoverGuidance', 'Caput +++: Placeholder guidance. Marked caput detected; review and act.');
162
+ return t(
163
+ 'babyCaputTokenHoverGuidance',
164
+ 'Caput +++: reassess for possible cephalopelvic disproportion or malposition, correlate with cervical progress/descent, and prepare operative delivery if labour is not progressing safely.',
165
+ );
158
166
  }
159
167
 
160
168
  if (normalized.includes('MOULDING')) {
161
169
  return t(
162
170
  'babyMouldingTokenHoverGuidance',
163
- 'Moulding +++: Placeholder guidance. Significant moulding detected; review and act.',
171
+ 'Moulding +++: reassess for possible cephalopelvic disproportion or malposition, correlate with cervical progress/descent, and prepare operative delivery if labour is not progressing safely.',
164
172
  );
165
173
  }
166
174
 
167
- return t('babyTokenHoverPlaceholder', 'Placeholder: baby-alert guidance to be added.');
175
+ return t(
176
+ 'babyTokenHoverPlaceholder',
177
+ 'Baby alert: optimize fetal condition and escalate monitoring/intervention promptly.',
178
+ );
168
179
  };
169
180
 
170
181
  export const isBabyAlertValue = (rowKey: BabyAlertKey, rawValue: string) => {
@@ -181,9 +192,16 @@ export const isBabyAlertValue = (rowKey: BabyAlertKey, rawValue: string) => {
181
192
  return Number.isFinite(numericValue) && (numericValue < 110 || numericValue >= 160);
182
193
  }
183
194
  case 'fhrDeceleration':
184
- return value === 'L';
195
+ return value === 'L' || value.includes('LATE');
185
196
  case 'amnioticFluid':
186
- return value === 'M+++' || value === 'B';
197
+ return (
198
+ value === 'B' ||
199
+ value === 'M' ||
200
+ value === 'M+' ||
201
+ value === 'M++' ||
202
+ value === 'M+++' ||
203
+ value.startsWith('M+')
204
+ );
187
205
  case 'fetalPosition':
188
206
  return value === 'P' || value === 'T';
189
207
  case 'caput':
@@ -199,29 +217,32 @@ export const getBabyAlertGuidance = (t: TFunction, rowKey: BabyAlertKey) => {
199
217
  case 'baselineFhr':
200
218
  return t(
201
219
  'babyBaselineFhrAlertGuidance',
202
- 'Baseline FHR: Placeholder guidance. Value is <110 or >=160; review and act.',
220
+ 'Baseline FHR alert: reposition left lateral, optimize perfusion, stop/reduce oxytocin if needed, reassess quickly, and escalate to continuous monitoring with expedited delivery preparation when persistent.',
203
221
  );
204
222
  case 'fhrDeceleration':
205
223
  return t(
206
224
  'babyFhrDecelerationAlertGuidance',
207
- 'FHR deceleration L: Placeholder guidance. Deceleration documented; review and act.',
225
+ 'FHR deceleration alert: optimize maternal condition and monitor continuously; prepare for expedited delivery if recurrent or persistent.',
208
226
  );
209
227
  case 'amnioticFluid':
210
228
  return t(
211
229
  'babyAmnioticFluidAlertGuidance',
212
- 'Amniotic fluid M+++ or B: Placeholder guidance. Abnormal fluid status; review and act.',
230
+ 'Amniotic fluid alert: increase fetal surveillance for meconium and start broad-spectrum IV antibiotics for suspected infection when foul odor is present.',
213
231
  );
214
232
  case 'fetalPosition':
215
233
  return t(
216
234
  'babyFetalPositionAlertGuidance',
217
- 'Fetal position P or T: Placeholder guidance. Position alert documented; review and act.',
235
+ 'Fetal position alert: reassess fetal position and descent, use corrective maternal positioning, and escalate review if persistent with poor progress.',
218
236
  );
219
237
  case 'caput':
220
- return t('babyCaputAlertGuidance', 'Caput +++: Placeholder guidance. Marked caput detected; review and act.');
238
+ return t(
239
+ 'babyCaputAlertGuidance',
240
+ 'Caput +++ alert: evaluate for cephalopelvic disproportion or malposition and prepare operative delivery if progress is halted.',
241
+ );
221
242
  case 'moulding':
222
243
  return t(
223
244
  'babyMouldingAlertGuidance',
224
- 'Moulding +++: Placeholder guidance. Significant moulding detected; review and act.',
245
+ 'Moulding +++ alert: evaluate for cephalopelvic disproportion or malposition and prepare operative delivery if progress is halted.',
225
246
  );
226
247
  default:
227
248
  return '';
@@ -286,26 +307,29 @@ export const isWomanAlertValue = (rowKey: WomanAlertKey, rawValue: string) => {
286
307
  export const getWomanAlertGuidance = (t: TFunction, rowKey: WomanAlertKey) => {
287
308
  switch (rowKey) {
288
309
  case 'pulse':
289
- return t('womanPulseAlertGuidance', 'Pulse alert: Placeholder guidance. Value is <60 or >=120; review and act.');
310
+ return t(
311
+ 'womanPulseAlertGuidance',
312
+ 'Pulse alert: assess for dehydration, pain, anxiety, fever, or bleeding; optimize hydration and pain control, then recheck promptly and manage hemorrhage urgently when suspected.',
313
+ );
290
314
  case 'systolicBp':
291
315
  return t(
292
316
  'womanSystolicAlertGuidance',
293
- 'Systolic BP alert: Placeholder guidance. Value is <80 or >=140; review and act.',
317
+ 'Systolic BP alert: for elevated BP, evaluate pre-eclampsia signs and urine protein; treat severe hypertension per protocol including antihypertensives and magnesium sulfate when indicated. For low BP, left lateral tilt, rapid IV fluids, and assess for bleeding.',
294
318
  );
295
319
  case 'diastolicBp':
296
320
  return t(
297
321
  'womanDiastolicAlertGuidance',
298
- 'Diastolic BP alert: Placeholder guidance. Value is >=90; review and act.',
322
+ 'Diastolic BP alert: evaluate and manage hypertensive disease urgently, including proteinuria screening and severe-feature assessment; escalate treatment per protocol.',
299
323
  );
300
324
  case 'temperature':
301
325
  return t(
302
326
  'womanTemperatureAlertGuidance',
303
- 'Temperature alert: Placeholder guidance. Value is <35 or >=37.5; review and act.',
327
+ 'Temperature alert: confirm hydration, institute cooling and infection assessment, check liquor odor and maternal pulse, start empirical IV antibiotics when intrapartum infection is suspected, and increase FHR monitoring frequency.',
304
328
  );
305
329
  case 'urine':
306
330
  return t(
307
331
  'womanUrineAlertGuidance',
308
- 'Urine alert: Placeholder guidance. Alert values are P++ or A++; review and act.',
332
+ 'Urine alert: correlate proteinuria with BP for pre-eclampsia assessment; treat ketonuria with oral intake support or IV dextrose where indicated.',
309
333
  );
310
334
  default:
311
335
  return '';
@@ -329,7 +353,7 @@ export const getLabourContractionAlertInfo = (
329
353
  guidance: isAlert
330
354
  ? t(
331
355
  'contractionsPer10AlertGuidance',
332
- 'Contractions per 10 min alert: Placeholder guidance. Value is <=2 or >5; review and act.',
356
+ 'Contractions per 10 min alert: if >5 (hyperstimulation), stop/reduce oxytocin, left-lateral position, and consider tocolysis when fetal distress is present. If <2 (inadequate), consider amniotomy or oxytocin augmentation after confirming fetal well-being.',
333
357
  )
334
358
  : '',
335
359
  };
@@ -341,7 +365,7 @@ export const getLabourContractionAlertInfo = (
341
365
  guidance: isAlert
342
366
  ? t(
343
367
  'contractionDurationAlertGuidance',
344
- 'Duration of contractions alert: Placeholder guidance. Value is <20 or >60; review and act.',
368
+ 'Contraction duration alert: reassess labour powers and fetal condition; if contractions are inadequate support augmentation, if excessive with distress reduce stimulation and stabilize mother/fetus.',
345
369
  )
346
370
  : '',
347
371
  };
@@ -367,23 +391,29 @@ export const getWomanAlertTokenGuidance = (t: TFunction, token: string): string
367
391
  .trim()
368
392
  .toUpperCase();
369
393
  if (normalized.includes('PULSE'))
370
- return t('womanPulseTokenGuidance', 'Pulse <60 or ≥120 bpm: Abnormal pulse rate detected; review and act.');
394
+ return t(
395
+ 'womanPulseTokenGuidance',
396
+ 'Pulse alert: assess cause (dehydration, pain, anxiety, fever, hemorrhage), optimize hydration and pain control, and recheck promptly.',
397
+ );
371
398
  if (normalized.includes('SYSTOLIC'))
372
399
  return t(
373
400
  'womanSystolicTokenGuidance',
374
- 'Systolic BP <80 or ≥140 mmHg: Abnormal systolic blood pressure; review and act.',
401
+ 'Systolic BP alert: for high values evaluate pre-eclampsia and severe features; for low values position left lateral, give IV fluids, and assess for bleeding.',
375
402
  );
376
403
  if (normalized.includes('DIASTOLIC'))
377
- return t('womanDiastolicTokenGuidance', 'Diastolic BP ≥90 mmHg: Elevated diastolic pressure; review and act.');
404
+ return t(
405
+ 'womanDiastolicTokenGuidance',
406
+ 'Diastolic BP alert: evaluate hypertensive disease urgently with proteinuria and danger-sign review; escalate treatment per protocol.',
407
+ );
378
408
  if (normalized.includes('TEMPERATURE'))
379
409
  return t(
380
410
  'womanTemperatureTokenGuidance',
381
- 'Temperature <35.0°C or ≥37.5°C: Abnormal maternal temperature; review and act.',
411
+ 'Temperature alert: evaluate for intrapartum infection, hydrate/cool, start empirical IV antibiotics when indicated, and increase fetal monitoring.',
382
412
  );
383
413
  if (normalized.includes('URINE'))
384
414
  return t(
385
415
  'womanUrineTokenGuidance',
386
- 'Urine P++ or A++: Significant proteinuria or ketonuria detected; review and act.',
416
+ 'Urine protein/ketone alert: correlate proteinuria with BP for pre-eclampsia, and manage ketonuria with nutrition support or IV dextrose as indicated.',
387
417
  );
388
418
  return t('womanAlertTokenGuidancePlaceholder', 'Woman vital-sign alert: review and act.');
389
419
  };
@@ -420,12 +450,12 @@ export const getContractionAlertTokenGuidance = (t: TFunction, token: string): s
420
450
  if (normalized.includes('CONTRACTIONS/10MIN'))
421
451
  return t(
422
452
  'contractionCountTokenGuidance',
423
- 'Contractions 2 or >5 per 10 min: Abnormal contraction frequency; review and act.',
453
+ 'Contractions <=2 or >5 per 10 min: for hyperstimulation stop/reduce oxytocin and stabilize; for inadequate contractions consider amniotomy/oxytocin after fetal safety check.',
424
454
  );
425
455
  if (normalized.includes('CONTRACTION DURATION'))
426
456
  return t(
427
457
  'contractionDurationTokenGuidance',
428
- 'Contraction duration <20s or >60s: Abnormal contraction duration; review and act.',
458
+ 'Contraction duration <20s or >60s: reassess powers, passenger, and passage; optimize uterine activity while maintaining fetal safety.',
429
459
  );
430
460
  return t('contractionAlertTokenGuidancePlaceholder', 'Contraction alert: review and act.');
431
461
  };
@@ -439,7 +469,91 @@ export const getCervixStallTokenGuidance = (t: TFunction, token: string): string
439
469
  if (normalized.includes('CM') && normalized.includes('NO PROGRESS'))
440
470
  return t(
441
471
  'cervixStallTokenGuidance',
442
- 'Cervical dilation stall: No progress at this dilation level for the expected duration; review and consider action.',
472
+ 'Cervical dilation stall: perform a full three-P assessment (powers, passenger, passage). If contractions are inadequate consider amniotomy/oxytocin; if adequate but no progress, evaluate for CPD and prepare for operative delivery.',
443
473
  );
444
474
  return t('cervixStallTokenGuidancePlaceholder', 'Labour progress alert: review and act.');
445
475
  };
476
+
477
+ // ─── Shared Decision Making assessment prefill ───────────────────────────────
478
+
479
+ export type SharedDecisionAlertItem = {
480
+ id?: string;
481
+ section: string;
482
+ code: string;
483
+ guidance: string;
484
+ timestampCreatedMs?: number;
485
+ };
486
+
487
+ export type SharedDecisionAssessmentLine = {
488
+ label: string;
489
+ comment: string;
490
+ };
491
+
492
+ export const formatAlertItemAsAssessmentLabel = (item: SharedDecisionAlertItem): string => {
493
+ const code = String(item.code ?? '').trim();
494
+ if (!code) {
495
+ return '';
496
+ }
497
+
498
+ const parenMatch = code.match(/^(.+?)\s+\((.+)\)$/);
499
+ if (parenMatch) {
500
+ return `${parenMatch[2].trim()} ${parenMatch[1].trim()}`;
501
+ }
502
+
503
+ const guidancePrefix = item.guidance.match(/^([^:\n]+):/);
504
+ if (guidancePrefix?.[1]) {
505
+ return guidancePrefix[1].trim();
506
+ }
507
+
508
+ return code;
509
+ };
510
+
511
+ export const buildAssessmentPrefillFromAlerts = (items: SharedDecisionAlertItem[]): string => {
512
+ const seen = new Set<string>();
513
+ const lines: string[] = [];
514
+
515
+ for (const item of items) {
516
+ if (!item?.code) {
517
+ continue;
518
+ }
519
+
520
+ const label = formatAlertItemAsAssessmentLabel(item);
521
+ const dedupeKey = label.toLowerCase();
522
+ if (!label || seen.has(dedupeKey)) {
523
+ continue;
524
+ }
525
+
526
+ seen.add(dedupeKey);
527
+ lines.push(`${label}: `);
528
+ }
529
+
530
+ return lines.join('\n');
531
+ };
532
+
533
+ export const parseAssessmentValue = (value: string): SharedDecisionAssessmentLine[] => {
534
+ if (!value.trim()) {
535
+ return [];
536
+ }
537
+
538
+ return value
539
+ .split('\n')
540
+ .map((line) => {
541
+ const colonIndex = line.indexOf(':');
542
+ if (colonIndex === -1) {
543
+ return { label: line.trim(), comment: '' };
544
+ }
545
+
546
+ return {
547
+ label: line.slice(0, colonIndex).trim(),
548
+ comment: line.slice(colonIndex + 1).replace(/^\s/, ''),
549
+ };
550
+ })
551
+ .filter((line) => line.label);
552
+ };
553
+
554
+ export const serializeAssessmentLines = (lines: SharedDecisionAssessmentLine[]): string => {
555
+ return lines
556
+ .map(({ label, comment }) => (label ? `${label}: ${comment}` : ''))
557
+ .filter(Boolean)
558
+ .join('\n');
559
+ };