Skip to content

feat(omron-ci): read every page of the Commercial Invoice form - #55

Merged
JoelA510 merged 2 commits into
mainfrom
claude/clever-allen-ua8u6k
Sep 16, 2026
Merged

JoelA510 merged 2 commits into
mainfrom
claude/clever-allen-ua8u6k

Conversation

@JoelA510

Copy link
Copy Markdown
Owner

The in-house form (00004-00202) holds eight commodity lines to a page, and a longer shipment runs onto further pages — one sheet per page in the workbook, one page per page in a print. Both were read as a one-page document:

  • PDF: refused a multi-page print outright (… has 4 pages, but the Commercial Invoice form (00004-00202) is a single page).
  • .xlsx: took the first sheet matching the form and filed a four-page invoice as its first eight lines, with nothing said — the silent short declaration this tool exists not to produce.

A real four-page invoice (30 lines, $3,764) now imports identically from either file.

Multi-page

Every page repeats the whole header and states its own SUBTOTAL, so each is read as the complete form it is printed as and the results merged (parseOmronCiPages). Concatenating the pages' rows was never an option: four address bands and four totals bands in one grid mean nothing, and PDF y restarts on every page.

Two figures prove nothing went missing on the way in:

  • the PAGE: 2 of 4 box, the only place the document states its own extent;
  • the grand TOTAL (USD), repeated on every page, which still counts the goods on a page that never arrived.

header.totalValue now comes from that grand total less TAX and FREIGHT (which are not commodity value), so a missing page fails the blocking total-value check instead of balancing against itself — verified at 3 of 4 pages: expected 280.00, actual 108.00. Duplicated pages, disagreeing page counts and pages of two different invoices are each reported.

Also fixed on the same form

  • SHIP DATE is imported and is the date of exportation. It gets its own header field, so the vendor layouts — which state no ship date, and whose filed SLIs date box 2 from the invoice — are unchanged.
  • Country of ultimate destination is resolved from the consignee block, matched against the ISO country list: ORADEA, 410085, BIHOR, ROMANIA yields Romania, and neither the county nor the postcode can be filed as one. A two-letter segment is never accepted — Pleasanton, CA is not Canada. Previously this form established no country at all and the blocking check held every shipment on it.
  • A struck-through header box (-, N/A) is read as empty rather than filed as the dash it holds. The invoice number - was becoming the output filename and the consignee PO on the SLI. Header grid only — a - in a commodity cell is still held for review.
  • Latent bug in the printed totals band: figures were taken as the rightmost number on the label's row, and TAX prints to the right of NET WT (KG). A blank tax cell handed back 9.7 kg as an amount of money. Now read rightward from the label.

Refactor worth flagging

carriers/keying-sheet/countries.ts moves to domain/countries.ts, re-exported from the keying sheet so its callers are unchanged. The parser needs the country list and domain/ must not import from carriers/; a duplicated name table was the worse alternative.

Verification

npm run check passes locally — typecheck, lint, 914 tests, production build. New coverage: multi-page reads from both the workbook and a printed PDF, per-page line attribution, id uniqueness across pages, document-level figures not summed, the missing-page block, mismatched invoice numbers, page-named warnings, header and address fallback to a later page, tax/freight subtraction, and the country resolver's near misses.

The 122 skipped tests are the suites needing the gitignored real-shipment fixtures. The vendor-a and vendor-b paths are unchanged (shipDate: null on both) but not exercised in this environment.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MfEau2Hgjm7Y7tBDQ2vBSw


Generated by Claude Code

The in-house form (00004-00202) holds eight commodity lines to a page, and a
longer shipment runs onto further pages — one sheet per page in the workbook,
one page per page in a print. Both were read as a one-page document: the PDF
path refused a multi-page print outright, and the workbook path took the first
sheet matching the form and filed a four-page invoice as its first eight lines
with nothing said, which is the silent short declaration this tool exists not
to produce.

Every page repeats the whole header and states its own SUBTOTAL, so each is now
read as the complete form it is printed as and the results merged. Two figures
prove nothing went missing on the way in: the `PAGE: 2 of 4` box, and the grand
`TOTAL (USD)` every page repeats, which still counts the goods on a page that
never arrived. The grand total (less tax and freight, which are not commodity
value) becomes the figure the rows reconcile against, so a missing page fails
the blocking total-value check instead of balancing against itself. Duplicated
pages, disagreeing page counts and pages of two different invoices are reported.

Also fixed on the same form:

- `SHIP DATE` is now imported and is the date of exportation. It has its own
  header field, so the vendor layouts — which state no ship date, and whose
  filed SLIs date box 2 from the invoice — are unchanged.
- The country of ultimate destination is resolved from the consignee block,
  matched against the ISO country list so `ORADEA, 410085, BIHOR, ROMANIA`
  yields Romania and neither the county nor the postcode can be filed as one. A
  two-letter segment is never accepted: `Pleasanton, CA` is not Canada. The
  country tables move to `domain/` for it, re-exported from the keying sheet.
- A struck-through header box (`-`, `N/A`) is read as empty rather than filed as
  the dash it holds.
- Totals on a print are read from the cells to the right of their label, never
  the rightmost number on the row: `TAX` prints beside `NET WT (KG)`, and a
  blank tax cell was handing back the shipment's weight as an amount of money.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MfEau2Hgjm7Y7tBDQ2vBSw
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown

Status

  • Head SHA: beefaf35aba125b91f31408a423aec87f245ca17
  • Base SHA: 0b798229839ec007164fd38344d34fb88ba6f818
  • CI state: pending
  • Files reviewed: 16
  • Patches proposed: 0
  • Change types: code, docs-only

Findings

No issues found in changed scope.

Proposed patches

None

CI failure mapping

CI is still running (pending).

Diagnostic plan

npm ci
npm run lint
npm test -- --watch=false
npm run build
npx --yes markdown-link-check <changed-docs>
npx --yes cspell <changed-docs>

Next actions

  • Awaiting updates for head beefaf35aba125b91f31408a423aec87f245ca17

Meta

Processed at 2026-09-16 21:09:27.346 UTC
Idempotency key = beefaf3

Correctness, in the order they matter:

- A missing page no longer reconciles against itself. Where the grand
  TOTAL (USD) cannot be read, the pages that did arrive balance against their
  own subtotals and the total-value check passes — the exact silent short
  declaration this work exists to prevent. `ParsedCipl.incompleteReason`
  carries what the document says about its own extent, and `reconcile` turns
  it into a blocking `document-complete` check. Duplicated pages and pages
  that disagree about any document-level box block on the same check.

- The country resolver no longer files a person as a country. It matched any
  comma-separated segment of any line, so `Attn: Miller, Chad` under a
  Romanian address yielded Chad, and `Atlanta, Georgia, 30301` yielded
  Georgia. Only the last segment of a line is considered, and a block naming
  two countries establishes neither.

- Tax and freight are taken from whichever page prints them, not all three
  totals from the page that first stated a grand total. A tax printed on page
  2 alone was left inside the merchandise total, failing the arithmetic on a
  complete import and reporting it as missing a page.

- The address blocks come from the page that states a consignee, not the
  first page to read any one of the three blocks. A page-1 band that read the
  shipper and not the consignee was winning over a complete page 2, and
  reporting nothing.

- Printed line numbers past 99 are read. Eight lines to a page puts line 100
  on page 13, now reachable; an unmatched LN does not shorten the table, it
  folds the line into the one above and drops the rest.

- Pages bound after the invoice — a terms sheet, a signed SLI — are no longer
  read as pages of the form. The detector inspects page 1 only; the doc
  number in each page's title bar is what tells them apart.

- The commodity lines take the document's order and invoice numbers, not the
  page's own, so a page whose box is struck through no longer files its rows
  against a different order number than the header carries.

- Warnings and line provenance name the page the document prints, not the
  page of the import: pages 1 and 3 of a 3-page print reported a defect on
  "page 2", a sheet the reader does not have.

- Every way the pages fail to be one document is reported, not the first.

Accuracy of what the reviewer is shown:

- `exportDate` and `resolveDestination` return the value with its source, and
  the draft, the review screen and the keying sheet all read them. The screen
  claimed "discharge port" for any non-null port, including one with no comma
  that box 7 was not filled from, and called the invoice date "superseded"
  where vendor-b states the same date in both boxes.

Cleanup: `pagesToGrid` is `pageToGrid` and takes one page, so feeding it two
is a type error rather than the baseline collision the comment warned about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MfEau2Hgjm7Y7tBDQ2vBSw
@JoelA510
JoelA510 merged commit 16ee90c into main Sep 16, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants