Fantasy Premier League analytics dashboard built with Next.js.
This project includes pre-configured plugins via .claude/settings.json. When you open the repo in Claude Code, you'll be prompted to trust the project settings, which automatically enables:
- Superpowers (obra/superpowers) — brainstorming, TDD enforcement, code review, git worktrees
- VoltAgent Subagents (VoltAgent/awesome-claude-code-subagents) — 127+ specialized subagents for frontend, backend, testing, infrastructure, and more
No manual plugin installation needed. Just accept the project settings prompt when opening the repo.
See DEVELOPING.md for full developer setup instructions including YOLO mode and Ralph Loops.
TDD is mandatory for all code changes. Follow RED-GREEN-REFACTOR:
- RED — Write a failing test first. Run it. It must fail.
- GREEN — Write the minimal code to make the test pass. Nothing more.
- REFACTOR — Clean up duplication and improve names. All tests must still pass.
Rules:
- No production code without a failing test first
- Code written before tests must be deleted and rewritten test-first
- All new features start with brainstorming, then planning, then TDD execution
- Run code review between tasks
- "Too simple to test" and "I already tested manually" are not valid excuses to skip TDD
- The Superpowers plugin enforces this workflow automatically when installed
- Framework: Next.js 16 (App Router)
- Language: TypeScript 5 (strict mode)
- Styling: Tailwind CSS v4 (PostCSS plugin,
@theme inlinefor custom tokens) - React: 19
- Database: SQLite (file-based, via better-sqlite3)
- Hosting: Docker container (standalone Next.js build)
- CI/CD: GitHub Actions (build & push to GHCR on push to
main) - Testing: Vitest with 920+ tests
- AI: Claude API (Sonnet for news search, extended thinking for optimization/simulation)
- PWA: Service worker, offline support, push notifications
- Linting: ESLint 9 + Prettier + lint-staged + Husky
- Scheduler: node-cron for background tasks
npm run dev— start dev servernpm run build— production build (also validates TypeScript)npm run lint— run ESLintnpm test— run tests (Vitest)npm run test:watch— run tests in watch modenpm run test:coverage— run tests with coverage reportnpm start— serve production buildnpm run mcp:start— run MCP server for Claude Code FPL data accessnpm run docker:build— build Docker imagenpm run docker:run— run Docker container (mounts ./data for SQLite persistence)npm run docker:dev— run development environment with Docker Compose
app/ # Next.js App Router pages and API routes
api/
fpl/ # Proxy routes to fantasy.premierleague.com/api
bootstrap-static/ # Bootstrap static data (players, teams, gameweeks)
fixtures/ # Fixtures data
element-summary/[id]/ # Player summary and history
event/[gw]/live/ # Live gameweek data
entry/[id]/ # Manager entry data
entry/[id]/history/ # Manager season history
entry/[id]/event/[gw]/picks/ # Manager picks for gameweek
leagues-classic/[id]/standings/ # League standings
optimize/ # Claude AI optimization endpoint
simulate/ # GW Decision Simulator endpoint (Claude extended thinking)
rival-analysis/ # Rival Gameplan Analyzer endpoint (Claude extended thinking)
injury-prediction/ # Injury Return Predictor endpoint (Claude extended thinking)
news/ # Claude-powered FPL news search
injuries/ # Injury updates endpoint
team/[team]/ # Team news endpoint
notifications/
send/ # Push notification sending endpoint
preferences/ # Notification preferences CRUD
history/ # Notification history
session/ # Session management API
news/page.tsx # News feed with search and filters
offline/page.tsx # Offline fallback page (PWA)
globals.css # Tailwind + CSS custom properties (dark theme)
layout.tsx # Root layout with AppShell
page.tsx # Dashboard (home page)
fixtures/page.tsx # Fixture Planner (FDR grid, fixture swings)
transfers/page.tsx # Transfer Hub (recommendations, price changes, injury returns)
captain/page.tsx # Captain Selector (ranked picks with score breakdown)
live/page.tsx # Live Gameweek Tracker (scores, BPS, top performers)
players/page.tsx # Players (predictions, set-piece takers)
chips/page.tsx # Chip Strategy Advisor (timing recommendations, performance history)
leagues/page.tsx # Mini-Leagues (league list)
leagues/analyze/page.tsx # Mini-League Analyzer (rival picks, EO, differentials, chip tracking)
team/page.tsx # My Team (pitch view, squad value tracker)
optimize/page.tsx # AI Optimizer (Claude extended thinking)
simulator/page.tsx # AI Simulator (decision simulator, rival analyzer, injury predictor)
notifications/page.tsx # Notification preferences and history
components/
ui/ # Reusable primitives (Card, Badge, DataTable, StatCard, Skeleton variants, ErrorState)
layout/ # App shell, header, sidebar, mobile nav, nav-items, nav-icon, manager-input
dashboard/ # Dashboard sections (gameweek banner, stats, top players, fixtures, progress)
fixtures/ # Fixture planner grid, best teams ranking, fixture swing alerts
transfers/ # Transfer table, price changes table, price alerts, timing advice, injury returns
captain/ # Captain pick cards with detailed score breakdown
live/ # Match scores, top performers, BPS tracker
players/ # Set-piece takers section
chips/ # Chip timing grid, chip performance history
leagues/ # Connect prompt, league list, league standings table
league-analyzer/ # Analyzer tabs, rival picks section, differentials, swing scenarios
team/ # Team header, pitch view, gameweek summary/nav, squad value tracker
optimize/ # Optimize form, thinking display, recommendations display
simulator/ # Decision simulator, rival analyzer, injury predictor components
notifications/ # Notification preferences form, notification history list
news/ # News feed, injury tracker components
pwa/ # Service worker registration, pull-to-refresh
lib/db/
client.ts # SQLite connection and schema initialization
sessions.ts # Session repository (browser session tracking with FPL manager ID)
notifications.ts # Notification preferences and history repository
manager-cache.ts # FPL API response cache
index.ts # Barrel exports
lib/fpl/
types.ts # TypeScript interfaces for FPL API (691 lines)
client.ts # Server-side FPL API client with caching
hooks/
use-fpl.ts # Client-side React hooks (useBootstrapStatic, useFixtures, useLiveGameweek, etc.)
use-rival-picks.ts # Batch fetching hook for rival picks with AbortController
use-rival-histories.ts # Hook for fetching rival manager histories
utils.ts # Pure utility functions (sorting, filtering, enrichment, formatting)
fixture-planner.ts # Fixture grid builder, FDR calculations, DGW/BGW detection
fixture-swing.ts # Fixture swing detection (improving/worsening runs)
transfer-model.ts # Transfer recommendation scoring model
captain-model.ts # Captain scoring model (form, fixtures, xGI, set pieces)
price-model.ts # Price change prediction model with timing advice
injury-tracker.ts # Injury returns tracking and watchlist
points-model.ts # Player points prediction model
set-piece-tracker.ts # Set-piece taker tracking (penalties, corners, free kicks)
chip-model.ts # Chip strategy recommendation model
chip-history.ts # Chip performance history analysis with verdicts
rules-engine.ts # FPL rules (squad composition, formations, chips)
squad-value.ts # Squad value calculation and tracking
league-analyzer.ts # Mini-league analyzer (rival picks, EO, differentials, chip tracking)
manager-context.tsx # Manager ID context with SQLite session sync
index.ts # Barrel exports
lib/claude/
types.ts # Optimization request/response types
client.ts # Claude API client with extended thinking
prompts.ts # Prompt builders for transfer optimization
news-types.ts # News item, category, injury update types
news-client.ts # Claude web search for FPL news (searchFPLNews, getInjuryUpdates, getTeamNews)
hooks.ts # React hooks for news (useNews, useInjuryUpdates, useTeamNews)
simulator-types.ts # Types for decision simulator, rival analyzer, injury predictor
simulator-client.ts # Claude API client for simulateDecision, analyzeRival, predictInjuryReturn
simulator-hooks.ts # React hooks for simulator features (useSimulation, useRivalAnalysis, useInjuryPrediction)
lib/chat/
types.ts # Chat message and tool types
tools.ts # Claude tool definitions (15 tools: squad, players, transfers, captain, chips, league, etc.)
tool-executor.ts # Executes chat tools against the FPL data layer; uses managerId to personalise results
prompts.ts # System prompt builders for the chat assistant
storage.ts # localStorage-based chat history with versioning and quota handling
hooks.ts # React hooks for chat state and speech recognition
lib/notifications/
types.ts # Notification preference and history types
hooks.ts # useNotificationPreferences, useNotificationHistory, usePushNotificationStatus, subscribeToPushNotifications
email-client.ts # Resend email service with HTML templates for all notification types
quiet-hours.ts # Quiet hours enforcement with timezone support
lib/scheduler/
index.ts # node-cron scheduler initialization
deadline-reminder.ts # Hourly deadline check and reminders
weekly-summary.ts # Tuesday 10am UTC weekly AI summary
league-updates.ts # Every 6 hours post-gameweek league updates
lib/utils/
timing-safe.ts # Constant-time string comparison for API key validation
instrumentation.ts # Next.js instrumentation (starts scheduler on server boot)
data/ # SQLite database directory (gitignored)
.gitignore # Ignores *.db, *.db-wal, *.db-shm
public/
manifest.json # PWA manifest
sw.js # Service worker (caching, push notifications)
icons/ # PWA icons (192, 512, apple-touch-icon, favicons)
scripts/
generate-icons.mjs # Generate PWA icons from SVG using Sharp
.github/
workflows/ci.yml # GitHub Actions CI/CD pipeline (lint → test → build Docker → push GHCR)
mcp-server/
index.ts # MCP server for Claude Code FPL data access (8 tools, 3 resources)
package.json # MCP server package config
Dockerfile # Multi-stage production build
Dockerfile.dev # Development Dockerfile with hot reload
docker-compose.yml # Production Docker Compose
docker-compose.dev.yml # Development Docker Compose
.dockerignore # Docker build exclusions
vitest.config.ts # Vitest configuration
next.config.ts # Next.js config (standalone output, image optimization, PWA headers)
- Imports: Use
@/path alias (maps to project root). Never import from the@/lib/fplbarrel (lib/fpl/index.ts) in client components — the barrel re-exportsauth-client.ts→db/client.ts→ Node.jsfs, which breaks Turbopack. Import directly from the specific file instead (e.g.import { useBootstrapStatic } from "@/lib/fpl/hooks/use-fpl"). - Components: Named exports, one component per file, PascalCase filenames for components
- Styling: Tailwind utility classes; custom colors via CSS variables (
--fpl-purple,--fpl-green, etc.) - Theme: Dark-only (no light/dark toggle); PL brand palette
- Data fetching: Client-side hooks in
lib/fpl/hooks/; API routes proxy to FPL API with caching - No external UI libraries: All components built from scratch with Tailwind
- Sessions: Browser sessions tracked via localStorage + SQLite. Manager ID persists in session for cross-tab sync.
- Database: SQLite file at
./data/fpl.db. Schema auto-created on first run. Use Docker volume for persistence. - Environment variables:
ANTHROPIC_API_KEYfor Claude AI.NOTIFICATIONS_API_KEYfor scheduled functions.RESEND_API_KEYfor email.DATABASE_PATHfor SQLite location.NEXT_PUBLIC_APP_URLfor email links. Never commit.env*files. - Testing: Unit tests in
__tests__/directories adjacent to source files. Use Vitest with mock factories. - Pre-commit hooks: lint-staged runs ESLint and Prettier on staged files, then runs tests.
- Null/undefined handling: Use
nullfor "no value" in API responses and data that explicitly has no value. Useundefinedfor optional parameters and omitted fields. This provides clearer semantics between "value is explicitly empty" vs "value was not provided." - API validation: All API routes use Zod schemas from
lib/api/validation.tsfor runtime input validation. UsevalidationErrorResponse()for consistent error formatting.
# Build the Docker image
npm run docker:build
# Run with SQLite persistence
npm run docker:run
# Or use Docker Compose
docker compose up -dCreate a .env file based on .env.example:
# Required for AI features
ANTHROPIC_API_KEY=sk-ant-...
# Optional: Push notifications
NEXT_PUBLIC_VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=mailto:admin@example.com
# Optional: Scheduled notifications
NOTIFICATIONS_API_KEY=
# Optional: Email notifications
RESEND_API_KEY=
FROM_EMAIL=noreply@example.com
# Database (defaults to ./data/fpl.db)
DATABASE_PATH=/app/data/fpl.db
# App URL for emails
NEXT_PUBLIC_APP_URL=http://localhost:3000The SQLite database is stored in ./data/fpl.db. When using Docker, mount this directory as a volume:
volumes:
- ./data:/app/data# Check database exists
ls -la data/fpl.db
# View tables
sqlite3 data/fpl.db ".tables"
# Check sessions
sqlite3 data/fpl.db "SELECT * FROM sessions;"All API routes are rate-limited using Upstash Redis with in-memory fallback:
| Tier | Limit | Endpoints |
|---|---|---|
fpl |
100 requests/min | /api/fpl/* proxy routes |
claude |
10 requests/min | /api/optimize, /api/simulate, /api/rival-analysis, /api/injury-prediction, /api/news/* |
notifications |
20 requests/min | /api/notifications/* |
All API errors return a consistent JSON format:
{
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": {} // Optional additional context
}Common error codes:
VALIDATION_ERROR- Invalid request body/parametersRATE_LIMITED- Rate limit exceededNOT_FOUND- Resource not foundUNAUTHORIZED- Missing or invalid API keyFPL_API_ERROR- Upstream FPL API errorINTERNAL_ERROR- Server error
All FPL proxy routes cache responses server-side and validate parameters.
Returns all static FPL data including players, teams, and gameweeks.
- Cache TTL: 5 minutes
- Response:
BootstrapStatic(seelib/fpl/types.ts)
Returns all fixtures for the season.
- Cache TTL: 5 minutes
- Query params:
event(optional, filter by gameweek 1-38) - Response:
Fixture[]
Returns detailed player data including history and fixtures.
- Parameters:
id- Player element ID (1-800) - Cache TTL: 5 minutes
- Response:
ElementSummary
Returns live gameweek data with player scores.
- Parameters:
gw- Gameweek number (1-38) - Cache TTL: 1 minute (during matches), 5 minutes (otherwise)
- Response:
LiveGameweek
Returns manager entry information.
- Parameters:
id- Manager ID (1-15,000,000) - Cache TTL: 5 minutes
- Response:
ManagerEntry
Returns manager's season history and past seasons.
- Parameters:
id- Manager ID - Cache TTL: 5 minutes
- Response:
ManagerHistory
Returns manager's picks for a specific gameweek.
- Parameters:
id- Manager ID,gw- Gameweek (1-38) - Cache TTL: 5 minutes
- Response:
ManagerPicks
Returns classic league standings.
- Parameters:
id- League ID - Query params:
page(optional, default 1) - Cache TTL: 5 minutes
- Response:
LeagueStandings
All AI endpoints use Claude with extended thinking for complex analysis.
Claude-powered transfer optimization.
Request body:
{
type: "transfer" | "chip" | "wildcard",
query: string, // 1-1000 chars, user's optimization request
constraints?: {
budget?: number, // Available budget in 0.1m units
maxTransfers?: number, // Max transfers to suggest
excludePlayers?: number[] // Player IDs to exclude
},
currentTeam?: {
players: number[], // Current squad player IDs
bank: number, // Bank balance
freeTransfers: number
},
leagueContext?: {
rank?: number,
totalManagers?: number,
gameweeksRemaining?: number
}
}Response:
{
recommendations: Array<{
type: "transfer_in" | "transfer_out" | "captain" | "chip",
playerId?: number,
playerName?: string,
reasoning: string,
priority: "high" | "medium" | "low"
}>,
summary: string,
thinkingProcess?: string // Extended thinking output
}GW decision simulator with scenario analysis.
Request body:
{
action: "transfer" | "captain" | "chip" | "bench",
options: string[], // Options to compare
currentTeam: { ... }, // Squad details
gameweek: number,
additionalContext?: string
}Analyzes rival managers and suggests counter-strategies.
Request body:
{
managerId: number,
rivalIds: number[],
gameweek: number,
leagueContext?: { ... }
}Predicts injury return timelines based on news and historical data.
Request body:
{
playerId: number,
injuryDetails?: string
}Search FPL-related news using Claude web search.
- Query params:
q(search query),category(optional filter) - Response:
NewsItem[]
Get current injury updates across all teams.
- Response:
InjuryUpdate[]
Get team-specific news.
- Parameters:
team- Team short name (e.g., "ARS", "MCI") - Response:
NewsItem[]
Get session by ID.
- Query params:
id- Session UUID - Response:
Session
Create a new session.
- Response:
Session
Update session (e.g., set FPL manager ID).
- Request body:
{ id: string, fpl_manager_id?: number, display_name?: string }
Get notification preferences for a session.
- Query params:
sessionId- Session UUID - Response:
NotificationPreferences
Update notification preferences.
- Request body:
{ sessionId: string, ...preferences }
Get notification history for a session.
- Query params:
sessionId,limit(default 50) - Response:
NotificationHistory[]
Mark notification as read.
- Request body:
{ sessionId: string, notificationId: string }
Send push notifications (server-to-server only).
- Auth: Requires
x-api-keyheader matchingNOTIFICATIONS_API_KEY - Request body:
{
type: "deadline" | "price_change" | "injury" | "league_update" | "transfer_rec" | "weekly_summary",
criteria?: {
push_deadline_reminder?: boolean,
push_price_changes?: boolean,
push_injury_news?: boolean,
push_league_updates?: boolean
},
title: string,
body: string,
url?: string,
data?: Record<string, unknown>
}