Skip to content

Akv2021/Cashew-Reports

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Cashew-Reports

A Google Apps Script dashboard that reads your Cashew CSV export from Google Drive and serves a dark-themed financial report with 18 widgets, interactive charts, and PDF export. No hosting cost, no external services -- runs entirely on Google's infrastructure.

The dashboard is live: it re-reads your CSV and re-computes everything on every page load. There is no deploy step when your data changes -- you visit the URL and see the latest numbers.

Dashboard showing Monthly Summary KPIs and full category breakdown

How It Works

  1. Cashew exports transactions to CSV. The Outbox auto-syncs outbox.csv to a /Cashew/ folder on your Google Drive.
  2. You deploy two files to Google Apps Script. Code.gs (server-side metrics + widget renderers) and Dashboard.html (CSS shell + Chart.js).
  3. Visit the web app URL. GAS reads the CSV, computes all metrics, renders 18 widgets server-side, and serves the complete HTML page. Takes 2-5 seconds.
  4. Navigate and explore. Use the month picker to move between months. Click doughnut segments to drill down. Expand tables to see all subcategories. Hit "Save as PDF" to archive a snapshot to Drive.
flowchart LR
    subgraph Cashew["Cashew App"]
        EXP["Export Outbox"]
    end

    subgraph Drive["Google Drive"]
        CSV["/Cashew/outbox.csv"]
    end

    subgraph GAS["GAS Web App"]
        READ["readCSVFromDrive_()"]
        PARSE["parseCSV_()"]
        COMPUTE["computeMetrics_()"]
        RENDER["WIDGET_RENDERERS\n(18 functions)"]
        SHELL["Dashboard.html\n(3-line for-loop)"]
    end

    BROWSER["Your browser / phone"]

    EXP --> CSV
    CSV --> READ --> PARSE --> COMPUTE --> RENDER --> SHELL --> BROWSER
Loading

Request lifecycle

Every page load triggers the full pipeline. There is no caching or background job -- the dashboard is stateless and always fresh.

sequenceDiagram
    participant U as Browser
    participant G as doGet()
    participant D as Google Drive
    participant R as WIDGET_RENDERERS
    participant T as Dashboard.html

    U->>G: GET ?month=2026-03
    G->>D: readCSVFromDrive_()
    D-->>G: CSV content
    G->>G: parseCSV_() → filterActual_() / filterUpcoming_()
    G->>G: computeMetrics_()
    loop each visible widget
        G->>R: renderer(metrics, widgetConfig)
        R-->>G: HTML string
    end
    G->>T: template.renderedWidgets = [...]
    T->>T: for-loop injects widget HTML
    T-->>U: Complete page with CSS + Chart.js
Loading

Prerequisites

  • Cashew app with the Outbox export enabled (syncs to Google Drive)
  • A Google account (Drive + Apps Script)
  • Categories and subcategories configured in Cashew -- see Category Taxonomy for the expected structure

Quick Start

  1. Upload your Cashew outbox.csv to a folder named Cashew on Google Drive (or use the included sample_outbox.csv for testing)
  2. Go to script.google.com → New project → name it "Cashew Reports"
  3. Replace the default Code.gs content with the Code.gs from this repo
  4. Create a new HTML file named Dashboard → paste the Dashboard.html content
  5. Edit the CONFIG section in Code.gs to match your Cashew categories (see Category Taxonomy)
  6. Deploy → New deployment → Web app → "Only myself" → Deploy → bookmark the URL

Full step-by-step with troubleshooting: Setup Guide

Dashboard Sections

The dashboard has 6 sections containing 18 widgets. Each section answers a different question about your finances.

Full dashboard with all 6 sections and 18 widgets

Section Widgets What it answers
Monthly Summary burn_rate, money_received, savings_rate, data_quality How did this month go?
Where Did It Go cat_breakdown, disc_split, lifestyle_tax Where is the money going?
Spending Patterns utility_bills, grocery_trend Are recurring expenses on track?
Trends burn_trend, biggest_increases, top_subcats Are things getting better or worse?
Income & Savings investment, rewards_refunds, rent_trend What came in?
Structural Costs household_cost, cab_transit, people_payments What are the non-negotiable baselines?

Widget Renderer Pattern

The dashboard is built on a pattern that makes it easy to add, remove, or modify widgets without touching the HTML template.

The contract: every widget is a function in the WIDGET_RENDERERS object. It receives the full metrics object and its own config entry, and returns an HTML string.

WIDGET_RENDERERS.my_widget = function(metrics, w) {
  var d = metrics.myWidgetData;
  return `<div class="panel ws-${w.size}" data-widget="my_widget">
    <div class="panel-title">My Widget</div>
    <div class="kpi-value">${fmt_(d.value)}</div>
  </div>`;
};

The shell: Dashboard.html has zero knowledge of individual widgets. The entire widget injection is a 3-line loop:

<div class="widget-grid">
<? for (var i = 0; i < renderedWidgets.length; i++) { ?>
  <?!= renderedWidgets[i].html ?>
<? } ?>
</div>

Chart data bridge: since GAS serves pre-rendered HTML, chart data passes from server to client via <script type="text/json" id="..."> blocks embedded in widget HTML. Client-side IIFEs read them with getElementById + JSON.parse. Each IIFE is guarded (if (!el) return) so it silently skips when the widget is hidden.

flowchart TD
    A["New widget idea"] --> B{"Needs a Chart.js canvas?"}
    B -->|No| C{"Needs click interactions?"}
    B -->|Yes| D["<b>Chart.js widget</b>\nCode.gs: 3 changes\nDashboard.html: 1 IIFE"]
    C -->|No| E["<b>Static widget</b>\nCode.gs: 3 changes\nDashboard.html: 0 changes"]
    C -->|Yes| F{"Reuses existing toggle?"}
    F -->|Yes| E
    F -->|No| G["<b>Interactive widget</b>\nCode.gs: 3 changes\nDashboard.html: 1 JS function"]

    style E fill:#1a3a2a,stroke:#34d399,color:#e1e4ed
    style D fill:#1a2a3a,stroke:#60a5fa,color:#e1e4ed
    style G fill:#2a2a1a,stroke:#fbbf24,color:#e1e4ed
Loading

Widget config is declared in CONFIG.WIDGETS -- an array that controls render order, visibility, and sizing:

{ id: "section_title", visible: true, title: "Monthly Summary — {month}" },
{ id: "burn_rate",     visible: true, size: "small" },

Reorder = move entries in the array. Hide = set visible: false. Resize = change size ("small" 3-col, "third" 4-col, "medium" 6-col, "large" 12-col).

Full details: Widget Guide -- config reference, chart data bridge examples, step-by-step instructions for adding each widget type.

Included Widgets

Universal Widgets -- work with any categories

These 7 widgets iterate dynamically over whatever categories and subcategories exist in your CSV. They auto-discover your data and produce useful output regardless of how you have set up your Cashew categories.

Widget Type What it shows
burn_rate KPI card Total monthly expenses (excl. configurable exclusion list) with MoM % delta and top 3 subcategories
money_received KPI card Total income with MoM % delta and per-category breakdown
savings_rate KPI card (Core Income - Burn) / Core Income, trailing 3 months, with pp delta vs previous quarter
cat_breakdown Full table Every expense category with colored bar, amount, and % of burn -- sorted by amount
burn_trend Line/area chart 6-month burn rate trend with red highlight when current month is near the max
top_subcats Expandable table Top 5 subcategories by rolling 12-month spend with QoQ trend arrows (expandable to all)
biggest_increases Expandable table Subcategories with the largest quarter-over-quarter spend increases

These widgets only reference configurable exclusion lists (BURN_RATE_EXCLUDED, CORE_INCOME_CATEGORIES) -- not hardcoded category names. They will work with any Cashew export.

Pre-Built Widgets -- configured for our category taxonomy

These 11 widgets are built for a specific category structure. They ship pre-configured and work out of the box if you use the same categories. If your categories differ, you have three options:

  1. Adopt this taxonomy in Cashew (recommended if starting fresh)
  2. Edit the CONFIG lists in Code.gs to match your own categories
  3. Set visible: false to hide widgets that don't apply to your setup
Widget Type What it shows CONFIG dependency
disc_split Doughnut + drill-down Non-Disc / Semi / Discretionary split with per-category breakdown DISC_CATEGORIES, NON_DISC_CATEGORIES, SEMI_DISC_CATEGORIES
lifestyle_tax KPI card Eating Out + Personal Purchases as % of burn Hardcoded: Food > Eating Out, Personal Purchases
data_quality Badge panel Uncategorized + Untraced as % of burn (target: under 2%) Hardcoded: Misc Expense > Uncategorized/Untraced Expense
utility_bills Checklist Paid / upcoming / missing for each expected monthly bill EXPECTED_UTILITY_BILLS
grocery_trend Bar chart 6-month grocery spend with average and deviation highlight Hardcoded: Home Essentials > Groceries
investment Card + breakdown Monthly investment total with subcategory breakdown INVESTMENT_SUBCATEGORIES
rewards_refunds Card + breakdown Cashback + refunds recovered as % of expenses REWARDS_SUBCATEGORIES
household_cost Card + breakdown 12-month running cost of essentials HOUSEHOLD_RUNNING_CATEGORIES
cab_transit Doughnut + insight Cab vs Metro + Public Transport with savings estimate Hardcoded: Transit > Cab / Metro / Public Transport
people_payments Card + breakdown Household Staff + Family Transfer + Gifts + Donations PEOPLE_PAYMENT_FILTERS
rent_trend Dual-line chart 6-month rent income trend split by property Hardcoded: Passive Income > Rent

Full calculation logic for all 18 widgets: Widget Catalog

Category taxonomy and CONFIG reference: Category Taxonomy

Customization

Common tasks at a glance:

What you want to do Where
Hide a widget Set visible: false in CONFIG.WIDGETS
Reorder or resize widgets Move entries in CONFIG.WIDGETS, change size field
Change category classifications Edit CONFIG arrays in Code.gs (DISC_CATEGORIES, etc.)
Change expected utility bills Edit EXPECTED_UTILITY_BILLS in CONFIG
Add a new widget See the Widget Guide
Change currency symbol Edit fmt_() and fK_() in Code.gs

Full customization guide with code examples: Customization Guide

Project Structure

File Purpose
Code.gs Server-side GAS script: CONFIG, CSV reader/parser, metric computation (15 functions), rendering helpers, 18 widget renderers (~1300 lines)
Dashboard.html Client-side shell: CSS (dark theme, 12-col grid, responsive), 3-line widget loop, Chart.js inits (5 charts), interaction JS (~430 lines)
WIDGET_CATALOG.md All 18 widgets with calculation logic, formulas, and data model notes
sample_outbox.csv Test dataset (26 rows, single month) for verifying the setup
resources/SETUP_GUIDE.md Step-by-step deployment with troubleshooting
resources/WIDGET_GUIDE.md Widget renderer pattern deep-dive, config reference, adding new widgets
resources/CATEGORY_TAXONOMY.md Full category/subcategory tree and CONFIG reference table
resources/CUSTOMIZATION.md All customization tasks with code examples

Relationship to Mail2Cashew-TPS

This dashboard is the reporting companion to Mail2Cashew-TPS, which automates transaction entry into Cashew from bank emails and SMS.

flowchart LR
    subgraph INPUT["Transaction Entry"]
        TPS["Mail2Cashew-TPS\n(emails/SMS → Cashew)"]
    end

    subgraph CASHEW["Cashew App"]
        APP["Budget Tracker\n(categories, accounts)"]
    end

    subgraph REPORTING["Reporting"]
        DASH["Cashew-Reports\n(CSV → dashboard)"]
    end

    TPS -->|"auto-categorized\ntransactions"| APP
    APP -->|"Outbox CSV\nexport"| DASH
Loading

You do not need TPS to use this dashboard -- any Cashew CSV export works. But if you use both, your TPS categorySubcategoryKeywordMap and this dashboard's CONFIG should use the same category and subcategory names so that transactions categorized by TPS render correctly in the dashboard widgets.

About

Google Apps Script dashboard for Cashew - reads your Outbox CSV from Drive, computes multiple financial widgets (burn rate, savings, trends, category breakdowns), and serves a dark-themed HTML report. Zero hosting cost.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors