| name | analytics |
| description | Understand and work with EPA and OPR analytics algorithms in FTC Metrics. Use when calculating team ratings, implementing match predictions, troubleshooting analytics calculations, or understanding the scoring methodology. |
| license | MIT |
| compatibility | ["Claude Code"] |
| metadata | {"author":"ftcmetrics","version":"1.0.0","category":"analytics"} |
FTC Metrics Analytics
FTC Metrics uses two primary rating systems to evaluate team performance:
- EPA (Expected Points Added) - Bayesian-style incremental updates
- OPR (Offensive Power Rating) - Linear algebra matrix solution
Quick Reference
| Metric | Best For | Updates |
|---|
| EPA | Predicting future performance | After each match |
| OPR | Analyzing historical contribution | Recalculated per event |
EPA (Expected Points Added)
EPA represents how many points above/below average a team contributes to their alliance score.
Algorithm Overview
- Start with season baseline (average scores)
- After each match, calculate expected vs actual
- Distribute score difference to teams
- Apply adaptive K-factor for learning rate
Usage
import { calculateEPA, type MatchForEPA } from "@ftcmetrics/api/lib/stats";
const matches: MatchForEPA[] = [
{
matchNumber: 1,
redTeam1: 12345,
redTeam2: 12346,
blueTeam1: 12347,
blueTeam2: 12348,
redScore: 45,
blueScore: 38,
redAutoScore: 12,
redTeleopScore: 28,
redEndgameScore: 5,
},
];
const epaResults = calculateEPA(matches);
const team12345 = epaResults.get(12345);
EPA Result Structure
interface EPAResult {
teamNumber: number;
epa: number;
autoEpa: number;
teleopEpa: number;
endgameEpa: number;
matchCount: number;
recentEpa?: number;
trend?: "up" | "down" | "stable";
}
Adaptive K-Factor
K-factor determines how much each new match affects the rating:
function getAdaptiveKFactor(matchCount: number): number {
const minK = 0.1;
const maxK = 0.4;
const decayRate = 0.1;
return Math.max(minK, maxK * Math.exp(-decayRate * matchCount));
}
OPR (Offensive Power Rating)
OPR uses linear algebra to decompose alliance scores into individual team contributions.
Algorithm Overview
- Build matrix of alliance compositions
- Build vector of alliance scores
- Solve using iterative least squares
- Each team's OPR = their contribution to alliance score
Usage
import { calculateOPR, type MatchForOPR } from "@ftcmetrics/api/lib/stats";
const matches: MatchForOPR[] = [...];
const oprResults = calculateOPR(matches);
const team12345 = oprResults.get(12345);
OPR Result Structure
interface OPRResult {
teamNumber: number;
opr: number;
autoOpr: number;
teleopOpr: number;
endgameOpr: number;
dpr: number;
ccwm: number;
}
Match Predictions
Predictions combine EPA values to estimate match outcomes:
interface PredictionResult {
redExpectedScore: number;
blueExpectedScore: number;
redWinProbability: number;
blueWinProbability: number;
predictedMargin: number;
}
Prediction Formula
const redExpected = baseline + redTeam1EPA + redTeam2EPA;
const blueExpected = baseline + blueTeam1EPA + blueTeam2EPA;
const margin = redExpected - blueExpected;
const redWinProb = 1 / (1 + Math.exp(-margin / SCORE_VARIANCE));
DECODE Season Baselines
const DECODE_BASELINE = {
autoScore: 8,
teleopScore: 25,
endgameScore: 5,
totalScore: 38,
};
These baselines are updated dynamically based on actual match data.
Common Patterns
Calculate Rankings for Event
async function getEventRankings(eventCode: string) {
const matches = await fetchMatchesWithScores(eventCode);
const epaResults = calculateEPA(matches);
const oprResults = calculateOPR(matches);
const rankings = Array.from(epaResults.values())
.map((epa) => ({
...epa,
opr: oprResults.get(epa.teamNumber),
}))
.sort((a, b) => b.epa - a.epa);
return rankings;
}
Track EPA Over Time
interface EPAHistory {
teamNumber: number;
eventCode: string;
matchNumber: number;
epaValue: number;
recordedAt: Date;
}
Anti-Patterns
- ❌ Using OPR for predictions (use EPA instead - OPR overfits to past data)
- ❌ Calculating EPA without sorting matches chronologically
- ❌ Using static K-factor (adaptive K improves accuracy)
- ❌ Ignoring component scores (auto/teleop/endgame give better insights)
Implementation Files
packages/api/src/lib/stats/epa.ts - EPA calculator
packages/api/src/lib/stats/opr.ts - OPR calculator
packages/api/src/routes/analytics.ts - Analytics API endpoints
References