Unit: merchant pattern, header validation, reconciliation tolerance, verbatim line items, one-call message shape, card action ids; unpdf over the two real Uber Eats exports (Coles $85.74 / 15 lines, Burgertory $22.40 / Michael + Joel). Integration: fetch stubbed at the LiteLLM and pantry boundaries so the PDF path, persistence, match and writes run for real against personal_test. intake-live.test.ts is opt-in (INTAKE_LIVE_TEST=1) and was run green against cloud-budget on both PDFs.
38 lines
2.1 KiB
TypeScript
38 lines
2.1 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { readFileSync } from "fs";
|
|
import { resolve } from "path";
|
|
import { extractPdfText, liteLLMExtractor } from "../../lib/intake-extract";
|
|
import { reconcileItems, toLineItems, validateHeader } from "../../lib/intake-rules";
|
|
|
|
/**
|
|
* Opt-in: the real model over the real Burgertory export.
|
|
*
|
|
* INTAKE_LIVE_TEST=1 LITELLM_BASE_URL=http://localhost:4200 LITELLM_KEY=... npm test
|
|
*
|
|
* Skipped otherwise — CI has no LiteLLM, and a unit run must not depend on one.
|
|
* What it proves is the thing the mock cannot: that `cloud-budget` keeps the
|
|
* names verbatim ("Michael", "Joel", not "Burger") under the order-item prompt.
|
|
*/
|
|
const live = process.env.INTAKE_LIVE_TEST === "1" && !!process.env.LITELLM_BASE_URL && !!process.env.LITELLM_KEY;
|
|
|
|
describe.skipIf(!live)("live extraction via LiteLLM (opt-in)", () => {
|
|
it("Burgertory PDF: header exact, two items named verbatim, reconciles", async () => {
|
|
const text = await extractPdfText(readFileSync(resolve(__dirname, "../fixtures/intake/ubereats-burgertory-2026-08-19.pdf")));
|
|
const extracted = await liteLLMExtractor()([{ kind: "text", text, name: "Receipt_19Aug2026.pdf" }]);
|
|
const header = validateHeader(extracted);
|
|
expect(header.ok && header.header).toEqual({ merchant: "Burgertory (Werribee)", order_date: "2026-08-19", total: 22.4 });
|
|
const items = toLineItems(extracted.items);
|
|
expect(items.map((i) => i.description)).toEqual(["Michael", "Joel"]);
|
|
expect(reconcileItems(extracted).ok).toBe(true);
|
|
}, 120_000);
|
|
|
|
it("Coles PDF: header exact, 15 items", async () => {
|
|
const text = await extractPdfText(readFileSync(resolve(__dirname, "../fixtures/intake/ubereats-coles-2026-08-18.pdf")));
|
|
const extracted = await liteLLMExtractor()([{ kind: "text", text, name: "Receipt_18Aug2026.pdf" }]);
|
|
const header = validateHeader(extracted);
|
|
expect(header.ok && header.header).toEqual({ merchant: "Coles (Wyndham Vale)", order_date: "2026-08-18", total: 85.74 });
|
|
expect(toLineItems(extracted.items)).toHaveLength(15);
|
|
expect(reconcileItems(extracted).ok).toBe(true);
|
|
}, 120_000);
|
|
});
|