fix(vendor-a): read the weights a re-issued packing list prints - #56
Merged
Merged
Conversation
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
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Status
FindingsNo issues found in changed scope. Proposed patchesNone CI failure mappingCI is still running (pending). Diagnostic plannpm ci
npm run lint
npm test -- --watch=false
npm run buildNext actions
MetaProcessed at 2026-09-17 21:38:43.481 UTC |
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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
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.( 4 2.904 3.194 .800000 )— soinner[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.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
parseFigurestrips one bracket from each end of a cell, so both printings read the same way.figureColumnsOfcalibrates the quantity / net / gross / measurement columns from the page's own printed headings, falling back to the measured defaults;weightsInassigns each figure to the column it sits under rather than to an ordinal slot. An extra cell in the breakdown now costs nothing.figureRowInstrips 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.000as 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:
canGenerate = true00153031OP0060/1states a net weight of zeronpm run checkis 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