Expense CSV Dashboard Without a Heavy Framework

expense-csv-dashboard-without-a-heavy-framework

Expense CSV Dashboard Without a Heavy Framework. A Build Next Stack field guide for learners shipping small projects.

Scope: one CSV in, one HTML page out

You will ingest a monthly bank export CSV and produce a single self-contained HTML file with three views: total spend by category, top ten merchants, and month-over-month bar comparison. No React, no database, no login. Python or Node reads CSV, aggregates in memory, embeds JSON into a HTML template with vanilla JS charts—Chart.js from CDN is allowed, one script tag only.

The stack lesson is data cleaning before visualization. Most beginners draw charts on dirty strings and wonder why “food” and “Food” split bars. Your milestone order: validate columns, normalize categories, then draw. A dashboard that only works on a sanitized sample file teaches less than one that survives your actual bank export with weird encodings.

Name the project after the workflow, not the chart library. may-spend-report reminds you to rerun monthly; chart-playground invites endless styling. Boring repo names correlate with finished utilities.

Milestone one: parse and validate columns

Export one real month from your bank—redact account numbers before committing. Inspect headers: date, description, amount, maybe category. Write a schema check that fails fast:

  • Required columns present (map aliases: Transaction Datedate).
  • Dates parse to ISO; reject rows that fail with row number in stderr.
  • Amounts are numbers; handle parentheses negatives if your bank uses them.
  • Duplicate row hash optional but useful for re-import safety.

Output an intermediate clean.json array of objects. Human-readable JSON makes debugging faster than re-running CSV parse. Acceptance: script exits 0 on your export, prints row count and sum that matches the bank’s PDF statement within a dollar—rounding teaches you where floats lie.

Log skipped rows to warnings.txt with line numbers. When totals disagree with the bank app, that file is the first place to look. Silent drops are how dashboards lie politely.

Milestone two: charts without a component framework

Template HTML with three <canvas> elements and a <script> block that reads embedded window.DATA. Build aggregates in Python/Node, not in the browser—client only renders. Category pie uses ten slices max; roll the rest into Other. Merchant bar chart: top ten by absolute spend. Month-over-month needs at least two months of CSVs merged before build.

Keep colors accessible: tabulate ten distinct hues, label slices with percentage and amount. Tooltips show raw numbers; legends do not require hover to understand totals. Mobile: charts stack vertically; no side-by-side squeeze below 600px width.

Embed data as JSON in a <script type=”application/json” id=”data”> tag and parse in JS—cleaner than string interpolation into JS literals. Invalid JSON from unescaped quotes in merchant names is a classic trap; sanitize or use JSON.dumps from your builder language.

Acceptance: open HTML offline in Chrome and Firefox; numbers match clean.json sums when you spot-check one category with a calculator.

Acceptance checks for real bank exports

Run against exports from two different months and two formats if you switch banks:

  1. Encoding: UTF-8 with BOM does not break headers.
  2. Empty category column falls back to rule-based guess or Uncategorized.
  3. Transfers between your own accounts excluded via a config list of description patterns.
  4. Rebuild is deterministic: same CSV → same HTML hash.

Store a category_rules.yaml mapping substring → category (AMAZONShopping). Edit rules without touching code. That separation is how analysts ship; copy the pattern even on a toy dashboard.

Sanity charts for you, not investors

Skip animation, gradients, and dual y-axes. Your eye needs to spot spikes in groceries, not admire design. One annotation per chart highlighting the largest change week-over-week is enough narrative.

If a category dominates (>40% of spend), split it in rules before charting—Groceries vs Restaurants under food, for example. Aggregation teaches domain modeling; pretty slices teach presentation only.

Export the HTML to email yourself monthly. Opening on phone reveals layout bugs desktop hides. Responsive table fallback—stacked rows instead of canvas—is acceptable when charts fail on small screens.

Traps when dashboards become product fantasies

Trap: live bank API on day one. CSV monthly is enough to learn aggregation. Plaid integration is a different project with compliance rabbit holes.

Trap: bi-directional editing in HTML. View-only v1. If you want budget targets, add a second CSV for plans—do not mutate transaction source.

Trap: framework for three charts. Each npm dependency is a future upgrade chore. Vanilla plus one chart library keeps the repo understandable in a year.

Trap: hiding dirty data with log scale. Fix categories instead of smoothing charts. A pretty lie beats an ugly truth only in pitch decks—not in learning projects.

Refresh workflow you will run monthly

Script entry point: build_dashboard.py –csv exports/2026-05.csv exports/2026-04.csv -o report.html. Document in README: drop exports in folder, run command, open report. Optional Makefile target make report.

Version control: commit category rules and template; gitignore raw CSV if it contains sensitive memos. Commit sanitized sample CSV under samples/ for tests. One pytest asserts known totals on sample—guards against aggregate regressions when you tweak charts.

Schedule a calendar reminder first of month: download CSV, run build, skim anomalies (spike in Uncategorized). The habit matters more than animation polish. Dashboard you refresh beats dashboard you demo once.

When Uncategorized exceeds fifteen percent of spend, spend thirty minutes on rules—not on chart colors. Category hygiene is the skill employers care about in data roles; pie chart palettes are not.

Build with last month’s export tonight

Redact one real CSV, write the validator, and generate a HTML file you open locally. Compare total spend to your banking app; fix parser before adding a second chart. The skill is trusting your numbers, not picking gradient colors.

When categories stabilize after two months, consider sharing the template with a friend—same script, their rules file. Collaboration without multi-user auth is a good v2 design exercise.

Keep the repo small enough to explain in five minutes. That constraint forces good data hygiene, which transfers directly to larger analytics stacks later.