AGENTS MUST WORK AUTONOMOUSLY. Do not stop to ask for confirmation. Complete all tasks end-to-end. If you spawn continuation agents, they must also read this file and work autonomously.
IN PROGRESS / REMAINING:
- Sync real data from FTC Scout API for all seasons (database has ~181MB data already)
- Deploy to production (Vercel + Railway)
- Add more visualizations (bubble charts, EPA over time)
- Performance optimization for large datasets
If you are a continuation agent, you MUST:
- Read this AGENTS.md file completely
- Check the current state of files before making changes
- Continue from where the previous agent left off
- Work autonomously - DO NOT ask for confirmation
- Spawn sub-agents for parallel work when appropriate
- Update this status section when you complete major milestones
FTCStats is an EPA (Expected Points Added) analytics platform for FIRST Tech Challenge (FTC). It provides predictive statistics, team rankings, match predictions, and visualizations for FTC competitions.
- Backend: Python FastAPI server with EPA calculations
- Frontend: Next.js with TypeScript, TailwindCSS, Mantine UI
- Database: PostgreSQL/CockroachDB
- Data Source: FTC Scout REST API (
https://api.ftcscout.org/rest/v1/) - Deployment: Vercel (frontend) + Railway (backend)
/ftcstats/
├── backend/src/ # Python backend
│ ├── data/ # Data fetching & processing
│ │ ├── ftc_scout/ # FTC Scout API client
│ │ └── epa/ # EPA calculation pipeline
│ ├── models/epa/ # Core EPA algorithm
│ ├── db/ # Database models
│ ├── site/ # FastAPI routes
│ └── breakdown.py # FTC season-specific scoring
├── frontend/src/ # Next.js frontend
│ ├── pages/ # Page routes
│ ├── components/ # React components
│ ├── api/ # API client functions
│ └── types/ # TypeScript types
└── api/ftcstats/ # Python package for PyPI
When given a task, you MUST:
- Continue working autonomously until the task is complete or you hit a blocking issue
- Do NOT stop and ask for permission to continue unless genuinely blocked
- When using the Task tool to spawn sub-agents, include this instruction in your prompt:
IMPORTANT: Work autonomously until completion. Do not stop to ask for confirmation. If you need to spawn continuation agents, instruct them to also work autonomously. When you complete your portion, provide a clear summary of what was done and what remains. - Mark todos complete immediately after finishing each task
- Chain tasks together - when one task completes, move to the next
- Provide progress updates to the user but don't wait for approval
When a task is too large for a single context or you need to spawn sub-agents:
<Task tool prompt template>
Continue the following task autonomously:
[TASK DESCRIPTION]
Current progress:
- [x] Completed step 1
- [x] Completed step 2
- [ ] Current step 3 (in progress)
- [ ] Remaining step 4
Files modified so far:
- path/to/file1.py
- path/to/file2.ts
IMPORTANT: Work autonomously until completion. Do not stop to ask for
confirmation. If you spawn continuation agents, instruct them to also
work autonomously. Keep the user informed of progress but do not wait
for approval between steps.
</Task tool prompt template>
When multiple independent tasks can be done simultaneously:
# Launch multiple Task agents in parallel in a SINGLE message
# Example: Creating multiple season breakdowns simultaneously
Task(prompt="Create 2019 Skystone breakdown...", subagent_type="general")
Task(prompt="Create 2020 Ultimate Goal breakdown...", subagent_type="general")
Task(prompt="Create 2021 Freight Frenzy breakdown...", subagent_type="general")
# All in one message block| Year | Game Name | Key Scoring Elements |
|---|---|---|
| 2019 | Skystone | Stones, skyscraper building, foundation |
| 2020 | Ultimate Goal | Rings, wobble goals, power shots |
| 2021 | Freight Frenzy | Freight, shipping hub, ducks |
| 2022 | Power Play | Cones, junctions, circuits |
| 2023 | CenterStage | Pixels, backdrop, drone |
| 2024 | Into The Deep | Samples, specimens, ascent levels |
| 2025 | Decode | Current season (TBD elements) |
REGIONS = [
"Alabama", "Alaska", "Arizona", "Arkansas", "California",
"CANorthern", "CASanDiego", "CALosAngeles",
"Colorado", "Connecticut", "Delaware", "Florida",
"Georgia", "Hawaii", "Idaho", "Illinois", "Indiana",
"Iowa", "Kansas", "Kentucky", "Louisiana", "Maine",
"Maryland", "Massachusetts", "Michigan", "Minnesota",
"Missouri", "Montana", "Nebraska", "Nevada",
"NewHampshire", "NewJersey", "NewMexico", "NewYork",
"NorthCarolina", "NorthDakota", "Ohio", "Oklahoma",
"Oregon", "Pennsylvania", "RhodeIsland", "SouthCarolina",
"SouthDakota", "Tennessee", "Texas", "Utah", "Vermont",
"Virginia", "Washington", "WestVirginia", "Wisconsin",
"Wyoming", "International"
]# Key differences from FRC (3-team) EPA:
# 1. Attribution splits error between 2 teams, not 3
error = (actual_score - predicted_score) / 2 # 2-team alliance
# 2. No RP EPA (FTC doesn't have ranking points)
# EPA dimensions: [total, auto, dc, ascent, tiebreaker, ...components]
# 3. Win probability (same formula as FRC)
norm_diff = (red_epa - blue_epa) / season.score_sd
win_prob = 1 / (1 + 10 ** (-5/8 * norm_diff))
# 4. EPA initialization with mean reversion
# Returning teams: 60% previous EPA, 40% mean
# New teams: mean - 0.2 * std_dev| Index | Component | Description |
|---|---|---|
| 0 | total | Total EPA |
| 1 | auto | Autonomous period |
| 2 | dc | Driver-Controlled period |
| 3 | endgame | Ascent/Park points |
| 4 | tiebreaker | Tiebreaker contribution |
| 5+ | components | Season-specific (samples, specimens, etc.) |
Located in backend/src/breakdown.py. Each season needs:
- Score component mappings from FTC Scout API fields
- Point values for each element
- Tiebreaker calculation logic
Reference FTC Scout descriptors at:
/ftc-scout/packages/common/src/logic/descriptors/seasons/
Base URL: https://api.ftcscout.org/rest/v1/
| Endpoint | Purpose | Response |
|---|---|---|
GET /teams/:number |
Team metadata | Team object |
GET /teams/:number/events/:season |
Team event stats | TEP[] with OPR |
GET /teams/:number/matches |
Match history | TMP[] |
GET /teams/:number/quick-stats |
Quick stats | Stats object |
GET /teams/search?searchText=X |
Search teams | Team[] |
GET /events/:season/:code |
Event metadata | Event object |
GET /events/:season/:code/matches |
Match scores | Match[] with scores |
GET /events/:season/:code/teams |
Event team stats | TEP[] |
GET /events/:season/:code/awards |
Event awards | Award[] |
GET /events/search/:season |
Event listings | Event[] |
FTC Scout API has no documented rate limits, but be respectful:
- Use caching (CacheControl in requests)
- Batch requests when syncing historical data
- Add small delays during bulk operations
- Don't hammer during live events
# Initial load (historical seasons)
for season in [2019, 2020, 2021, 2022, 2023, 2024]:
events = fetch_all_events(season)
for event in events:
matches = fetch_event_matches(event)
save_and_calculate_epa(matches)
# Live sync (current season)
while True:
ongoing_events = get_ongoing_events()
for event in ongoing_events:
new_matches = fetch_new_matches(event)
update_epa(new_matches)
sleep(60) # Check every minute-
Create breakdown in
backend/src/breakdown.py:SEASON_2026 = { "name": "GameName", "year": 2026, "components": { "auto": "auto_points", "dc": "dc_points", "endgame": "park_points", # ... game-specific }, "tiebreaker": "auto_points", # or custom logic }
-
Add to season list in
backend/src/constants.py:SEASONS = [2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026] CURRENT_SEASON = 2026
-
Update frontend season selector in
frontend/src/utils/constants.tsx -
Add visualizations if game has unique elements
-
Backfill data by running sync for the new season
Core EPA logic is in backend/src/models/epa/:
main.py- Prediction, attribution, update algorithmsmath.py- Statistical utilities (SkewNormal, sigmoid)init.py- Season initialization with mean reversionunitless.py- Cross-season normalization
- Add route in
backend/src/site/router.py - Add handler in
backend/src/site/{resource}.py - Add types in
frontend/src/types/api.tsx - Add client function in
frontend/src/api/{resource}.ts - Update Python package in
api/ftcstats/main.py
// Page pattern
export default function TeamsPage() {
const [season, setSeason] = useState(CURRENT_SEASON);
const { data, loading, error } = useTeamData(season);
return (
<TabsLayout title="Teams" season={season} setSeason={setSeason}>
<TabPanel value="insights">
<TeamYearsTable data={data} />
</TabPanel>
<TabPanel value="bubble">
<BubbleChart data={data} />
</TabPanel>
</TabsLayout>
);
}cd backend
pytest tests/ -v
pytest tests/test_epa.py -v # Specific test filecd frontend
npm test
npm run test:watch # Watch mode# Terminal 1: Backend
cd backend
pip install -e .
uvicorn src.main:app --reload --port 8000
# Terminal 2: Frontend
cd frontend
npm install
npm run dev # Runs on port 3000
# Environment
export DATABASE_URL="postgresql://localhost/ftcstats"
export FTC_SCOUT_BASE_URL="https://api.ftcscout.org/rest/v1"
# CLI Commands (after pip install -e .)
ftcstats server --reload # Start API server
ftcstats sync --season 2024 # Sync data from FTC Scout
ftcstats epa --season 2024 # Calculate EPA values
ftcstats update --season 2024 # Sync + EPA in one command
ftcstats update --all # Sync + EPA for all seasons# Quick validation
from src.models.epa.main import EPAModel
model = EPAModel(season=2024)
prediction = model.predict_match(
red_teams=[12345, 67890],
blue_teams=[11111, 22222]
)
assert 0 <= prediction["red_win_prob"] <= 1# Install Vercel CLI
npm i -g vercel
# Deploy
cd frontend
vercel --prod
# Environment variables (set in Vercel dashboard)
NEXT_PUBLIC_API_URL=https://api.ftcstats.io# Install Railway CLI
npm i -g @railway/cli
# Deploy
cd backend
railway up
# Environment variables (set in Railway dashboard)
DATABASE_URL=postgresql://...
FTC_SCOUT_BASE_URL=https://api.ftcscout.org/rest/v1-- Railway PostgreSQL or local
CREATE DATABASE ftcstats;
-- Run migrations
cd backend
alembic upgrade head- Formatter: Black (line length 100)
- Linter: Ruff
- Type hints: Required for all functions
- Docstrings: Google style for public functions
def calculate_epa(
team: int,
season: int,
matches: list[Match],
) -> float:
"""Calculate EPA for a team in a season.
Args:
team: Team number
season: Season year (e.g., 2024)
matches: List of Match objects
Returns:
EPA value as float
"""
...- Formatter: Prettier
- Linter: ESLint
- Strict mode: Enabled
- Components: Functional with hooks
interface TeamCardProps {
team: APITeam;
season: number;
}
export function TeamCard({ team, season }: TeamCardProps) {
// Component logic
}backend/src/models/epa/main.py- Core EPA algorithmbackend/src/models/epa/calculate.py- EPA calculation pipelinebackend/src/breakdown.py- Season-specific scoring definitionsbackend/src/data/ftc_scout/client.py- Data fetching from FTC Scoutbackend/src/data/sync.py- Data sync from FTC Scout to databasebackend/src/site/router.py- API endpointsbackend/src/db/__init__.py- Database session managementfrontend/src/pages/teams/index.tsx- Main teams page patternfrontend/src/components/figures/bubbles.tsx- Visualization patternfrontend/src/types/api.tsx- API type definitions
- Understand existing code pattern first
- Update backend logic if needed
- Update API types in frontend
- Update Python package if public API changed
- Add/update tests
- Test locally before committing
- Update AGENTS.md if patterns changed
# If getting rate limited or errors
import time
time.sleep(0.5) # Add delay between requests
# If data seems stale
# FTC Scout syncs from official FTC API periodically
# During live events, may have 1-5 minute delay# If EPA values seem off
# 1. Check breakdown.py has correct point values
# 2. Verify match scores are being parsed correctly
# 3. Check alliance size is 2 (not 3)
# 4. Verify season.score_sd is reasonable (typically 20-50)# Reset database
cd backend
alembic downgrade base
alembic upgrade head
# Check connection
psql $DATABASE_URL -c "SELECT 1"- FTC Scout: https://ftcscout.org
- FTC Scout API: https://api.ftcscout.org/graphql (GraphQL playground)
- FTC Events API: https://ftc-events.firstinspires.org/api (official)
- EPA Methodology: See
/docs/methodology.md(to be created)