.claude/skills/analytics/SKILL.md
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.
npx skillsauth add ftc8569/ftcmetrics analyticsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
FTC Metrics uses two primary rating systems to evaluate team performance:
| Metric | Best For | Updates | |--------|----------|---------| | EPA | Predicting future performance | After each match | | OPR | Analyzing historical contribution | Recalculated per event |
EPA represents how many points above/below average a team contributes to their alliance score.
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,
// ... blue scores
},
];
const epaResults = calculateEPA(matches);
const team12345 = epaResults.get(12345);
// { epa: 5.2, autoEpa: 1.5, teleopEpa: 3.2, endgameEpa: 0.5, ... }
interface EPAResult {
teamNumber: number;
epa: number; // Total EPA
autoEpa: number; // Autonomous phase
teleopEpa: number; // Teleop phase
endgameEpa: number; // Endgame phase
matchCount: number; // Matches played
recentEpa?: number; // Last 5 matches average
trend?: "up" | "down" | "stable";
}
K-factor determines how much each new match affects the rating:
// High K (0.4) for new teams = ratings change quickly
// Low K (0.1) for experienced teams = ratings stabilize
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 uses linear algebra to decompose alliance scores into individual team contributions.
import { calculateOPR, type MatchForOPR } from "@ftcmetrics/api/lib/stats";
const matches: MatchForOPR[] = [...]; // Match data
const oprResults = calculateOPR(matches);
const team12345 = oprResults.get(12345);
// { opr: 22.5, autoOpr: 6.0, teleopOpr: 14.0, endgameOpr: 2.5, dpr: 3.2, ccwm: 19.3 }
interface OPRResult {
teamNumber: number;
opr: number; // Offensive Power Rating
autoOpr: number; // Auto contribution
teleopOpr: number; // Teleop contribution
endgameOpr: number; // Endgame contribution
dpr: number; // Defensive Power Rating
ccwm: number; // Calculated Contribution to Winning Margin
}
Predictions combine EPA values to estimate match outcomes:
interface PredictionResult {
redExpectedScore: number;
blueExpectedScore: number;
redWinProbability: number;
blueWinProbability: number;
predictedMargin: number;
}
// Sum EPAs and add baseline
const redExpected = baseline + redTeam1EPA + redTeam2EPA;
const blueExpected = baseline + blueTeam1EPA + blueTeam2EPA;
// Win probability using logistic function
const margin = redExpected - blueExpected;
const redWinProb = 1 / (1 + Math.exp(-margin / SCORE_VARIANCE));
const DECODE_BASELINE = {
autoScore: 8, // Average auto per alliance
teleopScore: 25, // Average teleop per alliance
endgameScore: 5, // Average endgame per alliance
totalScore: 38, // Average total per alliance
};
These baselines are updated dynamically based on actual match data.
async function getEventRankings(eventCode: string) {
const matches = await fetchMatchesWithScores(eventCode);
const epaResults = calculateEPA(matches);
const oprResults = calculateOPR(matches);
// Combine and sort by EPA
const rankings = Array.from(epaResults.values())
.map((epa) => ({
...epa,
opr: oprResults.get(epa.teamNumber),
}))
.sort((a, b) => b.epa - a.epa);
return rankings;
}
// Store EPA after each match for trend analysis
interface EPAHistory {
teamNumber: number;
eventCode: string;
matchNumber: number;
epaValue: number;
recordedAt: Date;
}
packages/api/src/lib/stats/epa.ts - EPA calculatorpackages/api/src/lib/stats/opr.ts - OPR calculatorpackages/api/src/routes/analytics.ts - Analytics API endpointsdevelopment
Configure TypeScript in a Bun/npm workspaces monorepo with shared base config, package-specific overrides, path aliases, and cross-package type sharing. Use when setting up tsconfig files, configuring path aliases, resolving module errors, or sharing types between packages.
development
Tailwind CSS styling for FTC Metrics with official FIRST colors and component patterns. Use when styling React components, creating responsive layouts, or implementing dark mode.
development
Configure and integrate Soketi WebSocket server with FTC Metrics for real-time updates. Use when setting up WebSocket connections, broadcasting scouting data changes, implementing presence channels for team collaboration, or debugging real-time features.
development
Create, structure, and optimize skills for the FTC Metrics project. Use when creating a new skill, improving an existing skill, or needing guidance on skill design patterns, triggers, frontmatter, and progressive disclosure.