docs: order verdicts are built; record the four load-bearing shape decisions
ci / lint-test (push) Successful in 41s

This commit is contained in:
2026-07-28 15:43:20 +10:00
parent b8919a4775
commit 66a6a51fb8
+39 -2
View File
@@ -183,8 +183,45 @@ category-level ratio that "looks wrong" usually is not.
offset account, 39 rows, $37,980.24) are categorised `transfers`, indistinguishable offset account, 39 rows, $37,980.24) are categorised `transfers`, indistinguishable
from ordinary internal transfers. The loan model below is unbuilt. from ordinary internal transfers. The loan model below is unbuilt.
**`order_reviews` is a table wired to nothing** — 0 rows, no API, no UI, no ### Order verdicts — "never order from here again"
writes. The "never order from here again" capability does not exist.
Built 2026-07-28 (migration 0024). `order_reviews` was previously a table wired
to nothing; it now backs `GET`/`PUT /api/transactions/[id]/review`, with the UI
inside `components/order-details.tsx`. Logic in `lib/order-reviews.ts`.
Four things about the shape, each of which is load-bearing:
- **Per person, not per order.** `UNIQUE (transaction_id, participant_id)`. A
shared meal produces two opinions that routinely disagree, and the
disagreement is the useful part. `PUT` defaults to the **signed-in user**,
not the owner — Sonu authenticates through the same Traefik OAuth as
participant 4, so an owner default would file her verdict under his name.
- **Four levels** — `loved`, `liked`, `ok`, `never`. Three collapsed the
distinction that decides a re-order: "loved" and "liked" both mean "would
order again" but only one is worth a detour, and "ok" is not a
recommendation.
- **Item verdicts key on the item DESCRIPTION**, not its index — an index is
meaningless across orders, and "the Pad Thai here is good" has to survive
into the next order from the same merchant. Pooled case-folded across the
merchant's orders. Only `loved`/`never`: a per-item "ok" answers neither
question you ask at order time.
- **An ABSENT `item_verdicts` means "leave them alone"; `[]` clears them.**
Without that distinction a note-only save wipes every per-item opinion — the
same shape as the bug that reset `settled` on split rewrites, and just as
invisible on screen.
**Sharing is a real 50/50 split, not a flag.** The "Shared with Sonu" toggle
writes `transaction_splits`, because the split already IS the record that an
order was shared and two records of one fact drift apart. Safe to clear on an
order because an ingested order is post-cutover by construction.
The merchant signal is *derived* by aggregating a merchant's orders on
`expense_metadata.merchant_normalized` — never `transactions.merchant_name`,
which is a bank descriptor and reads differently for the same restaurant on
different nights.
`/api/orders/ingest` returns `prior_verdict` so the n8n Slack nudge can warn at
the moment an order lands.
### The shared loan ### The shared loan