-- Intake router (plans/intake-router-slice-plan.md §11 C1, smarthome repo). -- -- One upload point for receipts — a screenshot, a PDF export, a pasted body — -- that reads what it was given and does every step that applies: fill the -- empty `line_items` on the order the email pipeline already booked, hand a -- grocery receipt to pantry, or ask when it cannot tell. -- -- Every batch is persisted BEFORE anything is extracted, for two reasons: -- 1. The Slack card it produces carries buttons ("Use this", "Create", -- "Groceries or a meal?") that are pressed minutes later in a fresh -- request with nothing but the batch id to go on. -- 2. A re-share of an already-handled order — a second crop, the PDF after -- the screenshot — must be recognised, and the only durable evidence of -- the first share is this row (`file_sha256` overlap, or the same -- (platform, merchant, date, total) header already resolved). -- -- `id` is the uuid n8n mints per webhook call, so a retried delivery is the -- same batch rather than a second one. `header` holds the extracted -- (merchant, order_date, total, platform, ...) and `items` the verbatim item -- list, so a later tap can enrich or create without re-reading the files. -- -- Idempotent: safe to re-run. CREATE TABLE IF NOT EXISTS intake_batches ( id uuid PRIMARY KEY, source text NOT NULL, shared_by text, file_sha256 text[] NOT NULL DEFAULT '{}', header jsonb, items jsonb, kind text, status text NOT NULL, transaction_id integer, capture_id integer, created_at timestamptz NOT NULL DEFAULT now(), resolved_at timestamptz ); COMMENT ON TABLE intake_batches IS 'One row per upload batch through POST /api/intake. Persisted before ' 'extraction so Slack taps can act on it later and re-shares are detected. ' 'kind: grocery|food|ask|held|unsupported. status: received|enriched|created|' 'candidates|forwarded_to_pantry|needs_header|duplicate|unsupported|held|error.'; -- Overlap lookups for the re-share check (`file_sha256 && $1`). CREATE INDEX IF NOT EXISTS intake_batches_file_sha256_gin ON intake_batches USING gin (file_sha256); CREATE INDEX IF NOT EXISTS intake_batches_transaction_id ON intake_batches (transaction_id) WHERE transaction_id IS NOT NULL; -- The "Groceries or a meal?" answer, remembered per merchant (plan §2, Codex -- #6). A tiny answer table, not merchant master-data: the pattern list in code -- decides the obvious cases and this catches the `Romeo's Food Hall`s. Keyed on -- the normalised merchant name the router already computes for matching. CREATE TABLE IF NOT EXISTS intake_merchant_kinds ( merchant_key text PRIMARY KEY, kind text NOT NULL CHECK (kind IN ('grocery', 'food')), created_at timestamptz NOT NULL DEFAULT now() ); COMMENT ON TABLE intake_merchant_kinds IS 'Per-merchant answer to "Groceries or a meal?" from the intake Slack card. ' 'Consulted after the grocery pattern list; an unknown merchant asks once.';