Skip to content

Repository files navigation

Taskmaster UK — Advanced Statistics

Live at statskmaster.com

Full-stack statistics app covering all 20 series of Taskmaster UK: 994 tasks, 182 episodes, 100 contestants.

Stack

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)

Project Structure

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

Running Locally

pnpm install
pnpm db:seed          # Import season data into SQLite (only needed once)
pnpm dev              # Starts server (:3001) + client (:3000) concurrently

Production build:

pnpm build            # Builds React client
pnpm start            # Serves everything on :3001

Database management:

pnpm db:seed          # Seed from season JSON files
pnpm db:reset         # Drop + reseed

Data Pipeline

Season 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:

  1. Create ../sNN/datasets/ with tasks.json, episodes.json, contestants.json (see existing seasons for schema)
  2. Run pnpm db:reset to rebuild the database
  3. Commit the updated server/taskmaster.db
  4. Push to main → auto-deploys to DO

Use the /taskmaster-season-processor skill to generate season datasets from taskmaster.info.

API Endpoints

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

Key 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.

Deployment

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 records

Infrastructure:

  • DO App ID: 79b841e3-68aa-40c8-9835-57e9dafb68b4
  • GitHub: brooksryan/taskmaster-stats (personal account, NOT Brooks-Ryan)
  • Cloudflare Zone: d92ff6c21ab7e727f94ee217b7aa23ac
  • Region: NYC

Database Schema

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).

Frontend Pages

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

Gotchas

  • 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: true in client tsconfig — Vite handles compilation, tsc only type-checks. Without this, tsc -b emits .js duplicates into src/.
  • Git push auth. The macOS keychain caches the wrong GitHub account. Use token-embedded URLs when pushing (see do-deploy skill).
  • 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 initial doctl apps create. Add domains after creation via doctl apps update.
  • S19 CR% > 100% is valid data (5/3 team splits pushed tTAP above tTPP structurally).

About

Taskmaster UK Advanced Statistics — 20 series, 994 tasks, 100 contestants

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages