intake: the needs-header card names what is missing and gives the right advice for it
ci / lint-test (push) Successful in 49s
ci / lint-test (push) Successful in 49s
A Woolworths eReceipt SCREENSHOT has no date anywhere on it — store, total, items, payment, savings, not one date (2026-08-22, batch 60ed68be…) — while the app's own Share button sends the raw receipt with the date line. The card said 'share the top of the screen too', which is delivery-app advice and sends the person back for a shot that cannot exist. Now: the missing field by name, and for a dateless in-store receipt, the Share button or pantry's own upload.
This commit is contained in:
@@ -403,3 +403,17 @@ describe("the prompt carries no real merchant to echo", () => {
|
||||
expect(ORDER_ITEM_PROMPT).toMatch(/Do not construct a plausible order/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("needsHeaderCard", () => {
|
||||
it("names the missing field and sends a dateless in-store screenshot to the app's Share button", async () => {
|
||||
const { needsHeaderCard } = await import("@/lib/intake-blocks");
|
||||
const dateless = needsHeaderCard(["date"], "woolworths").text;
|
||||
expect(dateless).toContain("no date");
|
||||
expect(dateless).toContain("Share button");
|
||||
expect(dateless).not.toContain("total");
|
||||
const delivery = needsHeaderCard(["total"], "uber_eats").text;
|
||||
expect(delivery).toContain("no total");
|
||||
expect(delivery).toContain("part of the screen");
|
||||
expect(needsHeaderCard().text).toContain("total or date");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,8 +125,22 @@ export function forwardedCard(c: CardInput & { captureId: number; enriching: boo
|
||||
return { text, blocks: [section(`:shopping_trolley: ${text}`)] };
|
||||
}
|
||||
|
||||
export function needsHeaderCard(): { blocks: Block[]; text: string } {
|
||||
const text = "I can see items but no order total or date — share the top of the screen too so I can match it.";
|
||||
/**
|
||||
* Names what is missing, because the advice differs. A delivery-app screenshot
|
||||
* that lost its header needs the top of the screen; a Woolworths eReceipt
|
||||
* SCREENSHOT has no date anywhere on it (seen 2026-08-22 — store, total, items,
|
||||
* payment, and not one date), while the app's own Share button sends the raw
|
||||
* receipt WITH the date. So for a dateless in-store receipt the right advice is
|
||||
* the Share button, or pantry's own upload — not "share more of the screen".
|
||||
*/
|
||||
export function needsHeaderCard(missing: string[] = ["total", "date"], source?: string | null): { blocks: Block[]; text: string } {
|
||||
const what = missing.length ? missing.join(" or ") : "total or date";
|
||||
const inStore = source === "coles" || source === "woolworths";
|
||||
const hint =
|
||||
missing.length === 1 && missing[0] === "date" && inStore
|
||||
? "an app screenshot carries no date — use the app's Share button instead, or upload the picture to pantry directly"
|
||||
: "share the part of the screen that shows it";
|
||||
const text = `I can see items but no ${esc(what)} — ${hint}, so I can match it.`;
|
||||
return { text, blocks: [section(`:warning: ${text}`)] };
|
||||
}
|
||||
|
||||
|
||||
@@ -656,7 +656,7 @@ export async function runIntake(input: IntakeInput, deps: IntakeDeps): Promise<I
|
||||
const headerCheck = validateHeader(extracted);
|
||||
if (!headerCheck.ok) {
|
||||
await updateBatch(batchId, { header: { merchant: extracted.merchant, order_date: extracted.order_date, total: extracted.total, platform }, items, status: "needs_header" });
|
||||
return fail("held", "needs_header", needsHeaderCard(), 200, { items });
|
||||
return fail("held", "needs_header", needsHeaderCard(headerCheck.missing, input.source), 200, { items });
|
||||
}
|
||||
const header = headerCheck.header;
|
||||
const storedHeader = { ...header, platform, subtotal: extracted.subtotal ?? null, currency: extracted.currency ?? "AUD" };
|
||||
|
||||
Reference in New Issue
Block a user