Live site (read-only catalog preview) → x7-u.github.io/EasyKPI
EasyKPI is a fully local, browser-based KPI analytics workbench. Clone the
repo, run EasyKPI.exe on Windows (or npm install && npm run dev on any OS),
and you get charts, forecasting, benchmarks, data ingest, goal-seek, anomaly
detection, AI narratives, dashboards, and Excel export — all running on your
own machine, with nothing leaving it unless you explicitly configure a cloud
connector.
The hosted link above is the original static catalog kept for quick browsing. Everything described below is what you get when you run the full workbench locally.
Every KPI in the catalog ships with:
Browse by any combination of:
No more “recommended chart type: Line Chart” with nothing drawn. Every KPI
detail page renders a live chart (ECharts) on your data — or a deterministic
mock series before you’ve uploaded anything. The single <KPIChart> adapter
handles every chart variant (line, bar, area) plus every overlay:
Set a target for any KPI and the app colours every instance of it green / amber / red based on:
Traffic-light badges show on every KPI card, catalog result, dashboard tile, comparison column, and KPI detail header.
Out of the box you get seeded p25 / p50 / p75 benchmarks for 15 of the most commonly used KPIs:
Revenue Growth, Net Profit Margin, Gross Profit Margin, Current Ratio, DSO, Conversion Rate, Win Rate, CAC, ROAS, CTR, NPS, CLV, Retention Rate, Churn Rate, OEE.
Chart bands and benchmark panels pick these up automatically. Custom workspace-scoped benchmarks live in the database and override the seeded values when present.
Every one of the 95 KPIs has a first-class calculator with three tabs:
%, $,
or a unit-less number) formatted to the KPI’s precision.period, remaining columns match the
formula inputs). The formula runs row-wise and the result is charted inline.
One click exports the computed series to Excel.Turn EasyKPI from a reference tool into a tool that answers your questions:
POST /sql/query) — runs Postgres / BigQuery /
Snowflake queries server-side so credentials never reach the browser.
Drivers are dynamic imports so they’re optional.The shared analytics package gives every chart a Phase-3 toolkit:
|z| > threshold are
flagged warn or critical and shaded on the chart + listed in an
“Anomalies” panel.apps/forecast is a FastAPI + statsmodels
service (with Prophet enabled when pip install prophet has run). The main
API proxies to it when PYTHON_FORECAST_URL is set and falls back to JS
Holt-Winters transparently.Explain button on every chart fires /narrate → Claude Sonnet 4.6
(with prompt caching on the system prompt and KPI context).Deep explain button escalates to Opus 4.7 for richer analysis, behind
a cost-confirmation modal.(kpiId, dataHash, model) so repeat views are
free.LlmSpend ledger table so every token is accounted for.ANTHROPIC_API_KEY the API returns a
deterministic stub narrative so the UI still works.period / value / target /
benchmark_* / forecast_* / anomaly columns, number-formatted per KPI
precision. Client-side (SheetJS), no server required.Summary — one row per tile with current / target / status with
conditional formatting (green / amber / red fill).Data_{kpiId} — full time series per tile, overlays included.Charts — slot for PNG charts (headless ECharts rendering comes next).Metadata — definitions, formulas, units, precisions, sources.{workspace}-{dashboard}-{YYYYMMDD}.xlsx.Pin up to 3 KPIs from anywhere in the catalog and the Compare page shows them in columns with:
Once you bring up the backend ( apps/api ) these unlock automatically:
local.CustomKPI table) that reference a formula
by key from the shared registry.GET /audit?entity=&entityId=.POST /permalinks returns a /s/:token URL
backed by an optional password, optional expiry, and a one-call revoke.POST /plugins/register accepts a Zod-validated manifest that can contribute:
Plugins are enabled per workspace; manifests are fetched and validated before activation.
A manifest ships out of the box so Chrome / Edge offer an Install option. Installed instances run in a standalone window — still 100 % web, no native shell.
easykpi/
├─ apps/
│ ├─ web/ React 19 + Vite + TypeScript + Tailwind 4 + ECharts + Zustand
│ ├─ api/ Fastify 5 + Prisma 5 (SQLite locally / Postgres in prod) + ExcelJS
│ └─ forecast/ FastAPI + statsmodels (+ optional Prophet) sidecar
├─ packages/
│ └─ shared/ Zod schemas, TS types, formula registry, seeded benchmarks,
│ driver DAGs, pure-JS Holt-Winters / anomalies / goal-seek,
│ golden-value tests for all 95 formulas
├─ build-launcher.ps1 Rebuilds EasyKPI.exe — installs, builds, starts api +
│ preview, opens the user's browser
└─ EasyKPI.exe Windows single-click launcher
The original repo was a single-page React demo: one 385-line App.jsx, inline
styles, a JSON of 95 KPIs, and a manual-input calculator that rendered no
charts. It has been rebuilt as the monorepo above while preserving every
original formula byte-for-byte (95 golden-value tests pin the behavior). The
Windows .exe single-click launch experience is kept, and a GitHub Pages
deployment of the original static catalog remains linked at the top of this
README for quick reference.
EasyKPI.exe. On first launch it runs
npm install --legacy-peer-deps, builds the web bundle, initialises the
SQLite database, starts the Fastify API on :8787 and vite preview on
:5173, and opens your default browser. Subsequent launches skip anything
already done.# Install dependencies
npm install --legacy-peer-deps
# Initialise the local SQLite database + seed the "local" workspace
cd apps/api && DATABASE_URL="file:./dev.db" npx prisma db push --skip-generate
DATABASE_URL="file:./dev.db" npx tsx prisma/seed.ts
cd ../..
# Start the API (terminal 1)
cd apps/api && DATABASE_URL="file:./dev.db" PORT=8787 npm run dev
# Start the web app (terminal 2)
cd apps/web && npm run dev
# (optional) Start the Python forecast sidecar (terminal 3)
cd apps/forecast
pip install -r requirements.txt
uvicorn main:app --port 8788
Open http://localhost:5173 — the web app proxies /api/* to
http://localhost:8787.
apps/api/.env)DATABASE_URL="file:./dev.db" # or postgres://... for production
PORT=8787
ANTHROPIC_API_KEY=sk-ant-... # optional — enables real AI narratives
CLERK_SECRET_KEY=... # optional — enables multi-user workspaces
CLERK_PUBLISHABLE_KEY=... # optional
PYTHON_FORECAST_URL=http://localhost:8788 # optional — Prophet sidecar
LLM_DAILY_CAP_USD=5 # per-workspace daily spend cap
Without any of the optional variables the app still fully works — narratives return a deterministic stub, auth is single-user / local, and forecasts use the pure-JS Holt-Winters implementation.
All routes are workspace-scoped via the x-workspace-id header (drops into
Clerk’s organisation model cleanly).
| Endpoint | Purpose |
|---|---|
GET /health |
Liveness probe |
GET /catalog/kpis, GET /catalog/kpis/:id, GET /catalog/count, GET /catalog/custom |
Catalog + per-workspace custom KPIs |
POST /sql/query |
Server-side Postgres / BigQuery / Snowflake queries |
POST /forecast |
Holt-Winters JS; proxies to Prophet sidecar when configured |
POST /forecast/anomalies |
Z-score anomaly list |
POST /forecast/goal-seek |
Formula inversion by bisection |
POST /narrate |
Claude Sonnet / Opus narratives with caching + spend cap + stub fallback |
POST /semantic/search |
Ranked KPI list for a natural-language question |
POST /export/dashboard |
Multi-sheet dashboard workbook via ExcelJS |
GET/POST/DELETE /dashboards[/:id] |
Dashboard CRUD (auto-writes audit events) |
GET /audit |
Filterable audit log |
GET/POST /comments |
Comments on KPIs and dashboard tiles with mentions |
POST /permalinks, POST /permalinks/:token/revoke, GET /s/:token |
Shareable read-only links |
GET/POST/DELETE /digests, POST /digests/:id/run |
Scheduled digest config + manual run |
GET/POST/DELETE /plugins[/:id] |
Plugin registry |
# Golden-value tests for all 95 formulas (the refactor safety net)
cd packages/shared && npx vitest run
# Strict typecheck across every workspace
cd packages/shared && npx tsc --noEmit
cd apps/web && npx tsc --noEmit
cd apps/api && npx tsc --noEmit
# Production web build
cd apps/web && npx vite build
# API smoke (with api running on :8787)
curl -s http://localhost:8787/health
curl -s http://localhost:8787/catalog/count
curl -s -X POST -H "Content-Type: application/json" \
-d '{"query":"are we losing customers","topK":5}' \
http://localhost:8787/semantic/search
index.ts so runtime
behaviour is identical. Golden tests pin that behaviour.useCatalogStore, useCompareStore, useTargetStore, useDatasetStore,
useDashboardStore. Persistence is transparent via
zustand/middleware/persist.<KPIChart> is the single ECharts wrapper used
everywhere; overlay props (bands / target / threshold / forecast /
anomalies) exist from day one so later phases didn’t require rewrites.citext, no Postgres arrays — the same migrations run under
the .exe launcher (SQLite) and in production (Postgres)./narrate call writes a
token-accounted LlmSpend row; the daily cap is enforced before each call..exe launcher runs vite preview over a built bundle, not the
dev server — avoids the PWA-vs-dev-server cache conflict.apps/api/src/lib/auth.ts (the seam already exists).@xenova/transformers embeddings in a
web worker — zero API cost, response shape unchanged.Charts sheet PNGs in the server-side
Excel export.better-queue for local-only) for scheduled
digests.packages/shared/src/drivers/index.ts.