Generated cases (dev/test)¶
Twenty hand-written cases per scenario are enough to show behaviour but too few to measure it. For each of scenarios 1–3, 80 more cases were generated from the 1,000-patient cohort, with reference labels known by construction rather than judged:
- Ward: notes are assembled from labelled snippets (a driver that sets concern and pattern, a mental-state snippet, an infection snippet), and vital signs are drawn to match.
- Discharge: the plan is built in code from per-medication actions, new drugs, reasons and phrasing variants. Allergy, duplicate and interaction labels are computed from the final regimen using class and interaction tables.
- Inbox: messages combine a labelled intent with optional benign add-ons, tone variants and a prompt-injection prefix that must not change the labels.
Cases are split 50/50 into dev (used for threshold tuning) and test (used only for reporting). The labels and splits were committed before any model saw these cases. The generator is generate.py.
Per-question results¶
Noul accuracy is at the default 0.5 threshold. The last column is test accuracy at the threshold that maximised dev accuracy.
| Scenario | Question | Primitive | Dev | Test | Test, dev-tuned threshold |
|---|---|---|---|---|---|
| 1. Ward | new_confusion |
Noul | 95% | 95% | 98% @ 0.80 |
| 1. Ward | infection |
Noul | 98% | 100% | 100% @ 0.55 |
| 1. Ward | concern |
Score | 90% · MAE 0.14 | 78% · MAE 0.25 | – |
| 1. Ward | pattern |
Choice | 95% | 92% | – |
| 2. Discharge | med_status |
Choice | 99% | 98% | – |
| 2. Discharge | allergy_conflict |
Noul | 100% | 100% | 100% @ 0.50 |
| 2. Discharge | duplicate_therapy |
Noul | 88% | 85% | 90% @ 0.70 |
| 2. Discharge | interaction |
Noul | 70% | 68% | 80% @ 0.75 |
| 2. Discharge | justification |
Score | 60% · MAE 0.55 | 60% · MAE 0.47 | – |
| 3. Inbox | route |
Choice | 85% | 85% | – |
| 3. Inbox | urgency |
Score | 80% · MAE 0.24 | 80% · MAE 0.22 | – |
| 3. Inbox | red_flag |
Noul | 85% | 82% | 95% @ 0.75 |
| 3. Inbox | medication_issue |
Noul | 100% | 95% | 95% @ 0.50 |
| 3. Inbox | safeguarding |
Noul | 78% | 72% | 95% @ 0.85 |
The tuned thresholds are informative in their own right. safeguarding wants 0.85 and red_flag 0.75: Jev leans towards yes on these, and a higher bar fixes most of the over-calling (safeguarding goes from 72% to 95% on test). interaction still tops out at 80%: no threshold rescues a multi-hop question.
Scenario policies¶
Ward: under-triage against the reference band, NEWS2 alone vs NEWS2 + Jev.
| Split | Cases | NEWS2 only: under-triaged | NEWS2 + Jev: under-triaged | NEWS2 + Jev: over-triaged | NEWS2 + Jev: band exact |
|---|---|---|---|---|---|
| dev | 40 | 23 | 3 | 1 | 90% |
| test | 40 | 22 | 8 | 1 | 78% |
The hand-case result holds: NEWS2 alone under-triages about half the patients, because the generated notes (like real ones) carry the decisive signal in text. On test, NEWS2 + Jev still under-triages 8 of 40. Every one is a concern Score one level low, with no confusion misses. Five are routine instead of ward review, and two are urgent instead of emergency.
Inbox: the confidence gate (route confidence ≥ 0.6, red flag outside 0.2–0.8, route and urgency agree, no safeguarding flag) auto-dispatched 24/40 dev and 16/40 test messages, all routed correctly (100%). The rest go to review. Overall route accuracy is 85% on test.
Discharge: decomposing the weak checks¶
In v1, a single Noul asks "is there a duplicate?" or "is there an interaction?" over the whole regimen. That is several hops at once, which the Jev jaggedness notes flag as unreliable. v2 asks each hop as its own narrow question in the same request, and code aggregates:
- one Choice per medication for its therapeutic class; duplicates are counted in code;
- new-drug candidates are found by exact formulary lookup in code, then one Noul per candidate asks whether it is prescribed after discharge;
- one Noul per (new drug, other drug) pair for interactions, and one per (drug, drug allergy) pair for allergy class.
A v2 request carries up to 71 questions, and p50 latency is still 337 ms.
| Version | Hand (20) | Generated dev (40) | Generated test (40) |
|---|---|---|---|
| v1 | 55% · 0 missed / 8 false holds | 95% · 0 missed / 2 false holds | 85% · 0 missed / 4 false holds |
| v2 | 75% · 2 missed / 2 false holds | 70% · 12 missed / 0 false holds | 75% · 9 missed / 0 false holds |
| v2.1 | 70% · 0 missed / 6 false holds | 100% · 0 missed / 0 false holds | 95% · 0 missed / 1 false holds |
v2 fixed the false holds but introduced missed holds. Its "new drug" Noul asked two things at once (is it prescribed, and was it absent before admission?). Code already guarantees the second, and asking again made Jev answer no for plainly new drugs, which switched off the downstream checks. v2.1 asks only what code can't know ("does the text tell the patient to take X after discharge?").
v2.1 is not a clean held-out result
v2.1 is one revision made after seeing v2's results on every split, including test. It was committed before it ran, but no split is untouched by the design, so treat its numbers as optimistic. The remaining weakness shows on every split: the pairwise interaction Nouls still over-call.
Per-flag accuracy by version
| Version | Flag | Hand | Generated dev | Generated test |
|---|---|---|---|---|
| v1 | allergy_conflict |
100% (0 FP, 0 FN) | 100% (0 FP, 0 FN) | 100% (0 FP, 0 FN) |
| v1 | duplicate_therapy |
70% (6 FP, 0 FN) | 88% (4 FP, 1 FN) | 85% (6 FP, 0 FN) |
| v1 | interaction |
75% (5 FP, 0 FN) | 70% (12 FP, 0 FN) | 68% (13 FP, 0 FN) |
| v2 | allergy_conflict |
90% (0 FP, 2 FN) | 90% (1 FP, 3 FN) | 85% (0 FP, 6 FN) |
| v2 | duplicate_therapy |
90% (2 FP, 0 FN) | 88% (0 FP, 5 FN) | 90% (0 FP, 4 FN) |
| v2 | interaction |
100% (0 FP, 0 FN) | 68% (1 FP, 12 FN) | 88% (1 FP, 4 FN) |
| v2.1 | allergy_conflict |
100% (0 FP, 0 FN) | 98% (1 FP, 0 FN) | 100% (0 FP, 0 FN) |
| v2.1 | duplicate_therapy |
75% (5 FP, 0 FN) | 100% (0 FP, 0 FN) | 95% (1 FP, 1 FN) |
| v2.1 | interaction |
75% (5 FP, 0 FN) | 78% (9 FP, 0 FN) | 72% (11 FP, 0 FN) |