Live at statskmaster.com
Full-stack statistics app covering all 20 series of Taskmaster UK: 994 tasks, 182 episodes, 100 contestants.
| Layer | Tech |
|---|---|
| Backend | Express + better-sqlite3 (synchronous, <5ms queries) |
| Frontend | React 18 + TypeScript + Tailwind + Recharts |
| Shared Types | @taskmaster/types (pnpm workspace package) |
| Build | Vite 6, pnpm workspaces |
| Hosting | Digital Ocean App Platform ($5/mo basic-xxs) |
| Domain/DNS | Cloudflare (statskmaster.com) |
| CI | GitHub Actions (build + verify on push to main) |
app/
├── packages/types/src/index.ts # Shared TS interfaces
├── server/
│ ├── src/
│ │ ├── index.ts # Express entry (serves API + static client)
│ │ ├── db.ts # SQLite connection + schema
│ │ ├── seed.ts # Imports season JSON → SQLite
│ │ ├── error.ts # Error middleware
│ │ ├── routes/ # Express route handlers
│ │ └── queries/ # SQL query functions + stat computations
│ └── taskmaster.db # Pre-seeded SQLite (committed, ~844KB)
├── client/
│ ├── src/
│ │ ├── App.tsx # Router with lazy-loaded pages
│ │ ├── pages/ # 9 page components
│ │ ├── components/
│ │ │ ├── charts/ # Recharts visualizations
│ │ │ ├── tables/ # Generic DataTable<T>
│ │ │ ├── stats/ # StatCards component
│ │ │ └── layout/ # Header, Footer
│ │ └── lib/
│ │ ├── api.ts # Typed fetch wrappers
│ │ └── utils.ts # Formatting helpers
│ └── tailwind.config.js
├── Dockerfile # Multi-stage: build client → production server
├── .do/app.yaml # DO App Platform spec
└── .github/workflows/deploy.yml # CI: build + verify on push
pnpm install
pnpm db:seed # Import season data into SQLite (only needed once)
pnpm dev # Starts server (:3001) + client (:3000) concurrentlyProduction build:
pnpm build # Builds React client
pnpm start # Serves everything on :3001Database management:
pnpm db:seed # Seed from season JSON files
pnpm db:reset # Drop + reseedSeason data lives in the parent directory (../s01/, ../s02/, ... ../s20/), each containing:
datasets/tasks.json— per-task scoring (scores, tTPP, tTAP, format, type)datasets/episodes.json— per-episode aggregates (eTPP, eTAP, winner, scores)datasets/contestants.json— per-contestant season summary (rank, points, wins)
The seed script (server/src/seed.ts) reads all s##/datasets/ directories from the parent project root and normalizes into SQLite tables: series, contestants, episodes, tasks, task_scores, episode_scores.
To add a new season:
- Create
../sNN/datasets/withtasks.json,episodes.json,contestants.json(see existing seasons for schema) - Run
pnpm db:resetto rebuild the database - Commit the updated
server/taskmaster.db - Push to main → auto-deploys to DO
Use the /taskmaster-season-processor skill to generate season datasets from taskmaster.info.
GET /api/series All series with advanced stats (CR%, competitiveness, Greg generosity)
GET /api/series/:id Single series with contestants, episodes, task-by-task data
GET /api/series/:id/episodes Episodes for a series
GET /api/series/:id/standings Contestant standings with computed metrics
GET /api/episodes/:id Episode detail with tasks, scores, running standings
GET /api/contestants All 100 contestants with Pts/Task, CR%, rankings, percentiles
GET /api/contestants/:id Full profile: episode scores, task scores, position distribution
GET /api/contestants/:id/tasks All task scores for a contestant
GET /api/contestants/compare?ids= Head-to-head comparison (comma-separated IDs)
GET /api/tasks All tasks (filterable: ?series=&format=&type=)
GET /api/tasks/anomalies Top over/underperformers (tTAP vs tTPP)
GET /api/stats/records All-time records by category
GET /api/stats/distributions Statistical distributions (PPT, episode scores, task scores)
GET /api/stats/leaderboards Leaderboards by multiple metrics
| Metric | Definition |
|---|---|
| TPP | Total Potential Points — structural ceiling based on scoring rules |
| TAP | Total Awarded Points — what Greg actually gave out |
| Delta | TAP - TPP — positive = generous, negative = harsh |
| CR% | Capture Rate — TAP/TPP × 100 |
| Pts/Task | Points per scored task (excludes tiebreaks/solos) |
| Consistency (σ) | Std deviation of episode scores — lower = more consistent |
| Greg Generosity | Avg delta per task for a season |
| Competitiveness | Std deviation of final standings — lower = tighter race |
Full definitions at statskmaster.com/definitions and in ../DEFINITIONS.md.
Auto-deploys on push to main via DO App Platform.
# Manual deploy commands (via Claude Code skills)
/do-deploy taskmaster-stats # Push + build + verify
/do-status taskmaster-stats # Health check + deployment history
/do-rollback taskmaster-stats # Revert to previous deployment
/cf-dns statskmaster.com list # View DNS recordsInfrastructure:
- DO App ID:
79b841e3-68aa-40c8-9835-57e9dafb68b4 - GitHub:
brooksryan/taskmaster-stats(personal account, NOT Brooks-Ryan) - Cloudflare Zone:
d92ff6c21ab7e727f94ee217b7aa23ac - Region: NYC
series → id, episodes, total_tasks, winner_id, eTPP, eTAP, capture_rate
contestants → id (slug), series, name, rank, points, wins, DQs, 5-pt, 0-pt
episodes → id, series, episode, title, air_date, eTPP, eTIPP, eTAP, winner
tasks → id, series, episode_id, task_number, title, type, format, tTPP, tTAP
task_scores → task_id, contestant_id, score, disqualified (junction table)
episode_scores → episode_id, contestant_id, score (junction table)
Contestant IDs are slugs: firstname_lastname (e.g., josh_widdicombe).
| Route | Page | Key Features |
|---|---|---|
/ |
Overview | Stat cards, CR% bar chart, series table, top 10 PPT |
/seasons |
All Series | Sortable table with advanced metrics |
/seasons/:id |
Season Detail | Standings, episode progression (per-episode/cumulative toggle), task-by-task |
/contestants |
All Contestants | 100 rows, search, sortable by any metric |
/contestants/:id |
Contestant Profile | Radar chart, episode bars, position distribution, task table |
/tasks |
Task Analysis | Over/underperformance anomaly tables |
/head-to-head |
Comparison | Multi-select contestants, side-by-side stats, radar overlay |
/records |
Records | All-time records grouped by category |
/definitions |
Definitions | 28 metric definitions with explanations |
- React 18, not 19. Recharts is incompatible with React 19 (hooks error #310). Pinned via pnpm overrides in root
package.json. - Hooks ordering. All
useNavigate(),useQuery(),useState()calls must come BEFORE any early returns in page components. noEmit: truein client tsconfig — Vite handles compilation,tsconly type-checks. Without this,tsc -bemits.jsduplicates intosrc/.- Git push auth. The macOS keychain caches the wrong GitHub account. Use token-embedded URLs when pushing (see
do-deployskill). - pnpm-lock.yaml is in the global gitignore. It must be force-added:
git add -f pnpm-lock.yaml. - DO app spec cannot include
domains:block during initialdoctl apps create. Add domains after creation viadoctl apps update. - S19 CR% > 100% is valid data (5/3 team splits pushed tTAP above tTPP structurally).