wip(intake): router, enrich endpoint, Slack handlers, migration 0034 — UNTESTED checkpoint
ci / lint-test (push) Successful in 44s
ci / lint-test (push) Successful in 44s
Agent was terminated by the session usage limit before running lint/tests or writing its report. Files are complete drafts against plan §11 (C1, C2, C5): src/app/api/intake/, src/app/api/transactions/[id]/line-items/, src/lib/intake-*.ts, src/lib/line-items.ts, prisma/migrations/0034_intake_batches, fixtures + tests. Next: npm ci && npx prisma generate && npm run lint && npm test && npm run test:integration.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
-- 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.';
|
||||
Reference in New Issue
Block a user