Skip to main content
Back to Racey School
Pro
10 min read
Pro-tier league admins and stewards

AI Power-User Guide

When to use Racey's three AI features (Protest Summary, Race Report, Scoring Recommendation), what each one does, and where they fall short.

Know which AI feature to reach for in each workflow
Understand the human-in-the-loop guardrails and rate limits
Spot the cases where AI output should be discarded, not edited
Use this guide during beta
1
Generate one AI Protest Summary on a real protest and review the output
2
Draft an AI Race Report after a published round and edit it down
3
Confirm the Pro plan gate behavior on a Free league
Open quick reference

Racey ships three AI features for league admins and stewards. Each one is a focused tool that takes data already in your league (a protest, a set of race results, your league configuration) and produces a draft you can edit, copy, or discard. The AI does not make decisions for you. It writes the first draft so you can spend your time reviewing instead of typing.

This guide covers when to use each feature, what to expect from the output, where it shows up in the UI, and where it falls short.

Pro tier required

All three AI features are gated behind the Pro plan (also included in Enterprise). On a Free league:

  • AI Protest Summary — the steward sees an "AI Summary — Pro" upgrade button instead of the live action. Flag: aiProtestSummary.
  • AI Race Report — the league admin sees the button on the Results page rendered as a Pro upgrade badge. Flag: aiRaceReport.
  • AI Scoring Recommendation — the "Analyze with AI" button on the scoring page redirects to /pricing?tier=pro&feature=ai-scoring. Flag: aiScoringRecommendation.

The plan gate is enforced in two places: the UI hides or redirects the button, and the API route returns 402 PLAN_REQUIRED if a Free-plan league tries to call the endpoint directly. Founding leagues and platform admins always see the live buttons regardless of plan.

If you are on Pro and a button is still hidden, it usually means the platform-wide ai_features_enabled feature flag is off (operator action) or ANTHROPIC_API_KEY is unset on the server (the route returns 503 AI_UNAVAILABLE).

Philosophy: assist, don't replace

These features are deliberately scoped to drafting and recommendation, not decision-making. They share three rules:

  1. Human in the loop. Every AI output appears in a dialog or side panel that you can copy, regenerate, edit, or dismiss. Nothing is published, applied, or sent to drivers automatically.
  2. Neutral framing. Prompts forbid the model from assigning blame in protests or speculating beyond the supplied data in race reports. Output reads like a draft from a junior assistant, not a verdict.
  3. Cited inputs only. The model only sees the structured league data we pass to it (protest description, race result rows, league configuration). It does not browse the web, read your other leagues, or have memory across runs.

If you are looking for full automation, these aren't it. If you want to cut a 20-minute task down to a 5-minute review, they will.


AI Protest Summary

A neutral, 2–3 sentence summary of an open protest, plus a short list of key points stewards should focus on. Lives in the steward ruling workspace.

When to use

  • A protest description runs to several paragraphs, references multiple drivers, or mixes timeline and opinion. You want a neutral restatement before you write your ruling.
  • You are catching up on a protest you did not file or witness, and want a fast read of what allegedly happened.
  • You are writing the "Reasoning" section of a ruling and want a starting paragraph you can edit instead of staring at an empty textarea.

If the protest is a one-line "T1 lap 1 contact, P5 hit P3" you do not need this. Use it on the messy ones.

What it does

When you click AI Summary in the "Issue Ruling" card on a protest page (/steward/protests/[protestId]), the steward UI calls POST /api/ai/protest-summary with just the protest id. The server fetches the protest, all parties (complainant, respondent, witnesses), the round and track context, and any attached evidence text, then asks Claude to summarize.

The model is instructed to:

  • Stay completely neutral — no blame, no fault assignment.
  • Use stewarding-panel language, not casual prose.
  • Identify involved parties by display name.
  • Limit the summary to 2–3 sentences.
  • Surface 2–4 key points stewards should focus on.

The response renders in a side drawer. You can:

  • Copy to reasoning — pastes the summary text straight into the ruling explanation textarea.
  • Regenerate — runs the prompt again with a fresh model call (often produces slightly different phrasing).
  • Close the drawer and ignore it.

A small "AI-generated. Always review for accuracy before using in a ruling." footer is included on the panel itself — that line is non-removable on purpose.

Limitations

  • The model only sees what's in the protest record. If the description omits a critical detail (e.g., "P3 was on cold tires after a yellow"), the summary will not include it. Garbage in, garbage out.
  • Evidence files are not parsed. If a steward attaches a video link or a screenshot, the model sees the URL or filename, not the contents. Telemetry, .ibt files, and image OCR are not part of the input.
  • Hallucination risk on edge cases. The model may occasionally invent a detail (e.g., a corner number that wasn't in the description). Always cross-check against the original protest before quoting.
  • No legal weight. This is a drafting aid. The actual ruling is yours. Copying the summary verbatim into a ruling is your call, not the platform's recommendation.
  • Not retroactive. This feature was added in April 2026; protests filed before then still work, but the feature did not exist when they were originally being adjudicated.

Example output

For a protest like:

Lap 12 at Spa, T5 (Les Combes). Complainant entered the corner inside, respondent had nose alongside on the outside. Respondent did not yield, contact made on apex, complainant spun and lost three positions. Multiple replays attached.

The model returns roughly:

{
  "summary": "On lap 12 at Spa-Francorchamps Turn 5 (Les Combes), an incident occurred between the complainant and respondent when both drivers were occupying the same corner. The complainant entered on the inside while the respondent was alongside on the outside, resulting in contact at the apex. The complainant subsequently spun and lost three positions.",
  "keyPoints": [
    "Both drivers had a reasonable claim to the corner at entry",
    "Contact occurred at the apex, not on entry or exit",
    "Multiple video replays are attached for review",
    "Complainant suffered measurable position loss (3 places)"
  ]
}

The steward UI renders the summary paragraph in the drawer and the Copy to reasoning button drops just the summary text into the ruling explanation field. Key points are shown alongside but are not auto-copied.


AI Race Report

A 3–5 paragraph post-race recap in motorsport-journalism style, plus a short headline. Lives on the league results page.

When to use

  • You publish race recaps to your league's Discord, website, or newsletter and want a draft instead of a blank page.
  • You are running a multi-round season and writing every recap by hand is the bottleneck between race night and publishing results.
  • You want a sanity check on the narrative of a race — who actually won the championship implications, which drivers had standout drives — before you write your own version.

If your league does not publish recaps, skip this. The feature exists to remove writing friction, not to create a publishing obligation.

What it does

On the league results page (/league/[slug]/results), select a round, then click AI Report in the round results card header. The button is disabled until you have a round selected with at least one race session and one classified result.

The UI calls POST /api/ai/race-report with the round id. The server fetches the round, its primary race session, every classified result (positions, start positions, laps led, incidents, finish status, gaps), and the top-10 championship standings after the round. That structured data is fed to Claude with instructions to:

  • Lead with the winner and the key story of the race.
  • Mention notable battles, overtakes, and position changes (start vs. finish).
  • Note DNFs and DSQs and their impact on the race.
  • Call out drivers who gained or lost the most positions.
  • Reference championship implications when standings are provided.
  • Use driver names — never "the leader" or "a midfield runner."
  • Avoid speculation beyond what the data shows.

The output appears in a dialog with three actions:

  • Copy — copies the headline + report to your clipboard for pasting into Discord, an email, your league site, etc.
  • Regenerate — runs the prompt again. Useful if the first draft missed an angle.
  • Post to Discord — only shown when the league has a Discord webhook configured (league.discordWebhookUrl) and Discord notifications enabled. Calls POST /api/leagues/[leagueId]/discord/publish-race-report with the headline and report body.

Heads up: external (non-registered) drivers are excluded from the AI input today. If a guest racer finished P3, they will not appear in the report. Race reports cover registered drivers only. This is by design until we settle on an anonymization rule for external entries.

Limitations

  • Numbers > storytelling. The model is given positions, gaps, lap counts, and incident counts — it does not see in-car footage or stewarding outcomes. A race that "felt" close because of a 30-lap battle for P8 will not get the same treatment as one with a dramatic lead change.
  • No protest awareness. Open protests, post-race penalties applied after the report runs, or DSQs added later are not reflected. Regenerate after final classification.
  • External drivers are skipped. As noted above. If half your field is non-registered guests, the report will read like a half-empty grid.
  • 80-character headline cap. The model is instructed to keep headlines short. Expect punchy, occasionally generic.
  • Style is "official recap." It will not write trash talk, fan-fiction, or driver-quote interviews. If you want personality, edit after generating.
  • Standings context is top-10 only. Implications for P11+ in the championship are not surfaced.

Example output

For a fictional 24-driver sprint at Monza with a championship leader retiring on lap 1:

{
  "headline": "Hartley Cashes In As Reyes DNFs From Pole at Monza",
  "report": "James Hartley took a commanding victory in Round 4 of the Midnight GT3 Championship at Monza, capitalizing on a chaotic opening lap that saw championship leader Marco Reyes retire from pole position with mechanical failure. Hartley converted his second-row start into the lead by the exit of the Variante della Roggia and never looked back, opening a 4.2-second gap by the chequered flag.\n\nThe race for second became the day's defining battle, with Sofia Aslanian and Theo Marchand swapping positions four times across the final ten laps before Aslanian made the move stick at the second chicane on the penultimate lap. Marchand settled for third, his first podium of the season after starting P8.\n\nFurther back, the standout drive came from Imani Okafor, who recovered from a lap-one spin to finish P7 from a dead-last restart — a gain of seventeen positions. Two DNFs (Reyes mechanical, Petrov contact damage) and one DSQ (Bauer for a track-limits accumulation post-race) reshaped the order behind the podium.\n\nWith Reyes scoring no points, Aslanian closes the championship gap to eleven points heading into Round 5 at Imola. Hartley moves into outright third in the standings, eighteen points off the lead."
}

(The above is illustrative — generated to show shape and tone, not a real race.) The dialog renders the headline as the bold subtitle and the report body as wrapping prose underneath.


AI Scoring Recommendation

A one-shot recommendation of which scoring preset best fits your league, with a 2–3 sentence rationale and up to 3 suggested customizations. Lives on the league scoring configuration page.

When to use

  • You are setting up a new league and the eight presets (f1, nascar, indycar, imsa, oval_weekly, road_weekly, gt3, heat_feature) all look plausible and you do not know which to pick.
  • You inherited a league or are running a new format (multiclass endurance, heat + feature) and want a sanity check on whether your current preset still fits.
  • You are pitching a scoring change to your league and want a third-party rationale to share with drivers.

If you already know exactly which preset you want and how to customize it, skip this — the configuration UI is faster.

What it does

On the league scoring page (/league/[slug]/scoring), click Analyze with AI next to the preset selector. The UI calls POST /api/ai/scoring-recommendation with just the league id. The server reads the league's discipline (road, oval, dirt, endurance, multiclass), counts approved + pending registrations across all seasons, infers the race format and average race length from the most recent season's settings, and detects whether multiple car classes are in use.

That league shape is fed to Claude with the rules of each preset and instructions to recommend exactly one. The model can also suggest up to three customizations (e.g., "add 1 drop week", "increase laps-led bonus to 2 points") if the preset doesn't fit perfectly.

The recommendation renders in a dialog showing:

  • The current preset (for comparison).
  • The recommended preset, highlighted.
  • The reasoning, as the dialog description.
  • Any suggested customizations as a labeled key-value list.

You can Apply the recommendation (which loads the recommended preset into your scoring form — a confirm step protects against accidental clicks) or Dismiss. Customizations are advisory; they are not auto-applied. You implement them by editing the scoring fields after the preset loads.

If the model returns the same preset you already have with no customizations, the dialog shows "Already applied" and disables the Apply button.

Limitations

  • Heuristic, not optimization. The recommendation is based on discipline + driver count + format heuristics. It is not running a simulation against your historical results to find the points system that maximizes championship excitement.
  • Snapshot in time. It uses your current registration count and most recent season's format. If you are mid-pivot from sprint to endurance, run it again after you have updated your settings.
  • Eight presets only. The model is constrained to recommend one of the eight built-in presets. It will not invent a custom point scale from scratch — for that, build manually after applying the closest preset.
  • Customizations are descriptions, not edits. If the model says "add a clean-race bonus of 1 point", you have to go open that field and add it yourself. The dialog shows the suggestion; the form does not pre-fill.
  • No notion of your league culture. It does not know whether your drivers want depth at the back, top-heavy drama at the front, or a forgiving drop-week scheme. Use the suggestion as a starting point.

Example output

For a 28-driver multiclass road racing league running 90-minute races:

{
  "preset": "imsa",
  "reasoning": "Multiclass + endurance-length races (90 minutes) point strongly to IMSA-style scoring, which natively handles per-class results and rewards finishing position with a deep enough field for 28 drivers. The F1-style points distribution scales well across two or more classes without flattening the title fight.",
  "customizations": {
    "Add 1 drop week": "Useful for an endurance season where mechanical DNFs are more common than in sprints.",
    "Enable team scoring": "Recommended for multiclass since drivers often share a car across stints; team standings give the partnership its own championship."
  }
}

(Illustrative — generated to show shape.) The dialog renders imsa as the recommended preset, the reasoning as the description, and the two customizations as a definition list.


Rate limits

All three AI endpoints share the same rate-limit budget shape but use separate keys, so generating a race report does not eat into your protest-summary budget for the day.

EndpointLimitWindowKey prefixKeyed on
AI Protest Summary10 requests15 minutesai:protest-summary:userId (composed with IP)
AI Race Report10 requests15 minutesai:race-report:userId
AI Scoring Recommendation10 requests15 minutesai:scoring-rec:userId

When you exceed a limit you get HTTP 429 RATE_LIMITED with a "Rate limit exceeded. Try again later." toast. The window is sliding, so a request you made 15 minutes ago drops out of your budget continuously, not in fixed buckets.

Source: docs/security/rate-limits.md (rate-limit audit details — every AI surface enforces both the middleware tier limit and a per-feature handler limit).

If you are hitting these in normal use, something is off — the limits are sized to absorb "regenerate four times in a row" but not "loop the endpoint from a script." Reach out if you have a legitimate workflow that needs more.


  • Racey School — table of contents
  • Steward Guide — full protest, voting, ruling, and appeals workflow (AI Protest Summary slots into Section 7 "Issuing a Ruling")
  • League Admin Guide — covers scoring setup (Section 4) and publishing results (Section 9), the two surfaces where AI Race Report and AI Scoring Recommendation live