# LAV Pulse — multi-site analytics hub

A self-hosted reporting dashboard that pulls live data from **all your GA4 properties** — even when they sit under different Google accounts — and gives you:

- **Portfolio overview** — combined traffic across every site: totals with vs-previous-period deltas, a combined trend chart, portfolio-wide traffic channels, and a sortable per-site table with sparklines.
- **Per-site drill-downs** — daily sessions trend, top pages, channels, devices, countries, conversions, engagement.
- **AI analyst (OpenAI)** — a chat panel that answers any question about your analytics by querying GA4 live through function calling: "which site grew most this month?", "write a monthly report for all sites", "why did Acme's traffic dip last week?".
- **Demo mode** — realistic generated data so you can run and explore everything before connecting a single property.

**Zero dependencies.** No `npm install`, no build step — it runs on plain Node 18+.

## Quick start (demo mode)

```bash
./setup.sh    # one-time check: Node present, .env created
./run.sh      # starts on http://localhost:5001 and opens your browser
```

Override the port with `PORT=3000 ./run.sh`. (Or run manually: `cp .env.example .env && node server/index.js`.)

## Connect your real GA4 properties

Follow **SETUP-GA4.md** — it's a one-time, ~10 minute setup:

1. Create a Google Cloud service account and enable the GA4 Data API.
2. Add the service account's email as **Viewer** on each GA4 property (works across different Google accounts — you just need admin access to each property).
3. List your sites and their numeric property IDs in `sites.config.json`.
4. In `.env`: set `DEMO_MODE=0` and point `GOOGLE_APPLICATION_CREDENTIALS` at your key file.

## Enable the AI analyst

1. Create an API key at https://platform.openai.com → API keys.
2. In `.env`, set `OPENAI_API_KEY=sk-...`.
3. Optionally change `OPENAI_MODEL` (default `gpt-5.6-terra`; `gpt-5.6-luna` is cheaper, `gpt-5.6-sol` is strongest). Any OpenAI model with tool calling works.

The assistant only ever **reads** analytics data. Server-side, it's a function-calling loop: the model requests specific GA4 queries (totals, timeseries, breakdowns, site comparisons), the server executes them, and the model writes its answer from the real numbers.

## Deploying

Any Node 18+ host works — a £5 VPS, Railway, Render, Fly.io:

```bash
node server/index.js                  # or: pm2 start server/index.js --name lav-pulse
```

Or with Docker: `docker build -t lav-pulse . && docker run -p 3000:3000 --env-file .env lav-pulse`

Since this contains client data, set `DASHBOARD_PASSWORD` in `.env` before putting it on the public internet (simple shared-password gate), and serve it behind HTTPS (e.g. Caddy or nginx).

## Project layout

```
server/index.js      HTTP server + API routes (no framework)
server/ga4.js        Data layer: per-site + portfolio queries, caching, date ranges
server/ga4-rest.js   GA4 Data API over REST with service-account JWT auth
server/demo-data.js  Deterministic sample-data generator (demo mode)
server/chat.js       OpenAI function-calling loop (the AI analyst)
public/              Frontend: vanilla JS SPA, hand-rolled SVG charts, light/dark
sites.config.json    Your list of websites + GA4 property IDs
```

## Notes on quotas

GA4 Data API quotas are **per property** (Standard: 25k tokens/day each), so 16+ sites each get their own allowance. Responses are cached for 10 minutes server-side, so dashboard refreshes and chat questions stay well within limits. If you later want unlimited history and heavier cross-site analysis, the natural upgrade is GA4's native BigQuery export — the data layer here is isolated in `server/ga4.js`, so it can be swapped without touching the UI or chat.

## Ideas for phase 2

Scheduled weekly/monthly PDF reports emailed per client; client-facing read-only report links; Search Console integration; anomaly alerts ("traffic on X dropped 30% yesterday"); BigQuery warehouse mode.
