Skip to content

fix(vendor-a): read the weights a re-issued packing list prints - #56

Merged
JoelA510 merged 2 commits into
mainfrom
claude/gallant-carson-ry7ebm
Sep 17, 2026
Merged

JoelA510 merged 2 commits into
mainfrom
claude/gallant-carson-ry7ebm

Conversation

@JoelA510

Copy link
Copy Markdown
Owner

A commercial invoice went out with the weight missing from several lines. The corrected re-issue redrew those rows, and the app then read fewer weights from the corrected document than from the wrong one: three lines came out blank, the rows totalled 36.917 kg against a stated 41.896, and the shipment was blocked twice over.

What the document actually does

The corrected PDF is not a fresh render. It is the original plus five incremental updates that replace the packing-list page content streams, and the replacement rows are drawn in a different font, kerned tight:

[ (\() -8629 (4) -6422 (2.904   3.194) -5816 (.800000) -321 (\)) ] TJ

That last kern is about 2.25pt, so the extractor merges the closing bracket into the figure beside it and reports one cell: .800000 ).

Three defects, all in the packing-block reader

  1. Brackets were matched by exact cell text. findIndex(i => i.str === ')') returned -1 on a re-issued row, so the row was skipped and all three of its weights dropped — which is why the corrected document parsed worse than the faulty one.
  2. Figures were read by position, not by column. A line packed across two cartons prints its quantity inside the brackets — ( 4 2.904 3.194 .800000 ) — so inner[0..2] filed the quantity as the net weight and shifted the rest a column right. On the original document that had already put 5 kg of net weight on a line weighing 2.904, silently; a different check happened to block first.
  3. The brackets decided what a weight row was at all. This layout prints some lines' weights bare, and those have never had a weight read from them, in any revision.

Because a packing line with no weight is indistinguishable from one that ran off the foot of a page, the last such line on each page was also held open as an unfinished block, which cost the following page's first line its commodity heading — a SWITCH filed under Helical Springs.

The fix

  • parseFigure strips one bracket from each end of a cell, so both printings read the same way.
  • figureColumnsOf calibrates the quantity / net / gross / measurement columns from the page's own printed headings, falling back to the measured defaults; weightsIn assigns each figure to the column it sits under rather than to an ordinal slot. An extra cell in the breakdown now costs nothing.
  • The weight row is told from the section's running totals by which row carries the quantity and the country, not by whether it is punctuated.
  • figureRowIn strips brackets too, so a re-issued row still anchors the heading floor.

One change beyond the parser, called out because it changes a check: reconciliation now refuses a line whose net weight is stated as zero. Reading 0.000 as the zero the document states is right; filing it is not — on a code Schedule B reports by weight, that zero is the quantity filed. The apportionment guard already documented this as the check's job.

Verification

Both revisions of the shipment were run end to end, parse → reconcile → SLI rows:

before after
corrected document 36.917 kg, 3 lines blank, 2 blocking checks failing 41.896 kg, every line weighted, every check passing, canGenerate = true
original (faulty) document blocked on the totals still blocked, and now names the line: 00153031OP0060/1 states a net weight of zero

npm run check is clean: typecheck, lint, 940 tests passing, build.

13 new tests. Eight of them fail against the parser as it was; four are no-regression guards on shapes that already worked.

Caveat: the fixture-gated suites (vendorA1–A4, vendorB1–B2) skip here — the real shipment PDFs are not in this environment. Their shapes are reproduced synthetically (divided weights, page-break carry, single-line-section fallback) and pass, but the real documents remain the final word and are worth a local run before merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XeY47Pdxpv1L3DroCZfDMS


Generated by Claude Code

A commercial invoice went out with the weight missing from several lines.
The corrected re-issue redrew those rows, and the app then read *fewer*
weights from the corrected document than from the wrong one: three lines
came out blank, the rows totalled 36.917 kg against a stated 41.896, and
the shipment was blocked twice over.

The corrected rows are drawn in a different font and kerned tight, so the
extractor reports the closing bracket as part of the figure beside it —
`.800000 )` in one cell. The reader matched brackets by exact cell text,
found no closing bracket, and dropped every weight on the row. Because a
packing line with no weight looks exactly like one that ran off the foot
of a page, the last such line on each page was also held open as an
unfinished block, which cost the next page's first line its commodity
heading.

Three things separately wrong, all of them fixed here:

- Brackets are stripped from a figure rather than matched as a cell of
  their own, so both printings read the same way.
- Figures are assigned to net, gross and measurement by the column they
  sit under, calibrated from the page's own headings. A line packed
  across two cartons prints its quantity inside the brackets as well, and
  reading the figures in order filed that quantity as the net weight and
  shifted the rest a column right — silently, on the wrong document.
- The weight row is told from the section's running totals by which row
  carries the quantity and the country, not by whether it is bracketed.
  This layout prints some lines' weights bare, and those had never been
  read at all.

Reconciliation now refuses a line whose net weight is stated as zero,
which the apportionment guard already assumed it did. Reading `0.000` as
the zero the document states is right; filing it is not — on a code
Schedule B reports by weight, that zero is the quantity filed.

The corrected document now reconciles to 41.896 kg with every check
passing. The original still blocks, naming the line at fault: the fault
was in the document, and the tool should say so rather than read past it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeY47Pdxpv1L3DroCZfDMS
@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 17, 2026

Copy link
Copy Markdown

Status

  • Head SHA: 3db2d5d381212e810d446d38d22d9300c884beba
  • Base SHA: 16ee90cd406044ef9ede5b62988ee413249deaa5
  • CI state: pending
  • Files reviewed: 6
  • Patches proposed: 0
  • Change types: code

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

Next actions

  • Awaiting updates for head 3db2d5d381212e810d446d38d22d9300c884beba

Meta

Processed at 2026-09-17 21:38:43.481 UTC
Idempotency key = 3db2d5d

Seven findings from the review of the weight reader, and the first three
are the same mistake in different places: a rule that looked safe because
its failure was assumed to be loud.

- A figure column is right-aligned under a left-aligned heading, so a
  wide figure starts left of its own heading while ending where every
  other figure in that column ends. Classified by its left edge it fell
  into the column before — and that is not a weight lost, which blocks,
  but a weight *shifted*: the gross slides into the empty net slot and
  files as the net weight with the packaging in it. Classified by the
  right edge now, which is the edge the typesetting actually fixes.
  `TextItem` carries the width pdfjs already reports.

- The fallback to the section's running totals ran whenever the weights
  came out undefined, including when a breakdown was printed and could
  not be read. Those totals are the section's: filing them on one line
  overstates it and counts the section twice, and it reconciles, because
  the same figures are then on both sides of the sum. It now runs only
  where no breakdown is printed at all.

- Nothing replaced the brackets as the anchor for which row a block's
  figures are on, so the first row with figures in the right columns won
  — and a packing block prints its description from the part-number
  column rightwards, straight through all three. The scan starts below
  the classification row instead, which is where this layout puts every
  figure a block states. That also stops a part number of digits alone
  being read as the quantity, which predates this branch.

- The quantity row was still found by a window measured off one document
  while the weights beside it were calibrated from the page's headings.
  It is read by column now, like everything else on the row.

- `(@ / 5)` was matched as a whole cell, so the same re-issue font that
  merges a bracket into a figure left it unrecognised and the line filed
  at a fifth of its weight.

- The bracket guard that skipped a row was removed with the anchor. It
  matched a bracketed country of origin — `Korea (Republic of)` split
  across cells — and cost that line its quantity and its country.

- `shareOut` counted a net weight of zero as a figure the document
  stated, so it held none of the row's weight and a proportional share
  handed it zero of whatever was entered. The check added in the last
  commit tells the operator to enter a weight against the row; this is
  what makes that possible. Only the weight: a line priced at nothing
  states its value as zero and means it.

Also hoists the column calibration to the page it is read from, rather
than recomputing it for every block on that page.

Declined: splitting a cell that holds two space-separated figures. The
extractor breaks a row on the wide kerns between columns and merges only
what is kerned tight against a figure, which is the bracket — both
documents show every column figure as its own cell.

The corrected document still reconciles to 41.896 kg with every check
passing; the faulty original still blocks, naming the line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeY47Pdxpv1L3DroCZfDMS
@JoelA510
JoelA510 merged commit 36a74dd into main Sep 17, 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