Skip to content

Generate DREAM's drug data from a pinned SCRATCH snapshot - #5

Merged
monchee merged 4 commits into
mainfrom
consume-scratch-snapshot
Aug 20, 2026
Merged

Generate DREAM's drug data from a pinned SCRATCH snapshot#5
monchee merged 4 commits into
mainfrom
consume-scratch-snapshot

Conversation

@monchee

@monchee monchee commented Aug 20, 2026

Copy link
Copy Markdown
Owner

This is the change that actually connects the two apps.

Why

DREAM's 116 protocol records were hand-typed copies of doses also authored in SCRATCH. The two silently diverged and nothing detected it. SCRATCH is now the source of truth.

How

drugMasterlist.ts becomes a thin module merging:

  • drugMasterlist.generated.ts — built from protocols.snapshot.json (6 drugs migrated so far)
  • dreamOnlyProtocols.ts — the 110 records SCRATCH hasn't migrated yet

Drugs move from hand-maintained to generated one tranche at a time. Public exports are unchanged, so all ~7 consumers needed no edits.

The snapshot is pinned and committed — never fetched. Doses must not change under a clinician mid-clinic. npm run protocols:sync refreshes it and prints a dose-level diff as the human review gate.

Schema mismatch, handled

SCRATCH carries spt, idt and challenge in one protocol entry. DREAM's UI selects skin tests and challenges separatelygetDrugsByCategory() excludes testType: 'challenge'. So the generator splits: Cefazolin's single SCRATCH protocol yields two DREAM records, iv and iv-challenge.

Verification

Check Result
Positional order vs main 0 mismatches / 116
Numeric doses changed None
Cefazolin split Correct
Regeneration determinism Byte-identical
fetch() in src/ None
tsc / lint / build 0 / 0 / 0
Coverage 81.6 / 73.3 / 72.9 / 83.2 (thresholds 76 / 67 / 67 / 79)

The order check was run at runtime, importing the real module and comparing against git show HEAD:, not by regex over source text. DREAM selects protocol variants by array index, so a single positional shift would silently re-point a saved clinical plan at a different dose.

Text changes, reviewed individually

24 presentational differences, no numbers among them — e.g. Neat (100mg/mL)Neat (100 mg/mL), 1g1 g powder for injection. Accepted as improvements.

Diluent wording was not accepted as-is: SCRATCH said Normal saline (NS) where DREAM prints 0.9% sodium chloride. The explicit concentration is the safer term on a document followed at the bench, so SCRATCH is being standardised to match in a companion PR. Merge that one first, then re-run npm run protocols:sync here before merging this.

Not in this PR

UI work — showing dilution tables inline, under_review badges, and deep-links to SCRATCH pages — is a separate change.

🤖 Generated with Claude Code

Monchee and others added 4 commits August 20, 2026 19:15
DREAM's 116 protocol records were hand-typed copies of doses also authored
in SCRATCH. The two silently diverged and nothing detected it. SCRATCH is
now the source of truth.

drugMasterlist.ts becomes a thin module merging drugMasterlist.generated.ts
(built from protocols.snapshot.json) with dreamOnlyProtocols.ts, which still
holds every drug SCRATCH has not migrated yet. Drugs move from hand-
maintained to generated one tranche at a time; today 6 of 96 are generated.
Its public exports are unchanged, so all ~7 consumers needed no edits.

The snapshot is committed and never fetched at runtime or build time. Doses
must not change under a clinician mid-clinic; every change reaches DREAM
through a deliberate, reviewable commit. `npm run protocols:sync` refreshes
it and prints a dose-level diff for review.

The two sides model protocols differently: SCRATCH carries spt, idt and
challenge in one entry, while DREAM's UI selects skin tests and challenges
separately. The generator splits accordingly, so Cefazolin still yields two
records (iv, iv-challenge).

Verified: all 116 records keep their exact positional order, checked at
runtime against git HEAD rather than by parsing text. That matters because
DREAM selects protocol variants by array index, so a single shift would
silently re-point a saved clinical plan at a different dose. Regeneration
is byte-identical; no fetch() exists in src/.

No numeric dose changed. The 24 text differences are presentational and
were reviewed individually.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SCRATCH is served at https://scratch.yuson.au, now verified. The sync
script's URL constant was a deliberate placeholder because the hostname was
unknown — scratch.pages.dev belongs to MIT Scratch, since a Pages project
only gets its requested *.pages.dev name if it is free.

Also records the succession consequence in HANDOVER.md: SCRATCH and DREAM
now share one personal domain. If yuson.au lapses, clinicians lose the
reference handbook and the encounter-recording app simultaneously, and
protocols:sync stops resolving. Moving to an institutional domain is one
piece of work, not two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First sync against the published endpoint at scratch.yuson.au/api/protocols.json.
The review gate reported exactly the expected change and nothing else: five
drugs, diluent wording plus their version and last_reviewed bumps. No dose
moved.

Also fixes a real defect in verify-order.mjs. It compared against
`HEAD:drugMasterlist.ts`, which stops being the hand-typed baseline the moment
this change is committed — so on its second run it compared 116 records against
an empty file and reported "116 of 116 positional mismatches, FAIL". That is a
false alarm on the one check that guards saved clinical plans from being
re-pointed at different doses, and a check that cries wolf is barely better than
one that stays silent.

It now defaults to origin/main, accepts a ref argument, and exits 2 with an
explicit BROKEN CHECK message when the baseline yields implausibly few records,
instead of reporting every position as a mismatch.

Verified against the true baseline: 0 mismatches out of 116.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ordering test compared DRUG_MASTERLIST against
`git show HEAD:drugMasterlist.ts`. That baseline stops existing the moment
this change is committed — and disappears permanently once it merges — so
the test compared 116 records against an empty file and reported "116 of 116
positional mismatches". A false alarm on the check that guards saved
clinical plans from being re-pointed at different doses.

The expected order is now a committed fixture, expectedProtocolOrder.json,
captured from origin/main at the cutover. It survives merging, needs no git
at test time, and still fails loudly on a real reorder.

Also updates three stale expectations that were correctly catching the
diluent standardisation and the accompanying last_reviewed bump:
Rocuronium, Cis-atracurium and Vecuronium now expect
"0.9% sodium chloride", and lastReviewed expects 2026-08-20.

Full suite: 766 passed, 92 files. Order check: 0 mismatches of 116.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@monchee
monchee merged commit 935fa67 into main Aug 20, 2026
3 checks passed
@monchee
monchee deleted the consume-scratch-snapshot branch August 20, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant