fix(orders): the restaurant is the merchant, not the courier
ci / lint-test (push) Successful in 43s

Reverses a change made on request. The platform in the headline
fragmented the merchant: the same restaurant read as two, depending on
who carried the bag, and that is not a distinction anyone rating the food
cares about. It also already has a home — the expandable Order details
panel renders expense_metadata.platform next to its heading, which is
where the user asked for it.

The fragmentation was worse than cosmetic. merchantVerdict joined on an
exact merchant_normalized, and the platforms capitalise differently
("TEG Kebabs & Biryani" on Uber Eats, "TEG KEBABS & BIRYANI" on
DoorDash), so one restaurant kept two separate histories and a "never
again" recorded through one app never warned in the other — silently
defeating the point of the memory. Now case-folded; verified on real
data, where the DoorDash order sees 1 prior verdict against 0 before.

81 existing descriptions backfilled in one transaction, dry-run first and
dumped beforehand. The regex is anchored to the end so suburb parens
survive: "Order - Coles (Wyndham Vale) (Uber Eats)" becomes
"Order - Coles (Wyndham Vale)", not "Order - Coles".
This commit is contained in:
2026-07-28 17:51:36 +10:00
parent 50c5b7c430
commit 0595d49d5c
5 changed files with 53 additions and 20 deletions
+10 -7
View File
@@ -26,13 +26,16 @@ export const PLATFORM_LABEL: Record<ParsedOrder["platform"], string> = {
* look for the detail, and there are restaurants on both platforms. The
* platform is the one thing the parser always knows and used to discard.
*/
export function orderDescription(platform: ParsedOrder["platform"], merchant: string): string {
const label = PLATFORM_LABEL[platform];
// A trip's merchant is literally "Uber Trip", so the suffix would restate it
// — "Order - Uber Trip (Uber)". The addresses that actually identify a trip
// are in the Order details panel, not squeezed into the description.
if (merchant.toLowerCase().includes(label.toLowerCase())) return `Order - ${merchant}`;
return `Order - ${merchant} (${label})`;
export function orderDescription(_platform: ParsedOrder["platform"], merchant: string): string {
// The platform is deliberately NOT in the headline. What identifies the
// transaction is the restaurant; which courier delivered it is provenance,
// and it already has a home — the expandable Order details panel renders
// `expense_metadata.platform` next to its heading (user, 2026-07-28).
//
// Keeping it here also fragmented the merchant: the same restaurant reads
// "Mad Mex (DoorDash)" and "Mad Mex (Uber Eats)" depending on who carried
// the bag, which is not a distinction anyone rating the food cares about.
return `Order - ${merchant}`;
}
export interface IngestResult {
+6 -1
View File
@@ -151,7 +151,12 @@ export async function merchantVerdict(
JOIN expense_metadata em
ON em.transaction_id = r.transaction_id
OR em.matched_transaction_id = r.transaction_id
WHERE em.merchant_normalized = $1
-- Case-folded: the platforms capitalise the same restaurant differently
-- ("TEG Kebabs & Biryani" on Uber Eats, "TEG KEBABS & BIRYANI" on
-- DoorDash). An exact match split one restaurant's history in two, so a
-- "never again" recorded through one app never warned you in the other —
-- silently defeating the whole point of the memory.
WHERE lower(em.merchant_normalized) = lower($1)
AND ($2::int IS NULL OR r.transaction_id <> $2)
ORDER BY t.transaction_date DESC, r.participant_id
LIMIT 50`,