Climb Intel — System Architecture
AI Kilter-board grading + movement coaching · PWA + Flask + PostgreSQL + ML ensemble
Climb Intel is a three-tier web application: a multi-page progressive web app (PWA) front end, a Flask REST API, and a data/ML layer that grades climbing routes and coaches movement from video. This document maps the structure top-down — the tiers, the front-end component tree, the API surface, the data model, and the machine-learning pipeline.
01System Overview
Every request flows browser → API → data. The front end is static HTML/CSS/JS served by Flask; all dynamic data comes from JSON endpoints. Deployed on Railway; the live site is climbintel.com.
Tier 1 — Client (PWA)
23 HTML pagesshared shell.js
31 JS modulesstyles.css (themes)
Service Worker / offlineMediaPipe-fed pose UI
▼ HTTPS · JSON / multipart (video) ▼
Tier 2 — Flask API (api/app.py)
13 blueprints
authsocialclimber
routesboardpredict
posestatsbilling
admintelemetry
▼ SQL · model inference · pose extraction ▼
Tier 3 — Data & ML
PostgreSQL
SQLite (board geometry)
XGBoost + LightGBM ensemble
MediaPipe pose
Stripe (billing)
Tech at a glance
Front end
Vanilla JS (no framework), one shared shell, theme system, PWA + service worker. Pages are real navigations, not a SPA.
API
Flask + blueprints, psycopg2 connection pool, bcrypt auth, Google OAuth, Stripe checkout.
ML
Stacked XGBoost + LightGBM → Ridge meta-learner → isotonic calibration. 72.8% within-1 grade.
02Front-End Architecture
The app was split from one monolith (index.html, still served at /app as a fallback) into focused pages that all share one shell. js/shell.js injects the intro loader, top nav, mobile bottom tab bar, auth modal, command palette (⌘K), and cross-page navigation into every page — so a page only supplies its own content + a bootstrap.
Component nesting
shell.js (shared on every app page)
├─ intro loader (once per session)
├─ top nav → tabs + "More ▾" dropdown + account
├─ mobile bottom tab bar (phones, ≤720px)
├─ auth modal (login / signup / Google)
├─ command palette (⌘K) + keyboard shortcuts
└─ switchTab() → real page navigation
PAGES (each = HTML content + a *-page.js bootstrap)
├─ home · explore · create · profile · friends
├─ video-analysis (Coach) · train · circuits
├─ discover · for-you · insights · leaderboard
├─ settings · welcome · climber (public) · route
└─ landing · process (marketing, own CSS)
FEATURE MODULES (js/, loaded as needed)
├─ board.js · explore.js · creator.js board + grading UI
├─ pose-player/animator · physics-animator beta playback
├─ profile.js · session.js · friends.js user data + social
├─ auth.js · billing.js · themes.js account + theme
└─ board-zoom · config · telemetry shared utilities
State & portability
Working state lives in localStorage (the synchronous UI cache); when signed in it mirrors to the server so a profile follows the account across devices. On login the full profile (body stats, onsight, theme, saved routes, circuits) hydrates from the API; on signup the guest's local data is pushed up.
guest localStorage → sign in → hydrate from API → mirror changes back → portable
03Back-End Architecture
api/app.py creates the Flask app, opens a PostgreSQL connection pool (api/db.py), and registers 13 blueprints. A catch-all route serves the static HTML pages, so new pages need no routing changes.
Blueprints (API surface)
| Blueprint | Owns | Representative endpoints |
| auth | Accounts, OAuth, profile | /api/auth/signup · /login · /oauth · /profile/<id> |
| social | Friends, feed, collections | /api/social/* · /api/profile/collections/<id> |
| climber | Body profile, recs | /api/climber/profile · /recommendations · /benchmarks |
| routes | Route list + detail | /api/routes · /api/route/<id> |
| board | Board geometry (holds) | /api/boards · /api/board |
| predict | Live grade prediction | /api/predict · /api/auto_generate |
| pose | Video upload + coaching | /api/pose/upload · /session/<id> · /my-videos |
| stats | Corpus stats, config | /api/stats · /api/config |
| billing | Stripe checkout (gated) | /api/billing/checkout · /webhook · /status |
| admin · telemetry | Signups dashboard, events | /api/admin/signups · /api/telemetry |
Request flow — predict a grade
Create board → POST /api/predict {holds, angle, profile} → ml_engine.compute_features()
→ ensemble inference → isotonic calibration → {grade, confidence, DNA, style tags} → UI
Request flow — coach a video
Upload clip (multipart) → background worker → MediaPipe pose frames
→ video_coach.analyse_attempt() → insights + predicted grade → poll /session/<id> → UI
04Data Model
PostgreSQL holds all user + route + pose data; a slim SQLite DB holds the physical Kilter board layout (hold positions per size). Key tables and how they nest:
climber_profiles (account root)
id (uuid) · email · password_hash · display_name · username · bio · height_cm · wingspan_cm · weight_kg · experience_yrs · onsight_grade · color_profile · oauth_* · plan
route_sends
climber_id → route_id · attempts · route_grade · sent_at (feeds the feed + pyramid)
user_collections
climber_id · saved_routes (jsonb) · circuits (jsonb) (portable)
friendships
requester_id · addressee_id · status (pending/accepted)
board_routes / route_holds
route + its lit holds (position, role, hold_type, sequence) · community_grade · difficulty_score
video_upload_sessions / pose_frames
upload → extracted per-frame pose metrics (tension, hip angle, reach, COM) → coaching_insights
Nested object — a climber profile as the app sees it
climber {
id, display_name, email,
body: { height_cm, wingspan_cm, weight_kg, ape_index, bmi },
climbing: { onsight_grade, experience_yrs, move_affinities[] },
settings: { color_profile, units },
collections: { saved_routes[], circuits[] },
social: { friends[], pending_requests[], feed[] },
activity: { sessions[] (local), route_sends[] (server) }
}
05Machine-Learning Pipeline
Grading is a stacked, calibrated ensemble trained on ~137K graded routes (357K augmented rows) with 86 geometric + interaction + hold-quality + pose features. The pipeline both trains offline and serves the identical feature computation at request time.
route holds + angle → feature_extraction (86 feats) → XGBoost + LightGBM
→ Ridge meta-learner → isotonic calibration → grade + q10/q90 bands
Top features (XGBoost gain)
board angle · angle × reach · hold density · angle × dyno · total holds · avg reach. Angle and its interactions dominate.
Accuracy (frozen holdout)
72.8% within ±1 V-grade · ~90% within ±2 · 28.9% exact. Strongest at core grades V4–V6; V9+ is the data-scarce frontier.
Pose / video coaching
MediaPipe → per-frame body metrics → benchmarked against per-grade medians → tension / hip / reach / footwork insights + drills.
Personalization (PDScore)
Body dims + logged sends adjust difficulty to the individual — the same route is graded differently for different bodies.