Confirmed with Luz, who handles GMX policies at the office: the three
fields the especificación does not carry are entered manually. The review
screen already supports it — all three are editable and `postPremium`
enables off the typed premium, so no code change was needed.
The parser's note said "esos datos están en la carátula de la póliza",
which now sends the reviewer looking for the wrong document. It says
"captúrelos a mano" instead, and names the consequence of leaving the
vigencia blank: `Policy.policyTo` is nullable and the renewals window
filters `policyTo: { gte, lte }`, so a policy confirmed without one never
matches and never gets a renewal notice — silently, permanently, with
nothing downstream erroring.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GMX ships two unrelated documents for the same policy and the office
downloads both from the same portal. The parser only knew the English
caratula, so a `…-CondicionesParticulares.pdf` parsed to an almost
entirely empty row — including the policy number, which the matcher needs.
`parseGmx` becomes a dispatcher over `parseGmxCaratula` (unchanged
behaviour) and the new `parseGmxEspecificacion`. Both still report
`provider: "GMX"`: the matcher keys on the policy number alone and must
not care which artifact was uploaded.
The especificación has no tables. Coverages are found by anchoring on
`Límite … Responsabilidad:` and walking backwards for the heading, where a
heading is a short line *preceded by a blank line* — length alone cannot
tell one from the wrapped tail of the paragraph above it, and without that
condition coverages get named after the last word of the preceding prose.
Also fixed, both pre-existing:
- The policy number's group widths are not the same across the two
families (`007-037-…-0000-02` vs `07-037-…-00000-01`). The pinned-width
regex is replaced by a shape, so both read.
- The caratula's ZIP fallback pushed a note saying it had read the ZIP
from the address, then never assigned it.
Verified against the full ten-page real document: all 17 coverages,
amounts, deductibles and the excluded earthquake section match what is
printed. 24 parser tests (was 8), four of them regressions for ways this
layout can silently attach the *wrong* value rather than none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gives receipt capture the same treatment policy OCR just got: a doc that
records what is in the code, separate from the spec that records what was
designed. RECEIPT_CAPTURE_SPEC.md §2 had accumulated three BUILT notes
totalling ~120 lines of findings, which is the right place for the
evidence but the wrong place to look up how the matcher picks a column.
docs/STATEMENT_OCR.md covers the pipeline, the OCR seam and its
text-layer-first rule, all eight parsers and the ordering constraints
between them, the matcher's two governing rules and the scopedRefField
table, confirm-through-BillingService, the learning write-back, and the
API surface.
Weight goes to the things that are load-bearing and invisible from the
code shape: brand detection must run to completion before layout because
Tijuana bills predial and zona federal off the same treasury header;
scopedRefField is exported because three call sites must agree or a
reference gets learned into a column nothing searches; FEDERAL_ZONE's
accountNumber holds a peso amount, so it fails the null-guards as well
as the lookup; a misread `$` is the dangerous failure, not a missing one.
Also records that CFE/CESPT/Telnor have no unit suite — they predate the
gas/predial extension and were only verified end to end.
Cross-linked from the spec, POLICY_OCR.md, PLAN.md, README and RESUME.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Policy OCR shipped 2026-08-01 (5e9cb12) and was documented nowhere. It is
not in INSURANCE_FEATURES_SPEC.md because it did not come from that
meeting — it came out of building the utility statement OCR pipeline in
RECEIPT_CAPTURE_SPEC.md §2 and noticing the same shape fits carrier
policy PDFs. A reader had no way to find that lineage.
New docs/POLICY_OCR.md covers it end to end, with weight on the three
things that are not obvious from the statement side:
- **One PDF = one policy.** Statements arrive bundled one customer per
page, so there a page is a document. A GMX certificate is one policy
across two pages, so the pages are concatenated and the parser runs
once per file — which is why `pageNumber` is a file ordinal and
`storageKey` is the source PDF, not a page image.
- **The GMX certificate carries no premium at all** — it lives on a
separate recibo PDF. Hence the null-preserving confirm and the
double-gated ledger write.
- **OcrModule was extracted out of StatementsModule to make this
possible**, and that was blocking rather than cosmetic.
Cross-referenced from RECEIPT_CAPTURE_SPEC.md §2 (where it came from),
INSURANCE_FEATURES_SPEC.md (which never proposed it, and whose §4 carrier
API it partly overlaps), PLAN.md step 11, README and RESUME.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>