formanitor 0.0.41 → 0.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -30,326 +30,6 @@ function fieldMetaRequestsMarMedicationOrdersButton(meta) {
30
30
  return meta[MAR_MEDICATION_ORDERS_META_KEY] === true;
31
31
  }
32
32
 
33
- // src/core/urologyClinicalPathwayMetadata.ts
34
- function isUrologyTemplate(template) {
35
- if (!template || typeof template !== "object") return false;
36
- const t = template;
37
- return t.meta?.specialization === "urology" || t.specialization === "urology" || t.id === "uro_prescription";
38
- }
39
- function shouldFoldUrologyClinicalPathway(template, payload) {
40
- if (isUrologyTemplate(template)) return true;
41
- return Object.keys(payload).some(
42
- (k) => k.startsWith("uro_") || k === "chief_complaints" || k === "chief_complaints_chips"
43
- );
44
- }
45
- var UROLOGY_EXTRA_METADATA_KEYS = ["symptoms", "notes", "vitals"];
46
- function isUroPlainObject(v) {
47
- return v !== null && typeof v === "object" && !Array.isArray(v);
48
- }
49
- function coerceUrologyBool(value) {
50
- if (value === true || value === false) return value;
51
- if (value === "true") return true;
52
- if (value === "false") return false;
53
- if (Array.isArray(value)) {
54
- if (value.includes("yes") || value.includes(true)) return true;
55
- if (value.length === 0) return false;
56
- }
57
- return value;
58
- }
59
- function urologyIpssItem(v) {
60
- const n = Number(v);
61
- if (!Number.isFinite(n)) return 0;
62
- return Math.min(5, Math.max(0, Math.round(n)));
63
- }
64
- function urologyIpssSevenTotalFromParts(ipss) {
65
- return ipss.incomplete + ipss.frequency + ipss.intermittency + ipss.urgency + ipss.weakStream + ipss.straining + ipss.nocturia;
66
- }
67
- function buildUrologySmartHistoryFromLegacyFlat(flat) {
68
- return {
69
- socrates_pain_flank_suprapubic: {
70
- site: flat.uro_sh_socrates_site,
71
- onset: flat.uro_sh_socrates_onset,
72
- character: flat.uro_sh_socrates_character,
73
- radiation: flat.uro_sh_socrates_radiation,
74
- associated: flat.uro_sh_socrates_associated,
75
- timing: flat.uro_sh_socrates_timing,
76
- exacerbating: flat.uro_sh_socrates_exacerbating,
77
- severity_0_to_10: flat.uro_sh_socrates_severity
78
- },
79
- ipss_international_symptom_score: {
80
- incomplete_emptying: flat.uro_sh_ipss_incomplete,
81
- frequency_lt_2h: flat.uro_sh_ipss_frequency,
82
- intermittency: flat.uro_sh_ipss_intermittency,
83
- urgency: flat.uro_sh_ipss_urgency,
84
- weak_stream: flat.uro_sh_ipss_weak_stream,
85
- straining: flat.uro_sh_ipss_straining,
86
- nocturia: flat.uro_sh_ipss_nocturia,
87
- qol_0_to_6: flat.uro_sh_ipss_qol,
88
- seven_item_total_computed_max_35: flat.uro_sh_ipss_total_display
89
- },
90
- haematuria_profile: {
91
- present: flat.uro_sh_haem_present,
92
- timing: flat.uro_sh_haem_timing,
93
- painful: flat.uro_sh_haem_painful,
94
- clots: flat.uro_sh_haem_clots
95
- },
96
- sexual_and_reproductive: {
97
- erectile_function: flat.uro_sh_sex_erectile,
98
- libido: flat.uro_sh_sex_libido,
99
- ejaculation: flat.uro_sh_sex_ejaculation,
100
- infertility_months: flat.uro_sh_sex_infertility_months
101
- },
102
- context_prior_stones_surgery_comorbidity: flat.uro_smart_notes_free ?? ""
103
- };
104
- }
105
- function buildUrologySmartHistoryFromComposite(raw, flat) {
106
- const soc = isUroPlainObject(raw.socrates) ? raw.socrates : {};
107
- const ipssIn = isUroPlainObject(raw.ipss) ? raw.ipss : {};
108
- const haem = isUroPlainObject(raw.haematuria) ? raw.haematuria : {};
109
- const sex = isUroPlainObject(raw.sexual) ? raw.sexual : {};
110
- const str2 = (x) => typeof x === "string" ? x : "";
111
- const sev = Number(soc.severity);
112
- const severity0to10 = Number.isFinite(sev) ? Math.min(10, Math.max(0, Math.round(sev))) : 0;
113
- const ipss = {
114
- incomplete: urologyIpssItem(ipssIn.incomplete),
115
- frequency: urologyIpssItem(ipssIn.frequency),
116
- intermittency: urologyIpssItem(ipssIn.intermittency),
117
- urgency: urologyIpssItem(ipssIn.urgency),
118
- weakStream: urologyIpssItem(
119
- ipssIn.weakStream ?? ipssIn.weak_stream
120
- ),
121
- straining: urologyIpssItem(ipssIn.straining),
122
- nocturia: urologyIpssItem(ipssIn.nocturia)
123
- };
124
- const sevenTotal = urologyIpssSevenTotalFromParts(ipss);
125
- const qolRaw = Number(raw.ipssQoL);
126
- const qol = Number.isFinite(qolRaw) ? Math.min(6, Math.max(0, Math.round(qolRaw))) : 0;
127
- const timingRaw = str2(haem.timing);
128
- const pastNotes = str2(raw.pastNotes);
129
- const legacyNotes = flat.uro_smart_notes_free;
130
- const contextPrior = pastNotes !== "" ? pastNotes : typeof legacyNotes === "string" ? legacyNotes : legacyNotes ?? "";
131
- const infertilityRaw = Number(sex.infertilityMonths);
132
- const infertilityMonths = Number.isFinite(infertilityRaw) ? Math.max(0, Math.round(infertilityRaw)) : 0;
133
- return {
134
- socrates_pain_flank_suprapubic: {
135
- site: str2(soc.site),
136
- onset: str2(soc.onset),
137
- character: str2(soc.character),
138
- radiation: str2(soc.radiation),
139
- associated: str2(soc.associated),
140
- timing: str2(soc.timing),
141
- exacerbating: str2(soc.exacerbating),
142
- severity_0_to_10: severity0to10
143
- },
144
- ipss_international_symptom_score: {
145
- incomplete_emptying: ipss.incomplete,
146
- frequency_lt_2h: ipss.frequency,
147
- intermittency: ipss.intermittency,
148
- urgency: ipss.urgency,
149
- weak_stream: ipss.weakStream,
150
- straining: ipss.straining,
151
- nocturia: ipss.nocturia,
152
- qol_0_to_6: qol,
153
- seven_item_total_computed_max_35: sevenTotal
154
- },
155
- haematuria_profile: {
156
- present: haem.present === true,
157
- timing: timingRaw,
158
- painful: haem.painful === true,
159
- clots: haem.clots === true
160
- },
161
- sexual_and_reproductive: {
162
- erectile_function: str2(sex.erectile),
163
- libido: str2(sex.libido),
164
- ejaculation: str2(sex.ejaculation),
165
- infertility_months: infertilityMonths
166
- },
167
- context_prior_stones_surgery_comorbidity: contextPrior
168
- };
169
- }
170
- function buildUrologyExaminationFromLegacyFlat(flat) {
171
- return {
172
- general: {
173
- pallor: flat.uro_ex_general_pallor,
174
- oedema: flat.uro_ex_general_oedema,
175
- icterus: flat.uro_ex_general_icterus,
176
- lymphadenopathy: flat.uro_ex_general_lymph
177
- },
178
- abdomen: {
179
- distension: flat.uro_ex_ab_distension,
180
- surgical_scars: flat.uro_ex_ab_scars,
181
- visible_mass: flat.uro_ex_ab_mass,
182
- kidney_palpable: flat.uro_ex_ab_kidney_palpable,
183
- bladder_palpable: flat.uro_ex_ab_bladder_palpable,
184
- renal_angle_tender: flat.uro_ex_ab_renal_angle_tender,
185
- bladder_dullness_retention: flat.uro_ex_ab_bladder_dull
186
- },
187
- external_genitalia_scrotum: {
188
- meatus_abnormal_hypospadias_query: flat.uro_ex_gu_meatus,
189
- phimosis_paraphimosis: flat.uro_ex_gu_phimosis,
190
- penile_lesions_ulcers: flat.uro_ex_gu_lesions,
191
- scrotal_swelling: flat.uro_ex_gu_scrotal_swelling,
192
- transilluminant: flat.uro_ex_gu_transilluminant,
193
- tender_testis: flat.uro_ex_gu_tender_testis,
194
- hydrocele: flat.uro_ex_gu_hydrocele,
195
- varicocele: flat.uro_ex_gu_varicocele,
196
- suspicious_mass_urgent: flat.uro_ex_gu_suspicious_mass
197
- },
198
- dre_prostate: {
199
- dre_performed: flat.uro_ex_dre_done,
200
- estimated_size_grams: flat.uro_ex_dre_size_g,
201
- consistency: flat.uro_ex_dre_consistency,
202
- median_sulcus: flat.uro_ex_dre_median_sulcus,
203
- tenderness_prostatitis_query: flat.uro_ex_dre_tender
204
- },
205
- examination_notes_free_text: flat.uro_examination_notes ?? ""
206
- };
207
- }
208
- function buildUrologyExaminationFromComposite(raw) {
209
- const regions = Array.isArray(raw.regions) ? raw.regions.filter((x) => typeof x === "string") : [];
210
- const g = isUroPlainObject(raw.general) ? raw.general : {};
211
- const ab = isUroPlainObject(raw.abdomen) ? raw.abdomen : {};
212
- const gu = isUroPlainObject(raw.gu) ? raw.gu : {};
213
- const dr = isUroPlainObject(raw.dre) ? raw.dre : {};
214
- const dreConsistency = typeof dr.consistency === "string" ? dr.consistency : "";
215
- const dreMedian = typeof dr.medianSulcus === "string" ? dr.medianSulcus : typeof dr.median_sulcus === "string" ? dr.median_sulcus : "";
216
- const sizeG = Number(dr.sizeGrams);
217
- const sizeGrams = Number.isFinite(sizeG) && sizeG >= 0 ? Math.round(sizeG) : 0;
218
- const notesRaw = raw.examinationNotes;
219
- const notes = typeof notesRaw === "string" ? notesRaw : "";
220
- const bool = (x) => x === true;
221
- return {
222
- regions_reviewed: regions,
223
- general: {
224
- pallor: bool(g.pallor),
225
- oedema: bool(g.oedema),
226
- icterus: bool(g.icterus),
227
- lymphadenopathy: bool(g.lymphadenopathy)
228
- },
229
- abdomen: {
230
- distension: bool(ab.distension),
231
- surgical_scars: bool(ab.scars),
232
- visible_mass: bool(ab.mass),
233
- kidney_palpable: bool(ab.kidneyPalpable),
234
- bladder_palpable: bool(ab.bladderPalpable),
235
- renal_angle_tender: bool(ab.renalAngleTender),
236
- bladder_dullness_retention: bool(ab.bladderDull)
237
- },
238
- external_genitalia_scrotum: {
239
- meatus_abnormal_hypospadias_query: bool(gu.meatusAbnormal),
240
- phimosis_paraphimosis: bool(gu.phimosis),
241
- penile_lesions_ulcers: bool(gu.lesions),
242
- scrotal_swelling: bool(gu.scrotalSwelling),
243
- transilluminant: bool(gu.transilluminant),
244
- tender_testis: bool(gu.tenderTestis),
245
- hydrocele: bool(gu.hydrocele),
246
- varicocele: bool(gu.varicocele),
247
- suspicious_mass_urgent: bool(gu.suspiciousMass)
248
- },
249
- dre_prostate: {
250
- dre_performed: bool(dr.done),
251
- estimated_size_grams: sizeGrams,
252
- consistency: dreConsistency === "none" ? "" : dreConsistency,
253
- median_sulcus: dreMedian === "none" ? "" : dreMedian,
254
- tenderness_prostatitis_query: bool(dr.tenderness)
255
- },
256
- examination_notes_free_text: notes
257
- };
258
- }
259
- function buildUrologyDecisionEngineAndRisk(flat) {
260
- const comp = flat.uro_pathway;
261
- if (isUroPlainObject(comp)) {
262
- const riskIn = isUroPlainObject(comp.risk) ? comp.risk : {};
263
- return {
264
- decision_engine: {
265
- stone_disease: {
266
- stone_size_mm: comp.stoneSizeMm ?? 0,
267
- hydronephrosis_on_imaging: coerceUrologyBool(comp.hydronephrosis)
268
- },
269
- prostate_bph_flow_lab: {
270
- prostate_volume_cc: comp.prostateVolumeCc ?? 0,
271
- psa_ng_ml: comp.psaNgMl ?? 0,
272
- uroflow_qmax_ml_s: comp.uroflowQmaxMlSec ?? 0
273
- }
274
- },
275
- risk_stratification: {
276
- ckd: coerceUrologyBool(riskIn.ckd),
277
- diabetes: coerceUrologyBool(riskIn.diabetes),
278
- anticoagulants: coerceUrologyBool(riskIn.anticoagulants),
279
- active_infection: coerceUrologyBool(riskIn.activeInfection)
280
- }
281
- };
282
- }
283
- return {
284
- decision_engine: {
285
- stone_disease: {
286
- stone_size_mm: flat.uro_stone_size_mm,
287
- hydronephrosis_on_imaging: coerceUrologyBool(flat.uro_stone_hydronephrosis)
288
- },
289
- prostate_bph_flow_lab: {
290
- prostate_volume_cc: flat.uro_prostate_vol_cc,
291
- psa_ng_ml: flat.uro_psa_ng_ml,
292
- uroflow_qmax_ml_s: flat.uro_flow_qmax
293
- }
294
- },
295
- risk_stratification: {
296
- ckd: coerceUrologyBool(flat.uro_risk_ckd),
297
- diabetes: coerceUrologyBool(flat.uro_risk_diabetes),
298
- anticoagulants: coerceUrologyBool(flat.uro_risk_anticoag),
299
- active_infection: coerceUrologyBool(flat.uro_risk_active_infection)
300
- }
301
- };
302
- }
303
- function attachUrologyClinicalPathwayMetadata(payload, template) {
304
- if (!shouldFoldUrologyClinicalPathway(template, payload)) return;
305
- const extra = UROLOGY_EXTRA_METADATA_KEYS;
306
- const keys = Object.keys(payload).filter(
307
- (k) => k.startsWith("uro_") || k === "chief_complaints" || k === "chief_complaints_chips" || extra.includes(k)
308
- );
309
- if (keys.length === 0) return;
310
- const flat = {};
311
- for (const k of keys) {
312
- flat[k] = payload[k];
313
- delete payload[k];
314
- }
315
- const triage = flat.uro_triage;
316
- const chiefComplaints = {
317
- narrative: flat.chief_complaints ?? "",
318
- chips: flat.chief_complaints_chips ?? [],
319
- duration: flat.uro_chief_duration ?? "",
320
- onset: flat.uro_chief_onset ?? "",
321
- progression: flat.uro_chief_progression ?? "",
322
- active_syndrome: flat.uro_active_syndrome ?? ""
323
- };
324
- const compositeSh = flat.uro_smart_history;
325
- const compositeEx = flat.uro_examination;
326
- const smart_history = isUroPlainObject(compositeSh) ? buildUrologySmartHistoryFromComposite(compositeSh, flat) : buildUrologySmartHistoryFromLegacyFlat(flat);
327
- const examination = isUroPlainObject(compositeEx) ? buildUrologyExaminationFromComposite(compositeEx) : buildUrologyExaminationFromLegacyFlat(flat);
328
- const { decision_engine, risk_stratification } = buildUrologyDecisionEngineAndRisk(flat);
329
- const structured = {
330
- triage_vas_and_flags: triage ?? null,
331
- chief_complaint_engine: chiefComplaints,
332
- smart_history,
333
- examination,
334
- pathway_triggers_objective_scores: decision_engine,
335
- risk_stratification,
336
- encounter_narrative: {
337
- symptoms: flat.symptoms ?? "",
338
- notes: flat.notes ?? "",
339
- vitals: flat.vitals ?? ""
340
- }
341
- };
342
- const existingMetadata = payload.metadata ?? {};
343
- const existingPathways = existingMetadata.clinical_pathways ?? {};
344
- payload.metadata = {
345
- ...existingMetadata,
346
- clinical_pathways: {
347
- ...existingPathways,
348
- urology: structured
349
- }
350
- };
351
- }
352
-
353
33
  // src/core/validate.ts
354
34
  var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
355
35
  function validateField(value, field) {
@@ -422,12 +102,82 @@ function validateForm(values, fields) {
422
102
  return errors;
423
103
  }
424
104
 
105
+ // src/core/suggestionTokens.ts
106
+ function getSuggestionTokensFromText(text) {
107
+ return text.split(/[,\n\r]+/).map((p) => p.trim()).filter(Boolean);
108
+ }
109
+ var DEFAULT_TOKEN_SEPARATOR = ", ";
110
+ function commaSplitLine(line) {
111
+ return line.split(",").map((p) => p.trim()).filter(Boolean);
112
+ }
113
+ function dedupeChipOrder(chips) {
114
+ const seen = /* @__PURE__ */ new Set();
115
+ const out = [];
116
+ for (const c of chips) {
117
+ if (seen.has(c)) continue;
118
+ seen.add(c);
119
+ out.push(c);
120
+ }
121
+ return out;
122
+ }
123
+ function parseProseAndChipTokens(text, allowed) {
124
+ const normalized = text.replace(/\r\n/g, "\n");
125
+ let lines = normalized.split("\n");
126
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
127
+ lines.pop();
128
+ }
129
+ if (lines.length === 0) return { prose: "", chips: [] };
130
+ let chipStart = lines.length;
131
+ for (let i = lines.length - 1; i >= 0; i--) {
132
+ const toks = commaSplitLine(lines[i]);
133
+ if (toks.length === 0) break;
134
+ if (!toks.every((t) => allowed.has(t))) break;
135
+ chipStart = i;
136
+ }
137
+ if (chipStart < lines.length) {
138
+ const chipLines = lines.slice(chipStart);
139
+ const prose = lines.slice(0, chipStart).join("\n");
140
+ const chips = dedupeChipOrder(chipLines.flatMap(commaSplitLine));
141
+ return { prose, chips };
142
+ }
143
+ const parts = getSuggestionTokensFromText(normalized);
144
+ const chipsFallback = dedupeChipOrder(parts.filter((t) => allowed.has(t)));
145
+ const proseParts = parts.filter((t) => !allowed.has(t));
146
+ return { prose: proseParts.join(DEFAULT_TOKEN_SEPARATOR), chips: chipsFallback };
147
+ }
148
+ function composeProseAndChips(prose, chips, separator = DEFAULT_TOKEN_SEPARATOR) {
149
+ const chipStr = dedupeChipOrder(chips).join(separator);
150
+ const p = prose.replace(/\s+$/, "");
151
+ if (p && chipStr) return `${p}
152
+ ${chipStr}`;
153
+ if (chipStr) return chipStr;
154
+ return p;
155
+ }
156
+ function toggleSuggestionTokenInText(current, token, separator = DEFAULT_TOKEN_SEPARATOR, allowedChipValues) {
157
+ const t = String(token).trim();
158
+ if (!t) return current;
159
+ if (!allowedChipValues || allowedChipValues.size === 0) {
160
+ const parts = getSuggestionTokensFromText(current);
161
+ const i = parts.findIndex((p) => p === t);
162
+ if (i >= 0) parts.splice(i, 1);
163
+ else parts.push(t);
164
+ return parts.join(separator);
165
+ }
166
+ if (!allowedChipValues.has(t)) return current;
167
+ let { prose, chips } = parseProseAndChipTokens(current, allowedChipValues);
168
+ const idx = chips.indexOf(t);
169
+ if (idx >= 0) chips.splice(idx, 1);
170
+ else chips.push(t);
171
+ chips = dedupeChipOrder(chips);
172
+ return composeProseAndChips(prose, chips, separator);
173
+ }
174
+
425
175
  // src/core/rules.ts
426
176
  function tokenListContainsString(text, needle) {
427
177
  if (typeof text !== "string") return false;
428
178
  const want = String(needle).trim();
429
179
  if (!want) return false;
430
- const parts = text.split(",").map((p) => p.trim()).filter(Boolean);
180
+ const parts = getSuggestionTokensFromText(text);
431
181
  return parts.includes(want);
432
182
  }
433
183
  function evaluateCondition(value, condition) {
@@ -638,7 +388,7 @@ function normalizeGeneralSurgerySmartHistory(raw) {
638
388
  ...painIn,
639
389
  severity: Number.isFinite(sev) ? Math.min(10, Math.max(0, Math.round(sev))) : d.pain.severity
640
390
  };
641
- const str2 = (x) => typeof x === "string" ? x : "";
391
+ const str4 = (x) => typeof x === "string" ? x : "";
642
392
  const strArr = (x) => Array.isArray(x) ? x.filter((i) => typeof i === "string") : [];
643
393
  const swell = v.swelling && typeof v.swelling === "object" ? v.swelling : {};
644
394
  const gi = v.gi && typeof v.gi === "object" ? v.gi : {};
@@ -648,44 +398,44 @@ function normalizeGeneralSurgerySmartHistory(raw) {
648
398
  return {
649
399
  pain,
650
400
  swelling: {
651
- duration: str2(swell.duration),
652
- sizeProgression: str2(swell.sizeProgression),
653
- painfulPainless: str2(swell.painfulPainless),
401
+ duration: str4(swell.duration),
402
+ sizeProgression: str4(swell.sizeProgression),
403
+ painfulPainless: str4(swell.painfulPainless),
654
404
  checks: strArr(swell.checks)
655
405
  },
656
406
  gi: {
657
- appetite: str2(gi.appetite),
658
- weightLoss: str2(gi.weightLoss),
659
- bowelHabits: str2(gi.bowelHabits),
407
+ appetite: str4(gi.appetite),
408
+ weightLoss: str4(gi.weightLoss),
409
+ bowelHabits: str4(gi.bowelHabits),
660
410
  checks: strArr(gi.checks)
661
411
  },
662
412
  breast: {
663
- lumpDuration: str2(breast.lumpDuration),
664
- nippleDischarge: str2(breast.nippleDischarge),
413
+ lumpDuration: str4(breast.lumpDuration),
414
+ nippleDischarge: str4(breast.nippleDischarge),
665
415
  checks: strArr(breast.checks)
666
416
  },
667
417
  ano: { checks: strArr(ano.checks) },
668
418
  thyroid: {
669
- swellingDuration: str2(thy.swellingDuration),
419
+ swellingDuration: str4(thy.swellingDuration),
670
420
  checks: strArr(thy.checks)
671
421
  }
672
422
  };
673
423
  }
674
424
  function normalizeBreastExamSide(partial) {
675
425
  const e = defaultBreastExamSide();
676
- const str2 = (x) => typeof x === "string" ? x : "";
677
- const bool = (x) => x === true;
426
+ const str4 = (x) => typeof x === "string" ? x : "";
427
+ const bool2 = (x) => x === true;
678
428
  if (!partial || typeof partial !== "object") return e;
679
429
  const p = partial;
680
430
  return {
681
- size: str2(p.size),
682
- quadrant: str2(p.quadrant),
683
- tenderness: str2(p.tenderness),
684
- fixity: str2(p.fixity),
685
- skinInvolvement: str2(p.skinInvolvement),
686
- consistency: str2(p.consistency),
687
- axillaryNodes: bool(p.axillaryNodes),
688
- nippleAreolarComplex: bool(p.nippleAreolarComplex)
431
+ size: str4(p.size),
432
+ quadrant: str4(p.quadrant),
433
+ tenderness: str4(p.tenderness),
434
+ fixity: str4(p.fixity),
435
+ skinInvolvement: str4(p.skinInvolvement),
436
+ consistency: str4(p.consistency),
437
+ axillaryNodes: bool2(p.axillaryNodes),
438
+ nippleAreolarComplex: bool2(p.nippleAreolarComplex)
689
439
  };
690
440
  }
691
441
  function normalizeBreastExaminationBlock(raw) {
@@ -709,7 +459,7 @@ function normalizeGeneralSurgeryExamination(raw) {
709
459
  const th = v.thyroid && typeof v.thyroid === "object" ? v.thyroid : {};
710
460
  const ar = v.anorectal && typeof v.anorectal === "object" ? v.anorectal : {};
711
461
  const lm = v.limb && typeof v.limb === "object" ? v.limb : {};
712
- const bool = (x) => x === true;
462
+ const bool2 = (x) => x === true;
713
463
  return {
714
464
  general: strArr(v.general),
715
465
  regions: strArr(v.regions),
@@ -722,27 +472,27 @@ function normalizeGeneralSurgeryExamination(raw) {
722
472
  },
723
473
  hernia: {
724
474
  site: typeof hb.site === "string" ? hb.site : "",
725
- reducible: bool(hb.reducible),
726
- coughImpulse: bool(hb.coughImpulse),
727
- tenderness: bool(hb.tenderness)
475
+ reducible: bool2(hb.reducible),
476
+ coughImpulse: bool2(hb.coughImpulse),
477
+ tenderness: bool2(hb.tenderness)
728
478
  },
729
479
  breast: normalizeBreastExaminationBlock(br),
730
480
  thyroid: {
731
481
  size: typeof th.size === "string" ? th.size : "",
732
- mobileDeglutition: bool(th.mobileDeglutition),
733
- nodules: bool(th.nodules),
734
- cervicalNodes: bool(th.cervicalNodes)
482
+ mobileDeglutition: bool2(th.mobileDeglutition),
483
+ nodules: bool2(th.nodules),
484
+ cervicalNodes: bool2(th.cervicalNodes)
735
485
  },
736
486
  anorectal: {
737
487
  inspection: typeof ar.inspection === "string" ? ar.inspection : "",
738
- dreDone: bool(ar.dreDone),
739
- proctoscopyDone: bool(ar.proctoscopyDone),
488
+ dreDone: bool2(ar.dreDone),
489
+ proctoscopyDone: bool2(ar.proctoscopyDone),
740
490
  notes: typeof ar.notes === "string" ? ar.notes : ""
741
491
  },
742
492
  limb: {
743
- dilatedVeins: bool(lm.dilatedVeins),
744
- ulcer: bool(lm.ulcer),
745
- oedema: bool(lm.oedema)
493
+ dilatedVeins: bool2(lm.dilatedVeins),
494
+ ulcer: bool2(lm.ulcer),
495
+ oedema: bool2(lm.oedema)
746
496
  }
747
497
  };
748
498
  }
@@ -1019,70 +769,102 @@ function formatGeneralSurgeryExaminationToNarrative(v) {
1019
769
  }
1020
770
 
1021
771
  // src/core/urologyPathway.ts
1022
- var defaultUrologySmartHistory = () => ({
1023
- socrates: {
1024
- site: "",
1025
- onset: "",
1026
- character: "",
1027
- radiation: "",
1028
- associated: "",
1029
- timing: "",
1030
- exacerbating: "",
1031
- severity: 0
1032
- },
1033
- ipss: {
1034
- incomplete: 0,
1035
- frequency: 0,
1036
- intermittency: 0,
1037
- urgency: 0,
1038
- weakStream: 0,
1039
- straining: 0,
1040
- nocturia: 0
1041
- },
1042
- ipssQoL: 0,
1043
- haematuria: { present: false, timing: "", painful: false, clots: false },
1044
- sexual: { erectile: "", libido: "", ejaculation: "", infertilityMonths: 0 },
1045
- pastNotes: ""
1046
- });
1047
- var defaultUrologyExamination = () => ({
1048
- regions: [],
1049
- general: { pallor: false, oedema: false, icterus: false, lymphadenopathy: false },
1050
- abdomen: {
1051
- distension: false,
1052
- scars: false,
1053
- mass: false,
1054
- kidneyPalpable: false,
1055
- bladderPalpable: false,
1056
- renalAngleTender: false,
1057
- bladderDull: false
1058
- },
1059
- gu: {
1060
- meatusAbnormal: false,
1061
- phimosis: false,
1062
- lesions: false,
1063
- scrotalSwelling: false,
1064
- transilluminant: false,
1065
- tenderTestis: false,
1066
- hydrocele: false,
1067
- varicocele: false,
1068
- suspiciousMass: false
1069
- },
1070
- dre: { done: false, sizeGrams: 0, consistency: "", medianSulcus: "", tenderness: false },
1071
- examinationNotes: ""
1072
- });
1073
- function clampInt(n, lo, hi) {
1074
- if (!Number.isFinite(n)) return lo;
1075
- return Math.min(hi, Math.max(lo, Math.round(n)));
772
+ function defaultUrologySmartHistory() {
773
+ return {
774
+ socrates: {
775
+ site: "",
776
+ onset: "",
777
+ character: "",
778
+ radiation: "",
779
+ associated: "",
780
+ timing: "",
781
+ exacerbating: "",
782
+ severity: 0
783
+ },
784
+ ipss: {
785
+ incomplete: 0,
786
+ frequency: 0,
787
+ intermittency: 0,
788
+ urgency: 0,
789
+ weakStream: 0,
790
+ straining: 0,
791
+ nocturia: 0
792
+ },
793
+ ipssQoL: 0,
794
+ haematuria: {
795
+ present: false,
796
+ timing: "none",
797
+ painful: false,
798
+ clots: false
799
+ },
800
+ sexual: {
801
+ erectile: "none",
802
+ libido: "none",
803
+ ejaculation: "none",
804
+ infertilityMonths: 0
805
+ },
806
+ pastNotes: ""
807
+ };
808
+ }
809
+ function defaultUrologyExamination() {
810
+ return {
811
+ regions: [],
812
+ general: {
813
+ pallor: false,
814
+ oedema: false,
815
+ icterus: false,
816
+ lymphadenopathy: false
817
+ },
818
+ abdomen: {
819
+ distension: false,
820
+ scars: false,
821
+ mass: false,
822
+ kidneyPalpable: false,
823
+ bladderPalpable: false,
824
+ renalAngleTender: false,
825
+ bladderDull: false
826
+ },
827
+ gu: {
828
+ meatusAbnormal: false,
829
+ phimosis: false,
830
+ lesions: false,
831
+ scrotalSwelling: false,
832
+ transilluminant: false,
833
+ tenderTestis: false,
834
+ hydrocele: false,
835
+ varicocele: false,
836
+ suspiciousMass: false
837
+ },
838
+ dre: {
839
+ done: false,
840
+ sizeGrams: 0,
841
+ consistency: "",
842
+ medianSulcus: "",
843
+ tenderness: false
844
+ },
845
+ examinationNotes: ""
846
+ };
847
+ }
848
+ function defaultUrologyPathway() {
849
+ return {
850
+ stoneSizeMm: 0,
851
+ hydronephrosis: false,
852
+ prostateVolumeCc: 0,
853
+ psaNgMl: 0,
854
+ uroflowQmaxMlSec: 0,
855
+ risk: {
856
+ ckd: false,
857
+ diabetes: false,
858
+ anticoagulants: false,
859
+ activeInfection: false
860
+ }
861
+ };
862
+ }
863
+ function ipssItem(v) {
864
+ const n = Number(v);
865
+ if (!Number.isFinite(n)) return 0;
866
+ return Math.min(5, Math.max(0, Math.round(n)));
1076
867
  }
1077
- var SOCRATES_KEYS = [
1078
- "site",
1079
- "onset",
1080
- "character",
1081
- "radiation",
1082
- "associated",
1083
- "timing",
1084
- "exacerbating"
1085
- ];
1086
868
  function urologyIpssSevenTotal(ipss) {
1087
869
  return ipss.incomplete + ipss.frequency + ipss.intermittency + ipss.urgency + ipss.weakStream + ipss.straining + ipss.nocturia;
1088
870
  }
@@ -1091,159 +873,269 @@ function urologyIpssBand(total) {
1091
873
  if (total <= 19) return "Moderate";
1092
874
  return "Severe";
1093
875
  }
876
+ function hopcMeaningfulStr(x) {
877
+ if (typeof x !== "string") return "";
878
+ const t = x.trim();
879
+ if (!t || t.toLowerCase() === "none") return "";
880
+ return t;
881
+ }
882
+ function hopcNonEmpty(lines) {
883
+ return lines.map((s) => (s ?? "").trim()).filter(Boolean);
884
+ }
885
+ function formatUrologySmartHistoryToSymptomsNarrative(safe, flags) {
886
+ const { showPain, showLUTS, showHaem, showSex } = flags;
887
+ const parts = [];
888
+ if (showPain) {
889
+ const s = safe.socrates;
890
+ const painLines = hopcNonEmpty([
891
+ s.site ? `Site: ${s.site}` : "",
892
+ s.onset ? `Onset: ${s.onset}` : "",
893
+ s.character ? `Character: ${s.character}` : "",
894
+ s.radiation ? `Radiation: ${s.radiation}` : "",
895
+ s.associated ? `Associated: ${s.associated}` : "",
896
+ s.timing ? `Timing: ${s.timing}` : "",
897
+ s.exacerbating ? `Exacerbating/relieving: ${s.exacerbating}` : "",
898
+ `Severity: ${s.severity}/10`
899
+ ]);
900
+ const hasNonDefaultPain = painLines.length > 1 || painLines.length === 1 && painLines[0] !== "Severity: 0/10";
901
+ if (hasNonDefaultPain) parts.push(`SOCRATES:
902
+ ${painLines.join("\n")}`);
903
+ }
904
+ if (showLUTS) {
905
+ const ip = safe.ipss;
906
+ const any = ip.incomplete || ip.frequency || ip.intermittency || ip.urgency || ip.weakStream || ip.straining || ip.nocturia || safe.ipssQoL;
907
+ if (any) {
908
+ const ipssTotal = urologyIpssSevenTotal(safe.ipss);
909
+ const ipssBandLabel = urologyIpssBand(ipssTotal);
910
+ parts.push(`IPSS: ${ipssTotal}/35 (${ipssBandLabel}) \xB7 QoL ${safe.ipssQoL}/6`);
911
+ }
912
+ }
913
+ if (showHaem) {
914
+ const h = safe.haematuria;
915
+ const timing = hopcMeaningfulStr(h.timing);
916
+ const haemLines = hopcNonEmpty([
917
+ h.present ? "Haematuria present" : "",
918
+ timing ? `Timing: ${timing}` : "",
919
+ h.painful ? "Painful" : "",
920
+ h.clots ? "Clots" : ""
921
+ ]);
922
+ if (haemLines.length) parts.push(`Haematuria:
923
+ ${haemLines.join("\n")}`);
924
+ }
925
+ if (showSex) {
926
+ const sx = safe.sexual;
927
+ const infertility = sx.infertilityMonths;
928
+ const sexLines = hopcNonEmpty([
929
+ hopcMeaningfulStr(sx.erectile) ? `Erectile function: ${hopcMeaningfulStr(sx.erectile)}` : "",
930
+ hopcMeaningfulStr(sx.libido) ? `Libido: ${hopcMeaningfulStr(sx.libido)}` : "",
931
+ hopcMeaningfulStr(sx.ejaculation) ? `Ejaculation: ${hopcMeaningfulStr(sx.ejaculation)}` : "",
932
+ Number.isFinite(infertility) && infertility > 0 ? `Infertility: ${infertility} months` : ""
933
+ ]);
934
+ if (sexLines.length) parts.push(`Sexual/reproductive:
935
+ ${sexLines.join("\n")}`);
936
+ }
937
+ return parts.join("\n\n").trim();
938
+ }
939
+ function formatUrologyExaminationToClinicalExamination(v) {
940
+ const sections = [];
941
+ const generalLabels = {
942
+ pallor: "Pallor (chronic dz)",
943
+ oedema: "Oedema (renal)",
944
+ icterus: "Icterus",
945
+ lymphadenopathy: "Lymphadenopathy"
946
+ };
947
+ const general = Object.keys(generalLabels).filter((k) => v.general[k]).map((k) => generalLabels[k]);
948
+ if (general.length) sections.push(`General: ${general.join(", ")}`);
949
+ const abdLabels = {
950
+ distension: "Distension",
951
+ scars: "Surgical scars",
952
+ mass: "Visible mass",
953
+ kidneyPalpable: "Kidney palpable",
954
+ bladderPalpable: "Bladder palpable",
955
+ renalAngleTender: "Renal angle tender",
956
+ bladderDull: "Bladder dullness (retention)"
957
+ };
958
+ const abd = Object.keys(abdLabels).filter((k) => v.abdomen[k]).map((k) => abdLabels[k]);
959
+ if (abd.length) sections.push(`Abdomen: ${abd.join(", ")}`);
960
+ const guLabels = {
961
+ meatusAbnormal: "Meatus abnormal (hypospadias?)",
962
+ phimosis: "Phimosis / paraphimosis",
963
+ lesions: "Penile lesions / ulcers",
964
+ scrotalSwelling: "Scrotal swelling",
965
+ transilluminant: "Transilluminant",
966
+ tenderTestis: "Tender testis",
967
+ hydrocele: "Hydrocele",
968
+ varicocele: "Varicocele",
969
+ suspiciousMass: "Suspicious mass"
970
+ };
971
+ const gu = Object.keys(guLabels).filter((k) => v.gu[k]).map((k) => guLabels[k]);
972
+ if (gu.length) sections.push(`GU: ${gu.join(", ")}`);
973
+ const dreBits = hopcNonEmpty([
974
+ v.dre.done ? "Performed" : "",
975
+ v.dre.sizeGrams > 0 ? `Size ~${v.dre.sizeGrams} g` : "",
976
+ v.dre.consistency ? `Consistency: ${v.dre.consistency}` : "",
977
+ v.dre.medianSulcus ? `Median sulcus: ${v.dre.medianSulcus}` : "",
978
+ v.dre.tenderness ? "Tenderness" : ""
979
+ ]);
980
+ if (dreBits.length) sections.push(`DRE: ${dreBits.join(", ")}`);
981
+ return sections.join("\n");
982
+ }
983
+ function str(x) {
984
+ return typeof x === "string" ? x : "";
985
+ }
986
+ function bool(x) {
987
+ return x === true;
988
+ }
989
+ function nonNegInt(x) {
990
+ const n = Number(x);
991
+ if (!Number.isFinite(n)) return 0;
992
+ return Math.max(0, Math.round(n));
993
+ }
1094
994
  function normalizeUrologySmartHistory(raw) {
1095
- const d = defaultUrologySmartHistory();
1096
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
1097
- const v = raw;
1098
- const socIn = v.socrates && typeof v.socrates === "object" ? v.socrates : {};
1099
- const socrates = { ...d.socrates };
1100
- for (const k of SOCRATES_KEYS) {
1101
- const x = socIn[k];
1102
- socrates[k] = typeof x === "string" ? x : "";
1103
- }
1104
- const sev = Number(socIn.severity);
1105
- socrates.severity = clampInt(Number.isFinite(sev) ? sev : d.socrates.severity, 0, 10);
1106
- const ipIn = v.ipss && typeof v.ipss === "object" ? v.ipss : {};
1107
- const ipss = { ...d.ipss };
1108
- for (const k of [
1109
- "incomplete",
1110
- "frequency",
1111
- "intermittency",
1112
- "urgency",
1113
- "weakStream",
1114
- "straining",
1115
- "nocturia"
1116
- ]) {
1117
- const n = Number(ipIn[k]);
1118
- ipss[k] = clampInt(Number.isFinite(n) ? n : 0, 0, 5);
1119
- }
1120
- const qol = Number(v.ipssQoL);
1121
- const ipssQoL = clampInt(Number.isFinite(qol) ? qol : d.ipssQoL, 0, 6);
1122
- const haIn = v.haematuria && typeof v.haematuria === "object" ? v.haematuria : {};
1123
- const timingRaw = haIn.timing;
995
+ const base = defaultUrologySmartHistory();
996
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
997
+ const r = raw;
998
+ const soc = r.socrates && typeof r.socrates === "object" && !Array.isArray(r.socrates) ? r.socrates : {};
999
+ const socObj = soc;
1000
+ const sev = Number(socObj.severity);
1001
+ const socrates = {
1002
+ site: str(socObj.site),
1003
+ onset: str(socObj.onset),
1004
+ character: str(socObj.character),
1005
+ radiation: str(socObj.radiation),
1006
+ associated: str(socObj.associated),
1007
+ timing: str(socObj.timing),
1008
+ exacerbating: str(socObj.exacerbating),
1009
+ severity: Number.isFinite(sev) ? Math.min(10, Math.max(0, Math.round(sev))) : 0
1010
+ };
1011
+ const ipRaw = r.ipss && typeof r.ipss === "object" && !Array.isArray(r.ipss) ? r.ipss : {};
1012
+ const ip = ipRaw;
1013
+ const ipss = {
1014
+ incomplete: ipssItem(ip.incomplete),
1015
+ frequency: ipssItem(ip.frequency),
1016
+ intermittency: ipssItem(ip.intermittency),
1017
+ urgency: ipssItem(ip.urgency),
1018
+ weakStream: ipssItem(ip.weakStream ?? ip.weak_stream),
1019
+ straining: ipssItem(ip.straining),
1020
+ nocturia: ipssItem(ip.nocturia)
1021
+ };
1022
+ const qolRaw = Number(r.ipssQoL);
1023
+ const ipssQoL = Number.isFinite(qolRaw) ? Math.min(6, Math.max(0, Math.round(qolRaw))) : 0;
1024
+ const haRaw = r.haematuria && typeof r.haematuria === "object" && !Array.isArray(r.haematuria) ? r.haematuria : {};
1025
+ const ha = haRaw;
1124
1026
  const haematuria = {
1125
- present: haIn.present === true,
1126
- timing: typeof timingRaw === "string" && timingRaw !== "none" && timingRaw.length > 0 ? timingRaw : "",
1127
- painful: haIn.painful === true,
1128
- clots: haIn.clots === true
1129
- };
1130
- const sxIn = v.sexual && typeof v.sexual === "object" ? v.sexual : {};
1131
- const str2 = (x) => typeof x === "string" && x.length > 0 && x !== "none" ? x : "";
1132
- const inf = Number(sxIn.infertilityMonths);
1027
+ present: bool(ha.present),
1028
+ timing: str(ha.timing) || "none",
1029
+ painful: bool(ha.painful),
1030
+ clots: bool(ha.clots)
1031
+ };
1032
+ const sxRaw = r.sexual && typeof r.sexual === "object" && !Array.isArray(r.sexual) ? r.sexual : {};
1033
+ const sx = sxRaw;
1034
+ const infertilityMonths = nonNegInt(sx.infertilityMonths);
1133
1035
  const sexual = {
1134
- erectile: str2(sxIn.erectile),
1135
- libido: str2(sxIn.libido),
1136
- ejaculation: str2(sxIn.ejaculation),
1137
- infertilityMonths: Number.isFinite(inf) && inf >= 0 ? Math.round(inf) : 0
1036
+ erectile: str(sx.erectile) || "none",
1037
+ libido: str(sx.libido) || "none",
1038
+ ejaculation: str(sx.ejaculation) || "none",
1039
+ infertilityMonths
1138
1040
  };
1139
- const pastNotes = typeof v.pastNotes === "string" ? v.pastNotes : d.pastNotes;
1140
- return { socrates, ipss, ipssQoL, haematuria, sexual, pastNotes };
1041
+ const pastNotes = str(r.pastNotes);
1042
+ return {
1043
+ socrates,
1044
+ ipss,
1045
+ ipssQoL,
1046
+ haematuria,
1047
+ sexual,
1048
+ pastNotes
1049
+ };
1050
+ }
1051
+ function dreSelectStr(x) {
1052
+ const t = str(x).trim();
1053
+ if (!t || t.toLowerCase() === "none") return "";
1054
+ return t;
1141
1055
  }
1142
1056
  function normalizeUrologyExamination(raw) {
1143
- const d = defaultUrologyExamination();
1144
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
1145
- const v = raw;
1146
- const strArr = (x) => Array.isArray(x) ? x.filter((i) => typeof i === "string") : [];
1147
- const bool = (x) => x === true;
1148
- const g = v.general && typeof v.general === "object" ? v.general : {};
1149
- const ab = v.abdomen && typeof v.abdomen === "object" ? v.abdomen : {};
1150
- const gu = v.gu && typeof v.gu === "object" ? v.gu : {};
1151
- const dr = v.dre && typeof v.dre === "object" ? v.dre : {};
1152
- const sg = Number(dr.sizeGrams);
1153
- const consistencyRaw = dr.consistency;
1154
- const medianRaw = dr.medianSulcus;
1155
- const dreEmpty = (x) => !(typeof x === "string" && x.length > 0 && x !== "none");
1057
+ const base = defaultUrologyExamination();
1058
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
1059
+ const r = raw;
1060
+ const regions = Array.isArray(r.regions) ? r.regions.filter((x) => typeof x === "string") : [];
1061
+ const g = r.general && typeof r.general === "object" && !Array.isArray(r.general) ? r.general : {};
1062
+ const gObj = g;
1063
+ const general = {
1064
+ pallor: bool(gObj.pallor),
1065
+ oedema: bool(gObj.oedema),
1066
+ icterus: bool(gObj.icterus),
1067
+ lymphadenopathy: bool(gObj.lymphadenopathy)
1068
+ };
1069
+ const ab = r.abdomen && typeof r.abdomen === "object" && !Array.isArray(r.abdomen) ? r.abdomen : {};
1070
+ const abObj = ab;
1071
+ const abdomen = {
1072
+ distension: bool(abObj.distension),
1073
+ scars: bool(abObj.scars),
1074
+ mass: bool(abObj.mass),
1075
+ kidneyPalpable: bool(abObj.kidneyPalpable),
1076
+ bladderPalpable: bool(abObj.bladderPalpable),
1077
+ renalAngleTender: bool(abObj.renalAngleTender),
1078
+ bladderDull: bool(abObj.bladderDull)
1079
+ };
1080
+ const guRaw = r.gu && typeof r.gu === "object" && !Array.isArray(r.gu) ? r.gu : {};
1081
+ const guObj = guRaw;
1082
+ const gu = {
1083
+ meatusAbnormal: bool(guObj.meatusAbnormal),
1084
+ phimosis: bool(guObj.phimosis),
1085
+ lesions: bool(guObj.lesions),
1086
+ scrotalSwelling: bool(guObj.scrotalSwelling),
1087
+ transilluminant: bool(guObj.transilluminant),
1088
+ tenderTestis: bool(guObj.tenderTestis),
1089
+ hydrocele: bool(guObj.hydrocele),
1090
+ varicocele: bool(guObj.varicocele),
1091
+ suspiciousMass: bool(guObj.suspiciousMass)
1092
+ };
1093
+ const dr = r.dre && typeof r.dre === "object" && !Array.isArray(r.dre) ? r.dre : {};
1094
+ const drObj = dr;
1095
+ const sizeG = Number(drObj.sizeGrams);
1096
+ const dre = {
1097
+ done: bool(drObj.done),
1098
+ sizeGrams: Number.isFinite(sizeG) && sizeG >= 0 ? Math.round(sizeG) : 0,
1099
+ consistency: dreSelectStr(drObj.consistency),
1100
+ medianSulcus: dreSelectStr(drObj.medianSulcus),
1101
+ tenderness: bool(drObj.tenderness)
1102
+ };
1103
+ const examinationNotes = str(r.examinationNotes);
1156
1104
  return {
1157
- regions: strArr(v.regions),
1158
- general: {
1159
- pallor: bool(g.pallor),
1160
- oedema: bool(g.oedema),
1161
- icterus: bool(g.icterus),
1162
- lymphadenopathy: bool(g.lymphadenopathy)
1163
- },
1164
- abdomen: {
1165
- distension: bool(ab.distension),
1166
- scars: bool(ab.scars),
1167
- mass: bool(ab.mass),
1168
- kidneyPalpable: bool(ab.kidneyPalpable),
1169
- bladderPalpable: bool(ab.bladderPalpable),
1170
- renalAngleTender: bool(ab.renalAngleTender),
1171
- bladderDull: bool(ab.bladderDull)
1172
- },
1173
- gu: {
1174
- meatusAbnormal: bool(gu.meatusAbnormal),
1175
- phimosis: bool(gu.phimosis),
1176
- lesions: bool(gu.lesions),
1177
- scrotalSwelling: bool(gu.scrotalSwelling),
1178
- transilluminant: bool(gu.transilluminant),
1179
- tenderTestis: bool(gu.tenderTestis),
1180
- hydrocele: bool(gu.hydrocele),
1181
- varicocele: bool(gu.varicocele),
1182
- suspiciousMass: bool(gu.suspiciousMass)
1183
- },
1184
- dre: {
1185
- done: bool(dr.done),
1186
- sizeGrams: Number.isFinite(sg) && sg >= 0 ? Math.round(sg) : 0,
1187
- consistency: dreEmpty(consistencyRaw) ? "" : String(consistencyRaw),
1188
- medianSulcus: dreEmpty(medianRaw) ? "" : String(medianRaw),
1189
- tenderness: bool(dr.tenderness)
1190
- },
1191
- examinationNotes: typeof v.examinationNotes === "string" ? v.examinationNotes : d.examinationNotes
1105
+ regions,
1106
+ general,
1107
+ abdomen,
1108
+ gu,
1109
+ dre,
1110
+ examinationNotes
1192
1111
  };
1193
1112
  }
1194
- var defaultUrologyPathway = () => ({
1195
- stoneSizeMm: 0,
1196
- hydronephrosis: false,
1197
- prostateVolumeCc: 0,
1198
- psaNgMl: 0,
1199
- uroflowQmaxMlSec: 0,
1200
- risk: {
1201
- ckd: false,
1202
- diabetes: false,
1203
- anticoagulants: false,
1204
- activeInfection: false
1205
- }
1206
- });
1207
- function clampNonNegativeNumber(x, fallback) {
1208
- const n = Number(x);
1209
- if (!Number.isFinite(n) || n < 0) return fallback;
1210
- return n;
1211
- }
1212
1113
  function normalizeUrologyPathway(raw) {
1213
- const d = defaultUrologyPathway();
1214
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
1215
- const v = raw;
1216
- const rIn = v.risk && typeof v.risk === "object" && !Array.isArray(v.risk) ? v.risk : {};
1114
+ const base = defaultUrologyPathway();
1115
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
1116
+ const r = raw;
1117
+ const stoneSizeMm = nonNegInt(r.stoneSizeMm);
1118
+ const hydronephrosis = bool(r.hydronephrosis);
1119
+ const prostateVolumeCc = Math.max(0, Number(r.prostateVolumeCc) || 0);
1120
+ const psaNgMl = Math.max(0, Number(r.psaNgMl) || 0);
1121
+ const uroflowQmaxMlSec = Math.max(0, Number(r.uroflowQmaxMlSec) || 0);
1122
+ const riskRaw = r.risk && typeof r.risk === "object" && !Array.isArray(r.risk) ? r.risk : {};
1123
+ const rk = riskRaw;
1124
+ const risk = {
1125
+ ckd: bool(rk.ckd),
1126
+ diabetes: bool(rk.diabetes),
1127
+ anticoagulants: bool(rk.anticoagulants),
1128
+ activeInfection: bool(rk.activeInfection)
1129
+ };
1217
1130
  return {
1218
- stoneSizeMm: clampNonNegativeNumber(v.stoneSizeMm, d.stoneSizeMm),
1219
- hydronephrosis: v.hydronephrosis === true,
1220
- prostateVolumeCc: clampNonNegativeNumber(v.prostateVolumeCc, d.prostateVolumeCc),
1221
- psaNgMl: clampNonNegativeNumber(v.psaNgMl, d.psaNgMl),
1222
- uroflowQmaxMlSec: clampNonNegativeNumber(v.uroflowQmaxMlSec, d.uroflowQmaxMlSec),
1223
- risk: {
1224
- ckd: rIn.ckd === true,
1225
- diabetes: rIn.diabetes === true,
1226
- anticoagulants: rIn.anticoagulants === true,
1227
- activeInfection: rIn.activeInfection === true
1228
- }
1131
+ stoneSizeMm,
1132
+ hydronephrosis,
1133
+ prostateVolumeCc,
1134
+ psaNgMl,
1135
+ uroflowQmaxMlSec,
1136
+ risk
1229
1137
  };
1230
1138
  }
1231
- function urologyStoneRecommendation(sizeMm, hydronephrosis) {
1232
- if (sizeMm <= 0) return { plan: "Enter stone size to auto-trigger plan.", surgical: false };
1233
- if (sizeMm < 5) return { plan: "Conservative + MET (\u03B1-blocker), hydration", surgical: false };
1234
- if (sizeMm <= 10)
1235
- return {
1236
- plan: hydronephrosis ? "URS / SWL \u2014 obstruction present" : "Medical expulsive therapy / URS",
1237
- surgical: hydronephrosis
1238
- };
1239
- return { plan: "URS / PCNL \u2014 surgical clearance indicated", surgical: true };
1240
- }
1241
- function urologyProstateProcedure(volumeCc) {
1242
- if (volumeCc <= 0) return "Enter prostate volume.";
1243
- if (volumeCc < 30) return "Medical (\u03B1-blocker \xB1 5-ARI)";
1244
- if (volumeCc <= 80) return "TURP";
1245
- return "HoLEP / Open simple prostatectomy";
1246
- }
1247
1139
 
1248
1140
  // src/core/painScaleFlags.ts
1249
1141
  function normalizePainScaleFlags(raw, bounds) {
@@ -1357,18 +1249,18 @@ var FormStore = class {
1357
1249
  values[field.id] = field.defaultValue ?? defaultGeneralSurgeryExamination();
1358
1250
  } else if (field.type === "general_surgery_grading") {
1359
1251
  values[field.id] = field.defaultValue ?? defaultGeneralSurgeryGrading();
1360
- } else if (field.type === "pain_scale_flags") {
1361
- const f = field;
1362
- values[field.id] = normalizePainScaleFlags(
1363
- f.defaultValue ?? { pain: f.min ?? 0, flags: [] },
1364
- { min: f.min, max: f.max }
1365
- );
1366
1252
  } else if (field.type === "urology_smart_history") {
1367
1253
  values[field.id] = field.defaultValue ?? defaultUrologySmartHistory();
1368
1254
  } else if (field.type === "urology_examination") {
1369
1255
  values[field.id] = field.defaultValue ?? defaultUrologyExamination();
1370
1256
  } else if (field.type === "urology_pathway") {
1371
1257
  values[field.id] = field.defaultValue ?? defaultUrologyPathway();
1258
+ } else if (field.type === "pain_scale_flags") {
1259
+ const f = field;
1260
+ values[field.id] = normalizePainScaleFlags(
1261
+ f.defaultValue ?? { pain: f.min ?? 0, flags: [] },
1262
+ { min: f.min, max: f.max }
1263
+ );
1372
1264
  } else values[field.id] = null;
1373
1265
  }
1374
1266
  });
@@ -1578,7 +1470,6 @@ var FormStore = class {
1578
1470
  }
1579
1471
  }
1580
1472
  });
1581
- attachUrologyClinicalPathwayMetadata(result, this.schema);
1582
1473
  return result;
1583
1474
  }
1584
1475
  // --- Core Logic ---
@@ -6551,7 +6442,7 @@ function safeJsonParse(val) {
6551
6442
  return null;
6552
6443
  }
6553
6444
  }
6554
- function clampInt2(val, min, max) {
6445
+ function clampInt(val, min, max) {
6555
6446
  if (!Number.isFinite(val)) return min;
6556
6447
  return Math.max(min, Math.min(max, Math.trunc(val)));
6557
6448
  }
@@ -6561,10 +6452,10 @@ function parseLegacyString(input) {
6561
6452
  if (gpalMatch) {
6562
6453
  const [, g, p, a, l] = gpalMatch;
6563
6454
  result.gpal = {
6564
- G: clampInt2(Number(g), 0, 20),
6565
- P: clampInt2(Number(p), 0, 20),
6566
- A: clampInt2(Number(a), 0, 20),
6567
- L: clampInt2(Number(l), 0, 20)
6455
+ G: clampInt(Number(g), 0, 20),
6456
+ P: clampInt(Number(p), 0, 20),
6457
+ A: clampInt(Number(a), 0, 20),
6458
+ L: clampInt(Number(l), 0, 20)
6568
6459
  };
6569
6460
  }
6570
6461
  const lmpMatch = input.match(/LMP:\s*([^\n]+)/i);
@@ -6597,10 +6488,10 @@ function normalizeToDraft(value) {
6597
6488
  isNewEntry: false,
6598
6489
  draft: {
6599
6490
  gpal: {
6600
- G: clampInt2(Number(gpal.G ?? 0), 0, 20),
6601
- P: clampInt2(Number(gpal.P ?? 0), 0, 20),
6602
- A: clampInt2(Number(gpal.A ?? 0), 0, 20),
6603
- L: clampInt2(Number(gpal.L ?? 0), 0, 20)
6491
+ G: clampInt(Number(gpal.G ?? 0), 0, 20),
6492
+ P: clampInt(Number(gpal.P ?? 0), 0, 20),
6493
+ A: clampInt(Number(gpal.A ?? 0), 0, 20),
6494
+ L: clampInt(Number(gpal.L ?? 0), 0, 20)
6604
6495
  },
6605
6496
  lmp: typeof lmp === "string" ? lmp : "",
6606
6497
  is_pregnant,
@@ -6618,10 +6509,10 @@ function normalizeToDraft(value) {
6618
6509
  }
6619
6510
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
6620
6511
  const gpalFromFlat = {
6621
- G: clampInt2(Number(parsed.G ?? parsed.g ?? 0), 0, 20),
6622
- P: clampInt2(Number(parsed.P ?? parsed.p ?? 0), 0, 20),
6623
- A: clampInt2(Number(parsed.A ?? parsed.a ?? 0), 0, 20),
6624
- L: clampInt2(Number(parsed.L ?? parsed.l ?? 0), 0, 20)
6512
+ G: clampInt(Number(parsed.G ?? parsed.g ?? 0), 0, 20),
6513
+ P: clampInt(Number(parsed.P ?? parsed.p ?? 0), 0, 20),
6514
+ A: clampInt(Number(parsed.A ?? parsed.a ?? 0), 0, 20),
6515
+ L: clampInt(Number(parsed.L ?? parsed.l ?? 0), 0, 20)
6625
6516
  };
6626
6517
  return {
6627
6518
  isNewEntry: false,
@@ -6773,7 +6664,7 @@ var ObstetricHistoryWidget = ({ fieldId }) => {
6773
6664
  const sanitizeGpalInput = (raw) => {
6774
6665
  const digits = raw.replace(/[^\d]/g, "").slice(0, 2);
6775
6666
  if (!digits) return 0;
6776
- return clampInt2(Number(digits), 0, 20);
6667
+ return clampInt(Number(digits), 0, 20);
6777
6668
  };
6778
6669
  const handleGpalChange = (key, raw) => {
6779
6670
  const prevWasEmpty = draft.gpal[key] === 0 && activeGpalKey === key;
@@ -9077,26 +8968,11 @@ var DischargeMedicationOrdersWidget = ({ fieldId }) => {
9077
8968
  showError && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-500", children: error })
9078
8969
  ] });
9079
8970
  };
9080
- var DEFAULT_TOKEN_SEPARATOR = ", ";
8971
+ var DEFAULT_TOKEN_SEPARATOR2 = ", ";
9081
8972
  function getAppendSeparator(def) {
9082
- const raw = def.meta && typeof def.meta.appendSeparator === "string" ? def.meta.appendSeparator : DEFAULT_TOKEN_SEPARATOR;
8973
+ const raw = def.meta && typeof def.meta.appendSeparator === "string" ? def.meta.appendSeparator : DEFAULT_TOKEN_SEPARATOR2;
9083
8974
  return raw;
9084
8975
  }
9085
- function getSuggestionTokensFromText(text) {
9086
- return text.split(",").map((p) => p.trim()).filter(Boolean);
9087
- }
9088
- function toggleSuggestionTokenInText(current, token, separator = DEFAULT_TOKEN_SEPARATOR) {
9089
- const t = String(token).trim();
9090
- if (!t) return current;
9091
- const parts = getSuggestionTokensFromText(current);
9092
- const i = parts.findIndex((p) => p === t);
9093
- if (i >= 0) {
9094
- parts.splice(i, 1);
9095
- } else {
9096
- parts.push(t);
9097
- }
9098
- return parts.join(separator);
9099
- }
9100
8976
  function isTokenSelected(text, optionValue) {
9101
8977
  const t = String(optionValue).trim();
9102
8978
  if (!t) return false;
@@ -9170,8 +9046,11 @@ var SuggestionTextareaWidget = ({ fieldId }) => {
9170
9046
  setOptions([]);
9171
9047
  }
9172
9048
  }, [def]);
9049
+ const chipAllowedSet = useMemo(
9050
+ () => new Set(options.map((o) => String(o.value).trim()).filter(Boolean)),
9051
+ [options]
9052
+ );
9173
9053
  useEffect(() => {
9174
- if (!parallelChipFieldId || !def) return;
9175
9054
  const parallelDef = store.getFieldDef(parallelChipFieldId);
9176
9055
  if (!parallelDef || parallelDef.type !== "multiselect") return;
9177
9056
  const next = deriveKnownSuggestionChipValues(textValue, options);
@@ -9197,7 +9076,7 @@ var SuggestionTextareaWidget = ({ fieldId }) => {
9197
9076
  const labelEl = theme ? /* @__PURE__ */ jsx(Label, { className: labelClass, children: labelContent }) : /* @__PURE__ */ jsx(Label, { children: labelContent });
9198
9077
  const onChipClick = (optValue) => {
9199
9078
  if (disabled) return;
9200
- setValue(toggleSuggestionTokenInText(textValue, String(optValue), appendSeparator));
9079
+ setValue(toggleSuggestionTokenInText(textValue, String(optValue), appendSeparator, chipAllowedSet));
9201
9080
  };
9202
9081
  const hasSuggestions = options.length > 0;
9203
9082
  const hasEmbedded = embeddedFieldIds.length > 0;
@@ -9636,7 +9515,7 @@ var EMPTY = {
9636
9515
  specularMicroscopyDone: false,
9637
9516
  endothelialCount: ""
9638
9517
  };
9639
- function str(x) {
9518
+ function str2(x) {
9640
9519
  return typeof x === "string" ? x : "";
9641
9520
  }
9642
9521
  function parseValue2(raw) {
@@ -9644,24 +9523,24 @@ function parseValue2(raw) {
9644
9523
  const o = raw;
9645
9524
  const m = o.method;
9646
9525
  const method = m === "ascan_immersion" || m === "ascan_contact" || m === "optical_biometry" ? m : "optical_biometry";
9647
- const formula = str(o.formula);
9526
+ const formula = str2(o.formula);
9648
9527
  return {
9649
9528
  ...EMPTY,
9650
- axialLengthOD: str(o.axialLengthOD),
9651
- axialLengthOS: str(o.axialLengthOS),
9652
- k1k2AxisOD: str(o.k1k2AxisOD),
9653
- k1k2AxisOS: str(o.k1k2AxisOS),
9654
- anteriorChamberDepthOD: str(o.anteriorChamberDepthOD),
9655
- anteriorChamberDepthOS: str(o.anteriorChamberDepthOS),
9656
- iolPowerOD: str(o.iolPowerOD),
9657
- iolPowerOS: str(o.iolPowerOS),
9658
- targetRefractionOD: str(o.targetRefractionOD),
9659
- targetRefractionOS: str(o.targetRefractionOS),
9529
+ axialLengthOD: str2(o.axialLengthOD),
9530
+ axialLengthOS: str2(o.axialLengthOS),
9531
+ k1k2AxisOD: str2(o.k1k2AxisOD),
9532
+ k1k2AxisOS: str2(o.k1k2AxisOS),
9533
+ anteriorChamberDepthOD: str2(o.anteriorChamberDepthOD),
9534
+ anteriorChamberDepthOS: str2(o.anteriorChamberDepthOS),
9535
+ iolPowerOD: str2(o.iolPowerOD),
9536
+ iolPowerOS: str2(o.iolPowerOS),
9537
+ targetRefractionOD: str2(o.targetRefractionOD),
9538
+ targetRefractionOS: str2(o.targetRefractionOS),
9660
9539
  formula: formula || "SRK/T",
9661
9540
  method,
9662
9541
  cornealTopoUploaded: o.cornealTopoUploaded === true,
9663
9542
  specularMicroscopyDone: o.specularMicroscopyDone === true,
9664
- endothelialCount: str(o.endothelialCount)
9543
+ endothelialCount: str2(o.endothelialCount)
9665
9544
  };
9666
9545
  }
9667
9546
  var paramCell = "emr-biometry-param text-[11px] font-semibold leading-snug text-foreground sm:text-xs py-2";
@@ -10101,7 +9980,7 @@ var THYROID_OPTS = [
10101
9980
  { value: "voice_change", label: "Voice change" },
10102
9981
  { value: "thyrotoxicosis", label: "Thyrotoxicosis symptoms" }
10103
9982
  ];
10104
- var SOCRATES_KEYS2 = [
9983
+ var SOCRATES_KEYS = [
10105
9984
  "site",
10106
9985
  "onset",
10107
9986
  "character",
@@ -10195,7 +10074,7 @@ var GsSmartHistoryWidget = ({ fieldId }) => {
10195
10074
  labelEl,
10196
10075
  /* @__PURE__ */ jsxs("div", { className: bodyClass, children: [
10197
10076
  showPain ? /* @__PURE__ */ jsx(Panel, { title: "SOCRATES \u2014 pain", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 md:grid-cols-4", children: [
10198
- SOCRATES_KEYS2.map((k) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
10077
+ SOCRATES_KEYS.map((k) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
10199
10078
  /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: k }),
10200
10079
  /* @__PURE__ */ jsx(
10201
10080
  Input,
@@ -11134,10 +11013,36 @@ var PainScaleFlagsWidget = ({ fieldId }) => {
11134
11013
  };
11135
11014
  var CHIEF_CHIPS_FIELD3 = "chief_complaints_chips";
11136
11015
  var ACTIVE_SYNDROME_FIELD = "uro_active_syndrome";
11016
+ var CHIEF_TEXT_FIELD = "chief_complaints";
11017
+ var SYMPTOMS_FIELD = "symptoms";
11018
+ var DIAGNOSIS_FIELD = "diagnosis";
11019
+ var URO_DURATION_FIELD = "uro_chief_duration";
11020
+ var URO_ONSET_FIELD = "uro_chief_onset";
11021
+ var URO_PROGRESSION_FIELD = "uro_chief_progression";
11022
+ var SYNDROME_LABELS = {
11023
+ luts: "LUTS / BPH",
11024
+ stone: "Urolithiasis (stone)",
11025
+ haematuria: "Haematuria",
11026
+ scrotal: "Scrotal swelling",
11027
+ retention: "Acute retention",
11028
+ ed_infertility: "ED / Infertility",
11029
+ uti: "UTI / infection"
11030
+ };
11137
11031
  function chipList2(state) {
11138
11032
  const raw = state.values[CHIEF_CHIPS_FIELD3];
11139
11033
  return Array.isArray(raw) ? raw.filter((x) => typeof x === "string") : [];
11140
11034
  }
11035
+ function str3(x) {
11036
+ return typeof x === "string" ? x : "";
11037
+ }
11038
+ function chiefOptionalFieldDisplay(x) {
11039
+ const s = str3(x).trim();
11040
+ if (!s || s.toLowerCase() === "none") return "";
11041
+ return s;
11042
+ }
11043
+ function nonEmptyLines(lines) {
11044
+ return lines.map((s) => (s ?? "").trim()).filter(Boolean);
11045
+ }
11141
11046
  var IPSS_LABELS = {
11142
11047
  incomplete: "Incomplete emptying",
11143
11048
  frequency: "Frequency (<2h)",
@@ -11147,7 +11052,7 @@ var IPSS_LABELS = {
11147
11052
  straining: "Straining",
11148
11053
  nocturia: "Nocturia (\xD7/night)"
11149
11054
  };
11150
- var SOCRATES_KEYS3 = [
11055
+ var SOCRATES_KEYS2 = [
11151
11056
  "site",
11152
11057
  "onset",
11153
11058
  "character",
@@ -11171,17 +11076,19 @@ function Panel2({
11171
11076
  }
11172
11077
  var UrologySmartHistoryWidget = ({ fieldId }) => {
11173
11078
  const { fieldDef, value, setValue, setTouched, error, touched, disabled } = useField(fieldId);
11079
+ const hopcField = useField(SYMPTOMS_FIELD);
11174
11080
  const syndromeField = useField(ACTIVE_SYNDROME_FIELD);
11175
11081
  const { state } = useForm();
11082
+ const store = useFormStore();
11176
11083
  const showError = !!error && (touched || state.submitAttempted);
11177
11084
  const safe = useMemo(() => normalizeUrologySmartHistory(value), [value]);
11178
11085
  const chips = useMemo(() => chipList2(state), [state]);
11179
11086
  const rawSyndrome = syndromeField.value ?? state.values[ACTIVE_SYNDROME_FIELD];
11180
11087
  const syndrome = typeof rawSyndrome === "string" && rawSyndrome !== "" && rawSyndrome !== "none" ? rawSyndrome : "none";
11181
- const showPain = chips.includes("pain_abdomen_flank") || chips.includes("scrotal_swelling_pain") || syndrome === "stone" || syndrome === "scrotal";
11182
- const showLUTS = syndrome === "luts" || syndrome === "retention" || chips.includes("frequency_urgency") || chips.includes("poor_stream") || chips.includes("acute_retention");
11088
+ const showPain = chips.includes("pain abdomen flank") || chips.includes("scrotal swelling pain") || syndrome === "stone" || syndrome === "scrotal";
11089
+ const showLUTS = syndrome === "luts" || syndrome === "retention" || chips.includes("frequency urgency") || chips.includes("poor stream") || chips.includes("acute retention");
11183
11090
  const showHaem = syndrome === "haematuria" || chips.includes("haematuria");
11184
- const showSex = syndrome === "ed_infertility" || chips.includes("erectile_dysfunction") || chips.includes("infertility_concern");
11091
+ const showSex = syndrome === "ed_infertility" || chips.includes("erectile dysfunction") || chips.includes("infertility concern");
11185
11092
  const update = (next) => {
11186
11093
  setValue(next);
11187
11094
  setTouched();
@@ -11201,6 +11108,55 @@ var UrologySmartHistoryWidget = ({ fieldId }) => {
11201
11108
  const setIpss = (key, v) => {
11202
11109
  update({ ...safe, ipss: { ...safe.ipss, [key]: v } });
11203
11110
  };
11111
+ const [hopcNarrativeLocked, setHopcNarrativeLocked] = useState(false);
11112
+ useEffect(() => {
11113
+ if (hopcNarrativeLocked || disabled) return;
11114
+ const next = formatUrologySmartHistoryToSymptomsNarrative(normalizeUrologySmartHistory(value), {
11115
+ showPain,
11116
+ showLUTS,
11117
+ showHaem,
11118
+ showSex
11119
+ });
11120
+ hopcField.setValue(next);
11121
+ }, [value, hopcNarrativeLocked, showPain, showLUTS, showHaem, showSex, disabled]);
11122
+ useEffect(() => {
11123
+ if (disabled) return;
11124
+ const label = syndrome && syndrome !== "none" ? SYNDROME_LABELS[syndrome] ?? syndrome : "";
11125
+ const prev = store.getFieldState(DIAGNOSIS_FIELD)?.value;
11126
+ const prevText = typeof prev === "string" ? prev : "";
11127
+ const syndromeLabels = new Set(Object.values(SYNDROME_LABELS));
11128
+ const keptLines = prevText.split("\n").map((l) => l.trim()).filter(Boolean).filter((l) => !syndromeLabels.has(l));
11129
+ const nextLines = label ? [...keptLines, label] : keptLines;
11130
+ const next = nextLines.join("\n");
11131
+ if (prevText.trim() === next.trim()) return;
11132
+ store.setValues({ [DIAGNOSIS_FIELD]: next }, { touch: false });
11133
+ }, [disabled, store, syndrome]);
11134
+ useEffect(() => {
11135
+ if (disabled) return;
11136
+ const duration = chiefOptionalFieldDisplay(state.values[URO_DURATION_FIELD]);
11137
+ const onset = chiefOptionalFieldDisplay(state.values[URO_ONSET_FIELD]);
11138
+ const progression = chiefOptionalFieldDisplay(state.values[URO_PROGRESSION_FIELD]);
11139
+ const prev = store.getFieldState(CHIEF_TEXT_FIELD)?.value;
11140
+ const prevText = typeof prev === "string" ? prev : "";
11141
+ const kept = prevText.split(",").map((t) => t.trim()).filter(Boolean).filter((t) => {
11142
+ const lower = t.toLowerCase();
11143
+ return !lower.startsWith("duration:") && !lower.startsWith("onset:") && !lower.startsWith("progression:");
11144
+ });
11145
+ const extra = nonEmptyLines([
11146
+ duration ? `Duration: ${duration}` : "",
11147
+ onset ? `Onset: ${onset}` : "",
11148
+ progression ? `Progression: ${progression}` : ""
11149
+ ]);
11150
+ const next = [...kept, ...extra].join(", ").trim();
11151
+ if (prevText.trim() === next.trim()) return;
11152
+ store.setValues({ [CHIEF_TEXT_FIELD]: next }, { touch: false });
11153
+ }, [
11154
+ disabled,
11155
+ state.values[URO_DURATION_FIELD],
11156
+ state.values[URO_ONSET_FIELD],
11157
+ state.values[URO_PROGRESSION_FIELD],
11158
+ store
11159
+ ]);
11204
11160
  if (!fieldDef) return null;
11205
11161
  const theme = getThemeConfig("textarea", fieldDef.theme);
11206
11162
  const wrapperClass = theme?.wrapperClassName ?? "rounded-md overflow-hidden flex flex-col border border-[#D0D0D0]";
@@ -11219,7 +11175,7 @@ var UrologySmartHistoryWidget = ({ fieldId }) => {
11219
11175
  /* @__PURE__ */ jsxs("div", { className: bodyClass, children: [
11220
11176
  /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "Panels follow chief complaint chips and active syndrome (Clearsight SmartHistoryUro)." }),
11221
11177
  showPain ? /* @__PURE__ */ jsx(Panel2, { title: "SOCRATES \u2014 Pain (flank / suprapubic / testicular)", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2 md:grid-cols-4", children: [
11222
- SOCRATES_KEYS3.map((k) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
11178
+ SOCRATES_KEYS2.map((k) => /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
11223
11179
  /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: k }),
11224
11180
  /* @__PURE__ */ jsx(
11225
11181
  Input,
@@ -11455,15 +11411,50 @@ var UrologySmartHistoryWidget = ({ fieldId }) => {
11455
11411
  ] })
11456
11412
  ] }) }) : null,
11457
11413
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
11458
- /* @__PURE__ */ jsx(Label, { className: "text-xs font-medium text-slate-700", children: "Past history (stones / surgeries / drugs)" }),
11414
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
11415
+ /* @__PURE__ */ jsx(Label, { className: "text-xs font-semibold text-slate-700", children: hopcField.fieldDef?.label ?? "History of presenting complaint" }),
11416
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
11417
+ hopcNarrativeLocked ? /* @__PURE__ */ jsxs("span", { className: "rounded bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium text-amber-900 ring-1 ring-amber-200/80", children: [
11418
+ "You edited this summary. Use",
11419
+ " ",
11420
+ /* @__PURE__ */ jsx("span", { className: "italic", children: "'Regenerate'" }),
11421
+ " to update it from the form."
11422
+ ] }) : null,
11423
+ hopcNarrativeLocked ? /* @__PURE__ */ jsx(
11424
+ Button,
11425
+ {
11426
+ type: "button",
11427
+ variant: "outline",
11428
+ size: "sm",
11429
+ className: "h-7 shrink-0 px-2 text-xs",
11430
+ disabled: disabled || hopcField.disabled,
11431
+ onClick: () => {
11432
+ setHopcNarrativeLocked(false);
11433
+ hopcField.setValue(
11434
+ formatUrologySmartHistoryToSymptomsNarrative(
11435
+ normalizeUrologySmartHistory(value),
11436
+ { showPain, showLUTS, showHaem, showSex }
11437
+ )
11438
+ );
11439
+ },
11440
+ children: "Regenerate"
11441
+ }
11442
+ ) : null
11443
+ ] })
11444
+ ] }),
11445
+ hopcNarrativeLocked ? /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "Edit this box anytime. Use Regenerate to replace with fresh text from the structured fields." }) : null,
11459
11446
  /* @__PURE__ */ jsx(
11460
11447
  Textarea,
11461
11448
  {
11462
- className: "min-h-[72px] rounded-md border border-input bg-white text-sm",
11463
- placeholder: "Prior calculi, urological surgeries, anticoagulants, DM/HTN\u2026",
11464
- value: safe.pastNotes,
11465
- onChange: (e) => update({ ...safe, pastNotes: e.target.value }),
11466
- disabled
11449
+ className: "min-h-[88px] rounded-md border border-input bg-white text-sm",
11450
+ placeholder: "Auto-filled from chief complaints + smart history\u2026",
11451
+ value: typeof hopcField.value === "string" ? hopcField.value : hopcField.value == null ? "" : String(hopcField.value),
11452
+ onChange: (e) => {
11453
+ setHopcNarrativeLocked(true);
11454
+ hopcField.setValue(e.target.value);
11455
+ hopcField.setTouched();
11456
+ },
11457
+ disabled: disabled || hopcField.disabled
11467
11458
  }
11468
11459
  )
11469
11460
  ] })
@@ -11471,6 +11462,7 @@ var UrologySmartHistoryWidget = ({ fieldId }) => {
11471
11462
  showError ? /* @__PURE__ */ jsx("p", { className: "px-3 pb-2 text-xs text-red-500", children: error }) : null
11472
11463
  ] });
11473
11464
  };
11465
+ var URO_CLINICAL_EXAMINATION_FIELD = "clinical_examination";
11474
11466
  var GENERAL_ROWS = [
11475
11467
  { key: "pallor", label: "Pallor (chronic dz)" },
11476
11468
  { key: "oedema", label: "Oedema (renal)" },
@@ -11501,21 +11493,32 @@ var DRE_CONSISTENCY = ["none", "Soft (benign)", "Firm", "Hard / nodular (maligna
11501
11493
  var DRE_SULCUS = ["none", "Preserved", "Obliterated"];
11502
11494
  var UrologyExaminationWidget = ({ fieldId }) => {
11503
11495
  const { fieldDef, value, setValue, setTouched, error, touched, disabled } = useField(fieldId);
11496
+ const clinicalField = useField(URO_CLINICAL_EXAMINATION_FIELD);
11504
11497
  const { state } = useForm();
11505
11498
  const showError = !!error && (touched || state.submitAttempted);
11506
11499
  const safe = useMemo(() => normalizeUrologyExamination(value), [value]);
11507
- const update = (next) => {
11500
+ const [clinicalNarrativeLocked, setClinicalNarrativeLocked] = useState(false);
11501
+ useEffect(() => {
11502
+ if (clinicalNarrativeLocked || disabled) return;
11503
+ const norm = normalizeUrologyExamination(value);
11504
+ const next = formatUrologyExaminationToClinicalExamination(norm);
11505
+ clinicalField.setValue(next);
11506
+ if (norm.examinationNotes !== next) {
11507
+ setValue({ ...norm, examinationNotes: next });
11508
+ }
11509
+ }, [value, clinicalNarrativeLocked, disabled]);
11510
+ const bump = (next) => {
11508
11511
  setValue(next);
11509
11512
  setTouched();
11510
11513
  };
11511
11514
  const toggleGeneral = (key) => {
11512
- update({ ...safe, general: { ...safe.general, [key]: !safe.general[key] } });
11515
+ bump({ ...safe, general: { ...safe.general, [key]: !safe.general[key] } });
11513
11516
  };
11514
11517
  const toggleAbdomen = (key) => {
11515
- update({ ...safe, abdomen: { ...safe.abdomen, [key]: !safe.abdomen[key] } });
11518
+ bump({ ...safe, abdomen: { ...safe.abdomen, [key]: !safe.abdomen[key] } });
11516
11519
  };
11517
11520
  const toggleGu = (key) => {
11518
- update({ ...safe, gu: { ...safe.gu, [key]: !safe.gu[key] } });
11521
+ bump({ ...safe, gu: { ...safe.gu, [key]: !safe.gu[key] } });
11519
11522
  };
11520
11523
  if (!fieldDef) return null;
11521
11524
  const theme = getThemeConfig("textarea", fieldDef.theme);
@@ -11583,7 +11586,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11583
11586
  Checkbox,
11584
11587
  {
11585
11588
  checked: safe.dre.done,
11586
- onCheckedChange: (c) => update({ ...safe, dre: { ...safe.dre, done: c === true } }),
11589
+ onCheckedChange: (c) => bump({ ...safe, dre: { ...safe.dre, done: c === true } }),
11587
11590
  disabled
11588
11591
  }
11589
11592
  ),
@@ -11600,7 +11603,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11600
11603
  value: safe.dre.sizeGrams,
11601
11604
  onChange: (e) => {
11602
11605
  const n = Number(e.target.value);
11603
- update({
11606
+ bump({
11604
11607
  ...safe,
11605
11608
  dre: {
11606
11609
  ...safe.dre,
@@ -11618,7 +11621,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11618
11621
  Select,
11619
11622
  {
11620
11623
  value: safe.dre.consistency || "none",
11621
- onValueChange: (val) => update({
11624
+ onValueChange: (val) => bump({
11622
11625
  ...safe,
11623
11626
  dre: { ...safe.dre, consistency: val === "none" ? "" : val }
11624
11627
  }),
@@ -11636,7 +11639,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11636
11639
  Select,
11637
11640
  {
11638
11641
  value: safe.dre.medianSulcus || "none",
11639
- onValueChange: (val) => update({
11642
+ onValueChange: (val) => bump({
11640
11643
  ...safe,
11641
11644
  dre: { ...safe.dre, medianSulcus: val === "none" ? "" : val }
11642
11645
  }),
@@ -11653,7 +11656,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11653
11656
  Checkbox,
11654
11657
  {
11655
11658
  checked: safe.dre.tenderness,
11656
- onCheckedChange: (c) => update({ ...safe, dre: { ...safe.dre, tenderness: c === true } }),
11659
+ onCheckedChange: (c) => bump({ ...safe, dre: { ...safe.dre, tenderness: c === true } }),
11657
11660
  disabled
11658
11661
  }
11659
11662
  ),
@@ -11662,15 +11665,49 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11662
11665
  ] })
11663
11666
  ] }),
11664
11667
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
11665
- /* @__PURE__ */ jsx(Label, { className: "text-xs font-semibold text-slate-700", children: "Examination notes" }),
11668
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
11669
+ /* @__PURE__ */ jsx(Label, { className: "text-xs font-semibold text-slate-700", children: clinicalField.fieldDef?.label ?? "Clinical examination" }),
11670
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
11671
+ clinicalNarrativeLocked ? /* @__PURE__ */ jsxs("span", { className: "rounded bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium text-amber-900 ring-1 ring-amber-200/80", children: [
11672
+ "You edited this summary. Use",
11673
+ " ",
11674
+ /* @__PURE__ */ jsx("span", { className: "italic", children: "'Regenerate'" }),
11675
+ " to update it from the form."
11676
+ ] }) : null,
11677
+ clinicalNarrativeLocked ? /* @__PURE__ */ jsx(
11678
+ Button,
11679
+ {
11680
+ type: "button",
11681
+ variant: "outline",
11682
+ size: "sm",
11683
+ className: "h-7 shrink-0 px-2 text-xs",
11684
+ disabled: disabled || clinicalField.disabled,
11685
+ onClick: () => {
11686
+ setClinicalNarrativeLocked(false);
11687
+ const norm = normalizeUrologyExamination(value);
11688
+ clinicalField.setValue(formatUrologyExaminationToClinicalExamination(norm));
11689
+ },
11690
+ children: "Regenerate"
11691
+ }
11692
+ ) : null
11693
+ ] })
11694
+ ] }),
11695
+ clinicalNarrativeLocked ? /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: "Edit this box anytime. Use Regenerate to replace with fresh text from the structured examination." }) : null,
11666
11696
  /* @__PURE__ */ jsx(
11667
11697
  Textarea,
11668
11698
  {
11669
11699
  className: "min-h-[88px] rounded-md border border-input bg-white text-sm",
11670
- placeholder: "Free-text exam findings\u2026",
11671
- value: safe.examinationNotes,
11672
- onChange: (e) => update({ ...safe, examinationNotes: e.target.value }),
11673
- disabled
11700
+ placeholder: "Auto-filled from selections above\u2026",
11701
+ value: typeof clinicalField.value === "string" ? clinicalField.value : clinicalField.value == null ? "" : String(clinicalField.value),
11702
+ onChange: (e) => {
11703
+ setClinicalNarrativeLocked(true);
11704
+ const t = e.target.value;
11705
+ clinicalField.setValue(t);
11706
+ clinicalField.setTouched();
11707
+ setValue({ ...safe, examinationNotes: t });
11708
+ setTouched();
11709
+ },
11710
+ disabled: disabled || clinicalField.disabled
11674
11711
  }
11675
11712
  )
11676
11713
  ] })
@@ -11678,265 +11715,142 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11678
11715
  showError ? /* @__PURE__ */ jsx("p", { className: "px-3 pb-2 text-xs text-red-500", children: error }) : null
11679
11716
  ] });
11680
11717
  };
11681
- var SMART_HISTORY_FIELD = "uro_smart_history";
11682
- var EXAMINATION_FIELD = "uro_examination";
11683
- var DRE_HARD = "Hard / nodular (malignancy?)";
11684
- function Panel3({
11685
- title,
11686
- className,
11687
- children
11688
- }) {
11689
- return /* @__PURE__ */ jsxs("div", { className: cn("overflow-hidden rounded-md border border-[#D0D0D0] bg-white", className), children: [
11690
- /* @__PURE__ */ jsx("div", { className: "border-b border-[#D0D0D0] bg-[#FEE8EC]/50 px-3 py-1.5", children: /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-slate-700", children: title }) }),
11691
- /* @__PURE__ */ jsx("div", { className: "p-3", children })
11692
- ] });
11693
- }
11718
+ var RISK_ROWS = [
11719
+ { key: "ckd", label: "Chronic kidney disease" },
11720
+ { key: "diabetes", label: "Diabetes" },
11721
+ { key: "anticoagulants", label: "Anticoagulants" },
11722
+ { key: "activeInfection", label: "Active infection" }
11723
+ ];
11694
11724
  var UrologyPathwayWidget = ({ fieldId }) => {
11695
11725
  const { fieldDef, value, setValue, setTouched, error, touched, disabled } = useField(fieldId);
11696
11726
  const { state } = useForm();
11697
11727
  const showError = !!error && (touched || state.submitAttempted);
11698
11728
  const safe = useMemo(() => normalizeUrologyPathway(value), [value]);
11699
- const smart = useMemo(
11700
- () => normalizeUrologySmartHistory(state.values[SMART_HISTORY_FIELD]),
11701
- [state.values]
11702
- );
11703
- const exam = useMemo(
11704
- () => normalizeUrologyExamination(state.values[EXAMINATION_FIELD]),
11705
- [state.values]
11706
- );
11707
- const ipssTotal = urologyIpssSevenTotal(smart.ipss);
11708
- const ipssBandCls = ipssTotal >= 20 ? "bg-red-500/15 text-red-700" : ipssTotal >= 8 ? "bg-amber-500/15 text-amber-900" : "bg-emerald-500/15 text-emerald-800";
11709
- const stone = useMemo(
11710
- () => urologyStoneRecommendation(safe.stoneSizeMm, safe.hydronephrosis),
11711
- [safe.stoneSizeMm, safe.hydronephrosis]
11712
- );
11713
- const prostateLine = useMemo(
11714
- () => urologyProstateProcedure(safe.prostateVolumeCc),
11715
- [safe.prostateVolumeCc]
11716
- );
11717
- const psaHigh = safe.psaNgMl > 4;
11718
- const qmaxLow = safe.uroflowQmaxMlSec > 0 && Number.isFinite(safe.uroflowQmaxMlSec) && safe.uroflowQmaxMlSec < 10;
11719
11729
  const update = (next) => {
11720
11730
  setValue(next);
11721
11731
  setTouched();
11722
11732
  };
11723
- const setRisk = (key, next) => {
11724
- update({ ...safe, risk: { ...safe.risk, [key]: next } });
11733
+ const setNum = (key, raw) => {
11734
+ const n = Number(raw);
11735
+ const v = Number.isFinite(n) && n >= 0 ? n : 0;
11736
+ if (key === "stoneSizeMm") {
11737
+ update({ ...safe, stoneSizeMm: Math.round(v) });
11738
+ return;
11739
+ }
11740
+ update({ ...safe, [key]: v });
11741
+ };
11742
+ const toggleRisk = (key) => {
11743
+ update({
11744
+ ...safe,
11745
+ risk: { ...safe.risk, [key]: !safe.risk[key] }
11746
+ });
11725
11747
  };
11726
11748
  if (!fieldDef) return null;
11727
11749
  const theme = getThemeConfig("textarea", fieldDef.theme);
11728
11750
  const wrapperClass = theme?.wrapperClassName ?? "rounded-md overflow-hidden flex flex-col border border-[#D0D0D0]";
11729
11751
  const labelClass = theme?.labelClassName;
11752
+ const subtitle = fieldDef.meta && typeof fieldDef.meta === "object" && fieldDef.meta !== null ? String(fieldDef.meta.subtitle ?? "") : "";
11730
11753
  const labelContent = /* @__PURE__ */ jsxs(Fragment, { children: [
11731
11754
  fieldDef.label,
11732
11755
  fieldDef.required ? /* @__PURE__ */ jsx("span", { className: "ml-1 text-red-500", children: "*" }) : null
11733
11756
  ] });
11734
11757
  const labelEl = theme ? /* @__PURE__ */ jsx(Label, { className: labelClass, children: labelContent }) : /* @__PURE__ */ jsx(Label, { className: "text-sm font-medium", children: labelContent });
11735
11758
  const bodyClass = cn(
11736
- "-mt-1 flex min-w-0 flex-col gap-3 border-t border-[#D0D0D0] bg-white px-3 py-3",
11759
+ "-mt-1 flex min-w-0 flex-col gap-4 border-t border-[#D0D0D0] bg-white px-3 py-3",
11737
11760
  disabled && "pointer-events-none opacity-60"
11738
11761
  );
11739
- const subtitle = typeof fieldDef.meta?.subtitle === "string" && fieldDef.meta.subtitle.trim().length > 0 ? fieldDef.meta.subtitle.trim() : "";
11740
11762
  return /* @__PURE__ */ jsxs("div", { className: cn("w-full min-w-0", wrapperClass), children: [
11741
11763
  labelEl,
11764
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "px-3 pt-2 text-[11px] text-muted-foreground", children: subtitle }) : null,
11742
11765
  /* @__PURE__ */ jsxs("div", { className: bodyClass, children: [
11743
- subtitle ? /* @__PURE__ */ jsx("p", { className: "-mt-0.5 mb-0 text-xs font-semibold tracking-wide text-slate-700", children: subtitle }) : null,
11744
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 text-xs md:grid-cols-2 xl:grid-cols-4", children: [
11745
- /* @__PURE__ */ jsxs(Panel3, { title: "Stone disease", children: [
11746
- /* @__PURE__ */ jsxs("label", { className: "block", children: [
11747
- /* @__PURE__ */ jsx("span", { className: "mb-1 block font-medium text-slate-700", children: "Stone size (mm)" }),
11748
- /* @__PURE__ */ jsx(
11749
- Input,
11750
- {
11751
- type: "number",
11752
- min: 0,
11753
- step: "any",
11754
- className: cn(
11755
- "h-8 text-xs",
11756
- disabled && "pointer-events-none"
11757
- ),
11758
- value: safe.stoneSizeMm,
11759
- onChange: (e) => {
11760
- const raw = e.target.value;
11761
- const n = Number(raw);
11762
- update({
11763
- ...safe,
11764
- stoneSizeMm: raw === "" || raw === "-" ? 0 : !Number.isFinite(n) ? 0 : Math.max(0, n)
11765
- });
11766
- },
11767
- disabled
11768
- }
11769
- )
11770
- ] }),
11771
- /* @__PURE__ */ jsxs("label", { className: "mt-2 flex items-center gap-2", children: [
11772
- /* @__PURE__ */ jsx(
11773
- Checkbox,
11774
- {
11775
- checked: safe.hydronephrosis,
11776
- disabled,
11777
- onCheckedChange: (checked) => update({ ...safe, hydronephrosis: checked === true })
11778
- }
11779
- ),
11780
- /* @__PURE__ */ jsx("span", { className: "text-slate-700", children: "Hydronephrosis on imaging" })
11781
- ] }),
11766
+ showError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: error }) : null,
11767
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [
11768
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-xs", children: [
11769
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Stone size (mm)" }),
11782
11770
  /* @__PURE__ */ jsx(
11783
- "div",
11771
+ Input,
11784
11772
  {
11785
- className: cn(
11786
- "mt-2 rounded p-2 text-[11px]",
11787
- stone.surgical && safe.stoneSizeMm > 0 ? "bg-red-500/10 text-red-800" : safe.stoneSizeMm > 0 ? "bg-emerald-500/10 text-emerald-900" : "bg-muted/40 text-muted-foreground"
11788
- ),
11789
- children: stone.plan
11773
+ type: "number",
11774
+ min: 0,
11775
+ className: "h-8 text-xs",
11776
+ value: safe.stoneSizeMm,
11777
+ onChange: (e) => setNum("stoneSizeMm", e.target.value),
11778
+ disabled
11790
11779
  }
11791
- ),
11792
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "Trigger: >10 mm OR obstruction \u2192 surgical." })
11780
+ )
11793
11781
  ] }),
11794
- /* @__PURE__ */ jsxs(Panel3, { title: "Prostate / BPH", children: [
11795
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
11796
- /* @__PURE__ */ jsxs("label", { className: "block", children: [
11797
- /* @__PURE__ */ jsx("span", { className: "mb-1 block font-medium text-slate-700", children: "Volume (cc)" }),
11798
- /* @__PURE__ */ jsx(
11799
- Input,
11800
- {
11801
- type: "number",
11802
- min: 0,
11803
- step: "any",
11804
- className: cn(
11805
- "h-8 text-xs",
11806
- disabled && "pointer-events-none"
11807
- ),
11808
- value: safe.prostateVolumeCc,
11809
- onChange: (e) => {
11810
- const raw = e.target.value;
11811
- const n = Number(raw);
11812
- update({
11813
- ...safe,
11814
- prostateVolumeCc: raw === "" || raw === "-" ? 0 : !Number.isFinite(n) ? 0 : Math.max(0, n)
11815
- });
11816
- },
11817
- disabled
11818
- }
11819
- )
11820
- ] }),
11821
- /* @__PURE__ */ jsxs("label", { className: "block", children: [
11822
- /* @__PURE__ */ jsx("span", { className: "mb-1 block font-medium text-slate-700", children: "PSA (ng/ml)" }),
11823
- /* @__PURE__ */ jsx(
11824
- Input,
11825
- {
11826
- type: "number",
11827
- min: 0,
11828
- step: 0.1,
11829
- className: cn(
11830
- "h-8 text-xs",
11831
- psaHigh && "border-red-400 text-red-800",
11832
- disabled && "pointer-events-none"
11833
- ),
11834
- value: safe.psaNgMl,
11835
- onChange: (e) => {
11836
- const raw = e.target.value;
11837
- const n = Number(raw);
11838
- update({
11839
- ...safe,
11840
- psaNgMl: raw === "" || raw === "-" ? 0 : !Number.isFinite(n) ? 0 : Math.max(0, n)
11841
- });
11842
- },
11843
- disabled
11844
- }
11845
- )
11846
- ] }),
11847
- /* @__PURE__ */ jsxs("label", { className: "col-span-2 block", children: [
11848
- /* @__PURE__ */ jsx("span", { className: "mb-1 block font-medium text-slate-700", children: "Uroflow Qmax (ml/s)" }),
11849
- /* @__PURE__ */ jsx(
11850
- Input,
11851
- {
11852
- type: "number",
11853
- min: 0,
11854
- step: 0.1,
11855
- className: cn(
11856
- "h-8 text-xs",
11857
- qmaxLow && "border-amber-500 text-amber-900",
11858
- disabled && "pointer-events-none"
11859
- ),
11860
- value: safe.uroflowQmaxMlSec,
11861
- onChange: (e) => {
11862
- const raw = e.target.value;
11863
- const n = Number(raw);
11864
- update({
11865
- ...safe,
11866
- uroflowQmaxMlSec: raw === "" || raw === "-" ? 0 : !Number.isFinite(n) ? 0 : Math.max(0, n)
11867
- });
11868
- },
11869
- disabled
11870
- }
11871
- )
11872
- ] })
11873
- ] }),
11782
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 pt-6 text-xs", children: [
11874
11783
  /* @__PURE__ */ jsx(
11875
- "div",
11784
+ Checkbox,
11876
11785
  {
11877
- className: cn(
11878
- "mt-2 rounded p-2 text-[11px]",
11879
- safe.prostateVolumeCc > 30 ? "bg-amber-500/10 text-amber-900" : safe.prostateVolumeCc > 0 ? "bg-emerald-500/10 text-emerald-900" : "bg-muted/40 text-muted-foreground"
11880
- ),
11881
- children: prostateLine
11786
+ checked: safe.hydronephrosis,
11787
+ onCheckedChange: (c) => update({ ...safe, hydronephrosis: c === true }),
11788
+ disabled
11882
11789
  }
11883
11790
  ),
11884
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "<30 cc medical \xB7 30\u201380 cc TURP \xB7 >80 cc HoLEP/Open" })
11791
+ "Hydronephrosis on imaging"
11885
11792
  ] }),
11886
- /* @__PURE__ */ jsxs(Panel3, { title: "IPSS & cues", children: [
11887
- /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
11888
- /* @__PURE__ */ jsx("span", { className: "text-slate-700", children: "IPSS total" }),
11889
- /* @__PURE__ */ jsxs("span", { className: `rounded-full px-2 py-0.5 text-[11px] font-semibold ${ipssBandCls}`, children: [
11890
- ipssTotal,
11891
- "/35 \xB7 ",
11892
- urologyIpssBand(ipssTotal)
11893
- ] })
11894
- ] }),
11895
- /* @__PURE__ */ jsx("div", { className: "mb-3 h-1.5 overflow-hidden rounded-full bg-slate-200", children: /* @__PURE__ */ jsx(
11896
- "div",
11793
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-xs", children: [
11794
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Prostate volume (cc)" }),
11795
+ /* @__PURE__ */ jsx(
11796
+ Input,
11897
11797
  {
11898
- className: cn(
11899
- "h-full transition-[width]",
11900
- ipssTotal >= 20 ? "bg-red-500" : ipssTotal >= 8 ? "bg-amber-500" : "bg-emerald-500"
11901
- ),
11902
- style: { width: `${Math.min(100, ipssTotal / 35 * 100)}%` }
11798
+ type: "number",
11799
+ min: 0,
11800
+ step: 0.1,
11801
+ className: "h-8 text-xs",
11802
+ value: safe.prostateVolumeCc,
11803
+ onChange: (e) => setNum("prostateVolumeCc", e.target.value),
11804
+ disabled
11903
11805
  }
11904
- ) }),
11905
- /* @__PURE__ */ jsxs("p", { className: "mb-3 text-[10px] text-muted-foreground", children: [
11906
- "From smart history (",
11907
- /* @__PURE__ */ jsx("span", { className: "font-mono text-foreground", children: SMART_HISTORY_FIELD }),
11908
- "). \u226520 \u2192 consider surgical intervention."
11909
- ] }),
11910
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 text-[11px]", children: [
11911
- smart.haematuria.present && !smart.haematuria.painful ? /* @__PURE__ */ jsx("div", { className: "rounded bg-red-500/10 p-1.5 text-red-800", children: "Painless haematuria \u2014 mandatory cystoscopy" }) : null,
11912
- psaHigh ? /* @__PURE__ */ jsx("div", { className: "rounded bg-amber-500/10 p-1.5 text-amber-900", children: "PSA >4 \u2014 workup for Ca prostate" }) : null,
11913
- exam.dre.consistency === DRE_HARD ? /* @__PURE__ */ jsxs("div", { className: "rounded bg-red-500/10 p-1.5 text-red-800", children: [
11914
- "Hard / nodular prostate (",
11915
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: EXAMINATION_FIELD }),
11916
- ") \u2014 biopsy pathway"
11917
- ] }) : null,
11918
- exam.gu.suspiciousMass ? /* @__PURE__ */ jsx("div", { className: "rounded bg-red-500/10 p-1.5 text-red-800", children: "Suspicious scrotal mass \u2014 urgent surgical eval" }) : null
11919
- ] })
11806
+ )
11807
+ ] }),
11808
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-xs", children: [
11809
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "PSA (ng/mL)" }),
11810
+ /* @__PURE__ */ jsx(
11811
+ Input,
11812
+ {
11813
+ type: "number",
11814
+ min: 0,
11815
+ step: 0.01,
11816
+ className: "h-8 text-xs",
11817
+ value: safe.psaNgMl,
11818
+ onChange: (e) => setNum("psaNgMl", e.target.value),
11819
+ disabled
11820
+ }
11821
+ )
11920
11822
  ] }),
11921
- /* @__PURE__ */ jsx(Panel3, { title: "Risk stratification", children: /* @__PURE__ */ jsx("div", { className: "space-y-2", children: [
11922
- ["ckd", "CKD"],
11923
- ["diabetes", "Diabetes"],
11924
- ["anticoagulants", "Anticoagulants"],
11925
- ["activeInfection", "Active infection"]
11926
- ].map(([key, lab]) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2", children: [
11823
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-xs md:col-span-2", children: [
11824
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Uroflow Qmax (mL/s)" }),
11825
+ /* @__PURE__ */ jsx(
11826
+ Input,
11827
+ {
11828
+ type: "number",
11829
+ min: 0,
11830
+ step: 0.1,
11831
+ className: "h-8 text-xs",
11832
+ value: safe.uroflowQmaxMlSec,
11833
+ onChange: (e) => setNum("uroflowQmaxMlSec", e.target.value),
11834
+ disabled
11835
+ }
11836
+ )
11837
+ ] })
11838
+ ] }),
11839
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t border-muted-foreground/20 pt-3", children: [
11840
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-slate-800", children: "Risk stratification" }),
11841
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: RISK_ROWS.map((r) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs", children: [
11927
11842
  /* @__PURE__ */ jsx(
11928
11843
  Checkbox,
11929
11844
  {
11930
- checked: safe.risk[key],
11931
- disabled,
11932
- onCheckedChange: (c) => setRisk(key, c === true)
11845
+ checked: safe.risk[r.key],
11846
+ onCheckedChange: () => toggleRisk(r.key),
11847
+ disabled
11933
11848
  }
11934
11849
  ),
11935
- /* @__PURE__ */ jsx("span", { className: "text-slate-700", children: lab })
11936
- ] }, key)) }) })
11850
+ r.label
11851
+ ] }, r.key)) })
11937
11852
  ] })
11938
- ] }),
11939
- showError ? /* @__PURE__ */ jsx("p", { className: "px-3 pb-2 text-xs text-red-500", children: error }) : null
11853
+ ] })
11940
11854
  ] });
11941
11855
  };
11942
11856
  var FieldRenderer = ({ fieldId }) => {
@@ -12889,22 +12803,32 @@ var ReadOnlyFieldRenderer = ({
12889
12803
  ].join("\n");
12890
12804
  return /* @__PURE__ */ jsx(ReadOnlyText, { fieldDef, value: lines });
12891
12805
  }
12892
- case "urology_smart_history":
12893
- return /* @__PURE__ */ jsx(
12894
- ReadOnlyText,
12895
- {
12896
- fieldDef,
12897
- value: value && typeof value === "object" ? JSON.stringify(value, null, 2) : value == null || value === "" ? "\u2014" : String(value)
12898
- }
12899
- );
12900
- case "urology_examination":
12901
- return /* @__PURE__ */ jsx(
12902
- ReadOnlyText,
12903
- {
12904
- fieldDef,
12905
- value: value && typeof value === "object" ? JSON.stringify(value, null, 2) : value == null || value === "" ? "\u2014" : String(value)
12906
- }
12907
- );
12806
+ case "urology_smart_history": {
12807
+ const structuredDisplay = value && typeof value === "object" ? JSON.stringify(normalizeUrologySmartHistory(value), null, 2) : value == null || value === "" ? "\u2014" : String(value);
12808
+ const hopcDef = resolveFieldDef?.("symptoms");
12809
+ const hopcRaw = allValues?.["symptoms"];
12810
+ const hopcStr = typeof hopcRaw === "string" ? hopcRaw : hopcRaw != null && hopcRaw !== "" ? String(hopcRaw) : "";
12811
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
12812
+ /* @__PURE__ */ jsx(ReadOnlyText, { fieldDef, value: structuredDisplay }),
12813
+ hopcDef ? /* @__PURE__ */ jsx(ReadOnlyText, { fieldDef: hopcDef, value: hopcStr }) : hopcStr ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
12814
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "History of presenting complaint" }),
12815
+ /* @__PURE__ */ jsx("div", { className: "text-gray-900 border border-gray-200 rounded-md p-3 bg-gray-50 min-h-[2.5rem] whitespace-pre-wrap", children: hopcStr })
12816
+ ] }) : null
12817
+ ] });
12818
+ }
12819
+ case "urology_examination": {
12820
+ const structuredDisplay = value && typeof value === "object" ? JSON.stringify(value, null, 2) : value == null || value === "" ? "\u2014" : String(value);
12821
+ const clinicalDef = resolveFieldDef?.("clinical_examination");
12822
+ const clinicalRaw = allValues?.["clinical_examination"];
12823
+ const clinicalStr = typeof clinicalRaw === "string" ? clinicalRaw : clinicalRaw != null && clinicalRaw !== "" ? String(clinicalRaw) : "";
12824
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
12825
+ /* @__PURE__ */ jsx(ReadOnlyText, { fieldDef, value: structuredDisplay }),
12826
+ clinicalDef ? /* @__PURE__ */ jsx(ReadOnlyText, { fieldDef: clinicalDef, value: clinicalStr }) : clinicalStr ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
12827
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: "Clinical examination" }),
12828
+ /* @__PURE__ */ jsx("div", { className: "text-gray-900 border border-gray-200 rounded-md p-3 bg-gray-50 min-h-[2.5rem] whitespace-pre-wrap", children: clinicalStr })
12829
+ ] }) : null
12830
+ ] });
12831
+ }
12908
12832
  case "urology_pathway":
12909
12833
  return /* @__PURE__ */ jsx(
12910
12834
  ReadOnlyText,
@@ -13154,6 +13078,6 @@ function createUploadHandler(options) {
13154
13078
  };
13155
13079
  }
13156
13080
 
13157
- export { AddInvestigationField, AddMedicationField, DifferentialDiagnosis, DynamicForm, EMAIL_REGEX, FieldRenderer, FormControls, FormProvider, FormStore, ImageUploadWidget, MAR_MEDICATION_ORDERS_META_KEY, MediaUploadWidget, ReadOnlyForm, ReadOnlyImageUpload, ReadOnlyMediaUpload, ReadOnlySignature, ReadOnlyTable, RichTextWidget, SignatureUploadWidget, SmartForm, SmartTextareaWidget, attachUrologyClinicalPathwayMetadata, createUploadHandler, evaluateRules, fieldMetaRequestsMarMedicationOrdersButton, isUrologyTemplate, shouldFoldUrologyClinicalPathway, useField, useFieldHandlers, useForm, useFormStore, useFrequentItems, usePackages, useSmartKeywords, validateField, validateForm };
13081
+ export { AddInvestigationField, AddMedicationField, DifferentialDiagnosis, DynamicForm, EMAIL_REGEX, FieldRenderer, FormControls, FormProvider, FormStore, ImageUploadWidget, MAR_MEDICATION_ORDERS_META_KEY, MediaUploadWidget, ReadOnlyForm, ReadOnlyImageUpload, ReadOnlyMediaUpload, ReadOnlySignature, ReadOnlyTable, RichTextWidget, SignatureUploadWidget, SmartForm, SmartTextareaWidget, createUploadHandler, evaluateRules, fieldMetaRequestsMarMedicationOrdersButton, useField, useFieldHandlers, useForm, useFormStore, useFrequentItems, usePackages, useSmartKeywords, validateField, validateForm };
13158
13082
  //# sourceMappingURL=index.mjs.map
13159
13083
  //# sourceMappingURL=index.mjs.map