Spending Page¶
Route: / (index.html)
Nav label: Spending
Last Updated: 2026-10-03
What It Does¶
The landing page. A two-period spending comparison, an analytics panel of charts, a category table, a YTD table with its share donut, a 12-month sparkline, and the income to spending Sankey.
Content spans up to 2400px on a wide monitor. The cap lives once in static/css/app.css for every
page; Statements and Record Balances opt back down to 900px with .container.narrow.
/bank, /purchases, and /trends are 302s back here — they were separate pages once and are now
sections of this one.
Period Selection¶
Not a date range picker. The bar at the top holds two independent year + month pairs, Period 1 and Period 2, and the whole page renders as a comparison between them.
| Control | Populated by |
|---|---|
p1Year / p1Month |
GET /api/available-periods |
p2Year / p2Month |
same |
The mobile menu carries duplicate p1YearMobile / p1MonthMobile / p2YearMobile /
p2MonthMobile selects, because the period bar does not fit in the phone navbar. Every mirror of a
period is rebuilt whenever any of them changes, so they cannot disagree.
Both selections persist to localStorage as heezyP1 and heezyP2. The
Reconcile page mounts the same control against heezyP1, so the month picked there
is the Period 1 this page opens on. The shared implementation is static/js/period-picker.js —
there is deliberately only one copy of it.
GET /api/latest-data-period drives the stale-data banner. When the newest bank_transactions row
predates the current calendar month, the banner says so — otherwise the current month reads as near
zero spend and looks like a bug rather than missing statements.
Sections¶
Hero Metrics¶
Four cards, all from GET /api/overview.
| Card | Content |
|---|---|
| Selected Period | Total spend for Period 1 |
| Prior Period | Total spend for Period 2 |
| vs Prior Period | Difference, with a percentage badge |
| Avg Order Value | Mean order value |
One row of four at 1200px and wider, two by two below that.
/api/overview accepts start_date and end_date for Period 1, and compare_start / compare_end
for Period 2. When the compare dates are omitted it computes them: a full calendar month in
Period 1 compares against the previous calendar month, anything else compares against the
immediately preceding window of equal length.
Spending Analytics¶
Six stat tiles and six Plotly charts for Period 1, all computed in the browser from the
/api/overview payload already on the page. No extra request, and the numbers agree with the tables.
| Tile | Meaning |
|---|---|
| Daily Average | Spend divided by days elapsed in the period, so a month in progress is not diluted |
| Median Purchase | Median of whole charges (split rows recombined by txn_id) |
| Largest Purchase | Biggest single charge, with vendor and date |
| Top Category / Top Vendor | Largest of each, with share of the period |
| Vendors | Distinct vendors, plus categories used |
| Chart | Content |
|---|---|
| Category Share | Donut of the seven largest categories plus "Everything else". Click a slice for its transactions |
| Category Change | Grouped bars, Period 1 vs Period 2, ten largest categories |
| Top Vendors | Ten largest vendors, labelled with share of the period |
| By Account | Donut by card or account |
| Daily Spend | One bar per calendar day, zero days included |
| Day of Week | Spend by weekday, Monday first |
Exclude Mortgage & Utilities removes FIXED_COST_CATS from every chart and tile in the panel
and from the YTD donut. One mortgage draft is most of a month and flattens everything else into a
sliver. The list is rendered into the page from categories.py, not copied into the template. The
choice persists in localStorage as heezyAnalyticsExclFixed. The tables are never filtered by it.
Colors are seven fixed categorical slots validated for colour-vision deficiency against the card surface. An eighth entity folds into the grey bucket rather than reusing slot one.
Category Table¶
Side-by-side per-category totals for both periods, not a grid of cards. Each period carries a
% Total column: the category's share of that period's header total, with a bar. Shares sum to
100 because /api/overview builds both from the same rows. Column groups are labelled
catP1Header and catP2Header. Two controls sit in the section header:
- Category filter — a multi-select dropdown, with an All toggle
- Hide zeros — drops categories with no activity in either period
Clicking a category opens the drilldown modal: per-vendor totals plus a monthly trend, from
GET /api/category-drilldown and GET /api/category-transactions/<category>.
YTD Spending by Category¶
Year-to-date totals per category with budget comparison, from GET /api/ytd-by-category. Shares the
budget data with the Budget page. % YTD is each category's share of all YTD spend.
At 1500px and wider the YTD Category Share donut sits beside the table and stays pinned while the table scrolls. Below that it stacks underneath.
Sparkline¶
12-month rolling spend trend, Plotly, from GET /api/overview.
Income → Spending (Sankey)¶
900px Plotly Sankey. See Sankey Architecture.
- Starts at gross pay for every deposit with a linked pay stub: taxes, benefits and retirement branch off before BofA Checking. Stubs come from the Paychecks page
- Gross Pay / Deposits Only and CC Detail / Combined CCs toggles, client-side, no extra API call
- Paystubs: X of Y payroll deposits above the chart links to the Paychecks page, amber when some deposits have no stub
- Powered by
GET /api/sankey?start_date=X&end_date=Y - The Sankey's date filter is month-granular while everything above is day-granular, so on a partial month the two will not agree
API Endpoints Used¶
| Endpoint | Purpose |
|---|---|
GET /api/available-periods |
Year/month options for both period pickers |
GET /api/latest-data-period |
Newest transaction month, drives the stale banner |
GET /api/overview |
Hero metrics, category totals for both periods, sparkline, Period 1 transactions for the analytics panel |
GET /api/ytd-by-category |
YTD table with budget comparison |
GET /api/sankey |
Sankey diagram |
GET /api/category-drilldown |
Per-vendor breakdown for a category |
GET /api/category-transactions/<category> |
Transaction list for the drilldown modal |
GET /api/receipt-detail/<id> |
Receipt detail modal |
POST /api/sync |
Trigger an on-demand order sync |
Reconciliation is its own page
This page carries no reconcile panel. Reconciliation has its own route and template — see Reconcile Tab.