feat(parser): attach modifier lines to their item #109

Merged
TimCane merged 10 commits from feat/97-modifier-attach into main 2026-07-07 14:30:55 +01:00
TimCane commented 2026-07-07 12:48:26 +01:00 (Migrated from github.com)

Summary

  • add a ModifierMerger pre-pass that folds amount-less modifier lines (+ Bacon, No Onion, Extra Sauce) into the priced line above them, splicing the note ahead of the trailing amount run so the item reads enriched (Burger + Bacon No Onion) with its price unchanged
  • compose with the WrappedNameMerger pre-pass (#111): wrapped names assemble first, then modifiers attach to whole priced rows; the wrapped-name pass steps over a leading +/* note so a modifier printed between a name and its price does not lose the item
  • keep the detector conservative: only short leading +/* additions or a short NO/EXTRA/ADD/HOLD/SUB/LESS/W/O/WITHOUT keyword form attach, and never a form naming a payment/status footer or a bill extra (service, gratuity, tip, tax, discount, ...) - so No payment received and Add Gratuity stay put instead of flipping the item to a bill line
  • never name a bare price: a note under a price the wrapped-name pass left un-named parks a warning rather than manufacturing a phantom item
  • share the money-token pattern (ReceiptPatterns.Money) between the engine and the modifier pass so the splice boundary cannot drift from what the engine reads as a price
  • document the two residual list-order limitations in docs/15 (a non-indented keyword modifier, an out-of-order note); update docs/06, docs/11, docs/15

Test plan

  • dotnet test backend/tests/BillSplitter.Tests/BillSplitter.Tests.csproj - 99 pass; existing corpus byte-for-byte green, including the three No payment received fixtures and #111's wrapped-name fixtures
  • hardening fixtures, each verified to fail before its fix: burger-modifiers, modifier-footnote, modifier-keyword-footer, modifier-nameless-price, modifier-between-wrapped-name, modifier-unit-price-column

Closes #97

## Summary - add a `ModifierMerger` pre-pass that folds amount-less modifier lines (`+ Bacon`, `No Onion`, `Extra Sauce`) into the priced line above them, splicing the note ahead of the trailing amount run so the item reads enriched (`Burger + Bacon No Onion`) with its price unchanged - compose with the `WrappedNameMerger` pre-pass (#111): wrapped names assemble first, then modifiers attach to whole priced rows; the wrapped-name pass steps over a leading `+`/`*` note so a modifier printed between a name and its price does not lose the item - keep the detector conservative: only short leading `+`/`*` additions or a short `NO`/`EXTRA`/`ADD`/`HOLD`/`SUB`/`LESS`/`W/O`/`WITHOUT` keyword form attach, and never a form naming a payment/status footer or a bill extra (`service`, `gratuity`, `tip`, `tax`, `discount`, ...) - so `No payment received` and `Add Gratuity` stay put instead of flipping the item to a bill line - never name a bare price: a note under a price the wrapped-name pass left un-named parks a warning rather than manufacturing a phantom item - share the money-token pattern (`ReceiptPatterns.Money`) between the engine and the modifier pass so the splice boundary cannot drift from what the engine reads as a price - document the two residual list-order limitations in `docs/15` (a non-indented keyword modifier, an out-of-order note); update `docs/06`, `docs/11`, `docs/15` ## Test plan - `dotnet test backend/tests/BillSplitter.Tests/BillSplitter.Tests.csproj` - 99 pass; existing corpus byte-for-byte green, including the three `No payment received` fixtures and #111's wrapped-name fixtures - hardening fixtures, each verified to fail before its fix: `burger-modifiers`, `modifier-footnote`, `modifier-keyword-footer`, `modifier-nameless-price`, `modifier-between-wrapped-name`, `modifier-unit-price-column` Closes #97
Sign in to join this conversation.
No description provided.