Commit Graph
4 Commits
Author SHA1 Message Date
siddharthd 0823244e4a intake: the word "Image" is not a receipt, and the prompt must not be data
ci / lint-test (push) Successful in 52s
Batch 3d2ffbe8 arrived as one text part whose entire content was the five
characters `Image` — an iOS Shortcut coerced the shared photo to text. The
model was asked anyway and answered out of its prompt: Burgertory
(Braybrook), two "The Pride" lines at $22.80, a $41.15 total. Nothing on it
existed. Burgertory was in ORDER_ITEM_PROMPT as the example of how to print
a branch in parentheses. Only the missing date kept it out of the matcher;
the invented header and items were stored on the batch regardless.

Three layers, none of them a model:

1. textCouldBeReceipt() — deterministic, before any model call. A TEXT part
   with fewer than two words, no digit, or under 40 characters and no money
   figure cannot be a receipt. Length is NOT the test on its own: `TOTAL
   $17.00` is twelve characters and is exactly what this door accepts. A
   rejected part is dropped like an unreadable file type, so one junk part
   beside a good screenshot still goes through; if nothing usable is left
   the card quotes what the phone actually sent. PDFs and images untouched.

2. The prompt names no real shop — merchant examples are
   PROMPT_PLACEHOLDER_MERCHANTS, exported from intake-rules so the prompt
   and the guard cannot drift — and it is told what to return when the input
   is not a receipt at all.

3. confabulationReason() — the belt. Reachable where the pre-check cannot
   look: a PDF that renders to a scrap of junk still reaches the model. Also
   rejects any answer whose merchant IS a placeholder.

All three file `unsupported`, never `needs_header`. A confabulated header is
not a partial one, and the needs_header path stores what it is given.

Same shape as the ingestion engine's 2026-08-05 empty-body defect: a stage
that can be handed nothing has to say so, because the model downstream will
always answer.

Tests: 16 unit cases over the two pure functions and the prompt; three
integration cases — `Image` is declined with zero model calls and nothing
but `unsupported` on the batch row, a junk part beside a real receipt still
enriches, and a junk PDF is unsupported rather than needs_header. The first
and third fail on the tree before this commit.
2026-08-22 11:46:05 +10:00
siddharthd 91586ce4a4 test(intake): the second review pass, and CLAUDE.md for the rules it set
ci / lint-test (push) Successful in 45s
Six of the nine fixes have a test that fails against the previous tree: the
metadata row's currency, the Create-path hold, two concurrent batches booking
once, mrkdwn escaping including plain_text, the escaped currency, and
candidate-row currency.

Three do not, and the reason is the same each time — the window needs a crash
or a lost race that a test cannot force. Asserted instead: a stale metadataId
falls back to the live row rather than reporting a phantom write; two writes
racing to create a metadata row both resolve to one; the create path records a
baseline at all. All three passed before as well.
2026-08-21 18:23:52 +10:00
siddharthd 631e75e58f test(intake): cover the review findings, and CLAUDE.md for the rules they set
ci / lint-test (push) Successful in 59s
Unit: word-boundary merchant matching (Bigals Pizza is not an IGA), mrkdwn
escaping vs the plain_text button label, a foreign total on a card, and the
finish_reason gate.

Integration: the NULL-platform wildcard on both the food and grocery paths,
candidates crossing platforms, EXCLUDE_RECONCILED_SOURCE, the pinned metadata
row, the panel reading the row the write landed on, the once-per-batch undo
baseline, the priceless-upload hold, Create re-matching, two pending cards
booking once, a pantry-resolved sibling, and a USD receipt.

Eleven of the thirteen fail against the pre-fix tree; the exceptions are the
no-batch-id baseline guard (a no-change assertion) and the concurrent-tap one,
which is a race and passed either way.
2026-08-21 17:53:06 +10:00
siddharthd 32a47bf8ee test(intake): rules, PDF text, C2 idempotency, enrich-vs-candidates, pantry hand-off, Slack taps
Unit: merchant pattern, header validation, reconciliation tolerance, verbatim
line items, one-call message shape, card action ids; unpdf over the two real
Uber Eats exports (Coles $85.74 / 15 lines, Burgertory $22.40 / Michael +
Joel). Integration: fetch stubbed at the LiteLLM and pantry boundaries so the
PDF path, persistence, match and writes run for real against personal_test.
intake-live.test.ts is opt-in (INTAKE_LIVE_TEST=1) and was run green against
cloud-budget on both PDFs.
2026-08-21 12:10:06 +10:00