Safety · the wrong-patient defense
When a computer writes to a chart, the one thing it can never do is write to the wrong patient.
Automating an EMR means a computer types into a patient record on its own. Get the field wrong and you fix a typo. Get the patient wrong and you have written someone else's medication, allergy, or note into the wrong chart. That is the failure this defense exists to prevent, and this page shows it working, case by case.
On legacy, Citrix, and RDP EMRs there is no browser to inspect and often no working accessibility tree, so the software reads the screen with OCR. The trap: some record numbers differ by a single look-alike character. A letter O against a zero. A lowercase L against a one. To a person they are nearly the same; to OCR they are the same bytes. Two different patients become one indistinguishable row of pixels.
So the rule is simple. When the check cannot prove the row on screen is the patient it recorded, it does not guess. It halts and hands the step to a person.
5 of 5 look-alike cases refused · 2 of 2 clean cases handled correctly.
Every image below is a real screen render. Every OCR string is what the repo's own OCR actually read. Every verdict is the shipping identity check (verify_target_identity) judging the recorded target against the live row. No model, no network, nothing hand-drawn: zero model calls.
How this evidence is generated →See a full run on a real EMR →
Five look-alike traps
In each pair below, the recorded target and the live row on screen are two different patients. Read what OCR made of each row: for the first four, it is byte-for-byte identical. The check must never verify these.
A letter O standing in for a zero
Two different patients share a name and a date of birth. Their record numbers differ by one character: MG4408 with a zero, MG44O8 with a letter O. OCR reads both as the same string.


mg4408sorensen,philip1975-03-12mactive2026-05-04open

mg4408sorensen,philip1975-03-12mactive2026-05-04openThe only difference is the 5th character: a zero in the recorded number, a letter O in the live one. On screen they're nearly identical; to OCR they're identical.
A lowercase L standing in for a one
Same name and date of birth. The record numbers differ by one character: MG4118 with a one, MG41l8 with a lowercase L. OCR collapses them into the same string.


mg4118okafor,daniel1968-11-04mactive2026-05-06open

mg4118okafor,daniel1968-11-04mactive2026-05-06openThe 5th character is a one in the recorded number and a lowercase L in the live one. Same shape to the eye, same bytes to OCR.
A purely numeric record number is just as fragile
There's no letter prefix to lean on here. 100512 (all digits) versus a namesake's 1OO512 (with letter O's). OCR reads both as the same digits.


100512delgado,maria1982-07-22factive2026-05-08open

100512delgado,maria1982-07-22factive2026-05-08openThe 2nd and 3rd characters are zeros in the recorded number and letter O's in the live one.
A dash-formatted number that used to slip past the check
A hyphenated record number, MG-4408 versus MG-44O8. An earlier version of the check exempted dashed numbers because they read as non-alphanumeric. It now strips separators before judging, so this halts too.


mg-4408bianchi,robert1959-02-18mactive2026-05-10open

mg-4408bianchi,robert1959-02-18mactive2026-05-10openIgnore the dash and it's the same O-for-zero swap in the final block: MG-4408 vs MG-44O8.
A same-name, same-birthday sibling with a readable, different number
Two different patients share a name and a date of birth but carry genuinely different record numbers that OCR reads cleanly: MG5439 versus MG7263. Even when the numbers are legible, the mismatch is caught.


mg5439halloran,susan1975-03-12factive2026-05-12open

mg7263halloran,susan1975-03-12factive2026-05-12openThe numbers are genuinely different and OCR reads the difference. Matching on name and birth date alone would have clicked the wrong person; matching on the record number catches it.
Two controls, so halting isn't a trick
A check that refused everything would look safe and be useless. These two prove it isn't doing that: the correct patient with a clean record number verifies and proceeds, and a plainly different patient is caught.
The correct patient, re-read
The same patient at replay, with a record number that contains none of the confusable characters (RC79284). This one must verify. It proves the check is not just abstaining on everything.


rc79284montgomery,james1990-09-30mactive2026-05-14open

rc79284montgomery,james1990-09-30mactive2026-05-14openSame patient, same number, no confusable characters. This is what a correct match looks like.
A plainly different patient
Someone else entirely stands where the target was: Castellano, Angela versus Nakamura, Thomas. This one must mismatch. It proves the check is not just verifying everything.


rc44823castellano,angela1977-05-14factive2026-05-16open

rc77235nakamura,thomas1963-08-08mactive2026-05-16openDifferent name, different birth date, different number. Nothing matches, and the check halts.
What this does not protect against
This defense turns a wrong-patient click into a halt. It does not make the rest of the run omniscient. These are open problems, disclosed on purpose, straight from the project's own limits doc and fault-model study.
- It only guards steps that carry a patient identity
The check runs only where a recorded step knows which patient it's for. In the most recent run against a real OpenEMR, 4 of 12 click steps carried that context; the rest (login buttons, icon-only pencils, bands too generic to pin to a patient) compiled with no identity check at all. A wrong click on one of those steps is still silent. We now report coverage per step so you can see it, but disclosing the gap doesn't close it.
- It reads the screen, not the database
A fault-model study drove 90 replays through a real save boundary and found that vision checks silently mishandle 5 of 7 transactional failure modes. A double submit writes a second record behind a clean-looking success; an optimistic UI update the backend later rejects shows success over an empty database; a partial save drops a field. None of these looks like a drifting screen, so self-healing can't catch them, and the screen showed success. Closing this needs a read against the system of record plus an at-most-once guard, neither of which a vision-only replay can express.
- On pure-pixel screens, refusing a look-alike also refuses the right patient
Because the OCR tier abstains on any confusable record number, it also halts the correct patient whenever that patient's own number happens to contain an O/0 or l/1. On the frozen adversarial corpora that's a 43.6 to 49.3 percent false-abort rate. That's the safe, cheap direction to fail, but it is a real availability cost. On browser (DOM) and native desktop (accessibility tree) surfaces the structured-text tier reads these correctly with no such cost, because 0 and O are distinct characters there. The abstain cost only bites on pure-pixel surfaces like Citrix, RDP, and VDI, or where the accessibility tree is broken.
- A font that draws O and 0 identically is an unsolvable floor
Where two characters rasterize to the exact same pixels, no vision method can tell them apart, because the difference does not exist in the image. We found no such collision among 14 common UI fonts, but it is a real disclosed limit rather than a solved problem.
We publish how we test this and where it still falls short: how we test it · the identity check's ROC.
Bring the workflow you're most afraid to automate
If the risk of a wrong-patient write is what's kept you from automating intake or EMR entry, that's exactly the conversation we want. Fifteen minutes, one workflow.