Skip to content

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.