🛡️ Turning scattered injury and performance data into a single, explainable recruiting call ⚽
Aegis is an internal recruiting-intelligence web app built for the San Jose Earthquakes front office. It helps scouts, analysts, and medical/performance staff evaluate recruiting targets by combining injury risk, availability, performance, and explainable model reasoning into fast player-profile and side-by-side comparison workflows. Aegis is intentionally a thin, trustworthy reader: all the scraping, modeling, and scoring happens in a separate pipeline, earthquakes-data, which writes serving tables to Postgres that Aegis simply queries and presents.
🌟 Features
- Player Search: Typeahead search over the full scraped player universe.
- Analysis: A complete profile per player with injury-risk score and category, season availability, injury history, performance vs. positional average, and a "Risk drivers" panel powered by SHAP explanations of why the model scored the player the way it did.
- AI Scouting Briefs: An auto-generated, data-grounded scouting note covering performance, availability, age/career stage, market value, and an overall recommendation.
- Compare: Two candidates evaluated side by side with an AI-written verdict on the stronger fit.
- History: Every evaluation and comparison is saved as a point-in-time snapshot, so past decisions can be revisited exactly as they were made, not re-computed with today's data.
- Google Sign-In: Supabase-backed auth gates every route except health checks.
🔑 How It Works
1. 📥 Data Pipeline
earthquakes-data runs weekly on AWS (EventBridge → Step Functions → ECS Fargate). It scrapes SofaScore (per-match performance, minutes, ratings) and Transfermarkt (injuries, market value, date of birth) across MLS, the Premier League, La Liga, the Bundesliga, Serie A, and Ligue 1. Raw scrape artifacts land in S3 as the permanent history, get loaded into Postgres staging tables, and are transformed by dbt into core (clean facts/dimensions) and mart (features, current-season profiles, career context, search index) layers.
2. 🎯 Injury Risk Model
An XGBoost classifier predicts the probability that a player suffers a "severe injury" next season, defined as missing 12 or more games. Features are leakage-safe and career, body-part, recurrence, and recency injury signals are computed only from each season's past, combined with performance, workload/rest, age, and market value. The model is isotonic-calibrated so the displayed number is a real probability, and uses monotonic constraints so more or more-severe injuries and higher age can only push risk up, never down. serving/score_and_serve.py scores every player weekly and computes per-player SHAP drivers; training/train_severe_injury_model.py handles periodic retraining.
3. 📊 Recruiting Assessment
The backend's risk engine combines the model's injury probability with performance, value efficiency (market value relative to output), and age fit into a single 0–100 recruit score, weighted 40/30/20/10 respectively. That score maps to a recommendation tier from "Strong Pursue" down to "Avoid," with a one-line rationale naming whichever sub-score is currently the strongest or weakest driver.
4. 🤖 AI Scouting Briefs & Verdicts
Player write-ups are generated by calling Claude server-side, grounded strictly in a whitelisted set of structured fields so the model can't invent stats, injuries, or market values. A single-player call produces a scouting brief; a two-player call produces a head-to-head verdict recommending the stronger recruiting target. Each brief is fingerprinted from the model version plus its grounding facts and cached in Postgres, so the same underlying data never regenerates, or re-bills, the same brief twice.
5. 🔐 Auth & History
Supabase handles Google sign-in, and every route besides health checks requires a valid session. Each risk prediction or comparison a staff member runs is persisted as a point-in-time snapshot, capturing both the profile data and the AI text exactly as they were at that moment, so History always reflects what staff actually saw rather than a value re-fetched later.
💻 Technology
| Layer | Stack |
|---|---|
| Frontend | React 18, Vite 6, TypeScript, Tailwind CSS v4, Radix UI, motion, react-router |
| Backend | FastAPI (Python) |
| Data Pipeline | Python scrapers (SofaScore, Transfermarkt), dbt, AWS (S3, ECS Fargate, Step Functions, EventBridge) |
| Database | Postgres — Supabase for local/dev, AWS RDS in production |
| Machine Learning | XGBoost (severe-injury-risk classifier) with SHAP explainability and isotonic calibration |
| Auth | Supabase (Google sign-in) |
| AI | Anthropic Claude for scouting briefs and comparison verdicts |
Deployment: Frontend on S3/CloudFront, backend on AWS Elastic Beanstalk, data pipeline on ECS Fargate, orchestrated weekly via EventBridge and Step Functions. Both services read the same RDS instance — the app serves whatever the latest weekly pipeline run produced.
🤝 Contributing
- Project Manager: Ayush Lenka
- Project Members: Vicky Chan, Dylan Tran, Rebekah Lee, Joshua Wei, Henry Nguyen
🎯 Areas of Improvement
- Full Historical Backfill: Retrain the model against 6–8 years of backfilled history on RDS to replace today's synthetic-data validation metrics with honest temporal ones.
- Deeper Leakage Fix:
position_baseline_frequencyis still computed all-time rather than as-of; making it leakage-safe would close the model's last known gap. - Richer AI Grounding: Feed each player's SHAP risk drivers into the scouting-brief prompt so the AI's narrative can cite the same signals driving the score.
- Multi-League Modeling: Add a competition feature so the injury-risk model accounts for differences in schedule and playing style across leagues.