Mirror YNAB budget into Postgres with delta sync and change log #55

Merged
ryan merged 5 commits from feat/ynab-mirror into main 2026-10-12 18:07:32 -04:00

5 Commits

Author SHA1 Message Date
Ryan Chen 0fa30edb82 Merge origin/main into feat/ynab-mirror
Resolve config/db.py (artifacts + ynab models) and regenerate the YNAB
migration with aerich as 18 on top of main's 17_add_artifacts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:07:26 -04:00
Ryan Chen 10f3f1bcab Merge origin/main into feat/ynab-mirror
Resolve the Makefile .PHONY conflict (desktop + ynab targets) and
regenerate the YNAB migration with aerich as 17 on top of main's
16_add_oidc_states, replacing the colliding 16_add_ynab_mirror.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:06:47 -04:00
Ryan Chen d1c3b7693e Fix second-round YNAB review findings
- Failure backoff is re-checked under the sync lock, so parallel tool
  calls share one failed YNAB request instead of retrying in turn.
- Budget id: YNAB_BUDGET_ID aliases ("last-used", "default") count as
  unset; state is always keyed by the real budget id, a mismatched
  response errors instead of silently skipping every sync, and more
  than one stored budget is an error rather than an arbitrary pick.
- Change log renames only id fields that resolve to names (account,
  payee, category, group); subtransaction transaction_id stays as is.
- ynab_recent_changes: correct singular for "categories", multi-word
  types, and the "month" type.
- The SQL wrap test now parses the wrapped query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:03:48 -04:00
Ryan Chen 9249f92db8 Fix YNAB mirror review findings
- Full resyncs mark mirror rows missing from the download as deleted;
  YNAB omits deleted entities from non-delta responses, so deletions
  made just before a full sync never reached the mirror.
- YNAB tools return errors as text instead of raising, since ToolNode
  re-raises and would fail the whole chat turn. ynab_sql catches all
  sqlglot errors (e.g. an unbalanced quote) and runs the SQL regenerated
  from the validated tree, which also drops comments that broke the
  LIMIT wrapper. Results keep duplicate column names.
- Sync fetches from YNAB before opening the DB transaction, discards the
  payload if the other container synced meanwhile, re-checks freshness
  under the lock so parallel tool calls share one sync, and backs off
  for 60s after a failure.
- The mirror is pinned to one budget; a changed YNAB_BUDGET_ID errors
  instead of mixing budgets, and an unset id stays on the budget the
  first sync resolved from "last-used".
- Change log: no entries for empty month rollover rows, and id fields
  are logged as names (category, payee, account). ynab_recent_changes
  accepts the names it prints ("assignment") and plurals.
- Outputs sized to the 4000-char tool result cap; ynab_sql reuses the
  shared admin message.
- YNAB API client moves back to utils/ynab_service.py, and the frontend
  tool status labels list the new tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:58:14 -04:00
Ryan Chen 8f02318542 Mirror YNAB budget into Postgres with delta sync and change log
YNAB has no event feed, so keep a local copy in ynab_* tables synced via
server_knowledge delta requests: full download on first run, deltas every
YNAB_SYNC_INTERVAL seconds and on demand before each tool call, full
resync daily. Each sync diffs against the mirror and appends real edits
(transactions, assignments, renames) to ynab_changes, the edit history
YNAB doesn't keep.

Agent YNAB tools now answer from the mirror: budget summary (spending
computed from transactions, since YNAB's month activity nets in credit
card payment categories), account balances, transaction search with
splits expanded, spending reports by category/payee/account/month,
category budget, and recent changes. Adds an admin-only read-only
ynab_sql tool guarded by a sqlglot allowlist (scope-resolved tables, no
dangerous functions), a READ ONLY transaction, a timeout, and a row cap.

Replaces utils/ynab_service.py and drops the unused ynab SDK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:32:38 -04:00