feat(orders): a fifth verdict, 'bad', between ok and never again
ci / lint-test (push) Successful in 47s
ci / lint-test (push) Successful in 47s
The jump from "OK" to "Never again" is too big and most disappointments live in the gap (user, 2026-07-28) — so a merely poor meal either flattered itself as OK or got blacklisted. Only 'never' raises the warning on a future order. A blacklist that fires for every mediocre delivery is one nobody reads, so 'bad' records the disappointment without triggering the alarm. Both set order_again = false — you would not choose either again — and that split between "would I order it" and "warn me about it" is the point of the extra level. Migration widens the CHECK; nothing is removed, so no existing row needs mapping.
This commit is contained in:
+1
-1
@@ -277,7 +277,7 @@ export function useOrderReceipt(transactionId: number) {
|
||||
});
|
||||
}
|
||||
|
||||
export type OrderRating = "loved" | "liked" | "ok" | "never";
|
||||
export type OrderRating = "loved" | "liked" | "ok" | "bad" | "never";
|
||||
export type ItemVerdict = "loved" | "never";
|
||||
|
||||
export interface ItemOpinion {
|
||||
|
||||
@@ -26,14 +26,19 @@ import { queryRaw, queryRow } from "@/lib/db";
|
||||
*/
|
||||
|
||||
/**
|
||||
* Four levels, because three collapsed the distinction that decides a
|
||||
* re-order: "loved" and "liked" are both "would order again", but only one is
|
||||
* worth going out of your way for, and "ok" is not a recommendation at all
|
||||
* (user, 2026-07-28).
|
||||
* Five levels. Three collapsed the distinction that decides a re-order —
|
||||
* "loved" and "liked" are both "would order again" but only one is worth going
|
||||
* out of your way for, and "ok" is not a recommendation at all. "bad" was added
|
||||
* because the jump from "ok" to "never again" is too big and most
|
||||
* disappointments live in the gap (user, 2026-07-28).
|
||||
*
|
||||
* Only "never" raises the warning on a future order. A blacklist that fires for
|
||||
* every mediocre meal stops being read, so "bad" records the disappointment
|
||||
* without triggering the alarm.
|
||||
*/
|
||||
export type Rating = "loved" | "liked" | "ok" | "never";
|
||||
export type Rating = "loved" | "liked" | "ok" | "bad" | "never";
|
||||
|
||||
export const RATINGS: Rating[] = ["loved", "liked", "ok", "never"];
|
||||
export const RATINGS: Rating[] = ["loved", "liked", "ok", "bad", "never"];
|
||||
|
||||
/**
|
||||
* Per-item opinions, keyed by the line item's description.
|
||||
@@ -85,7 +90,11 @@ export interface MerchantVerdict {
|
||||
transaction_date: string | null;
|
||||
}[];
|
||||
counts: Record<Rating, number>;
|
||||
/** True when this merchant has ever been marked `never`. */
|
||||
/**
|
||||
* True when this merchant has ever been marked `never` — and ONLY `never`.
|
||||
* "bad" is deliberately excluded: the warning is a blacklist, and one that
|
||||
* fires for every mediocre meal is one nobody reads.
|
||||
*/
|
||||
warn: boolean;
|
||||
/**
|
||||
* What to get and what to avoid here, pooled across every order from this
|
||||
@@ -163,7 +172,7 @@ export async function merchantVerdict(
|
||||
[merchant, exclude]
|
||||
);
|
||||
|
||||
const counts: Record<Rating, number> = { loved: 0, liked: 0, ok: 0, never: 0 };
|
||||
const counts: Record<Rating, number> = { loved: 0, liked: 0, ok: 0, bad: 0, never: 0 };
|
||||
for (const r of rows) if (r.rating) counts[r.rating] += 1;
|
||||
|
||||
// Pool item opinions across orders. Case-folded because the same dish comes
|
||||
|
||||
@@ -30,6 +30,7 @@ const RATING_LABEL: Record<Rating, string> = {
|
||||
loved: "Loved it",
|
||||
liked: "Liked it",
|
||||
ok: "OK",
|
||||
bad: "Bad",
|
||||
never: "Never again",
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user