# FanChants KPI Dashboard

A self-refreshing dashboard for every FanChants business metric tracked in this repo — streaming revenue, catalogue health, the Spotify playlist programme, GA4 web traffic, social reach, finance, B2B licensing pipeline, and operational state.

**Two ways to run it:**

1. **Localhost (Mac mini)** — `python3 dashboard/serve.py` → reads files off disk in real time. Best for active work.
2. **Online (Cloudflare Pages, password-gated)** — `python3 dashboard/build.py && wrangler pages deploy site` → both Mikey and Giles can hit `dashboard.fanchants.com` from anywhere. Full step-by-step in [`docs/operations/dashboard-deployment.md`](../docs/operations/dashboard-deployment.md).

---

## Launch (localhost mode)

From a terminal in the repo root (one command, copy-paste):

```bash
python3 dashboard/serve.py
```

Then open **http://localhost:8765/dashboard/index.html** in any modern browser.

If port 8765 is busy, pass a different port:

```bash
python3 dashboard/serve.py 9000
```

To stop the server, press **Ctrl-C** in the terminal.

That's it. The dashboard reads every data file directly off disk on each refresh — no rebuild step, no database, no caching layer.

---

## What it shows

Ten sections, top to bottom. Click any tile or table header to drill in.

- **Overview** — eight headline KPI cards.
- **A · Revenue & streams** — monthly trend, platform pie + stacked bar, top albums and tracks, top countries, Premium vs. ad-supported estimate.
- **B · Catalogue health** — chants by country / league / type, copyright breakdown, catalogue growth, short-tracks risk (Green/Yellow/Amber/Red tiered by annualised earnings), top Spotify artists, artist merge candidates.
- **C · Spotify playlists** — WC2026 publish progress vs. Q2 target line, daily publish bar, the @FanChants public-playlist audit, the full WC2026 spec table (54 playlists).
- **D · Web analytics (GA4)** — sessions over time, landing pages, country / source / device splits. Degrades gracefully if GA isn't configured (see **Setup — Google Analytics** below).
- **E · Operational pipelines** — last-run + health for every cron, the KPI workbook's Data Quality notes, the most recent league-update changes, the decisions log.
- **F · Extras** — Routenote Premium upgrade ROI, tournament uplift, fan-photo + video pipeline, original-tunes enrichment progress, leagues with the most chants.
- **G · Social reach** — YouTube subs/views auto-pulled at build time + per-platform tiles fed from `data/social/social-metrics.csv` (monthly manual entry for X, TikTok, Instagram, etc.).
- **H · Finance** — Monthly GBP revenue by channel (streaming / licensing / TikTok / other), expenses, net. Reads `data/finance/financial-summary.csv` (gitignored — see schema in `data/finance/README.md`).
- **I · B2B licensing pipeline** — Open inquiries, pipeline value, win rate, by-status + by-usage charts. Reads `data/licensing/inquiries.csv` (gitignored).

---

## Data sources

Every number on screen comes from a canonical file in this repo. The full inventory is in [`dashboard/discovery-report.md`](./discovery-report.md). The dashboard reads each file fresh on every page-load and on every Refresh click, with cache-busting query strings so the browser never serves a stale copy.

Headline sources:

- `FanChants_KPI_Master.xlsx` (Monthly Summary, Albums Master, Per-Album 12mo, Store Mix, Country Mix, Top Tracks, Premium Upgrade Analysis, Tournament Adjustments, Data Quality)
- `scripts/spotify-urls/all_albums_with_urls.xlsx` + `all_artists.xlsx`
- `scripts/enrichment/chants_by_originaltune_snapshot.csv` (22,649-row catalogue snapshot)
- `scripts/enrichment/Original_Tunes_Enriched.csv`
- `scripts/pictures/workspace/catalog.csv`
- `data/playlists/wc2026/playlist_specs.json`, `reports/wc2026/build_log.jsonl`, `data/playlists/wc2026/audit/official_playlists_audit.json`
- `reports/league-map.json`, `reports/2026-04/changes.csv`
- `docs/decisions/*.md`
- `data/social/social-metrics.csv` (monthly manual + YouTube auto)
- `data/finance/financial-summary.csv` (gitignored)
- `data/licensing/inquiries.csv` (gitignored)
- Build-time snapshots (deployed mode only): `dashboard/ga-snapshot.json`, `dashboard/youtube-snapshot.json`, `dashboard/build-meta.json`

---

## Setup — Google Analytics (Section D)

Section D shows GA4 sessions, landing pages, geography, sources, and devices. It will display a "Configure GA" panel until the steps below are completed. **This setup is one-time per machine.**

### 1. Create a Google Cloud service account

a. Go to <https://console.cloud.google.com/>. Pick (or create) a project.
b. Open **APIs & Services → Library**, search for **Google Analytics Data API**, click **Enable**.
c. Open **APIs & Services → Credentials → Create Credentials → Service account**.
d. Name it something obvious like `fanchants-dashboard-reader`. Click **Create and continue → Done** — no roles required at the project level.
e. On the credentials page, click into the new service account, open the **Keys** tab, **Add key → Create new key → JSON**, and save the downloaded file.

### 2. Save the JSON key locally

```bash
mkdir -p ~/.fanchants
mv ~/Downloads/<the-downloaded-file>.json ~/.fanchants/ga-credentials.json
chmod 600 ~/.fanchants/ga-credentials.json
```

The file path the dashboard looks for is exactly `~/.fanchants/ga-credentials.json`.

### 3. Grant the service account read access on the GA4 property

a. Open <https://analytics.google.com/>.
b. **Admin → Property Access Management**.
c. Click the **+** in the top right → **Add users**.
d. Paste the service account email (looks like `fanchants-dashboard-reader@<project>.iam.gserviceaccount.com`).
e. Role: **Viewer**. Uncheck "Notify by email." Click **Add**.

### 4. Find the GA4 property ID

In GA4 → **Admin → Property settings**. Copy the numeric **Property ID** (it is NOT the `G-XXXXXX` measurement ID). It looks like `123456789`.

### 5. Configure the dashboard

Open `dashboard/config.js` and paste the property ID into `ga.property_id`:

```js
ga: {
  property_id: "123456789",
},
```

### 6. Install the Python client

```bash
pip install google-analytics-data --break-system-packages
```

### 7. Refresh

Reload the dashboard. Section D should populate within a few seconds. If it's still degraded, check `dashboard/serve.py`'s terminal output for the error message and follow Troubleshooting below.

GA responses are cached server-side for one hour. To force-refresh, restart `serve.py`.

---

## How to add a new KPI

Roughly four moves, all in `dashboard/`:

1. **Locate the source.** If the metric already exists in a file under the repo, you're set. Otherwise add the file to its pipeline directory (`scripts/<pipeline>/...`) and document it in `docs/INDEX.md`.
2. **Register the source.** In `dashboard/config.js`, add a path under `sources`. The dashboard reads each entry as a relative URL from the repo root.
3. **Add a loader call.** In `index.html`, search for `// LOAD pipeline`. Add your file to the `Promise.all([...])` block, then stash the parsed data on `state`.
4. **Add a chart or table.**
   - For a chart, add a `<canvas id="chart-…">` in the right section and call `makeChart("chart-…", { type, data, options })` from the matching `render…()` function. Chart.js types are `line | bar | doughnut | pie | radar`.
   - For a table, add a `<table class="fc-table" id="tbl-…"><thead></thead><tbody></tbody></table>` and call `renderTable($("#tbl-…"), [...columns], rows)`. Columns are `{ key, label, render? }`. Headers are clickable for sort.
   - For a KPI tile, push another `kpiTile({ label, value, hint, accent, source })` into a card grid.

After editing, just reload the page. No build step.

---

## Troubleshooting

### "Address already in use" / "Port 8765 is already in use"

Either you have another `serve.py` still running (find it with `lsof -i :8765` and `kill <pid>`), or launch on a different port: `python3 dashboard/serve.py 9000`.

### "Refreshed … load failed — see console"

Open the browser dev tools, switch to the **Console** tab, and look at the error. Common causes:

- A canonical data file was renamed or moved → update `dashboard/config.js`'s `sources` block.
- A CSV has an unexpected header → check the change in the source pipeline.
- The XLSX file is open in Excel/Numbers and locked → close it.

### Charts blank, no errors

Make sure you opened the dashboard via the server (`http://localhost:8765/dashboard/…`), not by double-clicking the HTML. Browsers block `fetch()` on `file://` URLs.

### Section D stays in degraded mode

In order of likelihood:

1. `~/.fanchants/ga-credentials.json` doesn't exist. `ls -la ~/.fanchants/`.
2. `ga.property_id` in `dashboard/config.js` is empty.
3. The service account isn't a Viewer on the property — go back to step 3 of GA setup.
4. `google-analytics-data` isn't installed: `pip install google-analytics-data --break-system-packages`.
5. The Google Analytics Data API isn't enabled on the GCP project — back to step 1c.

The `serve.py` console will show the precise error from the Google API client.

### One pipeline's tile says "not installed" / health = warn

That's expected — those rows reflect actual pipeline state. The video-vision-tagging cron, for instance, is proposed but not yet installed (see `CLAUDE.md`).

### The KPI workbook "built at" date is older than expected

The KPI workbook is rebuilt on the 25th of each month by the `monthly-routenote-update` Cowork task. If a build was missed, see `docs/operations/routenote-monthly.md` for the manual rerun.

### Mobile view looks cramped

The dashboard is responsive; the grid collapses to two columns under 640 px. If a chart's tooltip is hard to hit on touch, hold-and-drag along the X-axis instead of tapping.

### Charts disappear when I toggle theme

The theme toggle destroys + rebuilds all charts so colours match the new mode. If a chart goes blank, hit Refresh.

---

## File layout

```
dashboard/
├── index.html              The dashboard itself — one HTML file, one IIFE.
├── serve.py                Localhost HTTP server + GA proxy. Run from repo root.
├── build.py                Bakes a static `site/` bundle for Cloudflare Pages.
│                           Fetches GA + YouTube at build time so creds stay
│                           on the Mac mini.
├── rebuild_server.py       Optional. Runs on Mac mini behind Cloudflare Tunnel
│                           so the deployed dashboard's "Rebuild" button can
│                           trigger a fresh build + deploy.
├── config.js               Data-source paths, GA property ID, trademark-safe map,
│                           short-tracks thresholds, playlist targets, rebuild
│                           webhook URL + shared secret. Edit this, not the HTML.
├── README.md               You are here.
└── discovery-report.md     The Phase-1 inventory of every data source.

site/                       Generated by build.py. Deploy with:
                            wrangler pages deploy site --project-name=fanchants-dashboard
                            (gitignored)

data/social/                Monthly manual entry (+ YouTube auto-overlay).
data/finance/               Monthly manual entry. Gitignored.
data/licensing/             Per-inquiry log. Gitignored.
docs/operations/dashboard-deployment.md   Full Cloudflare Pages + Access walkthrough.
```

---

## What the dashboard does NOT do

- Write to the KPI workbook (read-only by design).
- Hit any external API from the deployed bundle — GA + YouTube are baked in at build time on the Mac mini.
- Cache data client-side between sessions (every load is fresh).
- Edit data inline — finance + licensing CSVs are edited on the Mac mini and the dashboard re-reads them on the next build.
- Authenticate users itself — the deployed dashboard relies on Cloudflare Access for the email-gated password layer. Localhost stays open on `127.0.0.1`.

---

*Built May 2026. The standard is "holy shit, that's done."*
