Deterministic, staged, line-aware. No AI.
Email HTML (or DOCX text)
→ HTML normalization (line-semantic tags → \n, entities decoded, <a href> captured)
→ line normalization (CRLF/LF, nbsp, trim)
→ header removal (everything before the first boundary line)
→ entry boundary detection
→ field extraction
→ division normalization
→ confidence scoring → human review
A new legislative entry starts only at a line that consists entirely of
one identifier + number:
^(HF|SF|HSB|SSB|HJR|SJR|HCR|SCR|HR|SR)\s*-?\s*(\d+[A-Z]?)$
Because the whole line must match, references *inside* a brief — "Successor
to HSB171, as amended." — can never split an entry. They are instead
collected into referencedBills.
Identifier prefixes are configurable (opts.identifiers); longest-first
matching prevents "SF" from shadowing "SSB".
distributedTo.commentRequestedFrom; if absent, mirrors distributedTo with a warning (−0.10 confidence).brief (first line ≤120 chars also becomes title). Blank-line runs collapse."Looks like a division designation": ≤60 chars, no sentence-ending
punctuation, and every token (split on / ; , and &) is either a known
division (from the routing rules — codes, names, aliases), a 2–6 letter
all-caps code, or "<Word> Office/Bureau/Division". This is what stops the
first line of a lowercase brief ("open records") from being eaten as a code.
MVD/TDD, MVD; SOD, ELT and MVD, MVD, TDD, and SOD → [MVD, TDD, …].
Multi-word divisions ("AG Office") survive because splitting only happens on
separators, never on spaces.
sourceBlock, and warnings surface in the UI.