Extracting Drug Name, Dosage and Sig From a Prescription
11 min read · updated August 11, 2026
The drug name and strength on a prescription are ordinary extraction. The sig — the dosing instruction, written in abbreviated Latin — is not, because it is a compact grammar whose tokens are ambiguous by construction, and because a model asked to expand it will produce a fluent expansion of the token it thinks it saw.
The sig is a grammar, not a phrase
“Sig” is from signa, mark or write, and it labels the field that becomes the label on the bottle. A sig is not a sentence to be understood; it is a sequence of slots, most of them optional, in a fairly stable order:
verb dose form route frequency duration indication take 1 tablet po bid x 10 days for infection inject 0.5 mL subq q wk instil 1 gtt qid OU apply thin film bid x 7 days to affected area
Parsing it as slots rather than as prose is what makes the output usable. A sig rendered as one string cannot answer “how many doses per day” without being parsed again by whoever asked, and a sig rendered as a paraphrase has thrown away the token that was actually written.
The frequency slot is the one that carries the most information in the fewest characters. It is also where the abbreviations concentrate:
qd once daily (Latin quaque die) bid twice daily (bis in die) tid three times daily (ter in die) qid four times daily (quater in die) qhs every night at bedtime qod every other day q4h every 4 hours q6h every 6 hours prn as needed (pro re nata) ac before meals (ante cibum) pc after meals (post cibum) stat immediately
Note that prn is not a frequency; it is a modifier on one. A sig of q6h prn pain means up to four doses a day, not four doses a day, and the difference is a maximum rather than a schedule. Your schema needs an as_needed boolean and an indication string separate from the frequency, or the daily dose you compute from it will be an overestimate every time.
Route abbreviations form their own small vocabulary: po by mouth, pr rectally, sl sublingual, subq subcutaneous, IM intramuscular, IV intravenous, and the site codes OD, OS, OU for right eye, left eye and both eyes, with AD, AS, AU the equivalents for ears. Quantity units include gtt for drops and tab, cap, mL for the obvious things.
A lookup table, not a model
The design decision on this page is: do not ask the model to expand the abbreviations. Ask it to segment the sig into slots and return the tokens exactly as written, then expand the tokens with a table you control.
The argument is about failure shape rather than accuracy. A lookup table has one failure mode: a token is not in it, and you get a miss you can see, count and route to a human. A model expanding abbreviations has a different failure mode: it returns a confident, well-formed expansion of a token that was not there. qd and qid differ by one character and by a factor of four in daily dose. qhs and q6h are visually similar in handwriting. There is no confidence score that reliably distinguishes “I read qid” from “I read qd and inferred”, because the model is equally fluent either way — which is the calibration problem set out in calibrating extraction confidence.
# One direction only: token -> canonical structure.
# Anything not in the table is a miss, and a miss is an output.
SIG_FREQUENCY = {
"bid": {"times_per_day": 2},
"b.i.d":{"times_per_day": 2},
"tid": {"times_per_day": 3},
"qid": {"times_per_day": 4},
"qhs": {"times_per_day": 1, "timing": "bedtime"},
"q4h": {"interval_hours": 4},
"q6h": {"interval_hours": 6},
}
def expand(token):
key = token.lower().replace(".", "")
hit = SIG_FREQUENCY.get(key)
if hit is None:
return {"token": token, "status": "unmapped"}
return {"token": token, "status": "mapped", **hit}The unmapped status is the point of the whole design. It gives you a measurable rate, it tells you which tokens to add, and it means an unfamiliar abbreviation from a new prescriber produces a review item rather than a plausible number. Handwritten input makes this more important, not less — the reasons a handwritten token is hard are set out in handwriting recognition with an LLM, and none of them are fixed by a bigger model.
Where the table has to be bidirectional-safe
Some tokens are genuinely ambiguous and the table must say so rather than pick. OD is right eye in an ophthalmic sig and “once daily” in some prescriber shorthand. QD written by hand has been misread as QID often enough to be formally discouraged. Where a token has two readings, map it to an explicit ambiguous status with both candidates attached, and let it go to review, on the rules in confidence threshold review routing. A table that resolves ambiguity silently has all the failure modes of the model with none of its coverage.
The abbreviations that are known hazards
This is not folklore. The Institute for Safe Medication Practices publishes a list of error-prone abbreviations, symbols and dose designations, and the Joint Commission maintains a shorter “do not use” list that applies to accredited organisations. The items on them are on them because they have been misread in practice. The ones that matter for extraction:
- U for unit — misread as a zero or a four, turning 4U into 40 or 44. Insulin doses are the classic case.
- IU for international unit — misread as IV or as the number 10.
- QD and QOD — the period after the Q and the O are confusable, making daily and every-other-day interchangeable, and QD confusable with QID.
- A trailing zero —
1.0 mgread as 10 mg if the decimal point is faint. - A missing leading zero — a bare
.5 mgread as 5 mg. A tenfold error in the same direction as the trailing-zero one. - MS, MSO4, MgSO4 — morphine sulfate and magnesium sulfate abbreviated to strings that are confusable with each other.
For an extraction pipeline these are not just history. They are a ready-made list of tokens that should never be silently normalised. Treat every one of them as an automatic review trigger: if the source document contains a naked U, a trailing zero or a missing leading zero, flag the record regardless of how confident the reading was, because the document itself is in a form known to be misread. That is a validation rule you can implement in an afternoon and it is derived from published safety guidance rather than invented.
Drug, strength, form and quantity
The rest of the prescription is more tractable but has its own traps. The drug may be written as a brand or a generic, and the two resolve to the same clinical concept through RxNorm — normalise to the semantic clinical drug level as described in extracting medication lists, which is also where the NDC’s segment structure is worked through.
Strength and dose are two different numbers and are routinely conflated. A prescription for a 10 mg tablet with a sig of “take 2 tablets” is a 20 mg dose of a 10 mg product. Extract strength from the product and dose_quantity plus dose_unit from the sig, and compute the administered amount only where both are present and their units are compatible. If the sig expresses the dose in milligrams while the product is expressed in tablets, the conversion requires the strength and it should be recorded as derived rather than extracted.
Quantity dispensed is usually written after a hash — #30 — and the unit is implied by the dose form. “Dispense 30” of a suspension means 30 mL and of a tablet means 30 tablets, and a schema with a bare integer quantity has lost that. Days supply, where printed, is a useful internal consistency check: quantity divided by doses per day should approximate it, and a large disagreement usually means the frequency token was misread — which brings the check back to the sig.
Keep the verbatim sig
Whatever structure you produce, store the sig string exactly as it appeared, in its own field, unmodified. Three reasons, in increasing order of importance.
It is the only way to improve the table: the unmapped tokens are your backlog, and you cannot mine them from a parsed structure. It is the only way to re-run a corpus after a parser fix without going back to the source documents. And in a regulated setting it is the record of what the document said, which is the thing an audit asks for. A parsed sig is an interpretation; the string is evidence.
The same argument applies to the schema shape generally: model the parse as an annotation over the verbatim text rather than as a replacement for it. Where the parse and the source disagree later — and on a corpus of any size they will — you want to be able to find out which without a re-scan. The versioning mechanics for that are in schema versioning.