Skip to content

feat: improve statement imports and bounded ledger queries - #21

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

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

Conversation

@aafre

@aafre aafre commented Sep 6, 2026 •

Copy link
Copy Markdown
Owner

This PR completes the statement-import and ledger follow-up work accumulated on the branch, alongside the original bounded-transactions change.

  • Adds typed-account binding, HDFC Delimited import, PDF extraction hardening, balance reconciliation, auditable transfer matching, safe import undo, and manual transaction recategorisation.
  • Adds narrow multi-currency/FX support and fixes dashboard bootstrap, currency switching, and misleading empty states.
  • Bounds transaction reads by month/account and adds GET /transactions/months for dashboard month discovery.
  • Terminates timed-out PDF/OCR workers, retries classification at commit, and labels unresolved imported rows with truthful provenance.
  • Adds synthetic residual-case classifier and grounded-answer evals. Both require the configured local model; it was unavailable during this validation run.

Validation: Ruff, formatting, mypy (60 source files), and 153 tests pass locally. Both CI quality jobs and package builds pass.

The real-bank PDF parser has synthetic regression coverage, but clean imports from the private HSBC/AMEX/Barclaycard corpus remain unverified. The corpus stays outside git; record only hashes, adapter IDs, counts, and outcomes when running it on a disposable database. The ledger still caps each month at 200 rows.

aafre and others added 28 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
@aafre aafre changed the title perf(api,web): bound transactions query by date, add /transactions/months feat: improve statement imports and bounded ledger queries Sep 16, 2026
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