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.cjs CHANGED
@@ -65,326 +65,6 @@ function fieldMetaRequestsMarMedicationOrdersButton(meta) {
65
65
  return meta[MAR_MEDICATION_ORDERS_META_KEY] === true;
66
66
  }
67
67
 
68
- // src/core/urologyClinicalPathwayMetadata.ts
69
- function isUrologyTemplate(template) {
70
- if (!template || typeof template !== "object") return false;
71
- const t = template;
72
- return t.meta?.specialization === "urology" || t.specialization === "urology" || t.id === "uro_prescription";
73
- }
74
- function shouldFoldUrologyClinicalPathway(template, payload) {
75
- if (isUrologyTemplate(template)) return true;
76
- return Object.keys(payload).some(
77
- (k) => k.startsWith("uro_") || k === "chief_complaints" || k === "chief_complaints_chips"
78
- );
79
- }
80
- var UROLOGY_EXTRA_METADATA_KEYS = ["symptoms", "notes", "vitals"];
81
- function isUroPlainObject(v) {
82
- return v !== null && typeof v === "object" && !Array.isArray(v);
83
- }
84
- function coerceUrologyBool(value) {
85
- if (value === true || value === false) return value;
86
- if (value === "true") return true;
87
- if (value === "false") return false;
88
- if (Array.isArray(value)) {
89
- if (value.includes("yes") || value.includes(true)) return true;
90
- if (value.length === 0) return false;
91
- }
92
- return value;
93
- }
94
- function urologyIpssItem(v) {
95
- const n = Number(v);
96
- if (!Number.isFinite(n)) return 0;
97
- return Math.min(5, Math.max(0, Math.round(n)));
98
- }
99
- function urologyIpssSevenTotalFromParts(ipss) {
100
- return ipss.incomplete + ipss.frequency + ipss.intermittency + ipss.urgency + ipss.weakStream + ipss.straining + ipss.nocturia;
101
- }
102
- function buildUrologySmartHistoryFromLegacyFlat(flat) {
103
- return {
104
- socrates_pain_flank_suprapubic: {
105
- site: flat.uro_sh_socrates_site,
106
- onset: flat.uro_sh_socrates_onset,
107
- character: flat.uro_sh_socrates_character,
108
- radiation: flat.uro_sh_socrates_radiation,
109
- associated: flat.uro_sh_socrates_associated,
110
- timing: flat.uro_sh_socrates_timing,
111
- exacerbating: flat.uro_sh_socrates_exacerbating,
112
- severity_0_to_10: flat.uro_sh_socrates_severity
113
- },
114
- ipss_international_symptom_score: {
115
- incomplete_emptying: flat.uro_sh_ipss_incomplete,
116
- frequency_lt_2h: flat.uro_sh_ipss_frequency,
117
- intermittency: flat.uro_sh_ipss_intermittency,
118
- urgency: flat.uro_sh_ipss_urgency,
119
- weak_stream: flat.uro_sh_ipss_weak_stream,
120
- straining: flat.uro_sh_ipss_straining,
121
- nocturia: flat.uro_sh_ipss_nocturia,
122
- qol_0_to_6: flat.uro_sh_ipss_qol,
123
- seven_item_total_computed_max_35: flat.uro_sh_ipss_total_display
124
- },
125
- haematuria_profile: {
126
- present: flat.uro_sh_haem_present,
127
- timing: flat.uro_sh_haem_timing,
128
- painful: flat.uro_sh_haem_painful,
129
- clots: flat.uro_sh_haem_clots
130
- },
131
- sexual_and_reproductive: {
132
- erectile_function: flat.uro_sh_sex_erectile,
133
- libido: flat.uro_sh_sex_libido,
134
- ejaculation: flat.uro_sh_sex_ejaculation,
135
- infertility_months: flat.uro_sh_sex_infertility_months
136
- },
137
- context_prior_stones_surgery_comorbidity: flat.uro_smart_notes_free ?? ""
138
- };
139
- }
140
- function buildUrologySmartHistoryFromComposite(raw, flat) {
141
- const soc = isUroPlainObject(raw.socrates) ? raw.socrates : {};
142
- const ipssIn = isUroPlainObject(raw.ipss) ? raw.ipss : {};
143
- const haem = isUroPlainObject(raw.haematuria) ? raw.haematuria : {};
144
- const sex = isUroPlainObject(raw.sexual) ? raw.sexual : {};
145
- const str2 = (x) => typeof x === "string" ? x : "";
146
- const sev = Number(soc.severity);
147
- const severity0to10 = Number.isFinite(sev) ? Math.min(10, Math.max(0, Math.round(sev))) : 0;
148
- const ipss = {
149
- incomplete: urologyIpssItem(ipssIn.incomplete),
150
- frequency: urologyIpssItem(ipssIn.frequency),
151
- intermittency: urologyIpssItem(ipssIn.intermittency),
152
- urgency: urologyIpssItem(ipssIn.urgency),
153
- weakStream: urologyIpssItem(
154
- ipssIn.weakStream ?? ipssIn.weak_stream
155
- ),
156
- straining: urologyIpssItem(ipssIn.straining),
157
- nocturia: urologyIpssItem(ipssIn.nocturia)
158
- };
159
- const sevenTotal = urologyIpssSevenTotalFromParts(ipss);
160
- const qolRaw = Number(raw.ipssQoL);
161
- const qol = Number.isFinite(qolRaw) ? Math.min(6, Math.max(0, Math.round(qolRaw))) : 0;
162
- const timingRaw = str2(haem.timing);
163
- const pastNotes = str2(raw.pastNotes);
164
- const legacyNotes = flat.uro_smart_notes_free;
165
- const contextPrior = pastNotes !== "" ? pastNotes : typeof legacyNotes === "string" ? legacyNotes : legacyNotes ?? "";
166
- const infertilityRaw = Number(sex.infertilityMonths);
167
- const infertilityMonths = Number.isFinite(infertilityRaw) ? Math.max(0, Math.round(infertilityRaw)) : 0;
168
- return {
169
- socrates_pain_flank_suprapubic: {
170
- site: str2(soc.site),
171
- onset: str2(soc.onset),
172
- character: str2(soc.character),
173
- radiation: str2(soc.radiation),
174
- associated: str2(soc.associated),
175
- timing: str2(soc.timing),
176
- exacerbating: str2(soc.exacerbating),
177
- severity_0_to_10: severity0to10
178
- },
179
- ipss_international_symptom_score: {
180
- incomplete_emptying: ipss.incomplete,
181
- frequency_lt_2h: ipss.frequency,
182
- intermittency: ipss.intermittency,
183
- urgency: ipss.urgency,
184
- weak_stream: ipss.weakStream,
185
- straining: ipss.straining,
186
- nocturia: ipss.nocturia,
187
- qol_0_to_6: qol,
188
- seven_item_total_computed_max_35: sevenTotal
189
- },
190
- haematuria_profile: {
191
- present: haem.present === true,
192
- timing: timingRaw,
193
- painful: haem.painful === true,
194
- clots: haem.clots === true
195
- },
196
- sexual_and_reproductive: {
197
- erectile_function: str2(sex.erectile),
198
- libido: str2(sex.libido),
199
- ejaculation: str2(sex.ejaculation),
200
- infertility_months: infertilityMonths
201
- },
202
- context_prior_stones_surgery_comorbidity: contextPrior
203
- };
204
- }
205
- function buildUrologyExaminationFromLegacyFlat(flat) {
206
- return {
207
- general: {
208
- pallor: flat.uro_ex_general_pallor,
209
- oedema: flat.uro_ex_general_oedema,
210
- icterus: flat.uro_ex_general_icterus,
211
- lymphadenopathy: flat.uro_ex_general_lymph
212
- },
213
- abdomen: {
214
- distension: flat.uro_ex_ab_distension,
215
- surgical_scars: flat.uro_ex_ab_scars,
216
- visible_mass: flat.uro_ex_ab_mass,
217
- kidney_palpable: flat.uro_ex_ab_kidney_palpable,
218
- bladder_palpable: flat.uro_ex_ab_bladder_palpable,
219
- renal_angle_tender: flat.uro_ex_ab_renal_angle_tender,
220
- bladder_dullness_retention: flat.uro_ex_ab_bladder_dull
221
- },
222
- external_genitalia_scrotum: {
223
- meatus_abnormal_hypospadias_query: flat.uro_ex_gu_meatus,
224
- phimosis_paraphimosis: flat.uro_ex_gu_phimosis,
225
- penile_lesions_ulcers: flat.uro_ex_gu_lesions,
226
- scrotal_swelling: flat.uro_ex_gu_scrotal_swelling,
227
- transilluminant: flat.uro_ex_gu_transilluminant,
228
- tender_testis: flat.uro_ex_gu_tender_testis,
229
- hydrocele: flat.uro_ex_gu_hydrocele,
230
- varicocele: flat.uro_ex_gu_varicocele,
231
- suspicious_mass_urgent: flat.uro_ex_gu_suspicious_mass
232
- },
233
- dre_prostate: {
234
- dre_performed: flat.uro_ex_dre_done,
235
- estimated_size_grams: flat.uro_ex_dre_size_g,
236
- consistency: flat.uro_ex_dre_consistency,
237
- median_sulcus: flat.uro_ex_dre_median_sulcus,
238
- tenderness_prostatitis_query: flat.uro_ex_dre_tender
239
- },
240
- examination_notes_free_text: flat.uro_examination_notes ?? ""
241
- };
242
- }
243
- function buildUrologyExaminationFromComposite(raw) {
244
- const regions = Array.isArray(raw.regions) ? raw.regions.filter((x) => typeof x === "string") : [];
245
- const g = isUroPlainObject(raw.general) ? raw.general : {};
246
- const ab = isUroPlainObject(raw.abdomen) ? raw.abdomen : {};
247
- const gu = isUroPlainObject(raw.gu) ? raw.gu : {};
248
- const dr = isUroPlainObject(raw.dre) ? raw.dre : {};
249
- const dreConsistency = typeof dr.consistency === "string" ? dr.consistency : "";
250
- const dreMedian = typeof dr.medianSulcus === "string" ? dr.medianSulcus : typeof dr.median_sulcus === "string" ? dr.median_sulcus : "";
251
- const sizeG = Number(dr.sizeGrams);
252
- const sizeGrams = Number.isFinite(sizeG) && sizeG >= 0 ? Math.round(sizeG) : 0;
253
- const notesRaw = raw.examinationNotes;
254
- const notes = typeof notesRaw === "string" ? notesRaw : "";
255
- const bool = (x) => x === true;
256
- return {
257
- regions_reviewed: regions,
258
- general: {
259
- pallor: bool(g.pallor),
260
- oedema: bool(g.oedema),
261
- icterus: bool(g.icterus),
262
- lymphadenopathy: bool(g.lymphadenopathy)
263
- },
264
- abdomen: {
265
- distension: bool(ab.distension),
266
- surgical_scars: bool(ab.scars),
267
- visible_mass: bool(ab.mass),
268
- kidney_palpable: bool(ab.kidneyPalpable),
269
- bladder_palpable: bool(ab.bladderPalpable),
270
- renal_angle_tender: bool(ab.renalAngleTender),
271
- bladder_dullness_retention: bool(ab.bladderDull)
272
- },
273
- external_genitalia_scrotum: {
274
- meatus_abnormal_hypospadias_query: bool(gu.meatusAbnormal),
275
- phimosis_paraphimosis: bool(gu.phimosis),
276
- penile_lesions_ulcers: bool(gu.lesions),
277
- scrotal_swelling: bool(gu.scrotalSwelling),
278
- transilluminant: bool(gu.transilluminant),
279
- tender_testis: bool(gu.tenderTestis),
280
- hydrocele: bool(gu.hydrocele),
281
- varicocele: bool(gu.varicocele),
282
- suspicious_mass_urgent: bool(gu.suspiciousMass)
283
- },
284
- dre_prostate: {
285
- dre_performed: bool(dr.done),
286
- estimated_size_grams: sizeGrams,
287
- consistency: dreConsistency === "none" ? "" : dreConsistency,
288
- median_sulcus: dreMedian === "none" ? "" : dreMedian,
289
- tenderness_prostatitis_query: bool(dr.tenderness)
290
- },
291
- examination_notes_free_text: notes
292
- };
293
- }
294
- function buildUrologyDecisionEngineAndRisk(flat) {
295
- const comp = flat.uro_pathway;
296
- if (isUroPlainObject(comp)) {
297
- const riskIn = isUroPlainObject(comp.risk) ? comp.risk : {};
298
- return {
299
- decision_engine: {
300
- stone_disease: {
301
- stone_size_mm: comp.stoneSizeMm ?? 0,
302
- hydronephrosis_on_imaging: coerceUrologyBool(comp.hydronephrosis)
303
- },
304
- prostate_bph_flow_lab: {
305
- prostate_volume_cc: comp.prostateVolumeCc ?? 0,
306
- psa_ng_ml: comp.psaNgMl ?? 0,
307
- uroflow_qmax_ml_s: comp.uroflowQmaxMlSec ?? 0
308
- }
309
- },
310
- risk_stratification: {
311
- ckd: coerceUrologyBool(riskIn.ckd),
312
- diabetes: coerceUrologyBool(riskIn.diabetes),
313
- anticoagulants: coerceUrologyBool(riskIn.anticoagulants),
314
- active_infection: coerceUrologyBool(riskIn.activeInfection)
315
- }
316
- };
317
- }
318
- return {
319
- decision_engine: {
320
- stone_disease: {
321
- stone_size_mm: flat.uro_stone_size_mm,
322
- hydronephrosis_on_imaging: coerceUrologyBool(flat.uro_stone_hydronephrosis)
323
- },
324
- prostate_bph_flow_lab: {
325
- prostate_volume_cc: flat.uro_prostate_vol_cc,
326
- psa_ng_ml: flat.uro_psa_ng_ml,
327
- uroflow_qmax_ml_s: flat.uro_flow_qmax
328
- }
329
- },
330
- risk_stratification: {
331
- ckd: coerceUrologyBool(flat.uro_risk_ckd),
332
- diabetes: coerceUrologyBool(flat.uro_risk_diabetes),
333
- anticoagulants: coerceUrologyBool(flat.uro_risk_anticoag),
334
- active_infection: coerceUrologyBool(flat.uro_risk_active_infection)
335
- }
336
- };
337
- }
338
- function attachUrologyClinicalPathwayMetadata(payload, template) {
339
- if (!shouldFoldUrologyClinicalPathway(template, payload)) return;
340
- const extra = UROLOGY_EXTRA_METADATA_KEYS;
341
- const keys = Object.keys(payload).filter(
342
- (k) => k.startsWith("uro_") || k === "chief_complaints" || k === "chief_complaints_chips" || extra.includes(k)
343
- );
344
- if (keys.length === 0) return;
345
- const flat = {};
346
- for (const k of keys) {
347
- flat[k] = payload[k];
348
- delete payload[k];
349
- }
350
- const triage = flat.uro_triage;
351
- const chiefComplaints = {
352
- narrative: flat.chief_complaints ?? "",
353
- chips: flat.chief_complaints_chips ?? [],
354
- duration: flat.uro_chief_duration ?? "",
355
- onset: flat.uro_chief_onset ?? "",
356
- progression: flat.uro_chief_progression ?? "",
357
- active_syndrome: flat.uro_active_syndrome ?? ""
358
- };
359
- const compositeSh = flat.uro_smart_history;
360
- const compositeEx = flat.uro_examination;
361
- const smart_history = isUroPlainObject(compositeSh) ? buildUrologySmartHistoryFromComposite(compositeSh, flat) : buildUrologySmartHistoryFromLegacyFlat(flat);
362
- const examination = isUroPlainObject(compositeEx) ? buildUrologyExaminationFromComposite(compositeEx) : buildUrologyExaminationFromLegacyFlat(flat);
363
- const { decision_engine, risk_stratification } = buildUrologyDecisionEngineAndRisk(flat);
364
- const structured = {
365
- triage_vas_and_flags: triage ?? null,
366
- chief_complaint_engine: chiefComplaints,
367
- smart_history,
368
- examination,
369
- pathway_triggers_objective_scores: decision_engine,
370
- risk_stratification,
371
- encounter_narrative: {
372
- symptoms: flat.symptoms ?? "",
373
- notes: flat.notes ?? "",
374
- vitals: flat.vitals ?? ""
375
- }
376
- };
377
- const existingMetadata = payload.metadata ?? {};
378
- const existingPathways = existingMetadata.clinical_pathways ?? {};
379
- payload.metadata = {
380
- ...existingMetadata,
381
- clinical_pathways: {
382
- ...existingPathways,
383
- urology: structured
384
- }
385
- };
386
- }
387
-
388
68
  // src/core/validate.ts
389
69
  var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
390
70
  function validateField(value, field) {
@@ -457,12 +137,82 @@ function validateForm(values, fields) {
457
137
  return errors;
458
138
  }
459
139
 
140
+ // src/core/suggestionTokens.ts
141
+ function getSuggestionTokensFromText(text) {
142
+ return text.split(/[,\n\r]+/).map((p) => p.trim()).filter(Boolean);
143
+ }
144
+ var DEFAULT_TOKEN_SEPARATOR = ", ";
145
+ function commaSplitLine(line) {
146
+ return line.split(",").map((p) => p.trim()).filter(Boolean);
147
+ }
148
+ function dedupeChipOrder(chips) {
149
+ const seen = /* @__PURE__ */ new Set();
150
+ const out = [];
151
+ for (const c of chips) {
152
+ if (seen.has(c)) continue;
153
+ seen.add(c);
154
+ out.push(c);
155
+ }
156
+ return out;
157
+ }
158
+ function parseProseAndChipTokens(text, allowed) {
159
+ const normalized = text.replace(/\r\n/g, "\n");
160
+ let lines = normalized.split("\n");
161
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
162
+ lines.pop();
163
+ }
164
+ if (lines.length === 0) return { prose: "", chips: [] };
165
+ let chipStart = lines.length;
166
+ for (let i = lines.length - 1; i >= 0; i--) {
167
+ const toks = commaSplitLine(lines[i]);
168
+ if (toks.length === 0) break;
169
+ if (!toks.every((t) => allowed.has(t))) break;
170
+ chipStart = i;
171
+ }
172
+ if (chipStart < lines.length) {
173
+ const chipLines = lines.slice(chipStart);
174
+ const prose = lines.slice(0, chipStart).join("\n");
175
+ const chips = dedupeChipOrder(chipLines.flatMap(commaSplitLine));
176
+ return { prose, chips };
177
+ }
178
+ const parts = getSuggestionTokensFromText(normalized);
179
+ const chipsFallback = dedupeChipOrder(parts.filter((t) => allowed.has(t)));
180
+ const proseParts = parts.filter((t) => !allowed.has(t));
181
+ return { prose: proseParts.join(DEFAULT_TOKEN_SEPARATOR), chips: chipsFallback };
182
+ }
183
+ function composeProseAndChips(prose, chips, separator = DEFAULT_TOKEN_SEPARATOR) {
184
+ const chipStr = dedupeChipOrder(chips).join(separator);
185
+ const p = prose.replace(/\s+$/, "");
186
+ if (p && chipStr) return `${p}
187
+ ${chipStr}`;
188
+ if (chipStr) return chipStr;
189
+ return p;
190
+ }
191
+ function toggleSuggestionTokenInText(current, token, separator = DEFAULT_TOKEN_SEPARATOR, allowedChipValues) {
192
+ const t = String(token).trim();
193
+ if (!t) return current;
194
+ if (!allowedChipValues || allowedChipValues.size === 0) {
195
+ const parts = getSuggestionTokensFromText(current);
196
+ const i = parts.findIndex((p) => p === t);
197
+ if (i >= 0) parts.splice(i, 1);
198
+ else parts.push(t);
199
+ return parts.join(separator);
200
+ }
201
+ if (!allowedChipValues.has(t)) return current;
202
+ let { prose, chips } = parseProseAndChipTokens(current, allowedChipValues);
203
+ const idx = chips.indexOf(t);
204
+ if (idx >= 0) chips.splice(idx, 1);
205
+ else chips.push(t);
206
+ chips = dedupeChipOrder(chips);
207
+ return composeProseAndChips(prose, chips, separator);
208
+ }
209
+
460
210
  // src/core/rules.ts
461
211
  function tokenListContainsString(text, needle) {
462
212
  if (typeof text !== "string") return false;
463
213
  const want = String(needle).trim();
464
214
  if (!want) return false;
465
- const parts = text.split(",").map((p) => p.trim()).filter(Boolean);
215
+ const parts = getSuggestionTokensFromText(text);
466
216
  return parts.includes(want);
467
217
  }
468
218
  function evaluateCondition(value, condition) {
@@ -673,7 +423,7 @@ function normalizeGeneralSurgerySmartHistory(raw) {
673
423
  ...painIn,
674
424
  severity: Number.isFinite(sev) ? Math.min(10, Math.max(0, Math.round(sev))) : d.pain.severity
675
425
  };
676
- const str2 = (x) => typeof x === "string" ? x : "";
426
+ const str4 = (x) => typeof x === "string" ? x : "";
677
427
  const strArr = (x) => Array.isArray(x) ? x.filter((i) => typeof i === "string") : [];
678
428
  const swell = v.swelling && typeof v.swelling === "object" ? v.swelling : {};
679
429
  const gi = v.gi && typeof v.gi === "object" ? v.gi : {};
@@ -683,44 +433,44 @@ function normalizeGeneralSurgerySmartHistory(raw) {
683
433
  return {
684
434
  pain,
685
435
  swelling: {
686
- duration: str2(swell.duration),
687
- sizeProgression: str2(swell.sizeProgression),
688
- painfulPainless: str2(swell.painfulPainless),
436
+ duration: str4(swell.duration),
437
+ sizeProgression: str4(swell.sizeProgression),
438
+ painfulPainless: str4(swell.painfulPainless),
689
439
  checks: strArr(swell.checks)
690
440
  },
691
441
  gi: {
692
- appetite: str2(gi.appetite),
693
- weightLoss: str2(gi.weightLoss),
694
- bowelHabits: str2(gi.bowelHabits),
442
+ appetite: str4(gi.appetite),
443
+ weightLoss: str4(gi.weightLoss),
444
+ bowelHabits: str4(gi.bowelHabits),
695
445
  checks: strArr(gi.checks)
696
446
  },
697
447
  breast: {
698
- lumpDuration: str2(breast.lumpDuration),
699
- nippleDischarge: str2(breast.nippleDischarge),
448
+ lumpDuration: str4(breast.lumpDuration),
449
+ nippleDischarge: str4(breast.nippleDischarge),
700
450
  checks: strArr(breast.checks)
701
451
  },
702
452
  ano: { checks: strArr(ano.checks) },
703
453
  thyroid: {
704
- swellingDuration: str2(thy.swellingDuration),
454
+ swellingDuration: str4(thy.swellingDuration),
705
455
  checks: strArr(thy.checks)
706
456
  }
707
457
  };
708
458
  }
709
459
  function normalizeBreastExamSide(partial) {
710
460
  const e = defaultBreastExamSide();
711
- const str2 = (x) => typeof x === "string" ? x : "";
712
- const bool = (x) => x === true;
461
+ const str4 = (x) => typeof x === "string" ? x : "";
462
+ const bool2 = (x) => x === true;
713
463
  if (!partial || typeof partial !== "object") return e;
714
464
  const p = partial;
715
465
  return {
716
- size: str2(p.size),
717
- quadrant: str2(p.quadrant),
718
- tenderness: str2(p.tenderness),
719
- fixity: str2(p.fixity),
720
- skinInvolvement: str2(p.skinInvolvement),
721
- consistency: str2(p.consistency),
722
- axillaryNodes: bool(p.axillaryNodes),
723
- nippleAreolarComplex: bool(p.nippleAreolarComplex)
466
+ size: str4(p.size),
467
+ quadrant: str4(p.quadrant),
468
+ tenderness: str4(p.tenderness),
469
+ fixity: str4(p.fixity),
470
+ skinInvolvement: str4(p.skinInvolvement),
471
+ consistency: str4(p.consistency),
472
+ axillaryNodes: bool2(p.axillaryNodes),
473
+ nippleAreolarComplex: bool2(p.nippleAreolarComplex)
724
474
  };
725
475
  }
726
476
  function normalizeBreastExaminationBlock(raw) {
@@ -744,7 +494,7 @@ function normalizeGeneralSurgeryExamination(raw) {
744
494
  const th = v.thyroid && typeof v.thyroid === "object" ? v.thyroid : {};
745
495
  const ar = v.anorectal && typeof v.anorectal === "object" ? v.anorectal : {};
746
496
  const lm = v.limb && typeof v.limb === "object" ? v.limb : {};
747
- const bool = (x) => x === true;
497
+ const bool2 = (x) => x === true;
748
498
  return {
749
499
  general: strArr(v.general),
750
500
  regions: strArr(v.regions),
@@ -757,27 +507,27 @@ function normalizeGeneralSurgeryExamination(raw) {
757
507
  },
758
508
  hernia: {
759
509
  site: typeof hb.site === "string" ? hb.site : "",
760
- reducible: bool(hb.reducible),
761
- coughImpulse: bool(hb.coughImpulse),
762
- tenderness: bool(hb.tenderness)
510
+ reducible: bool2(hb.reducible),
511
+ coughImpulse: bool2(hb.coughImpulse),
512
+ tenderness: bool2(hb.tenderness)
763
513
  },
764
514
  breast: normalizeBreastExaminationBlock(br),
765
515
  thyroid: {
766
516
  size: typeof th.size === "string" ? th.size : "",
767
- mobileDeglutition: bool(th.mobileDeglutition),
768
- nodules: bool(th.nodules),
769
- cervicalNodes: bool(th.cervicalNodes)
517
+ mobileDeglutition: bool2(th.mobileDeglutition),
518
+ nodules: bool2(th.nodules),
519
+ cervicalNodes: bool2(th.cervicalNodes)
770
520
  },
771
521
  anorectal: {
772
522
  inspection: typeof ar.inspection === "string" ? ar.inspection : "",
773
- dreDone: bool(ar.dreDone),
774
- proctoscopyDone: bool(ar.proctoscopyDone),
523
+ dreDone: bool2(ar.dreDone),
524
+ proctoscopyDone: bool2(ar.proctoscopyDone),
775
525
  notes: typeof ar.notes === "string" ? ar.notes : ""
776
526
  },
777
527
  limb: {
778
- dilatedVeins: bool(lm.dilatedVeins),
779
- ulcer: bool(lm.ulcer),
780
- oedema: bool(lm.oedema)
528
+ dilatedVeins: bool2(lm.dilatedVeins),
529
+ ulcer: bool2(lm.ulcer),
530
+ oedema: bool2(lm.oedema)
781
531
  }
782
532
  };
783
533
  }
@@ -1054,70 +804,102 @@ function formatGeneralSurgeryExaminationToNarrative(v) {
1054
804
  }
1055
805
 
1056
806
  // src/core/urologyPathway.ts
1057
- var defaultUrologySmartHistory = () => ({
1058
- socrates: {
1059
- site: "",
1060
- onset: "",
1061
- character: "",
1062
- radiation: "",
1063
- associated: "",
1064
- timing: "",
1065
- exacerbating: "",
1066
- severity: 0
1067
- },
1068
- ipss: {
1069
- incomplete: 0,
1070
- frequency: 0,
1071
- intermittency: 0,
1072
- urgency: 0,
1073
- weakStream: 0,
1074
- straining: 0,
1075
- nocturia: 0
1076
- },
1077
- ipssQoL: 0,
1078
- haematuria: { present: false, timing: "", painful: false, clots: false },
1079
- sexual: { erectile: "", libido: "", ejaculation: "", infertilityMonths: 0 },
1080
- pastNotes: ""
1081
- });
1082
- var defaultUrologyExamination = () => ({
1083
- regions: [],
1084
- general: { pallor: false, oedema: false, icterus: false, lymphadenopathy: false },
1085
- abdomen: {
1086
- distension: false,
1087
- scars: false,
1088
- mass: false,
1089
- kidneyPalpable: false,
1090
- bladderPalpable: false,
1091
- renalAngleTender: false,
1092
- bladderDull: false
1093
- },
1094
- gu: {
1095
- meatusAbnormal: false,
1096
- phimosis: false,
1097
- lesions: false,
1098
- scrotalSwelling: false,
1099
- transilluminant: false,
1100
- tenderTestis: false,
1101
- hydrocele: false,
1102
- varicocele: false,
1103
- suspiciousMass: false
1104
- },
1105
- dre: { done: false, sizeGrams: 0, consistency: "", medianSulcus: "", tenderness: false },
1106
- examinationNotes: ""
1107
- });
1108
- function clampInt(n, lo, hi) {
1109
- if (!Number.isFinite(n)) return lo;
1110
- return Math.min(hi, Math.max(lo, Math.round(n)));
807
+ function defaultUrologySmartHistory() {
808
+ return {
809
+ socrates: {
810
+ site: "",
811
+ onset: "",
812
+ character: "",
813
+ radiation: "",
814
+ associated: "",
815
+ timing: "",
816
+ exacerbating: "",
817
+ severity: 0
818
+ },
819
+ ipss: {
820
+ incomplete: 0,
821
+ frequency: 0,
822
+ intermittency: 0,
823
+ urgency: 0,
824
+ weakStream: 0,
825
+ straining: 0,
826
+ nocturia: 0
827
+ },
828
+ ipssQoL: 0,
829
+ haematuria: {
830
+ present: false,
831
+ timing: "none",
832
+ painful: false,
833
+ clots: false
834
+ },
835
+ sexual: {
836
+ erectile: "none",
837
+ libido: "none",
838
+ ejaculation: "none",
839
+ infertilityMonths: 0
840
+ },
841
+ pastNotes: ""
842
+ };
843
+ }
844
+ function defaultUrologyExamination() {
845
+ return {
846
+ regions: [],
847
+ general: {
848
+ pallor: false,
849
+ oedema: false,
850
+ icterus: false,
851
+ lymphadenopathy: false
852
+ },
853
+ abdomen: {
854
+ distension: false,
855
+ scars: false,
856
+ mass: false,
857
+ kidneyPalpable: false,
858
+ bladderPalpable: false,
859
+ renalAngleTender: false,
860
+ bladderDull: false
861
+ },
862
+ gu: {
863
+ meatusAbnormal: false,
864
+ phimosis: false,
865
+ lesions: false,
866
+ scrotalSwelling: false,
867
+ transilluminant: false,
868
+ tenderTestis: false,
869
+ hydrocele: false,
870
+ varicocele: false,
871
+ suspiciousMass: false
872
+ },
873
+ dre: {
874
+ done: false,
875
+ sizeGrams: 0,
876
+ consistency: "",
877
+ medianSulcus: "",
878
+ tenderness: false
879
+ },
880
+ examinationNotes: ""
881
+ };
882
+ }
883
+ function defaultUrologyPathway() {
884
+ return {
885
+ stoneSizeMm: 0,
886
+ hydronephrosis: false,
887
+ prostateVolumeCc: 0,
888
+ psaNgMl: 0,
889
+ uroflowQmaxMlSec: 0,
890
+ risk: {
891
+ ckd: false,
892
+ diabetes: false,
893
+ anticoagulants: false,
894
+ activeInfection: false
895
+ }
896
+ };
897
+ }
898
+ function ipssItem(v) {
899
+ const n = Number(v);
900
+ if (!Number.isFinite(n)) return 0;
901
+ return Math.min(5, Math.max(0, Math.round(n)));
1111
902
  }
1112
- var SOCRATES_KEYS = [
1113
- "site",
1114
- "onset",
1115
- "character",
1116
- "radiation",
1117
- "associated",
1118
- "timing",
1119
- "exacerbating"
1120
- ];
1121
903
  function urologyIpssSevenTotal(ipss) {
1122
904
  return ipss.incomplete + ipss.frequency + ipss.intermittency + ipss.urgency + ipss.weakStream + ipss.straining + ipss.nocturia;
1123
905
  }
@@ -1126,159 +908,269 @@ function urologyIpssBand(total) {
1126
908
  if (total <= 19) return "Moderate";
1127
909
  return "Severe";
1128
910
  }
911
+ function hopcMeaningfulStr(x) {
912
+ if (typeof x !== "string") return "";
913
+ const t = x.trim();
914
+ if (!t || t.toLowerCase() === "none") return "";
915
+ return t;
916
+ }
917
+ function hopcNonEmpty(lines) {
918
+ return lines.map((s) => (s ?? "").trim()).filter(Boolean);
919
+ }
920
+ function formatUrologySmartHistoryToSymptomsNarrative(safe, flags) {
921
+ const { showPain, showLUTS, showHaem, showSex } = flags;
922
+ const parts = [];
923
+ if (showPain) {
924
+ const s = safe.socrates;
925
+ const painLines = hopcNonEmpty([
926
+ s.site ? `Site: ${s.site}` : "",
927
+ s.onset ? `Onset: ${s.onset}` : "",
928
+ s.character ? `Character: ${s.character}` : "",
929
+ s.radiation ? `Radiation: ${s.radiation}` : "",
930
+ s.associated ? `Associated: ${s.associated}` : "",
931
+ s.timing ? `Timing: ${s.timing}` : "",
932
+ s.exacerbating ? `Exacerbating/relieving: ${s.exacerbating}` : "",
933
+ `Severity: ${s.severity}/10`
934
+ ]);
935
+ const hasNonDefaultPain = painLines.length > 1 || painLines.length === 1 && painLines[0] !== "Severity: 0/10";
936
+ if (hasNonDefaultPain) parts.push(`SOCRATES:
937
+ ${painLines.join("\n")}`);
938
+ }
939
+ if (showLUTS) {
940
+ const ip = safe.ipss;
941
+ const any = ip.incomplete || ip.frequency || ip.intermittency || ip.urgency || ip.weakStream || ip.straining || ip.nocturia || safe.ipssQoL;
942
+ if (any) {
943
+ const ipssTotal = urologyIpssSevenTotal(safe.ipss);
944
+ const ipssBandLabel = urologyIpssBand(ipssTotal);
945
+ parts.push(`IPSS: ${ipssTotal}/35 (${ipssBandLabel}) \xB7 QoL ${safe.ipssQoL}/6`);
946
+ }
947
+ }
948
+ if (showHaem) {
949
+ const h = safe.haematuria;
950
+ const timing = hopcMeaningfulStr(h.timing);
951
+ const haemLines = hopcNonEmpty([
952
+ h.present ? "Haematuria present" : "",
953
+ timing ? `Timing: ${timing}` : "",
954
+ h.painful ? "Painful" : "",
955
+ h.clots ? "Clots" : ""
956
+ ]);
957
+ if (haemLines.length) parts.push(`Haematuria:
958
+ ${haemLines.join("\n")}`);
959
+ }
960
+ if (showSex) {
961
+ const sx = safe.sexual;
962
+ const infertility = sx.infertilityMonths;
963
+ const sexLines = hopcNonEmpty([
964
+ hopcMeaningfulStr(sx.erectile) ? `Erectile function: ${hopcMeaningfulStr(sx.erectile)}` : "",
965
+ hopcMeaningfulStr(sx.libido) ? `Libido: ${hopcMeaningfulStr(sx.libido)}` : "",
966
+ hopcMeaningfulStr(sx.ejaculation) ? `Ejaculation: ${hopcMeaningfulStr(sx.ejaculation)}` : "",
967
+ Number.isFinite(infertility) && infertility > 0 ? `Infertility: ${infertility} months` : ""
968
+ ]);
969
+ if (sexLines.length) parts.push(`Sexual/reproductive:
970
+ ${sexLines.join("\n")}`);
971
+ }
972
+ return parts.join("\n\n").trim();
973
+ }
974
+ function formatUrologyExaminationToClinicalExamination(v) {
975
+ const sections = [];
976
+ const generalLabels = {
977
+ pallor: "Pallor (chronic dz)",
978
+ oedema: "Oedema (renal)",
979
+ icterus: "Icterus",
980
+ lymphadenopathy: "Lymphadenopathy"
981
+ };
982
+ const general = Object.keys(generalLabels).filter((k) => v.general[k]).map((k) => generalLabels[k]);
983
+ if (general.length) sections.push(`General: ${general.join(", ")}`);
984
+ const abdLabels = {
985
+ distension: "Distension",
986
+ scars: "Surgical scars",
987
+ mass: "Visible mass",
988
+ kidneyPalpable: "Kidney palpable",
989
+ bladderPalpable: "Bladder palpable",
990
+ renalAngleTender: "Renal angle tender",
991
+ bladderDull: "Bladder dullness (retention)"
992
+ };
993
+ const abd = Object.keys(abdLabels).filter((k) => v.abdomen[k]).map((k) => abdLabels[k]);
994
+ if (abd.length) sections.push(`Abdomen: ${abd.join(", ")}`);
995
+ const guLabels = {
996
+ meatusAbnormal: "Meatus abnormal (hypospadias?)",
997
+ phimosis: "Phimosis / paraphimosis",
998
+ lesions: "Penile lesions / ulcers",
999
+ scrotalSwelling: "Scrotal swelling",
1000
+ transilluminant: "Transilluminant",
1001
+ tenderTestis: "Tender testis",
1002
+ hydrocele: "Hydrocele",
1003
+ varicocele: "Varicocele",
1004
+ suspiciousMass: "Suspicious mass"
1005
+ };
1006
+ const gu = Object.keys(guLabels).filter((k) => v.gu[k]).map((k) => guLabels[k]);
1007
+ if (gu.length) sections.push(`GU: ${gu.join(", ")}`);
1008
+ const dreBits = hopcNonEmpty([
1009
+ v.dre.done ? "Performed" : "",
1010
+ v.dre.sizeGrams > 0 ? `Size ~${v.dre.sizeGrams} g` : "",
1011
+ v.dre.consistency ? `Consistency: ${v.dre.consistency}` : "",
1012
+ v.dre.medianSulcus ? `Median sulcus: ${v.dre.medianSulcus}` : "",
1013
+ v.dre.tenderness ? "Tenderness" : ""
1014
+ ]);
1015
+ if (dreBits.length) sections.push(`DRE: ${dreBits.join(", ")}`);
1016
+ return sections.join("\n");
1017
+ }
1018
+ function str(x) {
1019
+ return typeof x === "string" ? x : "";
1020
+ }
1021
+ function bool(x) {
1022
+ return x === true;
1023
+ }
1024
+ function nonNegInt(x) {
1025
+ const n = Number(x);
1026
+ if (!Number.isFinite(n)) return 0;
1027
+ return Math.max(0, Math.round(n));
1028
+ }
1129
1029
  function normalizeUrologySmartHistory(raw) {
1130
- const d = defaultUrologySmartHistory();
1131
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
1132
- const v = raw;
1133
- const socIn = v.socrates && typeof v.socrates === "object" ? v.socrates : {};
1134
- const socrates = { ...d.socrates };
1135
- for (const k of SOCRATES_KEYS) {
1136
- const x = socIn[k];
1137
- socrates[k] = typeof x === "string" ? x : "";
1138
- }
1139
- const sev = Number(socIn.severity);
1140
- socrates.severity = clampInt(Number.isFinite(sev) ? sev : d.socrates.severity, 0, 10);
1141
- const ipIn = v.ipss && typeof v.ipss === "object" ? v.ipss : {};
1142
- const ipss = { ...d.ipss };
1143
- for (const k of [
1144
- "incomplete",
1145
- "frequency",
1146
- "intermittency",
1147
- "urgency",
1148
- "weakStream",
1149
- "straining",
1150
- "nocturia"
1151
- ]) {
1152
- const n = Number(ipIn[k]);
1153
- ipss[k] = clampInt(Number.isFinite(n) ? n : 0, 0, 5);
1154
- }
1155
- const qol = Number(v.ipssQoL);
1156
- const ipssQoL = clampInt(Number.isFinite(qol) ? qol : d.ipssQoL, 0, 6);
1157
- const haIn = v.haematuria && typeof v.haematuria === "object" ? v.haematuria : {};
1158
- const timingRaw = haIn.timing;
1030
+ const base = defaultUrologySmartHistory();
1031
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
1032
+ const r = raw;
1033
+ const soc = r.socrates && typeof r.socrates === "object" && !Array.isArray(r.socrates) ? r.socrates : {};
1034
+ const socObj = soc;
1035
+ const sev = Number(socObj.severity);
1036
+ const socrates = {
1037
+ site: str(socObj.site),
1038
+ onset: str(socObj.onset),
1039
+ character: str(socObj.character),
1040
+ radiation: str(socObj.radiation),
1041
+ associated: str(socObj.associated),
1042
+ timing: str(socObj.timing),
1043
+ exacerbating: str(socObj.exacerbating),
1044
+ severity: Number.isFinite(sev) ? Math.min(10, Math.max(0, Math.round(sev))) : 0
1045
+ };
1046
+ const ipRaw = r.ipss && typeof r.ipss === "object" && !Array.isArray(r.ipss) ? r.ipss : {};
1047
+ const ip = ipRaw;
1048
+ const ipss = {
1049
+ incomplete: ipssItem(ip.incomplete),
1050
+ frequency: ipssItem(ip.frequency),
1051
+ intermittency: ipssItem(ip.intermittency),
1052
+ urgency: ipssItem(ip.urgency),
1053
+ weakStream: ipssItem(ip.weakStream ?? ip.weak_stream),
1054
+ straining: ipssItem(ip.straining),
1055
+ nocturia: ipssItem(ip.nocturia)
1056
+ };
1057
+ const qolRaw = Number(r.ipssQoL);
1058
+ const ipssQoL = Number.isFinite(qolRaw) ? Math.min(6, Math.max(0, Math.round(qolRaw))) : 0;
1059
+ const haRaw = r.haematuria && typeof r.haematuria === "object" && !Array.isArray(r.haematuria) ? r.haematuria : {};
1060
+ const ha = haRaw;
1159
1061
  const haematuria = {
1160
- present: haIn.present === true,
1161
- timing: typeof timingRaw === "string" && timingRaw !== "none" && timingRaw.length > 0 ? timingRaw : "",
1162
- painful: haIn.painful === true,
1163
- clots: haIn.clots === true
1164
- };
1165
- const sxIn = v.sexual && typeof v.sexual === "object" ? v.sexual : {};
1166
- const str2 = (x) => typeof x === "string" && x.length > 0 && x !== "none" ? x : "";
1167
- const inf = Number(sxIn.infertilityMonths);
1062
+ present: bool(ha.present),
1063
+ timing: str(ha.timing) || "none",
1064
+ painful: bool(ha.painful),
1065
+ clots: bool(ha.clots)
1066
+ };
1067
+ const sxRaw = r.sexual && typeof r.sexual === "object" && !Array.isArray(r.sexual) ? r.sexual : {};
1068
+ const sx = sxRaw;
1069
+ const infertilityMonths = nonNegInt(sx.infertilityMonths);
1168
1070
  const sexual = {
1169
- erectile: str2(sxIn.erectile),
1170
- libido: str2(sxIn.libido),
1171
- ejaculation: str2(sxIn.ejaculation),
1172
- infertilityMonths: Number.isFinite(inf) && inf >= 0 ? Math.round(inf) : 0
1071
+ erectile: str(sx.erectile) || "none",
1072
+ libido: str(sx.libido) || "none",
1073
+ ejaculation: str(sx.ejaculation) || "none",
1074
+ infertilityMonths
1173
1075
  };
1174
- const pastNotes = typeof v.pastNotes === "string" ? v.pastNotes : d.pastNotes;
1175
- return { socrates, ipss, ipssQoL, haematuria, sexual, pastNotes };
1076
+ const pastNotes = str(r.pastNotes);
1077
+ return {
1078
+ socrates,
1079
+ ipss,
1080
+ ipssQoL,
1081
+ haematuria,
1082
+ sexual,
1083
+ pastNotes
1084
+ };
1085
+ }
1086
+ function dreSelectStr(x) {
1087
+ const t = str(x).trim();
1088
+ if (!t || t.toLowerCase() === "none") return "";
1089
+ return t;
1176
1090
  }
1177
1091
  function normalizeUrologyExamination(raw) {
1178
- const d = defaultUrologyExamination();
1179
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
1180
- const v = raw;
1181
- const strArr = (x) => Array.isArray(x) ? x.filter((i) => typeof i === "string") : [];
1182
- const bool = (x) => x === true;
1183
- const g = v.general && typeof v.general === "object" ? v.general : {};
1184
- const ab = v.abdomen && typeof v.abdomen === "object" ? v.abdomen : {};
1185
- const gu = v.gu && typeof v.gu === "object" ? v.gu : {};
1186
- const dr = v.dre && typeof v.dre === "object" ? v.dre : {};
1187
- const sg = Number(dr.sizeGrams);
1188
- const consistencyRaw = dr.consistency;
1189
- const medianRaw = dr.medianSulcus;
1190
- const dreEmpty = (x) => !(typeof x === "string" && x.length > 0 && x !== "none");
1092
+ const base = defaultUrologyExamination();
1093
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
1094
+ const r = raw;
1095
+ const regions = Array.isArray(r.regions) ? r.regions.filter((x) => typeof x === "string") : [];
1096
+ const g = r.general && typeof r.general === "object" && !Array.isArray(r.general) ? r.general : {};
1097
+ const gObj = g;
1098
+ const general = {
1099
+ pallor: bool(gObj.pallor),
1100
+ oedema: bool(gObj.oedema),
1101
+ icterus: bool(gObj.icterus),
1102
+ lymphadenopathy: bool(gObj.lymphadenopathy)
1103
+ };
1104
+ const ab = r.abdomen && typeof r.abdomen === "object" && !Array.isArray(r.abdomen) ? r.abdomen : {};
1105
+ const abObj = ab;
1106
+ const abdomen = {
1107
+ distension: bool(abObj.distension),
1108
+ scars: bool(abObj.scars),
1109
+ mass: bool(abObj.mass),
1110
+ kidneyPalpable: bool(abObj.kidneyPalpable),
1111
+ bladderPalpable: bool(abObj.bladderPalpable),
1112
+ renalAngleTender: bool(abObj.renalAngleTender),
1113
+ bladderDull: bool(abObj.bladderDull)
1114
+ };
1115
+ const guRaw = r.gu && typeof r.gu === "object" && !Array.isArray(r.gu) ? r.gu : {};
1116
+ const guObj = guRaw;
1117
+ const gu = {
1118
+ meatusAbnormal: bool(guObj.meatusAbnormal),
1119
+ phimosis: bool(guObj.phimosis),
1120
+ lesions: bool(guObj.lesions),
1121
+ scrotalSwelling: bool(guObj.scrotalSwelling),
1122
+ transilluminant: bool(guObj.transilluminant),
1123
+ tenderTestis: bool(guObj.tenderTestis),
1124
+ hydrocele: bool(guObj.hydrocele),
1125
+ varicocele: bool(guObj.varicocele),
1126
+ suspiciousMass: bool(guObj.suspiciousMass)
1127
+ };
1128
+ const dr = r.dre && typeof r.dre === "object" && !Array.isArray(r.dre) ? r.dre : {};
1129
+ const drObj = dr;
1130
+ const sizeG = Number(drObj.sizeGrams);
1131
+ const dre = {
1132
+ done: bool(drObj.done),
1133
+ sizeGrams: Number.isFinite(sizeG) && sizeG >= 0 ? Math.round(sizeG) : 0,
1134
+ consistency: dreSelectStr(drObj.consistency),
1135
+ medianSulcus: dreSelectStr(drObj.medianSulcus),
1136
+ tenderness: bool(drObj.tenderness)
1137
+ };
1138
+ const examinationNotes = str(r.examinationNotes);
1191
1139
  return {
1192
- regions: strArr(v.regions),
1193
- general: {
1194
- pallor: bool(g.pallor),
1195
- oedema: bool(g.oedema),
1196
- icterus: bool(g.icterus),
1197
- lymphadenopathy: bool(g.lymphadenopathy)
1198
- },
1199
- abdomen: {
1200
- distension: bool(ab.distension),
1201
- scars: bool(ab.scars),
1202
- mass: bool(ab.mass),
1203
- kidneyPalpable: bool(ab.kidneyPalpable),
1204
- bladderPalpable: bool(ab.bladderPalpable),
1205
- renalAngleTender: bool(ab.renalAngleTender),
1206
- bladderDull: bool(ab.bladderDull)
1207
- },
1208
- gu: {
1209
- meatusAbnormal: bool(gu.meatusAbnormal),
1210
- phimosis: bool(gu.phimosis),
1211
- lesions: bool(gu.lesions),
1212
- scrotalSwelling: bool(gu.scrotalSwelling),
1213
- transilluminant: bool(gu.transilluminant),
1214
- tenderTestis: bool(gu.tenderTestis),
1215
- hydrocele: bool(gu.hydrocele),
1216
- varicocele: bool(gu.varicocele),
1217
- suspiciousMass: bool(gu.suspiciousMass)
1218
- },
1219
- dre: {
1220
- done: bool(dr.done),
1221
- sizeGrams: Number.isFinite(sg) && sg >= 0 ? Math.round(sg) : 0,
1222
- consistency: dreEmpty(consistencyRaw) ? "" : String(consistencyRaw),
1223
- medianSulcus: dreEmpty(medianRaw) ? "" : String(medianRaw),
1224
- tenderness: bool(dr.tenderness)
1225
- },
1226
- examinationNotes: typeof v.examinationNotes === "string" ? v.examinationNotes : d.examinationNotes
1140
+ regions,
1141
+ general,
1142
+ abdomen,
1143
+ gu,
1144
+ dre,
1145
+ examinationNotes
1227
1146
  };
1228
1147
  }
1229
- var defaultUrologyPathway = () => ({
1230
- stoneSizeMm: 0,
1231
- hydronephrosis: false,
1232
- prostateVolumeCc: 0,
1233
- psaNgMl: 0,
1234
- uroflowQmaxMlSec: 0,
1235
- risk: {
1236
- ckd: false,
1237
- diabetes: false,
1238
- anticoagulants: false,
1239
- activeInfection: false
1240
- }
1241
- });
1242
- function clampNonNegativeNumber(x, fallback) {
1243
- const n = Number(x);
1244
- if (!Number.isFinite(n) || n < 0) return fallback;
1245
- return n;
1246
- }
1247
1148
  function normalizeUrologyPathway(raw) {
1248
- const d = defaultUrologyPathway();
1249
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return d;
1250
- const v = raw;
1251
- const rIn = v.risk && typeof v.risk === "object" && !Array.isArray(v.risk) ? v.risk : {};
1149
+ const base = defaultUrologyPathway();
1150
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
1151
+ const r = raw;
1152
+ const stoneSizeMm = nonNegInt(r.stoneSizeMm);
1153
+ const hydronephrosis = bool(r.hydronephrosis);
1154
+ const prostateVolumeCc = Math.max(0, Number(r.prostateVolumeCc) || 0);
1155
+ const psaNgMl = Math.max(0, Number(r.psaNgMl) || 0);
1156
+ const uroflowQmaxMlSec = Math.max(0, Number(r.uroflowQmaxMlSec) || 0);
1157
+ const riskRaw = r.risk && typeof r.risk === "object" && !Array.isArray(r.risk) ? r.risk : {};
1158
+ const rk = riskRaw;
1159
+ const risk = {
1160
+ ckd: bool(rk.ckd),
1161
+ diabetes: bool(rk.diabetes),
1162
+ anticoagulants: bool(rk.anticoagulants),
1163
+ activeInfection: bool(rk.activeInfection)
1164
+ };
1252
1165
  return {
1253
- stoneSizeMm: clampNonNegativeNumber(v.stoneSizeMm, d.stoneSizeMm),
1254
- hydronephrosis: v.hydronephrosis === true,
1255
- prostateVolumeCc: clampNonNegativeNumber(v.prostateVolumeCc, d.prostateVolumeCc),
1256
- psaNgMl: clampNonNegativeNumber(v.psaNgMl, d.psaNgMl),
1257
- uroflowQmaxMlSec: clampNonNegativeNumber(v.uroflowQmaxMlSec, d.uroflowQmaxMlSec),
1258
- risk: {
1259
- ckd: rIn.ckd === true,
1260
- diabetes: rIn.diabetes === true,
1261
- anticoagulants: rIn.anticoagulants === true,
1262
- activeInfection: rIn.activeInfection === true
1263
- }
1166
+ stoneSizeMm,
1167
+ hydronephrosis,
1168
+ prostateVolumeCc,
1169
+ psaNgMl,
1170
+ uroflowQmaxMlSec,
1171
+ risk
1264
1172
  };
1265
1173
  }
1266
- function urologyStoneRecommendation(sizeMm, hydronephrosis) {
1267
- if (sizeMm <= 0) return { plan: "Enter stone size to auto-trigger plan.", surgical: false };
1268
- if (sizeMm < 5) return { plan: "Conservative + MET (\u03B1-blocker), hydration", surgical: false };
1269
- if (sizeMm <= 10)
1270
- return {
1271
- plan: hydronephrosis ? "URS / SWL \u2014 obstruction present" : "Medical expulsive therapy / URS",
1272
- surgical: hydronephrosis
1273
- };
1274
- return { plan: "URS / PCNL \u2014 surgical clearance indicated", surgical: true };
1275
- }
1276
- function urologyProstateProcedure(volumeCc) {
1277
- if (volumeCc <= 0) return "Enter prostate volume.";
1278
- if (volumeCc < 30) return "Medical (\u03B1-blocker \xB1 5-ARI)";
1279
- if (volumeCc <= 80) return "TURP";
1280
- return "HoLEP / Open simple prostatectomy";
1281
- }
1282
1174
 
1283
1175
  // src/core/painScaleFlags.ts
1284
1176
  function normalizePainScaleFlags(raw, bounds) {
@@ -1392,18 +1284,18 @@ var FormStore = class {
1392
1284
  values[field.id] = field.defaultValue ?? defaultGeneralSurgeryExamination();
1393
1285
  } else if (field.type === "general_surgery_grading") {
1394
1286
  values[field.id] = field.defaultValue ?? defaultGeneralSurgeryGrading();
1395
- } else if (field.type === "pain_scale_flags") {
1396
- const f = field;
1397
- values[field.id] = normalizePainScaleFlags(
1398
- f.defaultValue ?? { pain: f.min ?? 0, flags: [] },
1399
- { min: f.min, max: f.max }
1400
- );
1401
1287
  } else if (field.type === "urology_smart_history") {
1402
1288
  values[field.id] = field.defaultValue ?? defaultUrologySmartHistory();
1403
1289
  } else if (field.type === "urology_examination") {
1404
1290
  values[field.id] = field.defaultValue ?? defaultUrologyExamination();
1405
1291
  } else if (field.type === "urology_pathway") {
1406
1292
  values[field.id] = field.defaultValue ?? defaultUrologyPathway();
1293
+ } else if (field.type === "pain_scale_flags") {
1294
+ const f = field;
1295
+ values[field.id] = normalizePainScaleFlags(
1296
+ f.defaultValue ?? { pain: f.min ?? 0, flags: [] },
1297
+ { min: f.min, max: f.max }
1298
+ );
1407
1299
  } else values[field.id] = null;
1408
1300
  }
1409
1301
  });
@@ -1613,7 +1505,6 @@ var FormStore = class {
1613
1505
  }
1614
1506
  }
1615
1507
  });
1616
- attachUrologyClinicalPathwayMetadata(result, this.schema);
1617
1508
  return result;
1618
1509
  }
1619
1510
  // --- Core Logic ---
@@ -6586,7 +6477,7 @@ function safeJsonParse(val) {
6586
6477
  return null;
6587
6478
  }
6588
6479
  }
6589
- function clampInt2(val, min, max) {
6480
+ function clampInt(val, min, max) {
6590
6481
  if (!Number.isFinite(val)) return min;
6591
6482
  return Math.max(min, Math.min(max, Math.trunc(val)));
6592
6483
  }
@@ -6596,10 +6487,10 @@ function parseLegacyString(input) {
6596
6487
  if (gpalMatch) {
6597
6488
  const [, g, p, a, l] = gpalMatch;
6598
6489
  result.gpal = {
6599
- G: clampInt2(Number(g), 0, 20),
6600
- P: clampInt2(Number(p), 0, 20),
6601
- A: clampInt2(Number(a), 0, 20),
6602
- L: clampInt2(Number(l), 0, 20)
6490
+ G: clampInt(Number(g), 0, 20),
6491
+ P: clampInt(Number(p), 0, 20),
6492
+ A: clampInt(Number(a), 0, 20),
6493
+ L: clampInt(Number(l), 0, 20)
6603
6494
  };
6604
6495
  }
6605
6496
  const lmpMatch = input.match(/LMP:\s*([^\n]+)/i);
@@ -6632,10 +6523,10 @@ function normalizeToDraft(value) {
6632
6523
  isNewEntry: false,
6633
6524
  draft: {
6634
6525
  gpal: {
6635
- G: clampInt2(Number(gpal.G ?? 0), 0, 20),
6636
- P: clampInt2(Number(gpal.P ?? 0), 0, 20),
6637
- A: clampInt2(Number(gpal.A ?? 0), 0, 20),
6638
- L: clampInt2(Number(gpal.L ?? 0), 0, 20)
6526
+ G: clampInt(Number(gpal.G ?? 0), 0, 20),
6527
+ P: clampInt(Number(gpal.P ?? 0), 0, 20),
6528
+ A: clampInt(Number(gpal.A ?? 0), 0, 20),
6529
+ L: clampInt(Number(gpal.L ?? 0), 0, 20)
6639
6530
  },
6640
6531
  lmp: typeof lmp === "string" ? lmp : "",
6641
6532
  is_pregnant,
@@ -6653,10 +6544,10 @@ function normalizeToDraft(value) {
6653
6544
  }
6654
6545
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
6655
6546
  const gpalFromFlat = {
6656
- G: clampInt2(Number(parsed.G ?? parsed.g ?? 0), 0, 20),
6657
- P: clampInt2(Number(parsed.P ?? parsed.p ?? 0), 0, 20),
6658
- A: clampInt2(Number(parsed.A ?? parsed.a ?? 0), 0, 20),
6659
- L: clampInt2(Number(parsed.L ?? parsed.l ?? 0), 0, 20)
6547
+ G: clampInt(Number(parsed.G ?? parsed.g ?? 0), 0, 20),
6548
+ P: clampInt(Number(parsed.P ?? parsed.p ?? 0), 0, 20),
6549
+ A: clampInt(Number(parsed.A ?? parsed.a ?? 0), 0, 20),
6550
+ L: clampInt(Number(parsed.L ?? parsed.l ?? 0), 0, 20)
6660
6551
  };
6661
6552
  return {
6662
6553
  isNewEntry: false,
@@ -6808,7 +6699,7 @@ var ObstetricHistoryWidget = ({ fieldId }) => {
6808
6699
  const sanitizeGpalInput = (raw) => {
6809
6700
  const digits = raw.replace(/[^\d]/g, "").slice(0, 2);
6810
6701
  if (!digits) return 0;
6811
- return clampInt2(Number(digits), 0, 20);
6702
+ return clampInt(Number(digits), 0, 20);
6812
6703
  };
6813
6704
  const handleGpalChange = (key, raw) => {
6814
6705
  const prevWasEmpty = draft.gpal[key] === 0 && activeGpalKey === key;
@@ -9112,26 +9003,11 @@ var DischargeMedicationOrdersWidget = ({ fieldId }) => {
9112
9003
  showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
9113
9004
  ] });
9114
9005
  };
9115
- var DEFAULT_TOKEN_SEPARATOR = ", ";
9006
+ var DEFAULT_TOKEN_SEPARATOR2 = ", ";
9116
9007
  function getAppendSeparator(def) {
9117
- const raw = def.meta && typeof def.meta.appendSeparator === "string" ? def.meta.appendSeparator : DEFAULT_TOKEN_SEPARATOR;
9008
+ const raw = def.meta && typeof def.meta.appendSeparator === "string" ? def.meta.appendSeparator : DEFAULT_TOKEN_SEPARATOR2;
9118
9009
  return raw;
9119
9010
  }
9120
- function getSuggestionTokensFromText(text) {
9121
- return text.split(",").map((p) => p.trim()).filter(Boolean);
9122
- }
9123
- function toggleSuggestionTokenInText(current, token, separator = DEFAULT_TOKEN_SEPARATOR) {
9124
- const t = String(token).trim();
9125
- if (!t) return current;
9126
- const parts = getSuggestionTokensFromText(current);
9127
- const i = parts.findIndex((p) => p === t);
9128
- if (i >= 0) {
9129
- parts.splice(i, 1);
9130
- } else {
9131
- parts.push(t);
9132
- }
9133
- return parts.join(separator);
9134
- }
9135
9011
  function isTokenSelected(text, optionValue) {
9136
9012
  const t = String(optionValue).trim();
9137
9013
  if (!t) return false;
@@ -9205,8 +9081,11 @@ var SuggestionTextareaWidget = ({ fieldId }) => {
9205
9081
  setOptions([]);
9206
9082
  }
9207
9083
  }, [def]);
9084
+ const chipAllowedSet = React15.useMemo(
9085
+ () => new Set(options.map((o) => String(o.value).trim()).filter(Boolean)),
9086
+ [options]
9087
+ );
9208
9088
  React15.useEffect(() => {
9209
- if (!parallelChipFieldId || !def) return;
9210
9089
  const parallelDef = store.getFieldDef(parallelChipFieldId);
9211
9090
  if (!parallelDef || parallelDef.type !== "multiselect") return;
9212
9091
  const next = deriveKnownSuggestionChipValues(textValue, options);
@@ -9232,7 +9111,7 @@ var SuggestionTextareaWidget = ({ fieldId }) => {
9232
9111
  const labelEl = theme ? /* @__PURE__ */ jsxRuntime.jsx(Label, { className: labelClass, children: labelContent }) : /* @__PURE__ */ jsxRuntime.jsx(Label, { children: labelContent });
9233
9112
  const onChipClick = (optValue) => {
9234
9113
  if (disabled) return;
9235
- setValue(toggleSuggestionTokenInText(textValue, String(optValue), appendSeparator));
9114
+ setValue(toggleSuggestionTokenInText(textValue, String(optValue), appendSeparator, chipAllowedSet));
9236
9115
  };
9237
9116
  const hasSuggestions = options.length > 0;
9238
9117
  const hasEmbedded = embeddedFieldIds.length > 0;
@@ -9671,7 +9550,7 @@ var EMPTY = {
9671
9550
  specularMicroscopyDone: false,
9672
9551
  endothelialCount: ""
9673
9552
  };
9674
- function str(x) {
9553
+ function str2(x) {
9675
9554
  return typeof x === "string" ? x : "";
9676
9555
  }
9677
9556
  function parseValue2(raw) {
@@ -9679,24 +9558,24 @@ function parseValue2(raw) {
9679
9558
  const o = raw;
9680
9559
  const m = o.method;
9681
9560
  const method = m === "ascan_immersion" || m === "ascan_contact" || m === "optical_biometry" ? m : "optical_biometry";
9682
- const formula = str(o.formula);
9561
+ const formula = str2(o.formula);
9683
9562
  return {
9684
9563
  ...EMPTY,
9685
- axialLengthOD: str(o.axialLengthOD),
9686
- axialLengthOS: str(o.axialLengthOS),
9687
- k1k2AxisOD: str(o.k1k2AxisOD),
9688
- k1k2AxisOS: str(o.k1k2AxisOS),
9689
- anteriorChamberDepthOD: str(o.anteriorChamberDepthOD),
9690
- anteriorChamberDepthOS: str(o.anteriorChamberDepthOS),
9691
- iolPowerOD: str(o.iolPowerOD),
9692
- iolPowerOS: str(o.iolPowerOS),
9693
- targetRefractionOD: str(o.targetRefractionOD),
9694
- targetRefractionOS: str(o.targetRefractionOS),
9564
+ axialLengthOD: str2(o.axialLengthOD),
9565
+ axialLengthOS: str2(o.axialLengthOS),
9566
+ k1k2AxisOD: str2(o.k1k2AxisOD),
9567
+ k1k2AxisOS: str2(o.k1k2AxisOS),
9568
+ anteriorChamberDepthOD: str2(o.anteriorChamberDepthOD),
9569
+ anteriorChamberDepthOS: str2(o.anteriorChamberDepthOS),
9570
+ iolPowerOD: str2(o.iolPowerOD),
9571
+ iolPowerOS: str2(o.iolPowerOS),
9572
+ targetRefractionOD: str2(o.targetRefractionOD),
9573
+ targetRefractionOS: str2(o.targetRefractionOS),
9695
9574
  formula: formula || "SRK/T",
9696
9575
  method,
9697
9576
  cornealTopoUploaded: o.cornealTopoUploaded === true,
9698
9577
  specularMicroscopyDone: o.specularMicroscopyDone === true,
9699
- endothelialCount: str(o.endothelialCount)
9578
+ endothelialCount: str2(o.endothelialCount)
9700
9579
  };
9701
9580
  }
9702
9581
  var paramCell = "emr-biometry-param text-[11px] font-semibold leading-snug text-foreground sm:text-xs py-2";
@@ -10136,7 +10015,7 @@ var THYROID_OPTS = [
10136
10015
  { value: "voice_change", label: "Voice change" },
10137
10016
  { value: "thyrotoxicosis", label: "Thyrotoxicosis symptoms" }
10138
10017
  ];
10139
- var SOCRATES_KEYS2 = [
10018
+ var SOCRATES_KEYS = [
10140
10019
  "site",
10141
10020
  "onset",
10142
10021
  "character",
@@ -10230,7 +10109,7 @@ var GsSmartHistoryWidget = ({ fieldId }) => {
10230
10109
  labelEl,
10231
10110
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: bodyClass, children: [
10232
10111
  showPain ? /* @__PURE__ */ jsxRuntime.jsx(Panel, { title: "SOCRATES \u2014 pain", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2 md:grid-cols-4", children: [
10233
- SOCRATES_KEYS2.map((k) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
10112
+ SOCRATES_KEYS.map((k) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
10234
10113
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: k }),
10235
10114
  /* @__PURE__ */ jsxRuntime.jsx(
10236
10115
  Input,
@@ -11169,10 +11048,36 @@ var PainScaleFlagsWidget = ({ fieldId }) => {
11169
11048
  };
11170
11049
  var CHIEF_CHIPS_FIELD3 = "chief_complaints_chips";
11171
11050
  var ACTIVE_SYNDROME_FIELD = "uro_active_syndrome";
11051
+ var CHIEF_TEXT_FIELD = "chief_complaints";
11052
+ var SYMPTOMS_FIELD = "symptoms";
11053
+ var DIAGNOSIS_FIELD = "diagnosis";
11054
+ var URO_DURATION_FIELD = "uro_chief_duration";
11055
+ var URO_ONSET_FIELD = "uro_chief_onset";
11056
+ var URO_PROGRESSION_FIELD = "uro_chief_progression";
11057
+ var SYNDROME_LABELS = {
11058
+ luts: "LUTS / BPH",
11059
+ stone: "Urolithiasis (stone)",
11060
+ haematuria: "Haematuria",
11061
+ scrotal: "Scrotal swelling",
11062
+ retention: "Acute retention",
11063
+ ed_infertility: "ED / Infertility",
11064
+ uti: "UTI / infection"
11065
+ };
11172
11066
  function chipList2(state) {
11173
11067
  const raw = state.values[CHIEF_CHIPS_FIELD3];
11174
11068
  return Array.isArray(raw) ? raw.filter((x) => typeof x === "string") : [];
11175
11069
  }
11070
+ function str3(x) {
11071
+ return typeof x === "string" ? x : "";
11072
+ }
11073
+ function chiefOptionalFieldDisplay(x) {
11074
+ const s = str3(x).trim();
11075
+ if (!s || s.toLowerCase() === "none") return "";
11076
+ return s;
11077
+ }
11078
+ function nonEmptyLines(lines) {
11079
+ return lines.map((s) => (s ?? "").trim()).filter(Boolean);
11080
+ }
11176
11081
  var IPSS_LABELS = {
11177
11082
  incomplete: "Incomplete emptying",
11178
11083
  frequency: "Frequency (<2h)",
@@ -11182,7 +11087,7 @@ var IPSS_LABELS = {
11182
11087
  straining: "Straining",
11183
11088
  nocturia: "Nocturia (\xD7/night)"
11184
11089
  };
11185
- var SOCRATES_KEYS3 = [
11090
+ var SOCRATES_KEYS2 = [
11186
11091
  "site",
11187
11092
  "onset",
11188
11093
  "character",
@@ -11206,17 +11111,19 @@ function Panel2({
11206
11111
  }
11207
11112
  var UrologySmartHistoryWidget = ({ fieldId }) => {
11208
11113
  const { fieldDef, value, setValue, setTouched, error, touched, disabled } = useField(fieldId);
11114
+ const hopcField = useField(SYMPTOMS_FIELD);
11209
11115
  const syndromeField = useField(ACTIVE_SYNDROME_FIELD);
11210
11116
  const { state } = useForm();
11117
+ const store = useFormStore();
11211
11118
  const showError = !!error && (touched || state.submitAttempted);
11212
11119
  const safe = React15.useMemo(() => normalizeUrologySmartHistory(value), [value]);
11213
11120
  const chips = React15.useMemo(() => chipList2(state), [state]);
11214
11121
  const rawSyndrome = syndromeField.value ?? state.values[ACTIVE_SYNDROME_FIELD];
11215
11122
  const syndrome = typeof rawSyndrome === "string" && rawSyndrome !== "" && rawSyndrome !== "none" ? rawSyndrome : "none";
11216
- const showPain = chips.includes("pain_abdomen_flank") || chips.includes("scrotal_swelling_pain") || syndrome === "stone" || syndrome === "scrotal";
11217
- const showLUTS = syndrome === "luts" || syndrome === "retention" || chips.includes("frequency_urgency") || chips.includes("poor_stream") || chips.includes("acute_retention");
11123
+ const showPain = chips.includes("pain abdomen flank") || chips.includes("scrotal swelling pain") || syndrome === "stone" || syndrome === "scrotal";
11124
+ const showLUTS = syndrome === "luts" || syndrome === "retention" || chips.includes("frequency urgency") || chips.includes("poor stream") || chips.includes("acute retention");
11218
11125
  const showHaem = syndrome === "haematuria" || chips.includes("haematuria");
11219
- const showSex = syndrome === "ed_infertility" || chips.includes("erectile_dysfunction") || chips.includes("infertility_concern");
11126
+ const showSex = syndrome === "ed_infertility" || chips.includes("erectile dysfunction") || chips.includes("infertility concern");
11220
11127
  const update = (next) => {
11221
11128
  setValue(next);
11222
11129
  setTouched();
@@ -11236,6 +11143,55 @@ var UrologySmartHistoryWidget = ({ fieldId }) => {
11236
11143
  const setIpss = (key, v) => {
11237
11144
  update({ ...safe, ipss: { ...safe.ipss, [key]: v } });
11238
11145
  };
11146
+ const [hopcNarrativeLocked, setHopcNarrativeLocked] = React15.useState(false);
11147
+ React15.useEffect(() => {
11148
+ if (hopcNarrativeLocked || disabled) return;
11149
+ const next = formatUrologySmartHistoryToSymptomsNarrative(normalizeUrologySmartHistory(value), {
11150
+ showPain,
11151
+ showLUTS,
11152
+ showHaem,
11153
+ showSex
11154
+ });
11155
+ hopcField.setValue(next);
11156
+ }, [value, hopcNarrativeLocked, showPain, showLUTS, showHaem, showSex, disabled]);
11157
+ React15.useEffect(() => {
11158
+ if (disabled) return;
11159
+ const label = syndrome && syndrome !== "none" ? SYNDROME_LABELS[syndrome] ?? syndrome : "";
11160
+ const prev = store.getFieldState(DIAGNOSIS_FIELD)?.value;
11161
+ const prevText = typeof prev === "string" ? prev : "";
11162
+ const syndromeLabels = new Set(Object.values(SYNDROME_LABELS));
11163
+ const keptLines = prevText.split("\n").map((l) => l.trim()).filter(Boolean).filter((l) => !syndromeLabels.has(l));
11164
+ const nextLines = label ? [...keptLines, label] : keptLines;
11165
+ const next = nextLines.join("\n");
11166
+ if (prevText.trim() === next.trim()) return;
11167
+ store.setValues({ [DIAGNOSIS_FIELD]: next }, { touch: false });
11168
+ }, [disabled, store, syndrome]);
11169
+ React15.useEffect(() => {
11170
+ if (disabled) return;
11171
+ const duration = chiefOptionalFieldDisplay(state.values[URO_DURATION_FIELD]);
11172
+ const onset = chiefOptionalFieldDisplay(state.values[URO_ONSET_FIELD]);
11173
+ const progression = chiefOptionalFieldDisplay(state.values[URO_PROGRESSION_FIELD]);
11174
+ const prev = store.getFieldState(CHIEF_TEXT_FIELD)?.value;
11175
+ const prevText = typeof prev === "string" ? prev : "";
11176
+ const kept = prevText.split(",").map((t) => t.trim()).filter(Boolean).filter((t) => {
11177
+ const lower = t.toLowerCase();
11178
+ return !lower.startsWith("duration:") && !lower.startsWith("onset:") && !lower.startsWith("progression:");
11179
+ });
11180
+ const extra = nonEmptyLines([
11181
+ duration ? `Duration: ${duration}` : "",
11182
+ onset ? `Onset: ${onset}` : "",
11183
+ progression ? `Progression: ${progression}` : ""
11184
+ ]);
11185
+ const next = [...kept, ...extra].join(", ").trim();
11186
+ if (prevText.trim() === next.trim()) return;
11187
+ store.setValues({ [CHIEF_TEXT_FIELD]: next }, { touch: false });
11188
+ }, [
11189
+ disabled,
11190
+ state.values[URO_DURATION_FIELD],
11191
+ state.values[URO_ONSET_FIELD],
11192
+ state.values[URO_PROGRESSION_FIELD],
11193
+ store
11194
+ ]);
11239
11195
  if (!fieldDef) return null;
11240
11196
  const theme = getThemeConfig("textarea", fieldDef.theme);
11241
11197
  const wrapperClass = theme?.wrapperClassName ?? "rounded-md overflow-hidden flex flex-col border border-[#D0D0D0]";
@@ -11254,7 +11210,7 @@ var UrologySmartHistoryWidget = ({ fieldId }) => {
11254
11210
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: bodyClass, children: [
11255
11211
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: "Panels follow chief complaint chips and active syndrome (Clearsight SmartHistoryUro)." }),
11256
11212
  showPain ? /* @__PURE__ */ jsxRuntime.jsx(Panel2, { title: "SOCRATES \u2014 Pain (flank / suprapubic / testicular)", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2 md:grid-cols-4", children: [
11257
- SOCRATES_KEYS3.map((k) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
11213
+ SOCRATES_KEYS2.map((k) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
11258
11214
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: k }),
11259
11215
  /* @__PURE__ */ jsxRuntime.jsx(
11260
11216
  Input,
@@ -11490,15 +11446,50 @@ var UrologySmartHistoryWidget = ({ fieldId }) => {
11490
11446
  ] })
11491
11447
  ] }) }) : null,
11492
11448
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
11493
- /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-xs font-medium text-slate-700", children: "Past history (stones / surgeries / drugs)" }),
11449
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
11450
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-xs font-semibold text-slate-700", children: hopcField.fieldDef?.label ?? "History of presenting complaint" }),
11451
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
11452
+ hopcNarrativeLocked ? /* @__PURE__ */ jsxRuntime.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: [
11453
+ "You edited this summary. Use",
11454
+ " ",
11455
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: "'Regenerate'" }),
11456
+ " to update it from the form."
11457
+ ] }) : null,
11458
+ hopcNarrativeLocked ? /* @__PURE__ */ jsxRuntime.jsx(
11459
+ Button,
11460
+ {
11461
+ type: "button",
11462
+ variant: "outline",
11463
+ size: "sm",
11464
+ className: "h-7 shrink-0 px-2 text-xs",
11465
+ disabled: disabled || hopcField.disabled,
11466
+ onClick: () => {
11467
+ setHopcNarrativeLocked(false);
11468
+ hopcField.setValue(
11469
+ formatUrologySmartHistoryToSymptomsNarrative(
11470
+ normalizeUrologySmartHistory(value),
11471
+ { showPain, showLUTS, showHaem, showSex }
11472
+ )
11473
+ );
11474
+ },
11475
+ children: "Regenerate"
11476
+ }
11477
+ ) : null
11478
+ ] })
11479
+ ] }),
11480
+ hopcNarrativeLocked ? /* @__PURE__ */ jsxRuntime.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,
11494
11481
  /* @__PURE__ */ jsxRuntime.jsx(
11495
11482
  Textarea,
11496
11483
  {
11497
- className: "min-h-[72px] rounded-md border border-input bg-white text-sm",
11498
- placeholder: "Prior calculi, urological surgeries, anticoagulants, DM/HTN\u2026",
11499
- value: safe.pastNotes,
11500
- onChange: (e) => update({ ...safe, pastNotes: e.target.value }),
11501
- disabled
11484
+ className: "min-h-[88px] rounded-md border border-input bg-white text-sm",
11485
+ placeholder: "Auto-filled from chief complaints + smart history\u2026",
11486
+ value: typeof hopcField.value === "string" ? hopcField.value : hopcField.value == null ? "" : String(hopcField.value),
11487
+ onChange: (e) => {
11488
+ setHopcNarrativeLocked(true);
11489
+ hopcField.setValue(e.target.value);
11490
+ hopcField.setTouched();
11491
+ },
11492
+ disabled: disabled || hopcField.disabled
11502
11493
  }
11503
11494
  )
11504
11495
  ] })
@@ -11506,6 +11497,7 @@ var UrologySmartHistoryWidget = ({ fieldId }) => {
11506
11497
  showError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pb-2 text-xs text-red-500", children: error }) : null
11507
11498
  ] });
11508
11499
  };
11500
+ var URO_CLINICAL_EXAMINATION_FIELD = "clinical_examination";
11509
11501
  var GENERAL_ROWS = [
11510
11502
  { key: "pallor", label: "Pallor (chronic dz)" },
11511
11503
  { key: "oedema", label: "Oedema (renal)" },
@@ -11536,21 +11528,32 @@ var DRE_CONSISTENCY = ["none", "Soft (benign)", "Firm", "Hard / nodular (maligna
11536
11528
  var DRE_SULCUS = ["none", "Preserved", "Obliterated"];
11537
11529
  var UrologyExaminationWidget = ({ fieldId }) => {
11538
11530
  const { fieldDef, value, setValue, setTouched, error, touched, disabled } = useField(fieldId);
11531
+ const clinicalField = useField(URO_CLINICAL_EXAMINATION_FIELD);
11539
11532
  const { state } = useForm();
11540
11533
  const showError = !!error && (touched || state.submitAttempted);
11541
11534
  const safe = React15.useMemo(() => normalizeUrologyExamination(value), [value]);
11542
- const update = (next) => {
11535
+ const [clinicalNarrativeLocked, setClinicalNarrativeLocked] = React15.useState(false);
11536
+ React15.useEffect(() => {
11537
+ if (clinicalNarrativeLocked || disabled) return;
11538
+ const norm = normalizeUrologyExamination(value);
11539
+ const next = formatUrologyExaminationToClinicalExamination(norm);
11540
+ clinicalField.setValue(next);
11541
+ if (norm.examinationNotes !== next) {
11542
+ setValue({ ...norm, examinationNotes: next });
11543
+ }
11544
+ }, [value, clinicalNarrativeLocked, disabled]);
11545
+ const bump = (next) => {
11543
11546
  setValue(next);
11544
11547
  setTouched();
11545
11548
  };
11546
11549
  const toggleGeneral = (key) => {
11547
- update({ ...safe, general: { ...safe.general, [key]: !safe.general[key] } });
11550
+ bump({ ...safe, general: { ...safe.general, [key]: !safe.general[key] } });
11548
11551
  };
11549
11552
  const toggleAbdomen = (key) => {
11550
- update({ ...safe, abdomen: { ...safe.abdomen, [key]: !safe.abdomen[key] } });
11553
+ bump({ ...safe, abdomen: { ...safe.abdomen, [key]: !safe.abdomen[key] } });
11551
11554
  };
11552
11555
  const toggleGu = (key) => {
11553
- update({ ...safe, gu: { ...safe.gu, [key]: !safe.gu[key] } });
11556
+ bump({ ...safe, gu: { ...safe.gu, [key]: !safe.gu[key] } });
11554
11557
  };
11555
11558
  if (!fieldDef) return null;
11556
11559
  const theme = getThemeConfig("textarea", fieldDef.theme);
@@ -11618,7 +11621,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11618
11621
  Checkbox,
11619
11622
  {
11620
11623
  checked: safe.dre.done,
11621
- onCheckedChange: (c) => update({ ...safe, dre: { ...safe.dre, done: c === true } }),
11624
+ onCheckedChange: (c) => bump({ ...safe, dre: { ...safe.dre, done: c === true } }),
11622
11625
  disabled
11623
11626
  }
11624
11627
  ),
@@ -11635,7 +11638,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11635
11638
  value: safe.dre.sizeGrams,
11636
11639
  onChange: (e) => {
11637
11640
  const n = Number(e.target.value);
11638
- update({
11641
+ bump({
11639
11642
  ...safe,
11640
11643
  dre: {
11641
11644
  ...safe.dre,
@@ -11653,7 +11656,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11653
11656
  Select,
11654
11657
  {
11655
11658
  value: safe.dre.consistency || "none",
11656
- onValueChange: (val) => update({
11659
+ onValueChange: (val) => bump({
11657
11660
  ...safe,
11658
11661
  dre: { ...safe.dre, consistency: val === "none" ? "" : val }
11659
11662
  }),
@@ -11671,7 +11674,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11671
11674
  Select,
11672
11675
  {
11673
11676
  value: safe.dre.medianSulcus || "none",
11674
- onValueChange: (val) => update({
11677
+ onValueChange: (val) => bump({
11675
11678
  ...safe,
11676
11679
  dre: { ...safe.dre, medianSulcus: val === "none" ? "" : val }
11677
11680
  }),
@@ -11688,7 +11691,7 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11688
11691
  Checkbox,
11689
11692
  {
11690
11693
  checked: safe.dre.tenderness,
11691
- onCheckedChange: (c) => update({ ...safe, dre: { ...safe.dre, tenderness: c === true } }),
11694
+ onCheckedChange: (c) => bump({ ...safe, dre: { ...safe.dre, tenderness: c === true } }),
11692
11695
  disabled
11693
11696
  }
11694
11697
  ),
@@ -11697,15 +11700,49 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11697
11700
  ] })
11698
11701
  ] }),
11699
11702
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
11700
- /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-xs font-semibold text-slate-700", children: "Examination notes" }),
11703
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
11704
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-xs font-semibold text-slate-700", children: clinicalField.fieldDef?.label ?? "Clinical examination" }),
11705
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
11706
+ clinicalNarrativeLocked ? /* @__PURE__ */ jsxRuntime.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: [
11707
+ "You edited this summary. Use",
11708
+ " ",
11709
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "italic", children: "'Regenerate'" }),
11710
+ " to update it from the form."
11711
+ ] }) : null,
11712
+ clinicalNarrativeLocked ? /* @__PURE__ */ jsxRuntime.jsx(
11713
+ Button,
11714
+ {
11715
+ type: "button",
11716
+ variant: "outline",
11717
+ size: "sm",
11718
+ className: "h-7 shrink-0 px-2 text-xs",
11719
+ disabled: disabled || clinicalField.disabled,
11720
+ onClick: () => {
11721
+ setClinicalNarrativeLocked(false);
11722
+ const norm = normalizeUrologyExamination(value);
11723
+ clinicalField.setValue(formatUrologyExaminationToClinicalExamination(norm));
11724
+ },
11725
+ children: "Regenerate"
11726
+ }
11727
+ ) : null
11728
+ ] })
11729
+ ] }),
11730
+ clinicalNarrativeLocked ? /* @__PURE__ */ jsxRuntime.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,
11701
11731
  /* @__PURE__ */ jsxRuntime.jsx(
11702
11732
  Textarea,
11703
11733
  {
11704
11734
  className: "min-h-[88px] rounded-md border border-input bg-white text-sm",
11705
- placeholder: "Free-text exam findings\u2026",
11706
- value: safe.examinationNotes,
11707
- onChange: (e) => update({ ...safe, examinationNotes: e.target.value }),
11708
- disabled
11735
+ placeholder: "Auto-filled from selections above\u2026",
11736
+ value: typeof clinicalField.value === "string" ? clinicalField.value : clinicalField.value == null ? "" : String(clinicalField.value),
11737
+ onChange: (e) => {
11738
+ setClinicalNarrativeLocked(true);
11739
+ const t = e.target.value;
11740
+ clinicalField.setValue(t);
11741
+ clinicalField.setTouched();
11742
+ setValue({ ...safe, examinationNotes: t });
11743
+ setTouched();
11744
+ },
11745
+ disabled: disabled || clinicalField.disabled
11709
11746
  }
11710
11747
  )
11711
11748
  ] })
@@ -11713,265 +11750,142 @@ var UrologyExaminationWidget = ({ fieldId }) => {
11713
11750
  showError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pb-2 text-xs text-red-500", children: error }) : null
11714
11751
  ] });
11715
11752
  };
11716
- var SMART_HISTORY_FIELD = "uro_smart_history";
11717
- var EXAMINATION_FIELD = "uro_examination";
11718
- var DRE_HARD = "Hard / nodular (malignancy?)";
11719
- function Panel3({
11720
- title,
11721
- className,
11722
- children
11723
- }) {
11724
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("overflow-hidden rounded-md border border-[#D0D0D0] bg-white", className), children: [
11725
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-[#D0D0D0] bg-[#FEE8EC]/50 px-3 py-1.5", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-slate-700", children: title }) }),
11726
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children })
11727
- ] });
11728
- }
11753
+ var RISK_ROWS = [
11754
+ { key: "ckd", label: "Chronic kidney disease" },
11755
+ { key: "diabetes", label: "Diabetes" },
11756
+ { key: "anticoagulants", label: "Anticoagulants" },
11757
+ { key: "activeInfection", label: "Active infection" }
11758
+ ];
11729
11759
  var UrologyPathwayWidget = ({ fieldId }) => {
11730
11760
  const { fieldDef, value, setValue, setTouched, error, touched, disabled } = useField(fieldId);
11731
11761
  const { state } = useForm();
11732
11762
  const showError = !!error && (touched || state.submitAttempted);
11733
11763
  const safe = React15.useMemo(() => normalizeUrologyPathway(value), [value]);
11734
- const smart = React15.useMemo(
11735
- () => normalizeUrologySmartHistory(state.values[SMART_HISTORY_FIELD]),
11736
- [state.values]
11737
- );
11738
- const exam = React15.useMemo(
11739
- () => normalizeUrologyExamination(state.values[EXAMINATION_FIELD]),
11740
- [state.values]
11741
- );
11742
- const ipssTotal = urologyIpssSevenTotal(smart.ipss);
11743
- 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";
11744
- const stone = React15.useMemo(
11745
- () => urologyStoneRecommendation(safe.stoneSizeMm, safe.hydronephrosis),
11746
- [safe.stoneSizeMm, safe.hydronephrosis]
11747
- );
11748
- const prostateLine = React15.useMemo(
11749
- () => urologyProstateProcedure(safe.prostateVolumeCc),
11750
- [safe.prostateVolumeCc]
11751
- );
11752
- const psaHigh = safe.psaNgMl > 4;
11753
- const qmaxLow = safe.uroflowQmaxMlSec > 0 && Number.isFinite(safe.uroflowQmaxMlSec) && safe.uroflowQmaxMlSec < 10;
11754
11764
  const update = (next) => {
11755
11765
  setValue(next);
11756
11766
  setTouched();
11757
11767
  };
11758
- const setRisk = (key, next) => {
11759
- update({ ...safe, risk: { ...safe.risk, [key]: next } });
11768
+ const setNum = (key, raw) => {
11769
+ const n = Number(raw);
11770
+ const v = Number.isFinite(n) && n >= 0 ? n : 0;
11771
+ if (key === "stoneSizeMm") {
11772
+ update({ ...safe, stoneSizeMm: Math.round(v) });
11773
+ return;
11774
+ }
11775
+ update({ ...safe, [key]: v });
11776
+ };
11777
+ const toggleRisk = (key) => {
11778
+ update({
11779
+ ...safe,
11780
+ risk: { ...safe.risk, [key]: !safe.risk[key] }
11781
+ });
11760
11782
  };
11761
11783
  if (!fieldDef) return null;
11762
11784
  const theme = getThemeConfig("textarea", fieldDef.theme);
11763
11785
  const wrapperClass = theme?.wrapperClassName ?? "rounded-md overflow-hidden flex flex-col border border-[#D0D0D0]";
11764
11786
  const labelClass = theme?.labelClassName;
11787
+ const subtitle = fieldDef.meta && typeof fieldDef.meta === "object" && fieldDef.meta !== null ? String(fieldDef.meta.subtitle ?? "") : "";
11765
11788
  const labelContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
11766
11789
  fieldDef.label,
11767
11790
  fieldDef.required ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-red-500", children: "*" }) : null
11768
11791
  ] });
11769
11792
  const labelEl = theme ? /* @__PURE__ */ jsxRuntime.jsx(Label, { className: labelClass, children: labelContent }) : /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-sm font-medium", children: labelContent });
11770
11793
  const bodyClass = cn(
11771
- "-mt-1 flex min-w-0 flex-col gap-3 border-t border-[#D0D0D0] bg-white px-3 py-3",
11794
+ "-mt-1 flex min-w-0 flex-col gap-4 border-t border-[#D0D0D0] bg-white px-3 py-3",
11772
11795
  disabled && "pointer-events-none opacity-60"
11773
11796
  );
11774
- const subtitle = typeof fieldDef.meta?.subtitle === "string" && fieldDef.meta.subtitle.trim().length > 0 ? fieldDef.meta.subtitle.trim() : "";
11775
11797
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("w-full min-w-0", wrapperClass), children: [
11776
11798
  labelEl,
11799
+ subtitle ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pt-2 text-[11px] text-muted-foreground", children: subtitle }) : null,
11777
11800
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: bodyClass, children: [
11778
- subtitle ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "-mt-0.5 mb-0 text-xs font-semibold tracking-wide text-slate-700", children: subtitle }) : null,
11779
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3 text-xs md:grid-cols-2 xl:grid-cols-4", children: [
11780
- /* @__PURE__ */ jsxRuntime.jsxs(Panel3, { title: "Stone disease", children: [
11781
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block", children: [
11782
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 block font-medium text-slate-700", children: "Stone size (mm)" }),
11783
- /* @__PURE__ */ jsxRuntime.jsx(
11784
- Input,
11785
- {
11786
- type: "number",
11787
- min: 0,
11788
- step: "any",
11789
- className: cn(
11790
- "h-8 text-xs",
11791
- disabled && "pointer-events-none"
11792
- ),
11793
- value: safe.stoneSizeMm,
11794
- onChange: (e) => {
11795
- const raw = e.target.value;
11796
- const n = Number(raw);
11797
- update({
11798
- ...safe,
11799
- stoneSizeMm: raw === "" || raw === "-" ? 0 : !Number.isFinite(n) ? 0 : Math.max(0, n)
11800
- });
11801
- },
11802
- disabled
11803
- }
11804
- )
11805
- ] }),
11806
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mt-2 flex items-center gap-2", children: [
11807
- /* @__PURE__ */ jsxRuntime.jsx(
11808
- Checkbox,
11809
- {
11810
- checked: safe.hydronephrosis,
11811
- disabled,
11812
- onCheckedChange: (checked) => update({ ...safe, hydronephrosis: checked === true })
11813
- }
11814
- ),
11815
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-700", children: "Hydronephrosis on imaging" })
11816
- ] }),
11801
+ showError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-600", children: error }) : null,
11802
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2", children: [
11803
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1 text-xs", children: [
11804
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Stone size (mm)" }),
11817
11805
  /* @__PURE__ */ jsxRuntime.jsx(
11818
- "div",
11806
+ Input,
11819
11807
  {
11820
- className: cn(
11821
- "mt-2 rounded p-2 text-[11px]",
11822
- 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"
11823
- ),
11824
- children: stone.plan
11808
+ type: "number",
11809
+ min: 0,
11810
+ className: "h-8 text-xs",
11811
+ value: safe.stoneSizeMm,
11812
+ onChange: (e) => setNum("stoneSizeMm", e.target.value),
11813
+ disabled
11825
11814
  }
11826
- ),
11827
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "Trigger: >10 mm OR obstruction \u2192 surgical." })
11815
+ )
11828
11816
  ] }),
11829
- /* @__PURE__ */ jsxRuntime.jsxs(Panel3, { title: "Prostate / BPH", children: [
11830
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
11831
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block", children: [
11832
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 block font-medium text-slate-700", children: "Volume (cc)" }),
11833
- /* @__PURE__ */ jsxRuntime.jsx(
11834
- Input,
11835
- {
11836
- type: "number",
11837
- min: 0,
11838
- step: "any",
11839
- className: cn(
11840
- "h-8 text-xs",
11841
- disabled && "pointer-events-none"
11842
- ),
11843
- value: safe.prostateVolumeCc,
11844
- onChange: (e) => {
11845
- const raw = e.target.value;
11846
- const n = Number(raw);
11847
- update({
11848
- ...safe,
11849
- prostateVolumeCc: raw === "" || raw === "-" ? 0 : !Number.isFinite(n) ? 0 : Math.max(0, n)
11850
- });
11851
- },
11852
- disabled
11853
- }
11854
- )
11855
- ] }),
11856
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block", children: [
11857
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 block font-medium text-slate-700", children: "PSA (ng/ml)" }),
11858
- /* @__PURE__ */ jsxRuntime.jsx(
11859
- Input,
11860
- {
11861
- type: "number",
11862
- min: 0,
11863
- step: 0.1,
11864
- className: cn(
11865
- "h-8 text-xs",
11866
- psaHigh && "border-red-400 text-red-800",
11867
- disabled && "pointer-events-none"
11868
- ),
11869
- value: safe.psaNgMl,
11870
- onChange: (e) => {
11871
- const raw = e.target.value;
11872
- const n = Number(raw);
11873
- update({
11874
- ...safe,
11875
- psaNgMl: raw === "" || raw === "-" ? 0 : !Number.isFinite(n) ? 0 : Math.max(0, n)
11876
- });
11877
- },
11878
- disabled
11879
- }
11880
- )
11881
- ] }),
11882
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "col-span-2 block", children: [
11883
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 block font-medium text-slate-700", children: "Uroflow Qmax (ml/s)" }),
11884
- /* @__PURE__ */ jsxRuntime.jsx(
11885
- Input,
11886
- {
11887
- type: "number",
11888
- min: 0,
11889
- step: 0.1,
11890
- className: cn(
11891
- "h-8 text-xs",
11892
- qmaxLow && "border-amber-500 text-amber-900",
11893
- disabled && "pointer-events-none"
11894
- ),
11895
- value: safe.uroflowQmaxMlSec,
11896
- onChange: (e) => {
11897
- const raw = e.target.value;
11898
- const n = Number(raw);
11899
- update({
11900
- ...safe,
11901
- uroflowQmaxMlSec: raw === "" || raw === "-" ? 0 : !Number.isFinite(n) ? 0 : Math.max(0, n)
11902
- });
11903
- },
11904
- disabled
11905
- }
11906
- )
11907
- ] })
11908
- ] }),
11817
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 pt-6 text-xs", children: [
11909
11818
  /* @__PURE__ */ jsxRuntime.jsx(
11910
- "div",
11819
+ Checkbox,
11911
11820
  {
11912
- className: cn(
11913
- "mt-2 rounded p-2 text-[11px]",
11914
- 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"
11915
- ),
11916
- children: prostateLine
11821
+ checked: safe.hydronephrosis,
11822
+ onCheckedChange: (c) => update({ ...safe, hydronephrosis: c === true }),
11823
+ disabled
11917
11824
  }
11918
11825
  ),
11919
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[10px] text-muted-foreground", children: "<30 cc medical \xB7 30\u201380 cc TURP \xB7 >80 cc HoLEP/Open" })
11826
+ "Hydronephrosis on imaging"
11920
11827
  ] }),
11921
- /* @__PURE__ */ jsxRuntime.jsxs(Panel3, { title: "IPSS & cues", children: [
11922
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
11923
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-700", children: "IPSS total" }),
11924
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `rounded-full px-2 py-0.5 text-[11px] font-semibold ${ipssBandCls}`, children: [
11925
- ipssTotal,
11926
- "/35 \xB7 ",
11927
- urologyIpssBand(ipssTotal)
11928
- ] })
11929
- ] }),
11930
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3 h-1.5 overflow-hidden rounded-full bg-slate-200", children: /* @__PURE__ */ jsxRuntime.jsx(
11931
- "div",
11828
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1 text-xs", children: [
11829
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Prostate volume (cc)" }),
11830
+ /* @__PURE__ */ jsxRuntime.jsx(
11831
+ Input,
11932
11832
  {
11933
- className: cn(
11934
- "h-full transition-[width]",
11935
- ipssTotal >= 20 ? "bg-red-500" : ipssTotal >= 8 ? "bg-amber-500" : "bg-emerald-500"
11936
- ),
11937
- style: { width: `${Math.min(100, ipssTotal / 35 * 100)}%` }
11833
+ type: "number",
11834
+ min: 0,
11835
+ step: 0.1,
11836
+ className: "h-8 text-xs",
11837
+ value: safe.prostateVolumeCc,
11838
+ onChange: (e) => setNum("prostateVolumeCc", e.target.value),
11839
+ disabled
11938
11840
  }
11939
- ) }),
11940
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mb-3 text-[10px] text-muted-foreground", children: [
11941
- "From smart history (",
11942
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-foreground", children: SMART_HISTORY_FIELD }),
11943
- "). \u226520 \u2192 consider surgical intervention."
11944
- ] }),
11945
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5 text-[11px]", children: [
11946
- smart.haematuria.present && !smart.haematuria.painful ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded bg-red-500/10 p-1.5 text-red-800", children: "Painless haematuria \u2014 mandatory cystoscopy" }) : null,
11947
- psaHigh ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded bg-amber-500/10 p-1.5 text-amber-900", children: "PSA >4 \u2014 workup for Ca prostate" }) : null,
11948
- exam.dre.consistency === DRE_HARD ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded bg-red-500/10 p-1.5 text-red-800", children: [
11949
- "Hard / nodular prostate (",
11950
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono", children: EXAMINATION_FIELD }),
11951
- ") \u2014 biopsy pathway"
11952
- ] }) : null,
11953
- exam.gu.suspiciousMass ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded bg-red-500/10 p-1.5 text-red-800", children: "Suspicious scrotal mass \u2014 urgent surgical eval" }) : null
11954
- ] })
11841
+ )
11842
+ ] }),
11843
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1 text-xs", children: [
11844
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "PSA (ng/mL)" }),
11845
+ /* @__PURE__ */ jsxRuntime.jsx(
11846
+ Input,
11847
+ {
11848
+ type: "number",
11849
+ min: 0,
11850
+ step: 0.01,
11851
+ className: "h-8 text-xs",
11852
+ value: safe.psaNgMl,
11853
+ onChange: (e) => setNum("psaNgMl", e.target.value),
11854
+ disabled
11855
+ }
11856
+ )
11955
11857
  ] }),
11956
- /* @__PURE__ */ jsxRuntime.jsx(Panel3, { title: "Risk stratification", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: [
11957
- ["ckd", "CKD"],
11958
- ["diabetes", "Diabetes"],
11959
- ["anticoagulants", "Anticoagulants"],
11960
- ["activeInfection", "Active infection"]
11961
- ].map(([key, lab]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2", children: [
11858
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "block space-y-1 text-xs md:col-span-2", children: [
11859
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Uroflow Qmax (mL/s)" }),
11860
+ /* @__PURE__ */ jsxRuntime.jsx(
11861
+ Input,
11862
+ {
11863
+ type: "number",
11864
+ min: 0,
11865
+ step: 0.1,
11866
+ className: "h-8 text-xs",
11867
+ value: safe.uroflowQmaxMlSec,
11868
+ onChange: (e) => setNum("uroflowQmaxMlSec", e.target.value),
11869
+ disabled
11870
+ }
11871
+ )
11872
+ ] })
11873
+ ] }),
11874
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 border-t border-muted-foreground/20 pt-3", children: [
11875
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-semibold text-slate-800", children: "Risk stratification" }),
11876
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: RISK_ROWS.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-xs", children: [
11962
11877
  /* @__PURE__ */ jsxRuntime.jsx(
11963
11878
  Checkbox,
11964
11879
  {
11965
- checked: safe.risk[key],
11966
- disabled,
11967
- onCheckedChange: (c) => setRisk(key, c === true)
11880
+ checked: safe.risk[r.key],
11881
+ onCheckedChange: () => toggleRisk(r.key),
11882
+ disabled
11968
11883
  }
11969
11884
  ),
11970
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-slate-700", children: lab })
11971
- ] }, key)) }) })
11885
+ r.label
11886
+ ] }, r.key)) })
11972
11887
  ] })
11973
- ] }),
11974
- showError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 pb-2 text-xs text-red-500", children: error }) : null
11888
+ ] })
11975
11889
  ] });
11976
11890
  };
11977
11891
  var FieldRenderer = ({ fieldId }) => {
@@ -12924,22 +12838,32 @@ var ReadOnlyFieldRenderer = ({
12924
12838
  ].join("\n");
12925
12839
  return /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyText, { fieldDef, value: lines });
12926
12840
  }
12927
- case "urology_smart_history":
12928
- return /* @__PURE__ */ jsxRuntime.jsx(
12929
- ReadOnlyText,
12930
- {
12931
- fieldDef,
12932
- value: value && typeof value === "object" ? JSON.stringify(value, null, 2) : value == null || value === "" ? "\u2014" : String(value)
12933
- }
12934
- );
12935
- case "urology_examination":
12936
- return /* @__PURE__ */ jsxRuntime.jsx(
12937
- ReadOnlyText,
12938
- {
12939
- fieldDef,
12940
- value: value && typeof value === "object" ? JSON.stringify(value, null, 2) : value == null || value === "" ? "\u2014" : String(value)
12941
- }
12942
- );
12841
+ case "urology_smart_history": {
12842
+ const structuredDisplay = value && typeof value === "object" ? JSON.stringify(normalizeUrologySmartHistory(value), null, 2) : value == null || value === "" ? "\u2014" : String(value);
12843
+ const hopcDef = resolveFieldDef?.("symptoms");
12844
+ const hopcRaw = allValues?.["symptoms"];
12845
+ const hopcStr = typeof hopcRaw === "string" ? hopcRaw : hopcRaw != null && hopcRaw !== "" ? String(hopcRaw) : "";
12846
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
12847
+ /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyText, { fieldDef, value: structuredDisplay }),
12848
+ hopcDef ? /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyText, { fieldDef: hopcDef, value: hopcStr }) : hopcStr ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
12849
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-gray-700", children: "History of presenting complaint" }),
12850
+ /* @__PURE__ */ jsxRuntime.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 })
12851
+ ] }) : null
12852
+ ] });
12853
+ }
12854
+ case "urology_examination": {
12855
+ const structuredDisplay = value && typeof value === "object" ? JSON.stringify(value, null, 2) : value == null || value === "" ? "\u2014" : String(value);
12856
+ const clinicalDef = resolveFieldDef?.("clinical_examination");
12857
+ const clinicalRaw = allValues?.["clinical_examination"];
12858
+ const clinicalStr = typeof clinicalRaw === "string" ? clinicalRaw : clinicalRaw != null && clinicalRaw !== "" ? String(clinicalRaw) : "";
12859
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
12860
+ /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyText, { fieldDef, value: structuredDisplay }),
12861
+ clinicalDef ? /* @__PURE__ */ jsxRuntime.jsx(ReadOnlyText, { fieldDef: clinicalDef, value: clinicalStr }) : clinicalStr ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
12862
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-gray-700", children: "Clinical examination" }),
12863
+ /* @__PURE__ */ jsxRuntime.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 })
12864
+ ] }) : null
12865
+ ] });
12866
+ }
12943
12867
  case "urology_pathway":
12944
12868
  return /* @__PURE__ */ jsxRuntime.jsx(
12945
12869
  ReadOnlyText,
@@ -13210,12 +13134,9 @@ exports.RichTextWidget = RichTextWidget;
13210
13134
  exports.SignatureUploadWidget = SignatureUploadWidget;
13211
13135
  exports.SmartForm = SmartForm;
13212
13136
  exports.SmartTextareaWidget = SmartTextareaWidget;
13213
- exports.attachUrologyClinicalPathwayMetadata = attachUrologyClinicalPathwayMetadata;
13214
13137
  exports.createUploadHandler = createUploadHandler;
13215
13138
  exports.evaluateRules = evaluateRules;
13216
13139
  exports.fieldMetaRequestsMarMedicationOrdersButton = fieldMetaRequestsMarMedicationOrdersButton;
13217
- exports.isUrologyTemplate = isUrologyTemplate;
13218
- exports.shouldFoldUrologyClinicalPathway = shouldFoldUrologyClinicalPathway;
13219
13140
  exports.useField = useField;
13220
13141
  exports.useFieldHandlers = useFieldHandlers;
13221
13142
  exports.useForm = useForm;