feat(parser): reorder scrambled lines by bounding box #112

Merged
TimCane merged 5 commits from feat/99-box-sort into main 2026-07-07 15:17:03 +01:00
TimCane commented 2026-07-07 13:04:34 +01:00 (Migrated from github.com)

Summary

  • Add BoxOrderer (Parsing/Spatial): sorts priced candidates by Box.Y then Box.X so rows the sidecar emitted out of reading order parse back top-to-bottom with a correctly anchored grand total.
  • Carry Box.X on Candidate as the same-row tiebreak.
  • Reorder the priced candidates after the line scan, not the raw lines, so a label captured above its amount (PreviousText) is never disturbed - which keeps the columnar lescargot-columnar fixture byte-for-byte green.
  • Gated: an already-ordered receipt is returned untouched. A name and price split into separate columns and drifted to non-adjacent rows (westminster's detached 6.50) is not reunified - the sort reorders items already read, it never invents one; that far-drift stays parked (documented in docs/15).
  • New fixture scrambled-line-order; the existing corpus stays byte-for-byte green (no existing expected.json changed).
  • Docs: flip the spatial box-sort entry to current in docs/15; note BoxOrderer in docs/06; record the fixture in docs/11.

Test plan

  • dotnet test backend/tests/BillSplitter.Tests -> 92 passed, 0 failed.
  • The new fixture fails without the ordering step (items come out in emission order), confirming the reorder is load-bearing; the rest of the corpus is unaffected because its priced rows are already Y-ordered.

Closes #99

## Summary - Add `BoxOrderer` (`Parsing/Spatial`): sorts priced candidates by `Box.Y` then `Box.X` so rows the sidecar emitted out of reading order parse back top-to-bottom with a correctly anchored grand total. - Carry `Box.X` on `Candidate` as the same-row tiebreak. - Reorder the priced candidates *after* the line scan, not the raw lines, so a label captured above its amount (`PreviousText`) is never disturbed - which keeps the columnar `lescargot-columnar` fixture byte-for-byte green. - Gated: an already-ordered receipt is returned untouched. A name and price split into separate columns and drifted to non-adjacent rows (westminster's detached `6.50`) is not reunified - the sort reorders items already read, it never invents one; that far-drift stays parked (documented in docs/15). - New fixture `scrambled-line-order`; the existing corpus stays byte-for-byte green (no existing `expected.json` changed). - Docs: flip the spatial box-sort entry to *current* in docs/15; note `BoxOrderer` in docs/06; record the fixture in docs/11. ## Test plan - `dotnet test backend/tests/BillSplitter.Tests` -> 92 passed, 0 failed. - The new fixture fails without the ordering step (items come out in emission order), confirming the reorder is load-bearing; the rest of the corpus is unaffected because its priced rows are already Y-ordered. Closes #99
Sign in to join this conversation.
No description provided.