formanitor 0.1.10 → 0.2.0

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 (290) hide show
  1. package/dist/AddInvestigationWidget-G7NPTSFY.cjs +32 -0
  2. package/dist/AddInvestigationWidget-G7NPTSFY.cjs.map +1 -0
  3. package/dist/AddInvestigationWidget-TQUKTGAW.mjs +30 -0
  4. package/dist/AddInvestigationWidget-TQUKTGAW.mjs.map +1 -0
  5. package/dist/AddMedicationWidget-26RHKIA6.cjs +32 -0
  6. package/dist/AddMedicationWidget-26RHKIA6.cjs.map +1 -0
  7. package/dist/AddMedicationWidget-I5UIT3WG.mjs +30 -0
  8. package/dist/AddMedicationWidget-I5UIT3WG.mjs.map +1 -0
  9. package/dist/AddProcedureWidget-B6OUF3BM.cjs +278 -0
  10. package/dist/AddProcedureWidget-B6OUF3BM.cjs.map +1 -0
  11. package/dist/AddProcedureWidget-NB46COB6.mjs +276 -0
  12. package/dist/AddProcedureWidget-NB46COB6.mjs.map +1 -0
  13. package/dist/BiometryIolWorkupWidget-F7HRYMOJ.mjs +298 -0
  14. package/dist/BiometryIolWorkupWidget-F7HRYMOJ.mjs.map +1 -0
  15. package/dist/BiometryIolWorkupWidget-TCU4MMVI.cjs +300 -0
  16. package/dist/BiometryIolWorkupWidget-TCU4MMVI.cjs.map +1 -0
  17. package/dist/DateTimeWidget-72YUK7PX.cjs +61 -0
  18. package/dist/DateTimeWidget-72YUK7PX.cjs.map +1 -0
  19. package/dist/DateTimeWidget-WKQIEQSA.mjs +59 -0
  20. package/dist/DateTimeWidget-WKQIEQSA.mjs.map +1 -0
  21. package/dist/DateWidget-K2TVTZ7Z.mjs +72 -0
  22. package/dist/DateWidget-K2TVTZ7Z.mjs.map +1 -0
  23. package/dist/DateWidget-SYDMSGWO.cjs +74 -0
  24. package/dist/DateWidget-SYDMSGWO.cjs.map +1 -0
  25. package/dist/DiagnosisTextareaWidget-RD65PGDY.mjs +184 -0
  26. package/dist/DiagnosisTextareaWidget-RD65PGDY.mjs.map +1 -0
  27. package/dist/DiagnosisTextareaWidget-UKWLE2SQ.cjs +190 -0
  28. package/dist/DiagnosisTextareaWidget-UKWLE2SQ.cjs.map +1 -0
  29. package/dist/DifferentialDiagnosisWidget-FENJP245.mjs +15 -0
  30. package/dist/DifferentialDiagnosisWidget-FENJP245.mjs.map +1 -0
  31. package/dist/DifferentialDiagnosisWidget-ZGFXP2BH.cjs +17 -0
  32. package/dist/DifferentialDiagnosisWidget-ZGFXP2BH.cjs.map +1 -0
  33. package/dist/DischargeMedicationOrdersWidget-26ALTGJM.mjs +161 -0
  34. package/dist/DischargeMedicationOrdersWidget-26ALTGJM.mjs.map +1 -0
  35. package/dist/DischargeMedicationOrdersWidget-B4CJQ3QS.cjs +163 -0
  36. package/dist/DischargeMedicationOrdersWidget-B4CJQ3QS.cjs.map +1 -0
  37. package/dist/EditableTable-LPAYCK36.mjs +279 -0
  38. package/dist/EditableTable-LPAYCK36.mjs.map +1 -0
  39. package/dist/EditableTable-YB4IC3ZK.cjs +281 -0
  40. package/dist/EditableTable-YB4IC3ZK.cjs.map +1 -0
  41. package/dist/FollowupWidget-EEKOI2TW.cjs +285 -0
  42. package/dist/FollowupWidget-EEKOI2TW.cjs.map +1 -0
  43. package/dist/FollowupWidget-NJ4QDBOG.mjs +283 -0
  44. package/dist/FollowupWidget-NJ4QDBOG.mjs.map +1 -0
  45. package/dist/FunctionalImpairmentScoreWidget-HAAPCMIS.cjs +93 -0
  46. package/dist/FunctionalImpairmentScoreWidget-HAAPCMIS.cjs.map +1 -0
  47. package/dist/FunctionalImpairmentScoreWidget-ZNLKKBYN.mjs +91 -0
  48. package/dist/FunctionalImpairmentScoreWidget-ZNLKKBYN.mjs.map +1 -0
  49. package/dist/GsExaminationWidget-EX6C3EF5.cjs +528 -0
  50. package/dist/GsExaminationWidget-EX6C3EF5.cjs.map +1 -0
  51. package/dist/GsExaminationWidget-MEIUHRQQ.mjs +526 -0
  52. package/dist/GsExaminationWidget-MEIUHRQQ.mjs.map +1 -0
  53. package/dist/GsGradingWidget-4UPOEYNL.mjs +90 -0
  54. package/dist/GsGradingWidget-4UPOEYNL.mjs.map +1 -0
  55. package/dist/GsGradingWidget-IV3UF63W.cjs +92 -0
  56. package/dist/GsGradingWidget-IV3UF63W.cjs.map +1 -0
  57. package/dist/GsSmartHistoryWidget-7QNJZEI4.cjs +398 -0
  58. package/dist/GsSmartHistoryWidget-7QNJZEI4.cjs.map +1 -0
  59. package/dist/GsSmartHistoryWidget-LGN7W2AV.mjs +396 -0
  60. package/dist/GsSmartHistoryWidget-LGN7W2AV.mjs.map +1 -0
  61. package/dist/HiddenVitalsWidget-AMH32HT2.cjs +55 -0
  62. package/dist/HiddenVitalsWidget-AMH32HT2.cjs.map +1 -0
  63. package/dist/HiddenVitalsWidget-W23IFSEF.mjs +53 -0
  64. package/dist/HiddenVitalsWidget-W23IFSEF.mjs.map +1 -0
  65. package/dist/LensAssessmentWidget-LSHBUTT5.cjs +234 -0
  66. package/dist/LensAssessmentWidget-LSHBUTT5.cjs.map +1 -0
  67. package/dist/LensAssessmentWidget-SJ6YUTSN.mjs +232 -0
  68. package/dist/LensAssessmentWidget-SJ6YUTSN.mjs.map +1 -0
  69. package/dist/MarMedicationOrdersWidget-IKMAFPRG.mjs +74 -0
  70. package/dist/MarMedicationOrdersWidget-IKMAFPRG.mjs.map +1 -0
  71. package/dist/MarMedicationOrdersWidget-ZKZLSZRN.cjs +76 -0
  72. package/dist/MarMedicationOrdersWidget-ZKZLSZRN.cjs.map +1 -0
  73. package/dist/OBGExaminationWidget-BK4ERETW.mjs +213 -0
  74. package/dist/OBGExaminationWidget-BK4ERETW.mjs.map +1 -0
  75. package/dist/OBGExaminationWidget-SMFZ2WCL.cjs +215 -0
  76. package/dist/OBGExaminationWidget-SMFZ2WCL.cjs.map +1 -0
  77. package/dist/OBGPathwayWidget-5YTUDBEH.cjs +519 -0
  78. package/dist/OBGPathwayWidget-5YTUDBEH.cjs.map +1 -0
  79. package/dist/OBGPathwayWidget-U5BCAESB.mjs +517 -0
  80. package/dist/OBGPathwayWidget-U5BCAESB.mjs.map +1 -0
  81. package/dist/ObstetricHistoryWidget-GC63CB3U.cjs +1115 -0
  82. package/dist/ObstetricHistoryWidget-GC63CB3U.cjs.map +1 -0
  83. package/dist/ObstetricHistoryWidget-WD572Q7H.mjs +1113 -0
  84. package/dist/ObstetricHistoryWidget-WD572Q7H.mjs.map +1 -0
  85. package/dist/OphthalDiagnosisWidget-QXFK446I.cjs +91 -0
  86. package/dist/OphthalDiagnosisWidget-QXFK446I.cjs.map +1 -0
  87. package/dist/OphthalDiagnosisWidget-Y3AHD6AG.mjs +89 -0
  88. package/dist/OphthalDiagnosisWidget-Y3AHD6AG.mjs.map +1 -0
  89. package/dist/OphthalmologyWidget-CRQMN5QU.mjs +1329 -0
  90. package/dist/OphthalmologyWidget-CRQMN5QU.mjs.map +1 -0
  91. package/dist/OphthalmologyWidget-U2HQMZ4J.cjs +1331 -0
  92. package/dist/OphthalmologyWidget-U2HQMZ4J.cjs.map +1 -0
  93. package/dist/OrthopedicExamWidget-LP7VN3W5.cjs +368 -0
  94. package/dist/OrthopedicExamWidget-LP7VN3W5.cjs.map +1 -0
  95. package/dist/OrthopedicExamWidget-PAOIVU6Z.mjs +366 -0
  96. package/dist/OrthopedicExamWidget-PAOIVU6Z.mjs.map +1 -0
  97. package/dist/PainScaleFlagsWidget-IBNGVVHD.cjs +153 -0
  98. package/dist/PainScaleFlagsWidget-IBNGVVHD.cjs.map +1 -0
  99. package/dist/PainScaleFlagsWidget-MRMQ7HPJ.mjs +151 -0
  100. package/dist/PainScaleFlagsWidget-MRMQ7HPJ.mjs.map +1 -0
  101. package/dist/ReadOnlyTable-4Y2XW4MX.mjs +6 -0
  102. package/dist/ReadOnlyTable-4Y2XW4MX.mjs.map +1 -0
  103. package/dist/ReadOnlyTable-L7G6GWEJ.cjs +15 -0
  104. package/dist/ReadOnlyTable-L7G6GWEJ.cjs.map +1 -0
  105. package/dist/ReferralWidget-IYTS32TS.mjs +169 -0
  106. package/dist/ReferralWidget-IYTS32TS.mjs.map +1 -0
  107. package/dist/ReferralWidget-PDNEKHFO.cjs +171 -0
  108. package/dist/ReferralWidget-PDNEKHFO.cjs.map +1 -0
  109. package/dist/RichTextWidget-5OOJ2LJN.mjs +11 -0
  110. package/dist/RichTextWidget-5OOJ2LJN.mjs.map +1 -0
  111. package/dist/RichTextWidget-XWLOHKLE.cjs +24 -0
  112. package/dist/RichTextWidget-XWLOHKLE.cjs.map +1 -0
  113. package/dist/SmartTextarea-L7Q4CORK.cjs +19 -0
  114. package/dist/SmartTextarea-L7Q4CORK.cjs.map +1 -0
  115. package/dist/SmartTextarea-ZCXGMZLG.mjs +10 -0
  116. package/dist/SmartTextarea-ZCXGMZLG.mjs.map +1 -0
  117. package/dist/SuggestionTextareaWidget-2TEITBPF.cjs +226 -0
  118. package/dist/SuggestionTextareaWidget-2TEITBPF.cjs.map +1 -0
  119. package/dist/SuggestionTextareaWidget-RN5LZYFC.mjs +215 -0
  120. package/dist/SuggestionTextareaWidget-RN5LZYFC.mjs.map +1 -0
  121. package/dist/SurgicalRiskFlagsWidget-DBICX4MB.mjs +144 -0
  122. package/dist/SurgicalRiskFlagsWidget-DBICX4MB.mjs.map +1 -0
  123. package/dist/SurgicalRiskFlagsWidget-JCSL3GWS.cjs +146 -0
  124. package/dist/SurgicalRiskFlagsWidget-JCSL3GWS.cjs.map +1 -0
  125. package/dist/UrologyExaminationWidget-74L3IN2X.cjs +273 -0
  126. package/dist/UrologyExaminationWidget-74L3IN2X.cjs.map +1 -0
  127. package/dist/UrologyExaminationWidget-IQEBP6CI.mjs +271 -0
  128. package/dist/UrologyExaminationWidget-IQEBP6CI.mjs.map +1 -0
  129. package/dist/UrologyPathwayWidget-S4QY6RD6.mjs +153 -0
  130. package/dist/UrologyPathwayWidget-S4QY6RD6.mjs.map +1 -0
  131. package/dist/UrologyPathwayWidget-UJY5EW2G.cjs +155 -0
  132. package/dist/UrologyPathwayWidget-UJY5EW2G.cjs.map +1 -0
  133. package/dist/UrologySmartHistoryWidget-NFTHHK6A.mjs +469 -0
  134. package/dist/UrologySmartHistoryWidget-NFTHHK6A.mjs.map +1 -0
  135. package/dist/UrologySmartHistoryWidget-XKZF63BV.cjs +471 -0
  136. package/dist/UrologySmartHistoryWidget-XKZF63BV.cjs.map +1 -0
  137. package/dist/VitalsWidget-44UWQGIF.cjs +208 -0
  138. package/dist/VitalsWidget-44UWQGIF.cjs.map +1 -0
  139. package/dist/VitalsWidget-MBYDGC7U.mjs +187 -0
  140. package/dist/VitalsWidget-MBYDGC7U.mjs.map +1 -0
  141. package/dist/chunk-4BAKQIPJ.mjs +27 -0
  142. package/dist/chunk-4BAKQIPJ.mjs.map +1 -0
  143. package/dist/chunk-4KSZUIJE.cjs +143 -0
  144. package/dist/chunk-4KSZUIJE.cjs.map +1 -0
  145. package/dist/chunk-4PMNVBJD.mjs +121 -0
  146. package/dist/chunk-4PMNVBJD.mjs.map +1 -0
  147. package/dist/chunk-5E3APMY6.cjs +54 -0
  148. package/dist/chunk-5E3APMY6.cjs.map +1 -0
  149. package/dist/chunk-AIZ3XYEZ.cjs +551 -0
  150. package/dist/chunk-AIZ3XYEZ.cjs.map +1 -0
  151. package/dist/chunk-AZHTIUOL.mjs +116 -0
  152. package/dist/chunk-AZHTIUOL.mjs.map +1 -0
  153. package/dist/chunk-AZSAPO2V.cjs +174 -0
  154. package/dist/chunk-AZSAPO2V.cjs.map +1 -0
  155. package/dist/chunk-AZVJU7NZ.mjs +137 -0
  156. package/dist/chunk-AZVJU7NZ.mjs.map +1 -0
  157. package/dist/chunk-B3GBIAHZ.mjs +285 -0
  158. package/dist/chunk-B3GBIAHZ.mjs.map +1 -0
  159. package/dist/chunk-BMNY7F6H.mjs +148 -0
  160. package/dist/chunk-BMNY7F6H.mjs.map +1 -0
  161. package/dist/chunk-BU4ZEAAP.mjs +102 -0
  162. package/dist/chunk-BU4ZEAAP.mjs.map +1 -0
  163. package/dist/chunk-CTW65YW2.cjs +388 -0
  164. package/dist/chunk-CTW65YW2.cjs.map +1 -0
  165. package/dist/chunk-DE7VZVKR.cjs +140 -0
  166. package/dist/chunk-DE7VZVKR.cjs.map +1 -0
  167. package/dist/chunk-DZYV47QC.mjs +54 -0
  168. package/dist/chunk-DZYV47QC.mjs.map +1 -0
  169. package/dist/chunk-E7WUDHSA.cjs +127 -0
  170. package/dist/chunk-E7WUDHSA.cjs.map +1 -0
  171. package/dist/chunk-FHHISVCH.cjs +13 -0
  172. package/dist/chunk-FHHISVCH.cjs.map +1 -0
  173. package/dist/chunk-FZEEWRW3.cjs +77 -0
  174. package/dist/chunk-FZEEWRW3.cjs.map +1 -0
  175. package/dist/chunk-GFDE2KZC.mjs +228 -0
  176. package/dist/chunk-GFDE2KZC.mjs.map +1 -0
  177. package/dist/chunk-GNZWSVTB.mjs +29 -0
  178. package/dist/chunk-GNZWSVTB.mjs.map +1 -0
  179. package/dist/chunk-GXXWMHAA.mjs +49 -0
  180. package/dist/chunk-GXXWMHAA.mjs.map +1 -0
  181. package/dist/chunk-GZPCYSJK.mjs +104 -0
  182. package/dist/chunk-GZPCYSJK.mjs.map +1 -0
  183. package/dist/chunk-HEFZA2AH.mjs +22 -0
  184. package/dist/chunk-HEFZA2AH.mjs.map +1 -0
  185. package/dist/chunk-I4AV6BQF.mjs +10 -0
  186. package/dist/chunk-I4AV6BQF.mjs.map +1 -0
  187. package/dist/chunk-IDSBJOFY.cjs +1885 -0
  188. package/dist/chunk-IDSBJOFY.cjs.map +1 -0
  189. package/dist/chunk-IFMQKLNW.mjs +39 -0
  190. package/dist/chunk-IFMQKLNW.mjs.map +1 -0
  191. package/dist/chunk-IFSKLIS2.cjs +12 -0
  192. package/dist/chunk-IFSKLIS2.cjs.map +1 -0
  193. package/dist/chunk-IKAKE7TR.mjs +527 -0
  194. package/dist/chunk-IKAKE7TR.mjs.map +1 -0
  195. package/dist/chunk-JE6VXRWW.cjs +86 -0
  196. package/dist/chunk-JE6VXRWW.cjs.map +1 -0
  197. package/dist/chunk-JMGUUFOJ.cjs +45 -0
  198. package/dist/chunk-JMGUUFOJ.cjs.map +1 -0
  199. package/dist/chunk-JNVSA77L.cjs +236 -0
  200. package/dist/chunk-JNVSA77L.cjs.map +1 -0
  201. package/dist/chunk-KJA5BLGX.cjs +31 -0
  202. package/dist/chunk-KJA5BLGX.cjs.map +1 -0
  203. package/dist/chunk-KMJOTMUQ.cjs +109 -0
  204. package/dist/chunk-KMJOTMUQ.cjs.map +1 -0
  205. package/dist/chunk-KPGTPJDH.cjs +32 -0
  206. package/dist/chunk-KPGTPJDH.cjs.map +1 -0
  207. package/dist/chunk-LBWB7MYU.cjs +287 -0
  208. package/dist/chunk-LBWB7MYU.cjs.map +1 -0
  209. package/dist/chunk-LGGOBIEA.mjs +61 -0
  210. package/dist/chunk-LGGOBIEA.mjs.map +1 -0
  211. package/dist/chunk-LQYWMYEC.mjs +28 -0
  212. package/dist/chunk-LQYWMYEC.mjs.map +1 -0
  213. package/dist/chunk-MIUUO4CI.mjs +361 -0
  214. package/dist/chunk-MIUUO4CI.mjs.map +1 -0
  215. package/dist/chunk-N4L76SHJ.cjs +65 -0
  216. package/dist/chunk-N4L76SHJ.cjs.map +1 -0
  217. package/dist/chunk-NJJTIL3U.cjs +73 -0
  218. package/dist/chunk-NJJTIL3U.cjs.map +1 -0
  219. package/dist/chunk-O3VQBPEW.cjs +72 -0
  220. package/dist/chunk-O3VQBPEW.cjs.map +1 -0
  221. package/dist/chunk-P62377LB.cjs +27 -0
  222. package/dist/chunk-P62377LB.cjs.map +1 -0
  223. package/dist/chunk-PHADAQSH.cjs +49 -0
  224. package/dist/chunk-PHADAQSH.cjs.map +1 -0
  225. package/dist/chunk-PZBJYPWW.cjs +52 -0
  226. package/dist/chunk-PZBJYPWW.cjs.map +1 -0
  227. package/dist/chunk-QKJ7BQSR.cjs +538 -0
  228. package/dist/chunk-QKJ7BQSR.cjs.map +1 -0
  229. package/dist/chunk-QS6WG2XJ.cjs +56 -0
  230. package/dist/chunk-QS6WG2XJ.cjs.map +1 -0
  231. package/dist/chunk-S7EMPE2D.cjs +104 -0
  232. package/dist/chunk-S7EMPE2D.cjs.map +1 -0
  233. package/dist/chunk-SCMXLRX5.mjs +42 -0
  234. package/dist/chunk-SCMXLRX5.mjs.map +1 -0
  235. package/dist/chunk-TJHG5WHX.cjs +47 -0
  236. package/dist/chunk-TJHG5WHX.cjs.map +1 -0
  237. package/dist/chunk-UGGUIXUS.cjs +41 -0
  238. package/dist/chunk-UGGUIXUS.cjs.map +1 -0
  239. package/dist/chunk-UULLSS4O.mjs +82 -0
  240. package/dist/chunk-UULLSS4O.mjs.map +1 -0
  241. package/dist/chunk-VNFBUAZD.mjs +25 -0
  242. package/dist/chunk-VNFBUAZD.mjs.map +1 -0
  243. package/dist/chunk-VPU66PI6.mjs +11 -0
  244. package/dist/chunk-VPU66PI6.mjs.map +1 -0
  245. package/dist/chunk-WYVXXEZI.mjs +46 -0
  246. package/dist/chunk-WYVXXEZI.mjs.map +1 -0
  247. package/dist/chunk-XDUVO3IZ.mjs +547 -0
  248. package/dist/chunk-XDUVO3IZ.mjs.map +1 -0
  249. package/dist/chunk-XR2ZM654.mjs +30 -0
  250. package/dist/chunk-XR2ZM654.mjs.map +1 -0
  251. package/dist/chunk-Y7TIWH4I.mjs +25 -0
  252. package/dist/chunk-Y7TIWH4I.mjs.map +1 -0
  253. package/dist/chunk-YEFM2ERQ.cjs +143 -0
  254. package/dist/chunk-YEFM2ERQ.cjs.map +1 -0
  255. package/dist/chunk-YYPMIUDW.mjs +69 -0
  256. package/dist/chunk-YYPMIUDW.mjs.map +1 -0
  257. package/dist/chunk-ZGPHTPAN.mjs +1851 -0
  258. package/dist/chunk-ZGPHTPAN.mjs.map +1 -0
  259. package/dist/chunk-ZSOIQVB3.mjs +75 -0
  260. package/dist/chunk-ZSOIQVB3.mjs.map +1 -0
  261. package/dist/index.cjs +1596 -14537
  262. package/dist/index.cjs.map +1 -1
  263. package/dist/index.d.cts +4 -767
  264. package/dist/index.d.ts +4 -767
  265. package/dist/index.mjs +1358 -14346
  266. package/dist/index.mjs.map +1 -1
  267. package/dist/read-only-table.cjs +15 -0
  268. package/dist/read-only-table.cjs.map +1 -0
  269. package/dist/read-only-table.d.cts +10 -0
  270. package/dist/read-only-table.d.ts +10 -0
  271. package/dist/read-only-table.mjs +6 -0
  272. package/dist/read-only-table.mjs.map +1 -0
  273. package/dist/rich-text.cjs +24 -0
  274. package/dist/rich-text.cjs.map +1 -0
  275. package/dist/rich-text.d.cts +8 -0
  276. package/dist/rich-text.d.ts +8 -0
  277. package/dist/rich-text.mjs +11 -0
  278. package/dist/rich-text.mjs.map +1 -0
  279. package/dist/richTextClassName--8wkuHkW.d.cts +10 -0
  280. package/dist/richTextClassName--8wkuHkW.d.ts +10 -0
  281. package/dist/smart.cjs +23 -0
  282. package/dist/smart.cjs.map +1 -0
  283. package/dist/smart.d.cts +23 -0
  284. package/dist/smart.d.ts +23 -0
  285. package/dist/smart.mjs +10 -0
  286. package/dist/smart.mjs.map +1 -0
  287. package/dist/types-D0iYXUOo.d.cts +736 -0
  288. package/dist/types-D0iYXUOo.d.ts +736 -0
  289. package/package.json +20 -2
  290. package/src/styles/index.css +35 -2
@@ -0,0 +1,1851 @@
1
+ import { createContext, useContext, useRef, useEffect, useState, useCallback } from 'react';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ // src/core/validate.ts
5
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6
+ function validateField(value, field, allValues) {
7
+ if (field.hidden || field.disabled) return null;
8
+ if (field.type === "otp_input") {
9
+ if (field.required) {
10
+ if (!value || typeof value !== "object" || value.verified !== true) {
11
+ return "Please verify your mobile number with OTP";
12
+ }
13
+ const otpField = field;
14
+ const phoneFieldId = otpField.meta?.phoneFieldId ?? "phone";
15
+ if (allValues) {
16
+ const phoneRaw = allValues[phoneFieldId] ?? "";
17
+ const e164 = phoneRaw.startsWith("+") ? phoneRaw : `+91${phoneRaw}`;
18
+ if (value.phone && value.phone !== e164) {
19
+ return "Mobile number changed \u2014 please re-verify";
20
+ }
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+ if (field.required) {
26
+ if (value === null || value === void 0 || value === "") {
27
+ return "This field is required";
28
+ }
29
+ if (Array.isArray(value) && value.length === 0) {
30
+ return "This field is required";
31
+ }
32
+ if (field.type === "smart_textarea" && typeof value === "object" && !Array.isArray(value)) {
33
+ if (!value.text || value.text.trim() === "") {
34
+ return "This field is required";
35
+ }
36
+ }
37
+ }
38
+ if (value === null || value === void 0 || value === "") {
39
+ return null;
40
+ }
41
+ const rules = field.validation;
42
+ if (!rules) return null;
43
+ if (field.type === "number") {
44
+ const num = Number(value);
45
+ if (isNaN(num)) return "Must be a valid number";
46
+ if (rules.min !== void 0 && num < rules.min) {
47
+ return `Must be at least ${rules.min}`;
48
+ }
49
+ if (rules.max !== void 0 && num > rules.max) {
50
+ return `Must be at most ${rules.max}`;
51
+ }
52
+ }
53
+ if (field.type === "slider") {
54
+ const num = Number(value);
55
+ if (isNaN(num)) return "Must be a valid number";
56
+ const s = field;
57
+ const min = s.min ?? 0;
58
+ const max = s.max ?? 10;
59
+ if (num < min) return `Must be at least ${min}`;
60
+ if (num > max) return `Must be at most ${max}`;
61
+ }
62
+ if (typeof value === "string") {
63
+ if (rules.minLength !== void 0 && value.length < rules.minLength) {
64
+ return `Must be at least ${rules.minLength} characters`;
65
+ }
66
+ if (rules.maxLength !== void 0 && value.length > rules.maxLength) {
67
+ return `Must be at most ${rules.maxLength} characters`;
68
+ }
69
+ if (rules.pattern) {
70
+ const regex = new RegExp(rules.pattern);
71
+ if (!regex.test(value)) {
72
+ return "Invalid format";
73
+ }
74
+ }
75
+ }
76
+ if (rules.custom) {
77
+ return rules.custom(value);
78
+ }
79
+ return null;
80
+ }
81
+ function validateForm(values, fields) {
82
+ const errors = {};
83
+ for (const [key, field] of Object.entries(fields)) {
84
+ if (field.type === "static_text") continue;
85
+ const error = validateField(values[key], field, values);
86
+ if (error) {
87
+ errors[key] = error;
88
+ }
89
+ }
90
+ return errors;
91
+ }
92
+
93
+ // src/core/suggestionTokens.ts
94
+ function getSuggestionTokensFromText(text) {
95
+ return text.split(/[,\n\r]+/).map((p) => p.trim()).filter(Boolean);
96
+ }
97
+ var DEFAULT_TOKEN_SEPARATOR = ", ";
98
+ function commaSplitLine(line) {
99
+ return line.split(",").map((p) => p.trim()).filter(Boolean);
100
+ }
101
+ function dedupeChipOrder(chips) {
102
+ const seen = /* @__PURE__ */ new Set();
103
+ const out = [];
104
+ for (const c of chips) {
105
+ if (seen.has(c)) continue;
106
+ seen.add(c);
107
+ out.push(c);
108
+ }
109
+ return out;
110
+ }
111
+ function parseProseAndChipTokens(text, allowed) {
112
+ const normalized = text.replace(/\r\n/g, "\n");
113
+ let lines = normalized.split("\n");
114
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
115
+ lines.pop();
116
+ }
117
+ if (lines.length === 0) return { prose: "", chips: [] };
118
+ let chipStart = lines.length;
119
+ for (let i = lines.length - 1; i >= 0; i--) {
120
+ const toks = commaSplitLine(lines[i]);
121
+ if (toks.length === 0) break;
122
+ if (!toks.every((t) => allowed.has(t))) break;
123
+ chipStart = i;
124
+ }
125
+ if (chipStart < lines.length) {
126
+ const chipLines = lines.slice(chipStart);
127
+ const prose = lines.slice(0, chipStart).join("\n");
128
+ const chips = dedupeChipOrder(chipLines.flatMap(commaSplitLine));
129
+ return { prose, chips };
130
+ }
131
+ const parts = getSuggestionTokensFromText(normalized);
132
+ const chipsFallback = dedupeChipOrder(parts.filter((t) => allowed.has(t)));
133
+ const proseParts = parts.filter((t) => !allowed.has(t));
134
+ return { prose: proseParts.join(DEFAULT_TOKEN_SEPARATOR), chips: chipsFallback };
135
+ }
136
+ function composeProseAndChips(prose, chips, separator = DEFAULT_TOKEN_SEPARATOR) {
137
+ const chipStr = dedupeChipOrder(chips).join(separator);
138
+ const p = prose.replace(/\s+$/, "");
139
+ if (p && chipStr) return `${p}
140
+ ${chipStr}`;
141
+ if (chipStr) return chipStr;
142
+ return p;
143
+ }
144
+ function toggleSuggestionTokenInText(current, token, separator = DEFAULT_TOKEN_SEPARATOR, allowedChipValues) {
145
+ const t = String(token).trim();
146
+ if (!t) return current;
147
+ if (!allowedChipValues || allowedChipValues.size === 0) {
148
+ const parts = getSuggestionTokensFromText(current);
149
+ const i = parts.findIndex((p) => p === t);
150
+ if (i >= 0) parts.splice(i, 1);
151
+ else parts.push(t);
152
+ return parts.join(separator);
153
+ }
154
+ if (!allowedChipValues.has(t)) return current;
155
+ let { prose, chips } = parseProseAndChipTokens(current, allowedChipValues);
156
+ const idx = chips.indexOf(t);
157
+ if (idx >= 0) chips.splice(idx, 1);
158
+ else chips.push(t);
159
+ chips = dedupeChipOrder(chips);
160
+ return composeProseAndChips(prose, chips, separator);
161
+ }
162
+
163
+ // src/core/rules.ts
164
+ function tokenListContainsString(text, needle) {
165
+ if (typeof text !== "string") return false;
166
+ const want = String(needle).trim();
167
+ if (!want) return false;
168
+ const parts = getSuggestionTokensFromText(text);
169
+ return parts.includes(want);
170
+ }
171
+ function evaluateCondition(value, condition) {
172
+ switch (condition.op) {
173
+ case "==":
174
+ return value == condition.value;
175
+ // Loose equality for flexibility
176
+ case "!=":
177
+ return value != condition.value;
178
+ case ">":
179
+ return Number(value) > Number(condition.value);
180
+ case "<":
181
+ return Number(value) < Number(condition.value);
182
+ case ">=":
183
+ return Number(value) >= Number(condition.value);
184
+ case "<=":
185
+ return Number(value) <= Number(condition.value);
186
+ case "in":
187
+ return Array.isArray(condition.value) && condition.value.includes(value);
188
+ case "contains":
189
+ if (Array.isArray(value)) {
190
+ return value.includes(condition.value);
191
+ }
192
+ if (typeof value === "string") {
193
+ return tokenListContainsString(value, condition.value);
194
+ }
195
+ return false;
196
+ default:
197
+ return false;
198
+ }
199
+ }
200
+ function evaluateRules(values, rules, fields) {
201
+ const result = {
202
+ visibility: {},
203
+ disabled: {},
204
+ required: {}
205
+ };
206
+ Object.keys(fields).forEach((key) => {
207
+ result.visibility[key] = !fields[key].hidden;
208
+ result.disabled[key] = !!fields[key].disabled;
209
+ result.required[key] = !!fields[key].required;
210
+ });
211
+ if (!rules) return result;
212
+ rules.forEach((rule) => {
213
+ const targetField = rule.if.field;
214
+ if (!targetField) return;
215
+ const sourceValue = values[targetField];
216
+ const isMatch = evaluateCondition(sourceValue, rule.if);
217
+ if (isMatch) {
218
+ rule.then.forEach((action) => {
219
+ applyAction(result, action);
220
+ });
221
+ }
222
+ });
223
+ return result;
224
+ }
225
+ function applyAction(result, action) {
226
+ switch (action.action) {
227
+ case "hide":
228
+ result.visibility[action.target] = false;
229
+ break;
230
+ case "show":
231
+ result.visibility[action.target] = true;
232
+ break;
233
+ case "disable":
234
+ result.disabled[action.target] = true;
235
+ break;
236
+ case "enable":
237
+ result.disabled[action.target] = false;
238
+ break;
239
+ case "setRequired":
240
+ result.required[action.target] = true;
241
+ break;
242
+ }
243
+ }
244
+
245
+ // src/core/generalSurgeryPathway.ts
246
+ var defaultGeneralSurgerySmartHistory = () => ({
247
+ pain: {
248
+ site: "",
249
+ onset: "",
250
+ character: "",
251
+ radiation: "",
252
+ associated: "",
253
+ timing: "",
254
+ exacerbating: "",
255
+ severity: 0
256
+ },
257
+ swelling: { duration: "", sizeProgression: "", painfulPainless: "", checks: [] },
258
+ gi: { appetite: "", weightLoss: "", bowelHabits: "", checks: [] },
259
+ breast: { lumpDuration: "", nippleDischarge: "", checks: [] },
260
+ ano: { checks: [] },
261
+ thyroid: { swellingDuration: "", checks: [] }
262
+ });
263
+ var defaultBreastExamSide = () => ({
264
+ size: "",
265
+ quadrant: "",
266
+ tenderness: "",
267
+ fixity: "",
268
+ skinInvolvement: "",
269
+ consistency: "",
270
+ axillaryNodes: false,
271
+ nippleAreolarComplex: false
272
+ });
273
+ var defaultBreastExaminationBlock = () => ({
274
+ left: defaultBreastExamSide(),
275
+ right: defaultBreastExamSide()
276
+ });
277
+ var defaultGeneralSurgeryExamination = () => ({
278
+ general: [],
279
+ regions: [],
280
+ abdomen: {
281
+ inspection: [],
282
+ palpation: [],
283
+ percussion: [],
284
+ massNotes: "",
285
+ bowelSounds: "none"
286
+ },
287
+ hernia: { site: "", reducible: false, coughImpulse: false, tenderness: false },
288
+ breast: defaultBreastExaminationBlock(),
289
+ thyroid: { size: "", mobileDeglutition: false, nodules: false, cervicalNodes: false },
290
+ anorectal: { inspection: "", dreDone: false, proctoscopyDone: false, notes: "" },
291
+ limb: { dilatedVeins: false, ulcer: false, oedema: false }
292
+ });
293
+ var ALL_GS_CONDITIONS = [
294
+ "hernia",
295
+ "appendicitis",
296
+ "gallbladder",
297
+ "breast",
298
+ "thyroid",
299
+ "piles",
300
+ "varicose",
301
+ "ulcer"
302
+ ];
303
+ var defaultGeneralSurgeryGrading = () => ({
304
+ hernia: 0,
305
+ appendicitis: 0,
306
+ gallbladder: 0,
307
+ breast: 0,
308
+ thyroid: 0,
309
+ piles: 0,
310
+ varicose: 0,
311
+ ulcer: 0
312
+ });
313
+ var GS_CONDITION_TRIGGERS = {
314
+ hernia: { regions: ["hernia", "abdomen"], chips: ["swelling_lump", "obstruction"] },
315
+ appendicitis: { regions: ["abdomen"], chips: ["pain", "obstruction"] },
316
+ gallbladder: { regions: ["abdomen"], chips: ["pain"] },
317
+ breast: { regions: ["breast"], chips: ["breast", "swelling_lump"] },
318
+ thyroid: { regions: ["thyroid"], chips: ["thyroid", "swelling_lump"] },
319
+ piles: { regions: ["anorectal"], chips: ["anorectal", "bleeding"] },
320
+ varicose: { regions: ["limb"], chips: ["swelling_lump"] },
321
+ ulcer: { regions: ["limb", "anorectal"], chips: ["ulcer_wound", "discharge"] }
322
+ };
323
+ var GS_CONDITION_LABELS = {
324
+ hernia: "Hernia",
325
+ appendicitis: "Appendicitis",
326
+ gallbladder: "Gallbladder disease",
327
+ breast: "Breast lump (BIRADS)",
328
+ thyroid: "Thyroid nodule (TIRADS)",
329
+ piles: "Haemorrhoids",
330
+ varicose: "Varicose veins (CEAP)",
331
+ ulcer: "Ulcer / wound"
332
+ };
333
+ var GS_GRADE_DESCRIPTIONS = {
334
+ hernia: ["\u2014", "Small reducible", "Large reducible", "Irreducible", "Obstructed", "Strangulated"],
335
+ appendicitis: ["\u2014", "Mild (clinical)", "Confirmed (USG/CT)", "Complicated (mass)", "Abscess", "Perforation"],
336
+ gallbladder: ["\u2014", "Asymptomatic stones", "Symptomatic", "Acute cholecystitis", "Complicated (CBD stone)", "Perforation/empyema"],
337
+ breast: ["\u2014", "BIRADS 1 \u2014 Negative", "BIRADS 2 \u2014 Benign", "BIRADS 3 \u2014 Probably benign", "BIRADS 4 \u2014 Suspicious", "BIRADS 5 \u2014 Malignant"],
338
+ thyroid: ["\u2014", "TIRADS 1 \u2014 Normal", "TIRADS 2 \u2014 Benign", "TIRADS 3 \u2014 Indeterminate", "TIRADS 4 \u2014 Suspicious", "TIRADS 5 \u2014 Highly suspicious"],
339
+ piles: ["\u2014", "Grade 1", "Grade 2", "Grade 3", "Grade 4", "Grade 5"],
340
+ varicose: ["\u2014", "C1 telangiectasia", "C2 varicose", "C3 oedema", "C4 skin changes", "C5-C6 ulcer"],
341
+ ulcer: ["\u2014", "Superficial", "Deep", "Infected", "Necrotic", "Gangrene"]
342
+ };
343
+ function normalizeGeneralSurgerySmartHistory(raw) {
344
+ const d = defaultGeneralSurgerySmartHistory();
345
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
346
+ const v = raw;
347
+ const painIn = v.pain && typeof v.pain === "object" ? v.pain : {};
348
+ const sev = Number(painIn.severity);
349
+ const pain = {
350
+ ...d.pain,
351
+ ...painIn,
352
+ severity: Number.isFinite(sev) ? Math.min(10, Math.max(0, Math.round(sev))) : d.pain.severity
353
+ };
354
+ const str2 = (x) => typeof x === "string" ? x : "";
355
+ const strArr = (x) => Array.isArray(x) ? x.filter((i) => typeof i === "string") : [];
356
+ const swell = v.swelling && typeof v.swelling === "object" ? v.swelling : {};
357
+ const gi = v.gi && typeof v.gi === "object" ? v.gi : {};
358
+ const breast = v.breast && typeof v.breast === "object" ? v.breast : {};
359
+ const ano = v.ano && typeof v.ano === "object" ? v.ano : {};
360
+ const thy = v.thyroid && typeof v.thyroid === "object" ? v.thyroid : {};
361
+ return {
362
+ pain,
363
+ swelling: {
364
+ duration: str2(swell.duration),
365
+ sizeProgression: str2(swell.sizeProgression),
366
+ painfulPainless: str2(swell.painfulPainless),
367
+ checks: strArr(swell.checks)
368
+ },
369
+ gi: {
370
+ appetite: str2(gi.appetite),
371
+ weightLoss: str2(gi.weightLoss),
372
+ bowelHabits: str2(gi.bowelHabits),
373
+ checks: strArr(gi.checks)
374
+ },
375
+ breast: {
376
+ lumpDuration: str2(breast.lumpDuration),
377
+ nippleDischarge: str2(breast.nippleDischarge),
378
+ checks: strArr(breast.checks)
379
+ },
380
+ ano: { checks: strArr(ano.checks) },
381
+ thyroid: {
382
+ swellingDuration: str2(thy.swellingDuration),
383
+ checks: strArr(thy.checks)
384
+ }
385
+ };
386
+ }
387
+ function normalizeBreastExamSide(partial) {
388
+ const e = defaultBreastExamSide();
389
+ const str2 = (x) => typeof x === "string" ? x : "";
390
+ const bool2 = (x) => x === true;
391
+ if (!partial || typeof partial !== "object") return e;
392
+ const p = partial;
393
+ return {
394
+ size: str2(p.size),
395
+ quadrant: str2(p.quadrant),
396
+ tenderness: str2(p.tenderness),
397
+ fixity: str2(p.fixity),
398
+ skinInvolvement: str2(p.skinInvolvement),
399
+ consistency: str2(p.consistency),
400
+ axillaryNodes: bool2(p.axillaryNodes),
401
+ nippleAreolarComplex: bool2(p.nippleAreolarComplex)
402
+ };
403
+ }
404
+ function normalizeBreastExaminationBlock(raw) {
405
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return defaultBreastExaminationBlock();
406
+ const br = raw;
407
+ return {
408
+ left: normalizeBreastExamSide(br.left),
409
+ right: normalizeBreastExamSide(br.right)
410
+ };
411
+ }
412
+ function normalizeGeneralSurgeryExamination(raw) {
413
+ const d = defaultGeneralSurgeryExamination();
414
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
415
+ const v = raw;
416
+ const strArr = (x) => Array.isArray(x) ? x.filter((i) => typeof i === "string") : [];
417
+ const ab = v.abdomen && typeof v.abdomen === "object" ? v.abdomen : {};
418
+ const bowel = ab.bowelSounds;
419
+ const bowels = bowel === "normal" || bowel === "up" || bowel === "down" || bowel === "absent" ? bowel : "none";
420
+ const hb = v.hernia && typeof v.hernia === "object" ? v.hernia : {};
421
+ const br = v.breast && typeof v.breast === "object" ? v.breast : {};
422
+ const th = v.thyroid && typeof v.thyroid === "object" ? v.thyroid : {};
423
+ const ar = v.anorectal && typeof v.anorectal === "object" ? v.anorectal : {};
424
+ const lm = v.limb && typeof v.limb === "object" ? v.limb : {};
425
+ const bool2 = (x) => x === true;
426
+ return {
427
+ general: strArr(v.general),
428
+ regions: strArr(v.regions),
429
+ abdomen: {
430
+ inspection: strArr(ab.inspection),
431
+ palpation: strArr(ab.palpation),
432
+ percussion: strArr(ab.percussion),
433
+ massNotes: typeof ab.massNotes === "string" ? ab.massNotes : d.abdomen.massNotes,
434
+ bowelSounds: bowels
435
+ },
436
+ hernia: {
437
+ site: typeof hb.site === "string" ? hb.site : "",
438
+ reducible: bool2(hb.reducible),
439
+ coughImpulse: bool2(hb.coughImpulse),
440
+ tenderness: bool2(hb.tenderness)
441
+ },
442
+ breast: normalizeBreastExaminationBlock(br),
443
+ thyroid: {
444
+ size: typeof th.size === "string" ? th.size : "",
445
+ mobileDeglutition: bool2(th.mobileDeglutition),
446
+ nodules: bool2(th.nodules),
447
+ cervicalNodes: bool2(th.cervicalNodes)
448
+ },
449
+ anorectal: {
450
+ inspection: typeof ar.inspection === "string" ? ar.inspection : "",
451
+ dreDone: bool2(ar.dreDone),
452
+ proctoscopyDone: bool2(ar.proctoscopyDone),
453
+ notes: typeof ar.notes === "string" ? ar.notes : ""
454
+ },
455
+ limb: {
456
+ dilatedVeins: bool2(lm.dilatedVeins),
457
+ ulcer: bool2(lm.ulcer),
458
+ oedema: bool2(lm.oedema)
459
+ }
460
+ };
461
+ }
462
+ function normalizeGeneralSurgeryGrading(raw) {
463
+ const d = defaultGeneralSurgeryGrading();
464
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
465
+ const v = raw;
466
+ const out = { ...d };
467
+ for (const k of ALL_GS_CONDITIONS) {
468
+ const n = Number(v[k]);
469
+ if (Number.isFinite(n) && n >= 0 && n <= 5) out[k] = Math.round(n);
470
+ }
471
+ return out;
472
+ }
473
+ function gsGradingRowVisible(cond, chips, regions, grade) {
474
+ if (grade > 0) return true;
475
+ const t = GS_CONDITION_TRIGGERS[cond];
476
+ if (t.chips.some((c) => chips.includes(c))) return true;
477
+ if (t.regions.some((r) => regions.includes(r))) return true;
478
+ return false;
479
+ }
480
+ var SWELLING_OPT_LABELS = {
481
+ reducible_hernia: "Reducible (hernia)",
482
+ cough_impulse: "Cough impulse",
483
+ skin_changes: "Skin changes"
484
+ };
485
+ var GI_OPT_LABELS = {
486
+ bleeding_pr: "Bleeding PR",
487
+ vomiting: "Vomiting",
488
+ haematemesis: "Haematemesis"
489
+ };
490
+ var BREAST_OPT_LABELS = {
491
+ pain: "Pain",
492
+ fh_ca_breast: "Family history of Ca breast"
493
+ };
494
+ var ANO_OPT_LABELS = {
495
+ pain_defecation: "Pain during defecation",
496
+ bleeding: "Bleeding",
497
+ constipation: "Constipation",
498
+ discharge: "Discharge"
499
+ };
500
+ var THYROID_OPT_LABELS = {
501
+ dysphagia: "Dysphagia",
502
+ voice_change: "Voice change",
503
+ thyrotoxicosis: "Thyrotoxicosis symptoms"
504
+ };
505
+ var SOCRATES_LABELS = {
506
+ site: "Site",
507
+ onset: "Onset",
508
+ character: "Character",
509
+ radiation: "Radiation",
510
+ associated: "Associated",
511
+ timing: "Timing",
512
+ exacerbating: "Exacerbating / relieving"
513
+ };
514
+ var GENERAL_EXAM_LABELS = {
515
+ pallor: "Pallor",
516
+ icterus: "Icterus",
517
+ cyanosis: "Cyanosis",
518
+ clubbing: "Clubbing",
519
+ lymphadenopathy: "Lymphadenopathy",
520
+ oedema: "Oedema"
521
+ };
522
+ var REGION_EXAM_LABELS = {
523
+ abdomen: "Abdomen",
524
+ hernia: "Hernia",
525
+ breast: "Breast",
526
+ thyroid: "Thyroid",
527
+ anorectal: "Anorectal",
528
+ limb: "Limb / varicose"
529
+ };
530
+ var ABD_INSP_LABELS = {
531
+ distension: "Distension",
532
+ scars: "Scars",
533
+ visible_peristalsis: "Visible peristalsis"
534
+ };
535
+ var ABD_PALP_LABELS = {
536
+ tenderness: "Tenderness",
537
+ guarding: "Guarding",
538
+ rigidity: "Rigidity",
539
+ mass: "Palpable mass"
540
+ };
541
+ var ABD_PERC_LABELS = {
542
+ tympany: "Tympany",
543
+ shifting_dullness: "Shifting dullness"
544
+ };
545
+ function trimLine(s) {
546
+ return s.replace(/\s+/g, " ").trim();
547
+ }
548
+ var SK_ORDER = ["site", "onset", "character", "radiation", "associated", "timing", "exacerbating"];
549
+ function formatGeneralSurgerySmartHistoryToNarrative(v, chips) {
550
+ const blocks = [];
551
+ const showPain = chips.includes("pain");
552
+ const showSwelling = chips.includes("swelling_lump");
553
+ const showGI = chips.includes("bleeding") || chips.includes("obstruction");
554
+ const showBreast = chips.includes("breast");
555
+ const showAno = chips.includes("anorectal");
556
+ const showThyroid = chips.includes("thyroid");
557
+ if (showPain) {
558
+ const painLines = [];
559
+ for (const key of SK_ORDER) {
560
+ const raw = v.pain[key];
561
+ if (typeof raw === "string" && raw.trim()) {
562
+ painLines.push(`${SOCRATES_LABELS[key]}: ${trimLine(raw)}`);
563
+ }
564
+ }
565
+ if (v.pain.severity > 0) {
566
+ painLines.push(`Severity (0\u201310): ${v.pain.severity}`);
567
+ }
568
+ if (painLines.length > 0) {
569
+ blocks.push(["SOCRATES \u2014 pain", ...painLines.map((l) => `\u2022 ${l}`)].join("\n"));
570
+ }
571
+ }
572
+ if (showSwelling) {
573
+ const parts = [];
574
+ if (v.swelling.duration.trim()) parts.push(`Duration: ${trimLine(v.swelling.duration)}`);
575
+ if (v.swelling.sizeProgression.trim())
576
+ parts.push(`Size progression: ${trimLine(v.swelling.sizeProgression)}`);
577
+ if (v.swelling.painfulPainless.trim())
578
+ parts.push(`Painful / painless: ${trimLine(v.swelling.painfulPainless)}`);
579
+ for (const c of v.swelling.checks) {
580
+ const lb = SWELLING_OPT_LABELS[c] ?? c;
581
+ parts.push(lb);
582
+ }
583
+ if (parts.length > 0) {
584
+ blocks.push(["Swelling history", ...parts.map((p) => `\u2022 ${p}`)].join("\n"));
585
+ }
586
+ }
587
+ if (showGI) {
588
+ const parts = [];
589
+ if (v.gi.appetite.trim()) parts.push(`Appetite: ${trimLine(v.gi.appetite)}`);
590
+ if (v.gi.weightLoss.trim()) parts.push(`Weight loss: ${trimLine(v.gi.weightLoss)}`);
591
+ if (v.gi.bowelHabits.trim()) parts.push(`Bowel habits: ${trimLine(v.gi.bowelHabits)}`);
592
+ for (const c of v.gi.checks) {
593
+ parts.push(GI_OPT_LABELS[c] ?? c);
594
+ }
595
+ if (parts.length > 0) {
596
+ blocks.push(["GI history", ...parts.map((p) => `\u2022 ${p}`)].join("\n"));
597
+ }
598
+ }
599
+ if (showBreast) {
600
+ const parts = [];
601
+ if (v.breast.lumpDuration.trim()) parts.push(`Lump duration: ${trimLine(v.breast.lumpDuration)}`);
602
+ if (v.breast.nippleDischarge.trim())
603
+ parts.push(`Nipple discharge: ${trimLine(v.breast.nippleDischarge)}`);
604
+ for (const c of v.breast.checks) {
605
+ parts.push(BREAST_OPT_LABELS[c] ?? c);
606
+ }
607
+ if (parts.length > 0) {
608
+ blocks.push(["Breast history", ...parts.map((p) => `\u2022 ${p}`)].join("\n"));
609
+ }
610
+ }
611
+ if (showAno) {
612
+ const parts = [];
613
+ for (const c of v.ano.checks) {
614
+ parts.push(ANO_OPT_LABELS[c] ?? c);
615
+ }
616
+ if (parts.length > 0) {
617
+ blocks.push(["Anorectal history", ...parts.map((p) => `\u2022 ${p}`)].join("\n"));
618
+ }
619
+ }
620
+ if (showThyroid) {
621
+ const parts = [];
622
+ if (v.thyroid.swellingDuration.trim())
623
+ parts.push(`Swelling duration: ${trimLine(v.thyroid.swellingDuration)}`);
624
+ for (const c of v.thyroid.checks) {
625
+ parts.push(THYROID_OPT_LABELS[c] ?? c);
626
+ }
627
+ if (parts.length > 0) {
628
+ blocks.push(["Thyroid history", ...parts.map((p) => `\u2022 ${p}`)].join("\n"));
629
+ }
630
+ }
631
+ return blocks.join("\n\n");
632
+ }
633
+ var BOWEL_SOUND_LABELS = {
634
+ none: "\u2014",
635
+ normal: "Normal",
636
+ up: "Increased (\u2191)",
637
+ down: "Decreased (\u2193)",
638
+ absent: "Absent"
639
+ };
640
+ function formatGeneralSurgeryExaminationToNarrative(v) {
641
+ const blocks = [];
642
+ const generalPositive = v.general.map((k) => GENERAL_EXAM_LABELS[k] ?? k).filter(Boolean);
643
+ if (generalPositive.length > 0) {
644
+ blocks.push(
645
+ ["General examination", ...generalPositive.map((g) => `\u2022 ${g}`)].join("\n")
646
+ );
647
+ }
648
+ if (v.regions.length > 0) {
649
+ const names = v.regions.map((r) => REGION_EXAM_LABELS[r] ?? r);
650
+ blocks.push(`Regional examination: ${names.join(", ")}`);
651
+ }
652
+ if (v.regions.includes("abdomen")) {
653
+ const sub = [];
654
+ const ins = v.abdomen.inspection.map((x) => ABD_INSP_LABELS[x] ?? x);
655
+ const pal = v.abdomen.palpation.map((x) => ABD_PALP_LABELS[x] ?? x);
656
+ const per = v.abdomen.percussion.map((x) => ABD_PERC_LABELS[x] ?? x);
657
+ if (ins.length) sub.push(`Inspection: ${ins.join(", ")}`);
658
+ if (pal.length) sub.push(`Palpation: ${pal.join(", ")}`);
659
+ if (v.abdomen.massNotes.trim()) sub.push(`Mass / mobility: ${trimLine(v.abdomen.massNotes)}`);
660
+ if (per.length) sub.push(`Percussion: ${per.join(", ")}`);
661
+ const bs = BOWEL_SOUND_LABELS[v.abdomen.bowelSounds];
662
+ if (v.abdomen.bowelSounds !== "none") {
663
+ sub.push(`Bowel sounds: ${bs}`);
664
+ }
665
+ if (sub.length > 0) {
666
+ blocks.push(["Abdomen", ...sub.map((s) => `\u2022 ${s}`)].join("\n"));
667
+ }
668
+ }
669
+ if (v.regions.includes("hernia")) {
670
+ const sub = [];
671
+ if (v.hernia.site.trim()) sub.push(`Site: ${trimLine(v.hernia.site)}`);
672
+ if (v.hernia.reducible) sub.push("Reducible");
673
+ if (v.hernia.coughImpulse) sub.push("Cough impulse positive");
674
+ if (v.hernia.tenderness) sub.push("Tenderness");
675
+ if (sub.length > 0) {
676
+ blocks.push(["Hernia", ...sub.map((s) => `\u2022 ${s}`)].join("\n"));
677
+ }
678
+ }
679
+ if (v.regions.includes("breast")) {
680
+ const sub = [];
681
+ const appendSide = (label, s) => {
682
+ const lines = [];
683
+ if (s.size.trim()) lines.push(`Size: ${trimLine(s.size)}`);
684
+ if (s.quadrant.trim()) lines.push(`Quadrant: ${trimLine(s.quadrant)}`);
685
+ if (s.tenderness.trim()) lines.push(`Tenderness: ${trimLine(s.tenderness)}`);
686
+ if (s.fixity.trim()) lines.push(`Fixity: ${trimLine(s.fixity)}`);
687
+ if (s.skinInvolvement.trim()) lines.push(`Skin involvement: ${trimLine(s.skinInvolvement)}`);
688
+ if (s.consistency.trim()) lines.push(`Consistency: ${trimLine(s.consistency)}`);
689
+ if (s.axillaryNodes) lines.push("Axillary nodes");
690
+ if (s.nippleAreolarComplex) lines.push("Nipple-areolar complex");
691
+ if (lines.length > 0) {
692
+ sub.push(`${label}: ${lines.join("; ")}`);
693
+ }
694
+ };
695
+ appendSide("Left breast", v.breast.left);
696
+ appendSide("Right breast", v.breast.right);
697
+ if (sub.length > 0) {
698
+ blocks.push(["Breast", ...sub.map((s) => `\u2022 ${s}`)].join("\n"));
699
+ }
700
+ }
701
+ if (v.regions.includes("thyroid")) {
702
+ const sub = [];
703
+ if (v.thyroid.size.trim()) sub.push(`Size: ${trimLine(v.thyroid.size)}`);
704
+ if (v.thyroid.mobileDeglutition) sub.push("Mobile with deglutition");
705
+ if (v.thyroid.nodules) sub.push("Nodules");
706
+ if (v.thyroid.cervicalNodes) sub.push("Cervical lymph nodes");
707
+ if (sub.length > 0) {
708
+ blocks.push(["Thyroid", ...sub.map((s) => `\u2022 ${s}`)].join("\n"));
709
+ }
710
+ }
711
+ if (v.regions.includes("anorectal")) {
712
+ const sub = [];
713
+ if (v.anorectal.inspection.trim())
714
+ sub.push(`Inspection: ${trimLine(v.anorectal.inspection)}`);
715
+ if (v.anorectal.dreDone) sub.push("DRE done");
716
+ if (v.anorectal.proctoscopyDone) sub.push("Proctoscopy done");
717
+ if (v.anorectal.notes.trim()) sub.push(`DRE / proctoscopy notes: ${trimLine(v.anorectal.notes)}`);
718
+ if (sub.length > 0) {
719
+ blocks.push(["Anorectal", ...sub.map((s) => `\u2022 ${s}`)].join("\n"));
720
+ }
721
+ }
722
+ if (v.regions.includes("limb")) {
723
+ const sub = [];
724
+ if (v.limb.dilatedVeins) sub.push("Dilated veins");
725
+ if (v.limb.ulcer) sub.push("Ulcer");
726
+ if (v.limb.oedema) sub.push("Oedema");
727
+ if (sub.length > 0) {
728
+ blocks.push(["Limb / varicose", ...sub.map((s) => `\u2022 ${s}`)].join("\n"));
729
+ }
730
+ }
731
+ return blocks.join("\n\n");
732
+ }
733
+
734
+ // src/core/urologyPathway.ts
735
+ function defaultUrologySmartHistory() {
736
+ return {
737
+ socrates: {
738
+ site: "",
739
+ onset: "",
740
+ character: "",
741
+ radiation: "",
742
+ associated: "",
743
+ timing: "",
744
+ exacerbating: "",
745
+ severity: 0
746
+ },
747
+ ipss: {
748
+ incomplete: 0,
749
+ frequency: 0,
750
+ intermittency: 0,
751
+ urgency: 0,
752
+ weakStream: 0,
753
+ straining: 0,
754
+ nocturia: 0
755
+ },
756
+ ipssQoL: 0,
757
+ haematuria: {
758
+ present: false,
759
+ timing: "none",
760
+ painful: false,
761
+ clots: false
762
+ },
763
+ sexual: {
764
+ erectile: "none",
765
+ libido: "none",
766
+ ejaculation: "none",
767
+ infertilityMonths: 0
768
+ },
769
+ pastNotes: ""
770
+ };
771
+ }
772
+ function defaultUrologyExamination() {
773
+ return {
774
+ regions: [],
775
+ general: {
776
+ pallor: false,
777
+ oedema: false,
778
+ icterus: false,
779
+ lymphadenopathy: false
780
+ },
781
+ abdomen: {
782
+ distension: false,
783
+ scars: false,
784
+ mass: false,
785
+ kidneyPalpable: false,
786
+ bladderPalpable: false,
787
+ renalAngleTender: false,
788
+ bladderDull: false
789
+ },
790
+ gu: {
791
+ meatusAbnormal: false,
792
+ phimosis: false,
793
+ lesions: false,
794
+ scrotalSwelling: false,
795
+ transilluminant: false,
796
+ tenderTestis: false,
797
+ hydrocele: false,
798
+ varicocele: false,
799
+ suspiciousMass: false
800
+ },
801
+ dre: {
802
+ done: false,
803
+ sizeGrams: 0,
804
+ consistency: "",
805
+ medianSulcus: "",
806
+ tenderness: false
807
+ },
808
+ examinationNotes: ""
809
+ };
810
+ }
811
+ function defaultUrologyPathway() {
812
+ return {
813
+ stoneSizeMm: 0,
814
+ hydronephrosis: false,
815
+ prostateVolumeCc: 0,
816
+ psaNgMl: 0,
817
+ uroflowQmaxMlSec: 0,
818
+ risk: {
819
+ ckd: false,
820
+ diabetes: false,
821
+ anticoagulants: false,
822
+ activeInfection: false
823
+ }
824
+ };
825
+ }
826
+ function ipssItem(v) {
827
+ const n = Number(v);
828
+ if (!Number.isFinite(n)) return 0;
829
+ return Math.min(5, Math.max(0, Math.round(n)));
830
+ }
831
+ function urologyIpssSevenTotal(ipss) {
832
+ return ipss.incomplete + ipss.frequency + ipss.intermittency + ipss.urgency + ipss.weakStream + ipss.straining + ipss.nocturia;
833
+ }
834
+ function urologyIpssBand(total) {
835
+ if (total <= 7) return "Mild";
836
+ if (total <= 19) return "Moderate";
837
+ return "Severe";
838
+ }
839
+ function hopcMeaningfulStr(x) {
840
+ if (typeof x !== "string") return "";
841
+ const t = x.trim();
842
+ if (!t || t.toLowerCase() === "none") return "";
843
+ return t;
844
+ }
845
+ function hopcNonEmpty(lines) {
846
+ return lines.map((s) => (s ?? "").trim()).filter(Boolean);
847
+ }
848
+ function formatUrologySmartHistoryToSymptomsNarrative(safe, flags) {
849
+ const { showPain, showLUTS, showHaem, showSex } = flags;
850
+ const parts = [];
851
+ if (showPain) {
852
+ const s = safe.socrates;
853
+ const painLines = hopcNonEmpty([
854
+ s.site ? `Site: ${s.site}` : "",
855
+ s.onset ? `Onset: ${s.onset}` : "",
856
+ s.character ? `Character: ${s.character}` : "",
857
+ s.radiation ? `Radiation: ${s.radiation}` : "",
858
+ s.associated ? `Associated: ${s.associated}` : "",
859
+ s.timing ? `Timing: ${s.timing}` : "",
860
+ s.exacerbating ? `Exacerbating/relieving: ${s.exacerbating}` : "",
861
+ `Severity: ${s.severity}/10`
862
+ ]);
863
+ const hasNonDefaultPain = painLines.length > 1 || painLines.length === 1 && painLines[0] !== "Severity: 0/10";
864
+ if (hasNonDefaultPain) parts.push(`SOCRATES:
865
+ ${painLines.join("\n")}`);
866
+ }
867
+ if (showLUTS) {
868
+ const ip = safe.ipss;
869
+ const any = ip.incomplete || ip.frequency || ip.intermittency || ip.urgency || ip.weakStream || ip.straining || ip.nocturia || safe.ipssQoL;
870
+ if (any) {
871
+ const ipssTotal = urologyIpssSevenTotal(safe.ipss);
872
+ const ipssBandLabel = urologyIpssBand(ipssTotal);
873
+ parts.push(`IPSS: ${ipssTotal}/35 (${ipssBandLabel}) \xB7 QoL ${safe.ipssQoL}/6`);
874
+ }
875
+ }
876
+ if (showHaem) {
877
+ const h = safe.haematuria;
878
+ const timing = hopcMeaningfulStr(h.timing);
879
+ const haemLines = hopcNonEmpty([
880
+ h.present ? "Haematuria present" : "",
881
+ timing ? `Timing: ${timing}` : "",
882
+ h.painful ? "Painful" : "",
883
+ h.clots ? "Clots" : ""
884
+ ]);
885
+ if (haemLines.length) parts.push(`Haematuria:
886
+ ${haemLines.join("\n")}`);
887
+ }
888
+ if (showSex) {
889
+ const sx = safe.sexual;
890
+ const infertility = sx.infertilityMonths;
891
+ const sexLines = hopcNonEmpty([
892
+ hopcMeaningfulStr(sx.erectile) ? `Erectile function: ${hopcMeaningfulStr(sx.erectile)}` : "",
893
+ hopcMeaningfulStr(sx.libido) ? `Libido: ${hopcMeaningfulStr(sx.libido)}` : "",
894
+ hopcMeaningfulStr(sx.ejaculation) ? `Ejaculation: ${hopcMeaningfulStr(sx.ejaculation)}` : "",
895
+ Number.isFinite(infertility) && infertility > 0 ? `Infertility: ${infertility} months` : ""
896
+ ]);
897
+ if (sexLines.length) parts.push(`Sexual/reproductive:
898
+ ${sexLines.join("\n")}`);
899
+ }
900
+ return parts.join("\n\n").trim();
901
+ }
902
+ function formatUrologyExaminationToClinicalExamination(v) {
903
+ const sections = [];
904
+ const generalLabels = {
905
+ pallor: "Pallor (chronic dz)",
906
+ oedema: "Oedema (renal)",
907
+ icterus: "Icterus",
908
+ lymphadenopathy: "Lymphadenopathy"
909
+ };
910
+ const general = Object.keys(generalLabels).filter((k) => v.general[k]).map((k) => generalLabels[k]);
911
+ if (general.length) sections.push(`General: ${general.join(", ")}`);
912
+ const abdLabels = {
913
+ distension: "Distension",
914
+ scars: "Surgical scars",
915
+ mass: "Visible mass",
916
+ kidneyPalpable: "Kidney palpable",
917
+ bladderPalpable: "Bladder palpable",
918
+ renalAngleTender: "Renal angle tender",
919
+ bladderDull: "Bladder dullness (retention)"
920
+ };
921
+ const abd = Object.keys(abdLabels).filter((k) => v.abdomen[k]).map((k) => abdLabels[k]);
922
+ if (abd.length) sections.push(`Abdomen: ${abd.join(", ")}`);
923
+ const guLabels = {
924
+ meatusAbnormal: "Meatus abnormal (hypospadias?)",
925
+ phimosis: "Phimosis / paraphimosis",
926
+ lesions: "Penile lesions / ulcers",
927
+ scrotalSwelling: "Scrotal swelling",
928
+ transilluminant: "Transilluminant",
929
+ tenderTestis: "Tender testis",
930
+ hydrocele: "Hydrocele",
931
+ varicocele: "Varicocele",
932
+ suspiciousMass: "Suspicious mass"
933
+ };
934
+ const gu = Object.keys(guLabels).filter((k) => v.gu[k]).map((k) => guLabels[k]);
935
+ if (gu.length) sections.push(`GU: ${gu.join(", ")}`);
936
+ const dreBits = hopcNonEmpty([
937
+ v.dre.done ? "Performed" : "",
938
+ v.dre.sizeGrams > 0 ? `Size ~${v.dre.sizeGrams} g` : "",
939
+ v.dre.consistency ? `Consistency: ${v.dre.consistency}` : "",
940
+ v.dre.medianSulcus ? `Median sulcus: ${v.dre.medianSulcus}` : "",
941
+ v.dre.tenderness ? "Tenderness" : ""
942
+ ]);
943
+ if (dreBits.length) sections.push(`DRE: ${dreBits.join(", ")}`);
944
+ return sections.join("\n");
945
+ }
946
+ function str(x) {
947
+ return typeof x === "string" ? x : "";
948
+ }
949
+ function bool(x) {
950
+ return x === true;
951
+ }
952
+ function nonNegInt(x) {
953
+ const n = Number(x);
954
+ if (!Number.isFinite(n)) return 0;
955
+ return Math.max(0, Math.round(n));
956
+ }
957
+ function normalizeUrologySmartHistory(raw) {
958
+ const base = defaultUrologySmartHistory();
959
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
960
+ const r = raw;
961
+ const soc = r.socrates && typeof r.socrates === "object" && !Array.isArray(r.socrates) ? r.socrates : {};
962
+ const socObj = soc;
963
+ const sev = Number(socObj.severity);
964
+ const socrates = {
965
+ site: str(socObj.site),
966
+ onset: str(socObj.onset),
967
+ character: str(socObj.character),
968
+ radiation: str(socObj.radiation),
969
+ associated: str(socObj.associated),
970
+ timing: str(socObj.timing),
971
+ exacerbating: str(socObj.exacerbating),
972
+ severity: Number.isFinite(sev) ? Math.min(10, Math.max(0, Math.round(sev))) : 0
973
+ };
974
+ const ipRaw = r.ipss && typeof r.ipss === "object" && !Array.isArray(r.ipss) ? r.ipss : {};
975
+ const ip = ipRaw;
976
+ const ipss = {
977
+ incomplete: ipssItem(ip.incomplete),
978
+ frequency: ipssItem(ip.frequency),
979
+ intermittency: ipssItem(ip.intermittency),
980
+ urgency: ipssItem(ip.urgency),
981
+ weakStream: ipssItem(ip.weakStream ?? ip.weak_stream),
982
+ straining: ipssItem(ip.straining),
983
+ nocturia: ipssItem(ip.nocturia)
984
+ };
985
+ const qolRaw = Number(r.ipssQoL);
986
+ const ipssQoL = Number.isFinite(qolRaw) ? Math.min(6, Math.max(0, Math.round(qolRaw))) : 0;
987
+ const haRaw = r.haematuria && typeof r.haematuria === "object" && !Array.isArray(r.haematuria) ? r.haematuria : {};
988
+ const ha = haRaw;
989
+ const haematuria = {
990
+ present: bool(ha.present),
991
+ timing: str(ha.timing) || "none",
992
+ painful: bool(ha.painful),
993
+ clots: bool(ha.clots)
994
+ };
995
+ const sxRaw = r.sexual && typeof r.sexual === "object" && !Array.isArray(r.sexual) ? r.sexual : {};
996
+ const sx = sxRaw;
997
+ const infertilityMonths = nonNegInt(sx.infertilityMonths);
998
+ const sexual = {
999
+ erectile: str(sx.erectile) || "none",
1000
+ libido: str(sx.libido) || "none",
1001
+ ejaculation: str(sx.ejaculation) || "none",
1002
+ infertilityMonths
1003
+ };
1004
+ const pastNotes = str(r.pastNotes);
1005
+ return {
1006
+ socrates,
1007
+ ipss,
1008
+ ipssQoL,
1009
+ haematuria,
1010
+ sexual,
1011
+ pastNotes
1012
+ };
1013
+ }
1014
+ function dreSelectStr(x) {
1015
+ const t = str(x).trim();
1016
+ if (!t || t.toLowerCase() === "none") return "";
1017
+ return t;
1018
+ }
1019
+ function normalizeUrologyExamination(raw) {
1020
+ const base = defaultUrologyExamination();
1021
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
1022
+ const r = raw;
1023
+ const regions = Array.isArray(r.regions) ? r.regions.filter((x) => typeof x === "string") : [];
1024
+ const g = r.general && typeof r.general === "object" && !Array.isArray(r.general) ? r.general : {};
1025
+ const gObj = g;
1026
+ const general = {
1027
+ pallor: bool(gObj.pallor),
1028
+ oedema: bool(gObj.oedema),
1029
+ icterus: bool(gObj.icterus),
1030
+ lymphadenopathy: bool(gObj.lymphadenopathy)
1031
+ };
1032
+ const ab = r.abdomen && typeof r.abdomen === "object" && !Array.isArray(r.abdomen) ? r.abdomen : {};
1033
+ const abObj = ab;
1034
+ const abdomen = {
1035
+ distension: bool(abObj.distension),
1036
+ scars: bool(abObj.scars),
1037
+ mass: bool(abObj.mass),
1038
+ kidneyPalpable: bool(abObj.kidneyPalpable),
1039
+ bladderPalpable: bool(abObj.bladderPalpable),
1040
+ renalAngleTender: bool(abObj.renalAngleTender),
1041
+ bladderDull: bool(abObj.bladderDull)
1042
+ };
1043
+ const guRaw = r.gu && typeof r.gu === "object" && !Array.isArray(r.gu) ? r.gu : {};
1044
+ const guObj = guRaw;
1045
+ const gu = {
1046
+ meatusAbnormal: bool(guObj.meatusAbnormal),
1047
+ phimosis: bool(guObj.phimosis),
1048
+ lesions: bool(guObj.lesions),
1049
+ scrotalSwelling: bool(guObj.scrotalSwelling),
1050
+ transilluminant: bool(guObj.transilluminant),
1051
+ tenderTestis: bool(guObj.tenderTestis),
1052
+ hydrocele: bool(guObj.hydrocele),
1053
+ varicocele: bool(guObj.varicocele),
1054
+ suspiciousMass: bool(guObj.suspiciousMass)
1055
+ };
1056
+ const dr = r.dre && typeof r.dre === "object" && !Array.isArray(r.dre) ? r.dre : {};
1057
+ const drObj = dr;
1058
+ const sizeG = Number(drObj.sizeGrams);
1059
+ const dre = {
1060
+ done: bool(drObj.done),
1061
+ sizeGrams: Number.isFinite(sizeG) && sizeG >= 0 ? Math.round(sizeG) : 0,
1062
+ consistency: dreSelectStr(drObj.consistency),
1063
+ medianSulcus: dreSelectStr(drObj.medianSulcus),
1064
+ tenderness: bool(drObj.tenderness)
1065
+ };
1066
+ const examinationNotes = str(r.examinationNotes);
1067
+ return {
1068
+ regions,
1069
+ general,
1070
+ abdomen,
1071
+ gu,
1072
+ dre,
1073
+ examinationNotes
1074
+ };
1075
+ }
1076
+ function normalizeUrologyPathway(raw) {
1077
+ const base = defaultUrologyPathway();
1078
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
1079
+ const r = raw;
1080
+ const stoneSizeMm = nonNegInt(r.stoneSizeMm);
1081
+ const hydronephrosis = bool(r.hydronephrosis);
1082
+ const prostateVolumeCc = Math.max(0, Number(r.prostateVolumeCc) || 0);
1083
+ const psaNgMl = Math.max(0, Number(r.psaNgMl) || 0);
1084
+ const uroflowQmaxMlSec = Math.max(0, Number(r.uroflowQmaxMlSec) || 0);
1085
+ const riskRaw = r.risk && typeof r.risk === "object" && !Array.isArray(r.risk) ? r.risk : {};
1086
+ const rk = riskRaw;
1087
+ const risk = {
1088
+ ckd: bool(rk.ckd),
1089
+ diabetes: bool(rk.diabetes),
1090
+ anticoagulants: bool(rk.anticoagulants),
1091
+ activeInfection: bool(rk.activeInfection)
1092
+ };
1093
+ return {
1094
+ stoneSizeMm,
1095
+ hydronephrosis,
1096
+ prostateVolumeCc,
1097
+ psaNgMl,
1098
+ uroflowQmaxMlSec,
1099
+ risk
1100
+ };
1101
+ }
1102
+
1103
+ // src/core/painScaleFlags.ts
1104
+ var UPT_VALUES = ["", "Positive", "Negative", "Not done"];
1105
+ function isUpt(x) {
1106
+ return typeof x === "string" && UPT_VALUES.includes(x);
1107
+ }
1108
+ function normalizePainScaleFlags(raw, bounds) {
1109
+ const min = bounds?.min ?? 0;
1110
+ const max = bounds?.max ?? 10;
1111
+ let pain = min;
1112
+ let flags = [];
1113
+ let bp = "";
1114
+ let upt = "";
1115
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
1116
+ const o = raw;
1117
+ const p = Number(o.pain);
1118
+ if (Number.isFinite(p)) pain = Math.min(max, Math.max(min, p));
1119
+ if (Array.isArray(o.flags)) {
1120
+ flags = o.flags.filter((x) => typeof x === "string");
1121
+ }
1122
+ if (typeof o.bp === "string") bp = o.bp;
1123
+ if (isUpt(o.upt)) upt = o.upt;
1124
+ }
1125
+ return { pain, flags, bp, upt };
1126
+ }
1127
+
1128
+ // src/core/persistence/index.ts
1129
+ var NO_OP_PROVIDER = {
1130
+ save: async () => {
1131
+ },
1132
+ load: async () => null,
1133
+ clear: async () => {
1134
+ }
1135
+ };
1136
+
1137
+ // src/core/prefill.ts
1138
+ function applyPrefill(currentValues, fields, prefilledData = {}) {
1139
+ const newValues = { ...currentValues };
1140
+ fields.forEach((field) => {
1141
+ if (field.prefill?.value !== void 0) {
1142
+ newValues[field.id] = field.prefill.value;
1143
+ return;
1144
+ }
1145
+ if (prefilledData[field.id] !== void 0) {
1146
+ newValues[field.id] = prefilledData[field.id];
1147
+ return;
1148
+ }
1149
+ });
1150
+ return newValues;
1151
+ }
1152
+
1153
+ // src/core/store.ts
1154
+ var FormStore = class {
1155
+ state;
1156
+ schema;
1157
+ fieldMap;
1158
+ listeners = /* @__PURE__ */ new Set();
1159
+ persistence;
1160
+ prefillData = {};
1161
+ config;
1162
+ // Widget-owned errors that schema validation cannot express (e.g. "OTP not sent yet").
1163
+ // Merged on top of validateForm output in evaluate().
1164
+ externalErrors = {};
1165
+ // Cache for debounce
1166
+ saveTimeout = null;
1167
+ draftCallbackTimeout = null;
1168
+ constructor(schema, config = {}) {
1169
+ this.schema = schema;
1170
+ this.config = config;
1171
+ this.persistence = config.persistence || NO_OP_PROVIDER;
1172
+ this.prefillData = config.prefillData || {};
1173
+ this.fieldMap = {};
1174
+ schema.fields.forEach((f) => {
1175
+ this.fieldMap[f.id] = f;
1176
+ });
1177
+ const initialWorkflowState = schema.workflow?.states?.[0] || "DRAFT";
1178
+ this.state = {
1179
+ values: {},
1180
+ errors: {},
1181
+ touched: {},
1182
+ visibility: {},
1183
+ disabled: {},
1184
+ workflowState: initialWorkflowState,
1185
+ isValid: true,
1186
+ isSubmitting: false,
1187
+ submitAttempted: false,
1188
+ pendingUploads: 0
1189
+ };
1190
+ this.initializeDefaults();
1191
+ this.evaluate();
1192
+ }
1193
+ initializeDefaults() {
1194
+ const values = {};
1195
+ this.schema.fields.forEach((field) => {
1196
+ if (field.type === "static_text") return;
1197
+ if (field.defaultValue !== void 0) {
1198
+ values[field.id] = field.defaultValue;
1199
+ } else {
1200
+ if (field.type === "repeatable") values[field.id] = [];
1201
+ else if (field.type === "checkbox" || field.type === "multiselect") values[field.id] = [];
1202
+ else if (field.type === "smart_textarea") values[field.id] = { text: "", extractedKeywords: [] };
1203
+ else if (field.type === "suggestion_textarea" || field.type === "complaint_chips") values[field.id] = "";
1204
+ else if (field.type === "lens_assessment")
1205
+ values[field.id] = {
1206
+ notes: "",
1207
+ assessmentKey: void 0,
1208
+ locs: {
1209
+ OD: { NO: 0, NC: 0, C: 0, PSC: 0 },
1210
+ OS: { NO: 0, NC: 0, C: 0, PSC: 0 }
1211
+ }
1212
+ };
1213
+ else if (field.type === "functional_impairment_score")
1214
+ values[field.id] = {
1215
+ reading: 0,
1216
+ nightDriving: 0,
1217
+ glare: 0,
1218
+ occupation: 0,
1219
+ adl: 0
1220
+ };
1221
+ else if (field.type === "biometry_iol_workup")
1222
+ values[field.id] = {
1223
+ axialLengthOD: "",
1224
+ axialLengthOS: "",
1225
+ k1k2AxisOD: "",
1226
+ k1k2AxisOS: "",
1227
+ anteriorChamberDepthOD: "",
1228
+ anteriorChamberDepthOS: "",
1229
+ iolPowerOD: "",
1230
+ iolPowerOS: "",
1231
+ targetRefractionOD: "",
1232
+ targetRefractionOS: "",
1233
+ formula: "SRK/T",
1234
+ method: "optical_biometry",
1235
+ cornealTopoUploaded: false,
1236
+ specularMicroscopyDone: false,
1237
+ endothelialCount: ""
1238
+ };
1239
+ else if (field.type === "surgical_risk_flags")
1240
+ values[field.id] = { OD: [], OS: [] };
1241
+ else if (field.type === "toggle") values[field.id] = false;
1242
+ else if (field.type === "slider") {
1243
+ const s = field;
1244
+ values[field.id] = s.defaultValue ?? s.min ?? 0;
1245
+ } else if (field.type === "general_surgery_smart_history") {
1246
+ values[field.id] = field.defaultValue ?? defaultGeneralSurgerySmartHistory();
1247
+ } else if (field.type === "general_surgery_examination") {
1248
+ values[field.id] = field.defaultValue ?? defaultGeneralSurgeryExamination();
1249
+ } else if (field.type === "general_surgery_grading") {
1250
+ values[field.id] = field.defaultValue ?? defaultGeneralSurgeryGrading();
1251
+ } else if (field.type === "urology_smart_history") {
1252
+ values[field.id] = field.defaultValue ?? defaultUrologySmartHistory();
1253
+ } else if (field.type === "urology_examination") {
1254
+ values[field.id] = field.defaultValue ?? defaultUrologyExamination();
1255
+ } else if (field.type === "urology_pathway") {
1256
+ values[field.id] = field.defaultValue ?? defaultUrologyPathway();
1257
+ } else if (field.type === "pain_scale_flags") {
1258
+ const f = field;
1259
+ values[field.id] = normalizePainScaleFlags(
1260
+ f.defaultValue ?? { pain: f.min ?? 0, flags: [] },
1261
+ { min: f.min, max: f.max }
1262
+ );
1263
+ } else values[field.id] = null;
1264
+ }
1265
+ });
1266
+ this.state.values = values;
1267
+ }
1268
+ // --- State Access ---
1269
+ getState() {
1270
+ return this.state;
1271
+ }
1272
+ getFieldState(fieldId) {
1273
+ return {
1274
+ value: this.state.values[fieldId],
1275
+ error: this.state.errors[fieldId],
1276
+ touched: this.state.touched[fieldId],
1277
+ visible: this.state.visibility[fieldId] ?? true,
1278
+ disabled: this.state.disabled[fieldId] ?? false
1279
+ };
1280
+ }
1281
+ getSchema() {
1282
+ return this.schema;
1283
+ }
1284
+ getConfig() {
1285
+ return this.config;
1286
+ }
1287
+ getFieldDef(fieldId) {
1288
+ return this.fieldMap[fieldId];
1289
+ }
1290
+ getVoice() {
1291
+ return this.config.voice;
1292
+ }
1293
+ hasPersistence() {
1294
+ return this.persistence !== NO_OP_PROVIDER;
1295
+ }
1296
+ // --- Actions ---
1297
+ setValue(fieldId, value) {
1298
+ if (this.state.values[fieldId] === value) return;
1299
+ this.state.values = { ...this.state.values, [fieldId]: value };
1300
+ this.state.touched = { ...this.state.touched, [fieldId]: true };
1301
+ this.checkEvents(fieldId, "onValueChange", value);
1302
+ this.evaluate();
1303
+ this.notify();
1304
+ this.scheduleDraftCallback();
1305
+ if (!this.config.onDraftChange) {
1306
+ this.scheduleSave();
1307
+ }
1308
+ }
1309
+ /**
1310
+ * Set multiple values at once (e.g. from AI analysis). Only updates keys that
1311
+ * exist in the schema. Runs evaluate/notify/save once.
1312
+ * @param partial Map of fieldId -> value
1313
+ * @param options.touch If false, do not mark fields as touched (e.g. for AI-suggested values)
1314
+ */
1315
+ setValues(partial, options) {
1316
+ const touch = options?.touch !== false;
1317
+ let changed = false;
1318
+ const newValues = { ...this.state.values };
1319
+ const newTouched = { ...this.state.touched };
1320
+ for (const [fieldId, value] of Object.entries(partial)) {
1321
+ if (!this.fieldMap[fieldId]) continue;
1322
+ if (newValues[fieldId] === value) continue;
1323
+ newValues[fieldId] = value;
1324
+ if (touch) newTouched[fieldId] = true;
1325
+ changed = true;
1326
+ }
1327
+ if (!changed) return;
1328
+ this.state.values = newValues;
1329
+ this.state.touched = newTouched;
1330
+ this.evaluate();
1331
+ this.notify();
1332
+ this.scheduleSave();
1333
+ }
1334
+ /**
1335
+ * Let a widget report an error that schema validation cannot express
1336
+ * (e.g. "Send OTP to continue" until the API call succeeds).
1337
+ * Pass `null` to clear a previously set external error.
1338
+ */
1339
+ setExternalError(fieldId, message) {
1340
+ if ((this.externalErrors[fieldId] ?? null) === message) return;
1341
+ this.externalErrors = { ...this.externalErrors, [fieldId]: message };
1342
+ this.evaluate();
1343
+ this.notify();
1344
+ }
1345
+ setTouched(fieldId) {
1346
+ if (this.state.touched[fieldId]) return;
1347
+ this.state.touched = { ...this.state.touched, [fieldId]: true };
1348
+ this.checkEvents(fieldId, "onBlur", this.state.values[fieldId]);
1349
+ this.evaluate();
1350
+ this.notify();
1351
+ }
1352
+ setRole(role) {
1353
+ this.config.role = role;
1354
+ this.evaluate();
1355
+ this.notify();
1356
+ }
1357
+ // --- Lifecycle ---
1358
+ async load() {
1359
+ const saved = await this.persistence.load(this.schema.id);
1360
+ let mergedValues = { ...this.state.values };
1361
+ let workflowState = this.state.workflowState;
1362
+ if (saved) {
1363
+ console.log("Loaded draft:", saved);
1364
+ mergedValues = { ...mergedValues, ...saved.values };
1365
+ workflowState = saved.meta.workflowState || workflowState;
1366
+ }
1367
+ mergedValues = applyPrefill(mergedValues, this.schema.fields, this.prefillData);
1368
+ this.state.values = mergedValues;
1369
+ this.state.workflowState = workflowState;
1370
+ this.evaluate();
1371
+ this.notify();
1372
+ }
1373
+ scheduleSave() {
1374
+ if (this.saveTimeout) clearTimeout(this.saveTimeout);
1375
+ this.saveTimeout = setTimeout(() => {
1376
+ this.save();
1377
+ }, 1e3);
1378
+ }
1379
+ scheduleDraftCallback() {
1380
+ if (!this.config.onDraftChange) return;
1381
+ if (this.draftCallbackTimeout) {
1382
+ clearTimeout(this.draftCallbackTimeout);
1383
+ }
1384
+ this.draftCallbackTimeout = setTimeout(() => {
1385
+ const payload = {
1386
+ // For consumer callbacks, send values in the same
1387
+ // serialized shape used for outbound submissions so
1388
+ // media/signature fields are wrapped consistently.
1389
+ values: this.serializeValuesForSubmission(this.state.values),
1390
+ meta: {
1391
+ updatedAt: Date.now(),
1392
+ workflowState: this.state.workflowState,
1393
+ version: this.schema.version
1394
+ }
1395
+ };
1396
+ this.config.onDraftChange?.(payload);
1397
+ }, 5e3);
1398
+ }
1399
+ async save() {
1400
+ const payload = {
1401
+ values: this.state.values,
1402
+ meta: {
1403
+ updatedAt: Date.now(),
1404
+ workflowState: this.state.workflowState,
1405
+ version: this.schema.version
1406
+ }
1407
+ };
1408
+ await this.persistence.save(this.schema.id, payload);
1409
+ this.emit("DRAFT_SAVED", payload);
1410
+ }
1411
+ /**
1412
+ * Prepare values for outbound submission to the backend.
1413
+ * For media-related fields, wrap the raw S3 key in a typed object so the
1414
+ * backend can recognise which fields need presigned GET URLs.
1415
+ *
1416
+ * Example:
1417
+ * "patient_signature": "forms/media-uploads/.../signature.svg"
1418
+ * becomes:
1419
+ * "patient_signature": {
1420
+ * _type: "s3_key",
1421
+ * value: "forms/media-uploads/.../signature.svg"
1422
+ * }
1423
+ */
1424
+ getSerializedValuesForSubmission() {
1425
+ return this.serializeValuesForSubmission(this.state.values);
1426
+ }
1427
+ /**
1428
+ * Raw form values as stored internally, without any serialization or
1429
+ * transformation. Useful when consumers need access to richer structures
1430
+ * (e.g. smart_textarea keyword metadata) alongside the flattened values
1431
+ * returned by getSerializedValuesForSubmission.
1432
+ */
1433
+ getRawValues() {
1434
+ return { ...this.state.values };
1435
+ }
1436
+ serializeValuesForSubmission(values) {
1437
+ const result = { ...values };
1438
+ this.schema.fields.forEach((field) => {
1439
+ if (field.type === "static_text") {
1440
+ delete result[field.id];
1441
+ return;
1442
+ }
1443
+ if (field.type === "smart_textarea") {
1444
+ const raw = values[field.id];
1445
+ result[field.id] = raw?.text ?? null;
1446
+ return;
1447
+ }
1448
+ if (field.type === "diagnosis_textarea") {
1449
+ const raw = values[field.id];
1450
+ result[field.id] = typeof raw === "string" ? raw : raw && typeof raw === "object" ? raw.text ?? null : null;
1451
+ return;
1452
+ }
1453
+ if (field.type === "ophthal_diagnosis") {
1454
+ const raw = values[field.id];
1455
+ const formatEye = (eyeData, eyeLabel) => {
1456
+ if (!eyeData || typeof eyeData !== "object") return "";
1457
+ const entries = Object.entries(eyeData).filter(([, value]) => typeof value === "string" && value.trim()).map(([key, value]) => `${key}: ${value.trim()}`);
1458
+ if (entries.length === 0) return "";
1459
+ return [eyeLabel, ...entries].join("\n");
1460
+ };
1461
+ const rightEyeText = formatEye(
1462
+ raw?.right_eye ?? raw?.rightEye,
1463
+ "Right Eye"
1464
+ );
1465
+ const leftEyeText = formatEye(
1466
+ raw?.left_eye ?? raw?.leftEye,
1467
+ "Left Eye"
1468
+ );
1469
+ const parts = [rightEyeText, leftEyeText].filter(Boolean);
1470
+ result[field.id] = parts.length > 0 ? parts.join("\n\n") : null;
1471
+ return;
1472
+ }
1473
+ if (field.type === "otp_input") {
1474
+ const raw = values[field.id];
1475
+ if (typeof raw === "string") {
1476
+ result[field.id] = raw;
1477
+ return;
1478
+ }
1479
+ if (raw && typeof raw === "object" && !Array.isArray(raw) && raw.verified === true) {
1480
+ const code = raw.code;
1481
+ result[field.id] = typeof code === "string" ? code : null;
1482
+ return;
1483
+ }
1484
+ result[field.id] = null;
1485
+ return;
1486
+ }
1487
+ if (field.type === "image_upload" || field.type === "media_upload" || field.type === "signature" || field.type === "file_upload") {
1488
+ const raw = values[field.id];
1489
+ if (raw === void 0 || raw === null || raw === "") {
1490
+ return;
1491
+ }
1492
+ const wrapKey = (key2) => {
1493
+ if (typeof key2 !== "string" || !key2) return null;
1494
+ return { _type: "s3_key", value: key2 };
1495
+ };
1496
+ if (Array.isArray(raw)) {
1497
+ const wrapped2 = raw.map(
1498
+ (item) => typeof item === "string" ? wrapKey(item) : wrapKey(item.s3_key ?? item.value)
1499
+ ).filter(Boolean);
1500
+ if (wrapped2.length > 0) {
1501
+ result[field.id] = wrapped2;
1502
+ }
1503
+ return;
1504
+ }
1505
+ const key = typeof raw === "string" ? raw : raw.s3_key ?? raw.value ?? null;
1506
+ const wrapped = wrapKey(key);
1507
+ if (wrapped) {
1508
+ result[field.id] = wrapped;
1509
+ }
1510
+ }
1511
+ });
1512
+ return result;
1513
+ }
1514
+ // --- Core Logic ---
1515
+ evaluate() {
1516
+ if (this.schema.computations) {
1517
+ this.schema.computations.forEach((comp) => {
1518
+ if (comp.type === "sum") {
1519
+ const sum = comp.fields.reduce((acc, fieldId) => {
1520
+ const raw = this.state.values[fieldId];
1521
+ const fieldDef = this.fieldMap[fieldId];
1522
+ const options = fieldDef?.type === "select" || fieldDef?.type === "multiselect" || fieldDef?.type === "radio" || fieldDef?.type === "checkbox" ? fieldDef.options : void 0;
1523
+ const hasScoreOptions = options?.some((o) => o.score !== void 0);
1524
+ let val;
1525
+ if (hasScoreOptions && options) {
1526
+ const selected = fieldDef?.type === "multiselect" || fieldDef?.type === "checkbox" ? (Array.isArray(raw) ? raw : [raw]).filter(Boolean) : [raw];
1527
+ val = selected.reduce((s, v) => {
1528
+ const opt = options.find((o) => o.value === v || String(o.value) === String(v));
1529
+ return s + (opt && "score" in opt && typeof opt.score === "number" ? opt.score : 0);
1530
+ }, 0);
1531
+ } else {
1532
+ val = Number(raw) || 0;
1533
+ }
1534
+ return acc + val;
1535
+ }, 0);
1536
+ if (this.state.values[comp.target] !== sum) {
1537
+ this.state.values[comp.target] = sum;
1538
+ }
1539
+ } else if (comp.type === "score") {
1540
+ const val = Number(this.state.values[comp.source]);
1541
+ if (this.state.values[comp.source] !== void 0 && this.state.values[comp.source] !== null && this.state.values[comp.source] !== "") {
1542
+ let score = 0;
1543
+ const matched = comp.ranges.find((r) => {
1544
+ const min = r.min ?? -Infinity;
1545
+ const max = r.max ?? Infinity;
1546
+ return val >= min && val <= max;
1547
+ });
1548
+ if (matched) {
1549
+ score = matched.value;
1550
+ }
1551
+ if (this.state.values[comp.target] !== score) {
1552
+ this.state.values[comp.target] = score;
1553
+ }
1554
+ } else {
1555
+ if (this.state.values[comp.target] !== 0) {
1556
+ this.state.values[comp.target] = 0;
1557
+ }
1558
+ }
1559
+ } else if (comp.type === "formula") {
1560
+ const evalExpr = (expr) => {
1561
+ if (expr.type === "field") {
1562
+ return Number(this.state.values[expr.field]) || 0;
1563
+ }
1564
+ if (expr.type === "number") {
1565
+ return expr.value;
1566
+ }
1567
+ const left = evalExpr(expr.left);
1568
+ const right = evalExpr(expr.right);
1569
+ switch (expr.op) {
1570
+ case "+":
1571
+ return left + right;
1572
+ case "-":
1573
+ return left - right;
1574
+ case "*":
1575
+ return left * right;
1576
+ case "/":
1577
+ return right === 0 ? 0 : left / right;
1578
+ case "**":
1579
+ return Math.pow(left, right);
1580
+ default:
1581
+ return 0;
1582
+ }
1583
+ };
1584
+ const result = evalExpr(comp.expression);
1585
+ const value = Number.isFinite(result) ? Math.round(result * 10) / 10 : "";
1586
+ if (this.state.values[comp.target] !== value) {
1587
+ this.state.values[comp.target] = value;
1588
+ }
1589
+ }
1590
+ });
1591
+ }
1592
+ const ruleResult = evaluateRules(this.state.values, this.schema.rules || [], this.fieldMap);
1593
+ this.state.visibility = ruleResult.visibility;
1594
+ this.state.disabled = ruleResult.disabled;
1595
+ if (this.config.role) {
1596
+ this.schema.fields.forEach((field) => {
1597
+ if (field.permissions && field.permissions[this.config.role]) {
1598
+ const perm = field.permissions[this.config.role];
1599
+ if (perm === "hide") this.state.visibility[field.id] = false;
1600
+ if (perm === "view") this.state.disabled[field.id] = true;
1601
+ }
1602
+ });
1603
+ }
1604
+ const effectiveFields = {};
1605
+ Object.keys(this.fieldMap).forEach((key) => {
1606
+ effectiveFields[key] = {
1607
+ ...this.fieldMap[key],
1608
+ required: ruleResult.required[key] ?? this.fieldMap[key].required,
1609
+ hidden: !this.state.visibility[key],
1610
+ // Inject current visibility
1611
+ disabled: this.state.disabled[key]
1612
+ };
1613
+ });
1614
+ const errors = validateForm(this.state.values, effectiveFields);
1615
+ const merged = { ...errors };
1616
+ for (const [id, msg] of Object.entries(this.externalErrors)) {
1617
+ if (msg) merged[id] = msg;
1618
+ }
1619
+ this.state.errors = merged;
1620
+ this.state.isValid = Object.keys(merged).length === 0;
1621
+ }
1622
+ // --- Workflow ---
1623
+ getAvailableTransitions() {
1624
+ const transitions = this.schema.workflow?.transitions || [];
1625
+ const role = this.config.role;
1626
+ return transitions.filter(
1627
+ (t) => t.from === this.state.workflowState && (!role || t.allowedRoles.includes(role))
1628
+ );
1629
+ }
1630
+ transition(toState) {
1631
+ const t = this.getAvailableTransitions().find((tr) => tr.to === toState);
1632
+ if (!t) throw new Error(`Transition from ${this.state.workflowState} to ${toState} not allowed`);
1633
+ this.state.workflowState = toState;
1634
+ if (t.webhookKey) {
1635
+ this.emit("FINALISE_REQUESTED", { webhookKey: t.webhookKey, values: this.state.values });
1636
+ }
1637
+ this.save();
1638
+ this.notify();
1639
+ }
1640
+ // --- Events ---
1641
+ checkEvents(fieldId, trigger, value) {
1642
+ const field = this.fieldMap[fieldId];
1643
+ if (!field?.events) return;
1644
+ field.events.forEach((evt) => {
1645
+ if (evt.trigger === trigger) {
1646
+ let match = true;
1647
+ if (evt.condition) {
1648
+ if (evt.condition.op === ">") match = Number(value) > Number(evt.condition.value);
1649
+ }
1650
+ if (match) {
1651
+ this.emit(evt.emit, { fieldId, value });
1652
+ }
1653
+ }
1654
+ });
1655
+ }
1656
+ emit(event, payload) {
1657
+ let finalPayload = payload;
1658
+ if (event === "FINALISE_REQUESTED" || event === "DRAFT_SAVED") {
1659
+ if (payload && typeof payload === "object" && "values" in payload) {
1660
+ finalPayload = {
1661
+ ...payload,
1662
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1663
+ values: this.serializeValuesForSubmission(
1664
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1665
+ payload.values ?? {}
1666
+ )
1667
+ };
1668
+ }
1669
+ }
1670
+ console.log("[Formanitor Event]", event, finalPayload);
1671
+ if (this.config.onEvent) {
1672
+ this.config.onEvent(event, finalPayload);
1673
+ }
1674
+ }
1675
+ /**
1676
+ * Update mutable config properties (callbacks, role) without recreating
1677
+ * the store. This allows the host app to pass fresh closures (e.g. an
1678
+ * upload handler whose auth token was initially undefined) after the
1679
+ * initial render.
1680
+ */
1681
+ updateConfig(next) {
1682
+ let needsReeval = false;
1683
+ if (next.onUpload !== void 0) this.config.onUpload = next.onUpload;
1684
+ if (next.onFileUpload !== void 0)
1685
+ this.config.onFileUpload = next.onFileUpload;
1686
+ if (next.onEvent !== void 0) this.config.onEvent = next.onEvent;
1687
+ if (next.onDraftChange !== void 0) this.config.onDraftChange = next.onDraftChange;
1688
+ if (next.voice !== void 0) this.config.voice = next.voice;
1689
+ if (next.role !== void 0 && next.role !== this.config.role) {
1690
+ this.config.role = next.role;
1691
+ needsReeval = true;
1692
+ }
1693
+ if (next.prefillData !== void 0 && next.prefillData !== this.prefillData) {
1694
+ this.prefillData = next.prefillData;
1695
+ this.state.values = applyPrefill(this.state.values, this.schema.fields, this.prefillData);
1696
+ needsReeval = true;
1697
+ }
1698
+ if (needsReeval) {
1699
+ this.evaluate();
1700
+ this.notify();
1701
+ }
1702
+ }
1703
+ getUploadHandler() {
1704
+ return this.config.onUpload;
1705
+ }
1706
+ /** Resolved handler for `file_upload` widgets: `onFileUpload` ?? `onUpload`. */
1707
+ getFileUploadHandler() {
1708
+ return this.config.onFileUpload ?? this.config.onUpload;
1709
+ }
1710
+ preSubmitHandlers = /* @__PURE__ */ new Map();
1711
+ registerPreSubmitHandler(fieldId, fn) {
1712
+ this.preSubmitHandlers.set(fieldId, fn);
1713
+ }
1714
+ unregisterPreSubmitHandler(fieldId) {
1715
+ this.preSubmitHandlers.delete(fieldId);
1716
+ }
1717
+ /**
1718
+ * Runs all registered pre-submit handlers in parallel.
1719
+ * Call this before getSerializedValuesForSubmission() to ensure any
1720
+ * unconfirmed signatures (or similar) are uploaded first.
1721
+ */
1722
+ async flushPendingUploads() {
1723
+ const handlers = Array.from(this.preSubmitHandlers.values());
1724
+ await Promise.all(handlers.map((fn) => fn()));
1725
+ }
1726
+ incrementPendingUploads() {
1727
+ this.state.pendingUploads = (this.state.pendingUploads ?? 0) + 1;
1728
+ this.notify();
1729
+ }
1730
+ decrementPendingUploads() {
1731
+ this.state.pendingUploads = Math.max(0, (this.state.pendingUploads ?? 0) - 1);
1732
+ this.notify();
1733
+ }
1734
+ // Mark that a submit attempt has been made so UI can decide when to show errors
1735
+ markSubmitAttempted() {
1736
+ if (!this.state.submitAttempted) {
1737
+ this.state.submitAttempted = true;
1738
+ this.notify();
1739
+ }
1740
+ }
1741
+ // --- Subscriptions ---
1742
+ subscribe(listener) {
1743
+ console.log("[FormStore] subscribe: adding listener. Total before add:", this.listeners.size);
1744
+ this.listeners.add(listener);
1745
+ return () => {
1746
+ console.log("[FormStore] unsubscribe: removing listener. Total before remove:", this.listeners.size);
1747
+ this.listeners.delete(listener);
1748
+ console.log("[FormStore] unsubscribe: removed listener. Total after remove:", this.listeners.size);
1749
+ };
1750
+ }
1751
+ notify() {
1752
+ console.log("[FormStore] notify: listener count", this.listeners.size, "state snapshot", {
1753
+ workflowState: this.state.workflowState,
1754
+ isValid: this.state.isValid
1755
+ });
1756
+ this.listeners.forEach((l) => l(this.state));
1757
+ }
1758
+ };
1759
+ var VoiceContext = createContext({
1760
+ activeFieldId: null,
1761
+ setActiveFieldId: () => {
1762
+ }
1763
+ });
1764
+ function useVoiceContext() {
1765
+ return useContext(VoiceContext);
1766
+ }
1767
+ function VoiceContextProvider({ children }) {
1768
+ const [activeFieldId, setActiveFieldIdState] = useState(null);
1769
+ const setActiveFieldId = useCallback((id) => {
1770
+ setActiveFieldIdState(id);
1771
+ }, []);
1772
+ return /* @__PURE__ */ jsx(VoiceContext.Provider, { value: { activeFieldId, setActiveFieldId }, children });
1773
+ }
1774
+ var FormContext = createContext(null);
1775
+ var emptyFrequentItems = {
1776
+ medications: [],
1777
+ investigations: [],
1778
+ procedures: []
1779
+ };
1780
+ var FrequentItemsContext = createContext(emptyFrequentItems);
1781
+ var PackagesContext = createContext([]);
1782
+ var FieldHandlersContext = createContext({});
1783
+ function useFieldHandlers(fieldId) {
1784
+ const map = useContext(FieldHandlersContext);
1785
+ return map[fieldId] ?? { onSearch: async () => [], recentlyUsed: [] };
1786
+ }
1787
+ function useFrequentItems() {
1788
+ return useContext(FrequentItemsContext);
1789
+ }
1790
+ function usePackages() {
1791
+ return useContext(PackagesContext);
1792
+ }
1793
+ var FormProvider = ({
1794
+ schema,
1795
+ config,
1796
+ fieldHandlers,
1797
+ frequentItems,
1798
+ packages,
1799
+ children
1800
+ }) => {
1801
+ const storeRef = useRef(null);
1802
+ if (!storeRef.current) {
1803
+ storeRef.current = new FormStore(schema, config);
1804
+ }
1805
+ useEffect(() => {
1806
+ if (storeRef.current && config) {
1807
+ storeRef.current.updateConfig(config);
1808
+ }
1809
+ });
1810
+ useEffect(() => {
1811
+ storeRef.current?.load();
1812
+ }, []);
1813
+ const frequentItemsValue = frequentItems ?? emptyFrequentItems;
1814
+ return /* @__PURE__ */ jsx(FormContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsx(FieldHandlersContext.Provider, { value: fieldHandlers ?? {}, children: /* @__PURE__ */ jsx(FrequentItemsContext.Provider, { value: frequentItemsValue, children: /* @__PURE__ */ jsx(PackagesContext.Provider, { value: packages ?? [], children: /* @__PURE__ */ jsx(VoiceContextProvider, { children }) }) }) }) });
1815
+ };
1816
+ function useFormStore() {
1817
+ const context = useContext(FormContext);
1818
+ if (!context) {
1819
+ throw new Error("useFormStore must be used within a FormProvider");
1820
+ }
1821
+ return context;
1822
+ }
1823
+ function useField(fieldId) {
1824
+ const store = useFormStore();
1825
+ const [fieldState, setFieldState] = useState(store.getFieldState(fieldId));
1826
+ useEffect(() => {
1827
+ const unsubscribe = store.subscribe(() => {
1828
+ const newState = store.getFieldState(fieldId);
1829
+ setFieldState((prev) => {
1830
+ if (prev.value === newState.value && prev.error === newState.error && prev.touched === newState.touched && prev.visible === newState.visible && prev.disabled === newState.disabled) {
1831
+ return prev;
1832
+ }
1833
+ return newState;
1834
+ });
1835
+ });
1836
+ return unsubscribe;
1837
+ }, [store, fieldId]);
1838
+ const setValue = (val) => store.setValue(fieldId, val);
1839
+ const setTouched = () => store.setTouched(fieldId);
1840
+ const fieldDef = store.getFieldDef(fieldId);
1841
+ return {
1842
+ ...fieldState,
1843
+ fieldDef,
1844
+ setValue,
1845
+ setTouched
1846
+ };
1847
+ }
1848
+
1849
+ export { ALL_GS_CONDITIONS, EMAIL_REGEX, FormProvider, FormStore, GS_CONDITION_LABELS, GS_GRADE_DESCRIPTIONS, composeProseAndChips, evaluateRules, formatGeneralSurgeryExaminationToNarrative, formatGeneralSurgerySmartHistoryToNarrative, formatUrologyExaminationToClinicalExamination, formatUrologySmartHistoryToSymptomsNarrative, getSuggestionTokensFromText, gsGradingRowVisible, normalizeGeneralSurgeryExamination, normalizeGeneralSurgeryGrading, normalizeGeneralSurgerySmartHistory, normalizePainScaleFlags, normalizeUrologyExamination, normalizeUrologyPathway, normalizeUrologySmartHistory, parseProseAndChipTokens, toggleSuggestionTokenInText, urologyIpssBand, urologyIpssSevenTotal, useField, useFieldHandlers, useFormStore, useFrequentItems, usePackages, useVoiceContext, validateField, validateForm };
1850
+ //# sourceMappingURL=chunk-ZGPHTPAN.mjs.map
1851
+ //# sourceMappingURL=chunk-ZGPHTPAN.mjs.map