How this
dashboard works

A personal fitness analytics platform built as both a functional training tool and a portfolio project — demonstrating real-world API integration, automated data pipelines, and cloud architecture across its phases of build.

V1 — Static Pipeline on GitHub Pages

The current production system uses a static JSON pipeline. A Python script runs daily via GitHub Actions, fetches data from the Intervals.icu API, and commits processed JSON files directly to the repository. GitHub Pages serves the static site, which reads those JSON files client-side. This approach avoids CORS issues, requires zero backend infrastructure, and keeps running costs at $0.

V1 Data Flow — GitHub Pages Architecture

SCHEDULE GitHub Actions Daily 06:00 UTC COLLECT Python 3.11 collect_data.py INTERVALS.ICU Athlete ID: i5718022 STRAVA API Client ID: 201642 COMMIT JSON files docs/data/*.json SERVE GitHub Pages Static HTML/CSS/JS runs push
Why static? The static JSON architecture means no server to manage, no costs, no CORS complexity. GitHub Actions handles scheduling and execution; GitHub Pages handles delivery. Intervals.icu already computes CTL/ATL/TSB, FTP, Critical Power and power curves server-side — these are fetched directly, never recalculated client-side.
Intervals.icu as the Source of Truth

All fitness metrics are sourced from the Intervals.icu API (athlete ID: 5718022). Intervals.icu serves as the primary analytics engine — it ingests activities from Garmin and Zwift, computes training load metrics, and exposes them via a well-structured REST API. Strava data via the integration is treated as secondary; Intervals.icu provides the authoritative, fully-populated activity fields.

Data Source Hierarchy

GARMIN GPS / HRM ZWIFT Indoor Cycling INTERVALS.ICU Primary Data Source CTL · ATL · TSB · FTP · W' · CP REST API STRAVA Secondary / stub data COLLECTOR Python 3.11 collect_data.py activities.json wellness.json power_curves.json DASH Chart.js
Key Data Endpoints
Method Endpoint Data Retrieved JSON File
GET /api/v1/athlete/{id}/activities All activities, sport type, TSS, NP, IF, HR, power activities.json
GET /api/v1/athlete/{id}/wellness CTL, ATL, TSB, HRV, sleep, weight — pre-computed wellness.json
GET /api/v1/athlete/{id} Current FTP, W'bal, weight, critical power athlete.json
GET /api/v1/athlete/{id}/power-curves Best power for each duration (1s → 3600s) power_curves.json
GET /api/v1/athlete/{id}/pace-curves Best pace for each distance (running) pace_curves.json
Phases of Build

The project is structured in phases, moving from a simple static pipeline toward a full serverless AWS architecture with an AI coaching layer. Each phase is independently valuable and the existing production system remains live throughout.

01

Static Dashboard

GitHub Actions → Python → JSON → GitHub Pages. Daily automated sync, Chart.js visualisations, full Intervals.icu integration.

Complete
02

AWS Foundation

CDK infrastructure, IAM setup, CLI configuration. Migrate data pipeline from GitHub Actions to Lambda + EventBridge + DynamoDB.

In Progress
03

AI Coaching Layer

Claude-powered training coach. Post-race debriefs, PR detection, adaptive tone, context-aware nutrition guidance driven by live Intervals.icu data.

In Progress
05

S3 + CloudFront

Migrate frontend from GitHub Pages to S3 static hosting behind CloudFront CDN. Custom domain, SSL, global edge delivery.

Upcoming
06

Multi-user Platform

Athlete auth, per-user data isolation, shareable profiles. Demonstrates production-grade SaaS architecture at portfolio scale.

Vision
V2 — AWS Serverless

The V1 static pipeline proves the concept — V2 is where the cloud engineering skills are demonstrated. The entire data pipeline is migrated from GitHub Actions into a fully serverless AWS stack, designed from the ground up as Infrastructure as Code using AWS CDK (Python). This phase exists primarily as a portfolio exercise in production cloud architecture: applying IAM least-privilege security, secrets management, serverless compute, NoSQL data modelling, REST API design, CDN delivery, and operational observability — all within a real system that genuinely runs and collects data daily. Deployed to eu-west-2 (London), the architecture separates concerns into two distinct pipelines — a scheduled collection pipeline triggered by EventBridge, and a synchronous query pipeline fronted by API Gateway — targeting sub-500ms API responses and a post-free-tier running cost of under £3/month.

V2 Target Architecture — AWS Serverless

COLLECTION PIPELINE EVENTBRIDGE cron(0 6 * * ? *) Daily trigger LAMBDA data_collector.py Python 3.11 · 512MB SECRETS MGR API keys · tokens INTERVALS.ICU STRAVA API DYNAMODB fitness-activities fitness-wellness fitness-curves eu-west-2 · London QUERY PIPELINE CLOUDFRONT CDN + S3 origin Global edge API GATEWAY REST endpoints GET /wellness etc. LAMBDA query functions < 500ms target CLOUDWATCH Metrics · Alarms Lambda · API GW · DB AWS CDK Infrastructure as Code Python · eu-west-2 Est. cost: £0/month (free tier) → £2–3/month thereafter
Agentic AI — From Data to Decisions

The AI coach is where applied machine learning, prompt engineering, and software architecture converge. Built on the Anthropic Claude API, it demonstrates a set of skills that are hard to fake: designing agentic systems that are grounded in real data, engineering prompts that produce reliably structured outputs rather than freeform prose, preventing hallucination at the system level rather than by post-processing, and implementing adaptive behaviour (tone, focus, urgency) programmatically rather than hoping the model infers context. Real Intervals.icu metrics — CTL, ATL, TSB, HRV, FTP, W', power curves, and calendar events — are assembled into a structured JSON payload before any prompt is issued. The model never invents numbers; it reasons over data the system has fetched and validated.

AI Coach — Data Flow & Context Assembly

CTL / ATL / TSB · HRV Recent activities (14d) Power curve · FTP · W'bal Upcoming races / events CONTEXT Lambda assembler Structured JSON payload PROMPT ENGINE System + user template Adaptive tone selector Role + constraints CLAUDE API claude-sonnet-4 Structured JSON output Workout rec. Race debrief Nutrition plan PR / alert flags
Prompt Engineering Approach

The quality of AI coaching output is entirely a function of prompt design. Rather than asking Claude a vague question, the system assembles a richly structured context payload from Intervals.icu before any prompt is sent. The prompt engineering follows several key principles:

Prompt Architecture — Layers

Layer 1 — Role & Constraints
The system prompt establishes Claude as a data-driven endurance coach with specific constraints: never invent metrics, always cite source data, flag when TSB is dangerously low, distinguish between "should train" and "could train." This prevents hallucination of fitness values.
Layer 2 — Athlete Context
A structured JSON block injects real metrics: ctl, atl, tsb, hrv, ftp, w_prime, cp_5min, cp_20min, recent activity list (sport, TSS, NP, IF, duration), and upcoming events from the Intervals.icu calendar. No value is computed client-side — all pulled directly from the API.
Layer 3 — Adaptive Tone Selector
Tone is programmatically selected before the prompt is sent. A negative TSB + declining HRV triggers a recovery-focused, cautious tone. A positive TSB + upcoming race triggers a sharp, performance-focused tone. The model receives the tone as an instruction, not as something it must infer.
Layer 4 — Structured Output Schema
Claude is instructed to respond only with a specific JSON schema: summary, workout_primary, workout_zwift_equivalent, nutrition, flags[]. This makes the output reliably parseable and renderable in the dashboard — turning a language model into a structured data source.
Nutrition Guidance

Nutrition recommendations are generated contextually — they vary based on training load, upcoming race proximity, and current TSB. The coach doesn't issue generic meal plans; it reasons about the athlete's energy state and what the next 48 hours of training demand.

Scenario Input Signals Nutrition Output
High Load Day TSS > 80, IF > 0.85, race < 48h High-carb pre-load, 60–90g/hr during, rapid glycogen replenishment post. Specific gram targets based on body weight from athlete.json.
Recovery Phase TSB negative, HRV suppressed, CTL declining Anti-inflammatory focus, protein priority (>1.8g/kg), lower carb until TSB recovers. Flags excessive training stress.
Base / Adaptation Moderate TSS, stable CTL build, no race <7d Periodised carb intake matching session intensity. Fat-adaptation windows on Z1/Z2 days. Fuelling windows calculated from planned workout IF.
Race Week Event on calendar, TSB rising, taper in progress Carb-load protocol, hydration strategy, pre-race meal timing relative to start time. Zwift race-specific (no physical warm-up cost factored).
Why this matters technically: All nutrition outputs reference metrics pulled from the Intervals.icu API — icu_weight, icu_training_load, icu_atl, icu_tsb. The AI is never given permission to invent numbers. This is enforced at the prompt level, not by post-processing.
AI Skills Demonstrated
Agentic AI Design Prompt Engineering Structured LLM Outputs Context-Aware AI Adaptive Tone Systems Hallucination Prevention RLHF Pattern MLOps
Technology Stack

The stack spans three distinct layers — each chosen deliberately rather than by default. The frontend is intentionally vanilla: no framework overhead, no build step, just well-structured HTML, CSS custom properties, and Chart.js doing exactly what it needs to do. The V1 backend replaces a traditional server entirely with a scheduled Python script and static files — a pragmatic architecture that runs at zero cost while demonstrating solid API integration and automation skills. The V2 AWS stack introduces every major serverless primitive: Lambda for compute, DynamoDB for storage, API Gateway for exposure, CloudFront for delivery, Secrets Manager for credentials, EventBridge for scheduling, and CloudWatch for observability — all provisioned through CDK as repeatable, version-controlled infrastructure code.

Frontend

HTML5 / CSS3 Vanilla JavaScript Chart.js 4.4.1 Syne + DM Mono GitHub Pages

Backend (V1)

Python 3.11 GitHub Actions requests library Static JSON

Infrastructure (V2 Target)

AWS CDK (Python) Lambda DynamoDB API Gateway S3 + CloudFront Secrets Manager EventBridge CloudWatch

Data Sources

Intervals.icu API Strava API Concept2 (planned) Athlete ID: i5718022
 Built by Lee Hopkins · github.com/lee-hop-dev/fitness-dashboard  ·  Live at lee-hop-dev.github.io/fitness-dashboard