From 14c4d44acb3f4e073e4df9d266fa8fa56bf7b237 Mon Sep 17 00:00:00 2001 From: Ricardo Mancinas Date: Sat, 15 Aug 2026 00:20:09 -0700 Subject: [PATCH] docs(policy-ocr): vigencia/agente/prima are keyed in by hand on the PVL layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../policy-ocr/parsers/policy-parser.spec.ts | 8 +++++++- .../src/policy-ocr/parsers/policy-parser.ts | 10 ++++++++-- docs/POLICY_OCR.md | 18 ++++++++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/api/src/policy-ocr/parsers/policy-parser.spec.ts b/apps/api/src/policy-ocr/parsers/policy-parser.spec.ts index ada16a2..361fa58 100644 --- a/apps/api/src/policy-ocr/parsers/policy-parser.spec.ts +++ b/apps/api/src/policy-ocr/parsers/policy-parser.spec.ts @@ -310,7 +310,13 @@ describe("parsePolicy / GMX especificación (PVL Hogar)", () => { expect(p.agentName).toBeNull(); expect(p.netPremium).toBeNull(); expect(p.total).toBeNull(); - expect(p.notes.join(" ")).toMatch(/no trae vigencia, agente ni prima/i); + // The note must tell the reviewer to key them in — those three are + // captured by hand on this layout — and must say what silently breaks if + // the vigencia is left empty. + const notes = p.notes.join(" "); + expect(notes).toMatch(/no trae vigencia, agente ni prima/i); + expect(notes).toMatch(/captúrelos a mano/i); + expect(notes).toMatch(/avisos de renovación/i); }); it("takes the currency from the printed limits, not from the M.N. sublimits", () => { diff --git a/apps/api/src/policy-ocr/parsers/policy-parser.ts b/apps/api/src/policy-ocr/parsers/policy-parser.ts index 17b6055..1109303 100644 --- a/apps/api/src/policy-ocr/parsers/policy-parser.ts +++ b/apps/api/src/policy-ocr/parsers/policy-parser.ts @@ -603,9 +603,15 @@ function parseGmxEspecificacion(page: OcrPage): ParsedPolicy { const { coverages, currency } = parseEspecificacionCoverages(lines, notes); // These are absent by design on this document, not failures to read. Say so - // explicitly, or the reviewer reads four empty fields as a broken parse. + // explicitly, or the reviewer reads three empty fields as a broken parse. + // + // The renewal consequence is spelled out rather than left implied: a Policy + // confirmed with a null `policyTo` never matches the renewals window query + // (`renewals.service.ts`, `policyTo: { gte, lte }`), so it drops out of the + // renewal notices silently and stays out. Nothing downstream errors. notes.push( - "la especificación PVL no trae vigencia, agente ni prima; esos datos están en la carátula de la póliza", + "la especificación PVL no trae vigencia, agente ni prima; captúrelos a mano " + + "(sin vigencia la póliza no entra en los avisos de renovación)", ); return { diff --git a/docs/POLICY_OCR.md b/docs/POLICY_OCR.md index 611e42b..e65122e 100644 --- a/docs/POLICY_OCR.md +++ b/docs/POLICY_OCR.md @@ -171,8 +171,22 @@ Three things about the especificación are worth knowing before touching it: above it (`efectuados.`, `Y CADA PÉRDIDA.`), and without it coverages get named after the last word of the preceding prose. - **Vigencia, agente and prima are absent by design**, not unread. The parser - says so in a note, so a reviewer seeing four empty fields does not read it - as a broken parse. The caratula is the document that carries them. + says so in a note, so a reviewer seeing three empty fields does not read it + as a broken parse. + + **These three are keyed in by hand** — confirmed 2026-08-14 with Luz, who + handles GMX policies at the office. The review screen already has editable + inputs for all three, and `postPremium` enables off the *typed* premium, so + a hand-entered prima posts to the ledger exactly like a parsed one. No code + change was needed to support this; it is a process decision, recorded here + because the parser's own note now instructs the reviewer accordingly. + + > **A blank vigencia is silently permanent.** `Policy.policyTo` is nullable + > and the renewals window query filters `policyTo: { gte, lte }` + > (`renewals.service.ts`), so a policy confirmed without one **never matches + > and never gets a renewal notice** — no error, no warning, and nothing + > later notices. This is why the parser's note names the consequence instead + > of just listing the missing fields. An excluded catastrophic risk is recorded as excluded **in the risk label** (`Terremoto o erupción volcánica — Sección Edificio: EXCLUIDO`) with a null