Skip to content

perf(api,web): bound transactions query by date, add /transactions/months - #21

Open
aafre wants to merge 24 commits into
mainfrom
perf/transactions-query
Open

perf(api,web): bound transactions query by date, add /transactions/months#21
aafre wants to merge 24 commits into
mainfrom
perf/transactions-query

Conversation

@aafre

@aafre aafre commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Bounds the transactions query by date instead of pulling an unbounded page, and adds GET /transactions/months so the dashboard can discover available months per currency.

  • Dashboard bootstrap is now three calls (/transactions?limit=1, /transactions/months?currency=…, /transactions?month=…) and lands on the newest currency's latest month. No more limit=500.
  • Bad account_id / malformed month now return 422 instead of silently matching nothing.
  • Gate: ruff, ruff format, mypy (60 files), pytest 151 passed.

Verified live on 8010 against the real data/pfa.db (429 rows, GBP + INR).

Note: this branch also carries the previously unpushed HDFC import / transfers / currency work that had accumulated on local main.

Known limitation (predates this change): the ledger's limit=200 per month silently truncates a month with more rows. Handoff: docs/plans/2026-09-05-transactions-query-handoff.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ATghuAGL4B1rYcAk97LmjE

aafre and others added 24 commits August 30, 2026 09:22
…X rates

Implements Phase 1 (narrow currency core) and the remaining Phase 2 defects from the
v0.2 plan, on top of work already staged on this branch.

Currency core:
- Settings.base_currency, an ISO-4217 SUPPORTED_CURRENCIES dict replacing the GBP-only
  reject, and per-currency minor-unit exponents (JPY=0) applied consistently through a
  single domain.money.minor_units helper.
- fx_rates table (migration 0004), FxRateRepository with at-or-before/inverse lookup,
  domain.fx.to_base, and `pfa fx set/fetch/list` plus /fx/* endpoints. Rates cross every
  boundary (API request/response, Frankfurter fetch) as decimal strings, never float.
- Analytics is per-currency throughout (monthly_summary, category/merchant totals,
  cashflow, budgets, current_cash), with a mixed GBP/INR regression test.
- A row's currency is validated against its destination account's at preview time
  (blocking issue, not a crash) and account currency lookup during preview no longer
  creates an account as a side effect.

Statement extraction:
- Fixed: an AMEX payment's own-line "CR" marker was silently dropped during extraction,
  so the statement's default debit-positive convention flipped it to spending. The
  marker now attaches to its row and marks the direction explicit, which the sign
  convention now respects everywhere (PDF debit/credit columns, CSV, inline/own-line CR).
- Fixed: parse_amount used a naive `* 100` instead of the currency's minor-unit exponent
  and rounding, corrupting non-2dp currencies (JPY) and truncating instead of rounding.
- Fixed: year-less dates ("Jul31") always took today's year, even during a replay of an
  old statement. Batches now infer the statement's year from any other year-bearing date
  in the same file and normalize every date up front, with a visible warning when no
  date carries a year at all.
- AMEX's duplicate "Date" column no longer leaks into the transaction description.
- Barclaycard two-column layout clustering and Money Out/Money In PDF header coverage.

Also fixes: ruff/format/mypy clean (was failing all three), restores a deleted PDF
header-alias regression test, and adds fetch_and_store_fx_rates coverage that proves
Frankfurter rates are parsed to Decimal rather than round-tripped through float.
pfa.config.get_settings() is @lru_cache'd. Any PdfStatementExtractor() built with
default settings (many unit tests do this) primes that cache with the real
sqlite:///data/pfa.db - and once cached, CliRunner(env={"PFA_DATABASE_URL": ...})
has no effect for the rest of the process, so test_cli_fx_commands ran against
whatever DB happened to be cached first instead of its own tmp_path DB.

Reproduced locally by forcing collection order:
pytest tests/unit/test_pdf_extractor.py tests/integration/test_cli.py
and confirmed the autouse cache-clear fixture fixes it regardless of order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8foThd2rk578JQmV3JHLQ
…ion WIP

Snapshot of in-progress statement-upload work: HDFC India delimited
adapter, PDF extractor hardening, reconciliation coverage, amount-sign
handling, and dashboard wiring for the new batch fields.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxBdKWe1SS9LP5ZBCT7m4M
The dashboard opened on the calendar-current month (almost always empty),
loaded only a single month, and hard-coded a single currency, so a real
ledger looked broken: £0.00 everywhere, self-referential "vs last month"
deltas, an empty 3-month cashflow chart, and every non-default-currency
account and month unreachable.

- Land on the most recent month that actually has transactions, in the
  currency of the latest activity (bootstrapDashboard / latestMonthWithData).
- Load the prior two months' summaries alongside the active month so the
  deltas and the 3-month cashflow chart have real comparison data.
- Add a currency switcher (shown only when the ledger holds >1 currency).
- Replace the fake hard-coded "Emergency Fund" goal placeholder with a
  real empty state.
- Soften the "Zero Hallucination Guarantee" label to an honest claim.
- Serve index.html with an mtime-based asset cache-buster so edited
  app.js/styles.css actually reach the browser.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxBdKWe1SS9LP5ZBCT7m4M
The statement-upload WIP left 43 ruff errors and 4 mypy errors. Fixes,
no behaviour change:

- Real bugs: B023 loop-variable capture in the HDFC fixed-width parser
  (bind col_spans/line as defaults); F821 undefined `Any` in the
  extraction-timeout cleanup callback (type it Future[ExtractionResult],
  rename the shadowing loop var); `pdf._merge_continuations` could assign
  None to fields["date"].
- Lint: drop unused imports, wrap over-long f-strings/messages/docstrings,
  noqa the fixed-width fixture rows that can't wrap.
- mypy: ignore missing stubs for the stub-less `xlrd` dependency.

Gate now fully green: ruff check, ruff format --check, mypy src, pytest
(149 passed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxBdKWe1SS9LP5ZBCT7m4M
…mmits

Two gaps that made a real import unusable after commit:

#1 Uncategorised rows were a dead end. Commit runs only deterministic
rules (no model), so statements rules can't match (e.g. Indian UPI)
stayed permanently uncategorised with no way to fix them in the browser.
- Add GET /categories and PATCH /transactions/{id} (a user correction:
  confidence 1.0 + a narrow exact-description merchant rule), sharing one
  correct_transaction() helper with the CLI.
- Activity Ledger: the category cell is now an editable dropdown that
  PATCHes and flips provenance to "user".

#2 amount_sign guard had holes. An all-positive generic statement could
commit booked as income once an account was assigned (assigning an
account cleared the sign check), and no-adapter batches skipped it
entirely. The convention is now required for every generic all-positive
batch regardless of account; the batch is blocked in the preview itself.

Gate: ruff, ruff format, mypy, pytest (151 passed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxBdKWe1SS9LP5ZBCT7m4M
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxBdKWe1SS9LP5ZBCT7m4M
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ATghuAGL4B1rYcAk97LmjE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant