Commit Graph
6 Commits
Author SHA1 Message Date
siddharthd db5c9a02a7 intake: POST /api/intake router + PUT /api/transactions/[id]/line-items (plan §11 C1, C2)
One upload point, every applicable step. Persist the batch, classify each
file, ONE LiteLLM call (cloud-budget, temperature 0, strict json_schema; PDFs
as unpdf text, never pixels) with an order-item prompt that keeps names
verbatim, header (merchant, date, total), merchant pattern
(coles|woolworths|woolies|aldi|iga|foodworks|costco) -> grocery | food | ask,
exact (platform, merchant, date, total) match against transactions +
expense_metadata, then act:

- food + unique match  -> replace line_items via the C2 path, 'enriched'
- food + miss/ambiguity -> 'candidates' (same merchant ±3d) with
  intake_use_candidate / intake_create buttons; never auto-creates
- grocery              -> original files to pantry POST /api/capture with
  finance_target_transaction_id + finance_action enrich|create (C3);
  409 -> 'duplicate'
- ask / needs_header / held / duplicate / unsupported as replies, not writes

Reconciliation is against the printed item subtotal (a delivery total is
items + fees - promotions: 18.94 of Coles lines settled at 5.74),  or
2%, else 'held'. Originals are kept under INTAKE_FILE_DIR by sha256 so a later
'Groceries' tap can still forward them.

C2 is an idempotent replace on transaction_id = :id OR
matched_transaction_id = :id, accepts INTAKE_TOKEN or RECEIPT_INGEST_TOKEN,
creates the expense_metadata row when absent, 404 when the transaction does
not exist, returns {previous_line_items, line_items} so pantry's undo is the
same call with the previous list.
2026-08-21 12:10:05 +10:00
siddharthd b958d05287 test(db): resolve the integration DSN at run time instead of storing an IP (#161)
ci / lint-test (push) Successful in 1m5s
setup-test-db.sh wrote .env.test once with the Postgres container's bridge IP
baked in, and Docker reassigns those on recreation. The file rotted silently:
its stored 172.22.0.18 belongs to **gitea** as of today, and an earlier drift
had the suite pointing its TRUNCATE at **postgres-pantry** — which it would
have wiped had the credentials happened to match.

Root-cause fix: stop persisting a host at all.

- scripts/resolve-test-db.sh re-derives the DSN from the live container and
  rewrites .env.test. Resolution order is TEST_DATABASE_URL, then the
  container's published host port, then the bridge IP. `docker port
  postgres-personal` reports 127.0.0.1:5432 since the 2026-08-17 PG17
  migration, so loopback is what gets used here — and a loopback address
  cannot be reassigned to another container, which is the whole point.
  (Note this supersedes the smarthome CLAUDE.md's "postgres-personal publishes
  no host port"; that is now stale.) The bridge-IP fallback is kept for a host
  where the port is not published, and the script never prints the password.
- Wired as `pretest:integration`, so npm runs it before every integration run.
  .env.test becomes a generated artifact rather than stored configuration.
- setup-test-db.sh now only creates the database and copies the schema, then
  delegates to the resolver.
- helpers.ts::assertTestDatabase stays and its message now points at the
  resolver rather than at `docker inspect`. It still earns its place: running
  vitest directly bypasses the pretest hook, and a stale .env.test would then
  still be read.

Docs: CLAUDE.md gains a Testing section carrying this, the prisma-generate
trap for a fresh checkout or worktree, and the frozen-clock rule for tests
whose subject is defined relative to "now". AGENTS.md updated for the same, and
for lint now being blocking.
2026-08-21 00:52:54 +10:00
siddharthd 1296555f17 test: add unit and integration test suites
- Extract evaluateCondition + rule types into src/lib/rules.ts for testability
- 48 unit tests for evaluateCondition (all fields/operators) and formatCategory
- 21 integration tests for getTransactions filters and getParticipantBalances
- Vitest configs for unit (vitest.config.ts) and integration (vitest.integration.config.ts)
- setup-test-db.sh creates personal_test DB from production schema via pg_dump
- Use vi.doMock + dynamic import pattern to isolate test DB from Prisma singleton
2026-04-01 19:59:29 +11:00
siddharthd 30a7857d13 feat(analytics): replace budget page with spending analytics + split-adjusted amounts
- Rename 'Budget' → 'Analytics' in sidebar
- Rewrite /budget page: summary cards, recharts bar charts (monthly trend + category breakdown), 6-month trend table
- Fix analytics API to count only user's share for split transactions (CASE WHEN ts.share_percent IS NOT NULL THEN amount * share_percent / 100 ELSE amount END)
- Install recharts
2026-03-08 17:58:33 +11:00
siddharthd 35a5be97b0 feat: initial finance SPA — phases 1 & 2
Next.js 16 personal finance dashboard connected to postgres-personal.

Phase 1 (Foundation):
- API routes: GET /api/transactions (paginated, filterable, sortable),
  GET /api/statements, GET /api/merchants
- Transactions data table with date/category/bank/search filters, pagination, sort
- Statements card grid with period, due date, amount, transaction count
- Sidebar layout with nav for all planned sections

Phase 2 (Normalisation):
- PATCH /api/transactions/[id] — upsert transaction_overrides
- POST /api/transactions/bulk — bulk categorize/normalize
- Inline click-to-edit category (22 options) and merchant name
- Blue dot override indicator, bulk action bar
- Effective values via COALESCE(override, llm_value) pattern

Stack: Next.js 16 (App Router, standalone), Prisma 7.x + @prisma/adapter-pg,
TanStack Query, Tailwind CSS. Auth via Traefik chain-oauth@file.
2026-03-07 23:31:40 +11:00
siddharthd 28207b42b5 Initial commit from Create Next App 2026-03-07 19:32:57 +11:00