intake: the needs-header card names what is missing and gives the right advice for it
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:
2026-08-22 12:35:09 +10:00
parent 0823244e4a
commit 258fe3b617
3 changed files with 31 additions and 3 deletions
+14
View File
@@ -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");
});
});
+16 -2
View File
@@ -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}`)] };
}
+1 -1
View File
@@ -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" };