feat(parser): warn when receipt totals do not reconcile #108

Closed
TimCane wants to merge 3 commits from feat/95-reconcile-warning into main
TimCane commented 2026-07-07 12:45:24 +01:00 (Migrated from github.com)

Summary

  • Add ReconciliationValidator (Parsing/Validation): sum(items) + tax + tip + service vs the printed grand total, warning on a gap over a 2 minor-unit tolerance
  • Skip when no grand total was anchored (total is zero) or a negative amount was already parked (an unmodeled discount already explains the gap)
  • Wire it in last in ReceiptParseEngine.ParseTraced; cross-line, so it appends a warning but adds no trace entry
  • Two new fixtures: reconciling-simple (balances, no warning) and mismatch-missing-item (dropped item line fires the warning)
  • Three existing fixtures legitimately gain the reconcile warning:
    • defune-at-quantity: printed subtotal 151.20 + service 22.68 = 173.88 total, but parsed items sum to 149.80 - a genuine ~GBP1.40 receipt/OCR discrepancy
    • ding-dong-amount: "Whole @$28.00" parses as a phantom item, inflating items by 28.00 over the receipt's own 164.00 + service 16.40 = 180.40
    • westminster-column-drift: the "6.50" price whose name drifted onto a separate line is parked, losing GBP6.50 from the item sum
  • Docs: flip reconciliation to current in docs/15, describe the validate stage in docs/06, note the validator and fixtures in docs/11

Test plan

  • dotnet test backend/tests/BillSplitter.Tests/BillSplitter.Tests.csproj --nologo - 93 passed
  • Corpus diff confirms only the three named fixtures changed; the discount receipts (crowne-plaza-iva, holland-discount, toby-carvery) stay unchanged

Closes #95

## Summary - Add `ReconciliationValidator` (`Parsing/Validation`): `sum(items) + tax + tip + service` vs the printed grand total, warning on a gap over a 2 minor-unit tolerance - Skip when no grand total was anchored (total is zero) or a negative amount was already parked (an unmodeled discount already explains the gap) - Wire it in last in `ReceiptParseEngine.ParseTraced`; cross-line, so it appends a warning but adds no trace entry - Two new fixtures: `reconciling-simple` (balances, no warning) and `mismatch-missing-item` (dropped item line fires the warning) - Three existing fixtures legitimately gain the reconcile warning: - `defune-at-quantity`: printed subtotal 151.20 + service 22.68 = 173.88 total, but parsed items sum to 149.80 - a genuine ~GBP1.40 receipt/OCR discrepancy - `ding-dong-amount`: "Whole @\$28.00" parses as a phantom item, inflating items by 28.00 over the receipt's own 164.00 + service 16.40 = 180.40 - `westminster-column-drift`: the "6.50" price whose name drifted onto a separate line is parked, losing GBP6.50 from the item sum - Docs: flip reconciliation to *current* in docs/15, describe the validate stage in docs/06, note the validator and fixtures in docs/11 ## Test plan - `dotnet test backend/tests/BillSplitter.Tests/BillSplitter.Tests.csproj --nologo` - 93 passed - Corpus diff confirms only the three named fixtures changed; the discount receipts (crowne-plaza-iva, holland-discount, toby-carvery) stay unchanged Closes #95
TimCane commented 2026-07-07 15:54:53 +01:00 (Migrated from github.com)

Closing: the reconcile signal this adds already exists. SplitCalculator computes the items-vs-total checksum (bill.checksumMinor), and ChecksumBanner surfaces it on the review screen - formatted and live as the host edits ("Items + extras are X over/under the printed total").

ReconciliationValidator was a third copy of that arithmetic, and routing it through ocr.warnings misframed it in ParserWarnings as a skipped line with raw minor units. After merging main and dropping the warning, this branch nets to zero diff vs main, so there is nothing left to merge.

Closing: the reconcile signal this adds already exists. `SplitCalculator` computes the items-vs-total checksum (`bill.checksumMinor`), and `ChecksumBanner` surfaces it on the review screen - formatted and live as the host edits ("Items + extras are X over/under the printed total"). `ReconciliationValidator` was a third copy of that arithmetic, and routing it through `ocr.warnings` misframed it in `ParserWarnings` as a skipped line with raw minor units. After merging main and dropping the warning, this branch nets to zero diff vs main, so there is nothing left to merge.

Pull request closed

Sign in to join this conversation.
No description provided.