Cross-session backlog — open work only. Phases 0–8 (housekeeping, data
integrity, auth, testing/CI setup, MVP/import/reporting, docs, Commsec email
ingestion, dividend data + reporting) are complete — see git log for that
history rather than a checklist here.
- Make mypy blocking in CI — currently advisory (
continue-on-error: true); needs targeted# type: ignores at ~21 known SQLModel/ SQLAlchemy-typing-friction call sites, or better upstream SQLModel stubs - FIFO Method Support — accurate gain/loss computation using FIFO (and per-parcel tracking), as an alternative/addition to the current average-cost-basis capital-gains calculation
- CLI Interface — interact via command line with exportable summaries
Full scope, architecture, and phasing: see web-implementation-brief.md.
Roughly, in order:
- Pin
greentechhub-fastapi/greentechhub-uias dependencies; wireregister_auth(AUTH_ADAPTER=local) intopyfinbot.py— done: web login route (credential check +create_session_cookie) + base shell (web/templates/, extendinggreentechhub_ui'sapp.html) + a placeholder dashboard also landed in the same pass, verified viatests/test_web_auth.py(full session-cookie login→dashboard→logout round trip, 7 tests, 100% coverage on the newweb/routes)-
register_core+SettingsextendingGTHBaseSettings— done:Settingsnow extendsGTHBaseSettings,CORS_ORIGINSrenamed toCORS_ALLOWED_ORIGINS(matching whatregister_corereads), the hand-rolled CORS middleware +cors_origins_listretired in favor ofregister_core(app, settings)(also picks up request-id/timing/ security-header/trusted-proxy middleware for free)
-
- Stocks + Transactions pages —
gth_data_table(sortable headers,gth_table_filter, load-more paging, refreshed on change), modal create/edit/delete, ASX sync action with titled toasts,gth_badgestatus/type pills, searchable stock picker; transactions are fully editable (derived fields recomputed viaTransaction.recompute(), whichPUT /api/transactions/{id}now uses too); covered bytests/test_web_stocks.py/test_web_transactions.py - Import page — upload +
gth-toast— done:/importposts the file over HTMX and swaps in a result card (row/imported/skipped badges, a Row/Problem table for skipped rows) with a success/warning/danger toast that also firestransactionsChanged; the import itself moved tocore/transaction_import.py, shared withPOST /api/transactions/import(which also returns structuredrow_errorsnow); covered bytests/test_web_import.py - Emails + Dividends pages — manual sync triggers +
gth-toast— done:/emails(Sync Commsec emails, with a not-configured hint) and/dividends(sync all my stocks, or one picked stock) swap in a result card with badges + a problems list and a toast; each sync takes async_guardlock so a double-click can't run two at once. The logic moved tocore/commsec_import.py/dividend_sync.user_stock_ids, shared with the API routes; covered bytests/test_web_emails.py/test_web_dividends.py - Reports page — holdings, capital gains, and dividend income — done:
/reportswith lazygth_tabspanes (holdings as-of date, capital gains by FY, dividend income by FY or all time),gth_stat_cardtotals + tables, and a CSV download per report for its current filter; the computations moved tocore/reports.py, shared with/api/reports/*; covered bytests/test_web_reports.py - Dashboard placeholder —
gth-stat-cards + empty Grafana iframe slot - Later: swap
AUTH_ADAPTER=forward_authonce Authentik is live (needsregister_core'sTRUSTED_PROXIESwired first); real Grafana panels once embedding is configured
v0.11.0 (the Data & forms release) adds date range presets, file drop, bulk
selection, column view options, CSV export URLs, form-field extras and shared
money/number/date filters. One PR per item, in this order (item 1 first:
the rest need the pin). Each PR puts its tests in its own new test file, and
must pass pytest (coverage ≥ 80), ruff check src tests and
mypy src/pyfinbot.
-
build(deps): greentechhub-ui v0.11.0; shared formatting filters— done; covered bytests/test_web_formatting.py. Pin@v0.11.0inrequirements.txtand reinstall the local.venv(it currently has gth-ui 0.6.0, not the pinned 0.10.0). Drop_money/_qtyfromweb/templating.py(keep_fy; gth has no FY filter) —install()suppliesmoney/number/date, and our own assignments after it would shadow them.|qtyand|string|qty→|number(it takes the report schemas' floats directly).|moneycall sites stay but now print$(-$264.95for a buy's cost); per-unit prices (Price, Per share) stay|numberso they keep full precision. Raw dates →|date("5 Feb 2025"): the transaction table'sstrftime("%d/%m/%Y"), ex/pay dates in_report_dividends.html, the holdings empty state (keep the ISOas_offor input values and CSV hrefs). Updatetest_web_reports.py:88,152for the new date format -
feat(transactions): date range presets—gth_date_rangereplacestransactions.html's hand-built From/To inputs (hide_label=True,field_class="mb-0",fy_start_month=7default); samedate_from/date_toparams, so the route is unchanged. Keep the FY select -
feat(import): drag-and-drop upload with a 5 MB limit—gth_file_drop("file", "File", accept=ACCEPTED_EXTENSIONS, max_size=MAX_UPLOAD_BYTES)inimport.html(the form already hashx-encoding);MAX_UPLOAD_BYTES = 5 * 1024 * 1024incore/transaction_import.py;imports.pyreads at most limit + 1 bytes and raisesImportFileError("File is larger than 5 MB.")— the existing 422 result panel + toast. Updatetest_web_import.py:42'sacceptassertion -
feat(transactions): CSV export and column view options— split_query_transactionsinto a statement builder + paging so a newGET /transactions.csvtakes the table's filters and sort with no paging (thereports.pyCSV pattern;pyfinbot-transactions.csv: Date, Market, Symbol, Type, Units, Price, Fees, Total, Cost, FY, Notes; user-scoped);export_base_url="/transactions.csv"on_table_state. The 11-column table getsview_options=True: Date and Stockhideable: False, Noteshidden: True -
feat(stocks): bulk archive and unarchive—POST /stocks/bulk-archiveand/stocks/bulk-unarchivereadidsand shareupdate_stock'sis_active/archived_at/write_datetimerules (factor them into one helper); toast "Archived 3 stocks" +stocksChanged._stock_table.html:bulk_actions+gth_table_select_cell. (Stocks are global, not per-user, like the existing stock routes) -
feat(web): form polish—_transaction_form.html:prefix="$"on Price and Fees; Notes →type="textarea", rows=3, maxlength=500.notesgetsmax_length=500in the create/update schemas (the form's 422 re-render shows the error; the API enforces it too) via a sharedNOTES_MAX; the CSV importer and Commsec email import truncate notes to 500 instead of failing the row. No DB migration (enforced in the app)._stock_form.html:maxlength=20on symbol and market (the model's limit) -
feat(transactions): bulk delete— after the CSV export item (same route and table files).POST /transactions/bulk-deletereadsidsand deletes only the current user's rows (a user-scoped query, so another user's ids are silently ignored, like the 404-not-403 single delete); toast "Deleted N transactions" +transactionsChanged. The table getsbulk_actions=[{"label": "Delete", "style": "btn-outline-danger", "confirm": "Delete the selected transactions?", ...}]+ select cells
GET /users/requires a valid token but returns every user unfiltered — no admin/RBAC system built; deliberate scope boundary.- Stateless JWT, 24h expiry, no refresh/revocation — a leaked token is valid up to 24h with no force-logout. Acceptable for personal-use scale; would need a blocklist or refresh tokens to harden.
SECRET_KEYauto-generates a random value each process start if unset (never a hardcoded/empty fallback) — a deployment that needs tokens to survive a restart must set it explicitly.