DEV Community

Cover image for I didn't understand F1, so I built a quiz app to teach myself
Hem Bahadur Pun
Hem Bahadur Pun Subscriber

Posted on

I didn't understand F1, so I built a quiz app to teach myself

Sanity Challenge Path One Submission

This is a submission for the Sanity Challenge, Path One: Ship an Agent That Queries Real Content

What I Built

I want to get into Formula 1, but I didn't know what pole, DNF or undercut meant. Pit Wall is the quiz I built to learn.

Pick a 2026 Grand Prix and a tyre. The start lights go out and you answer five questions, one per lap. After each answer, a card explains the F1 term the question used.

Tyre Asks about
Soft one fact from the race
Medium comparing drivers
Hard the season so far

An agent writes every question, but it never grades you. With each question it also writes a GROQ query that answers it. The server runs that query against real race results in Sanity, and the result becomes the answer.

After an answer: the verdict, the F1 term explained, and the timing tower sliding into the finishing order

To check this matters, I gave 30 questions from the app to the same model with no access to the data.

Correct
Pit Wall (answer from a GROQ query) 30 / 30
Same model, no data 16 / 30
Same model, Hard questions only 3 / 10

Random guessing gets 7 or 8. Asked who had the most podiums after Monaco, the model said Max Verstappen. The data says Kimi Antonelli.

Demo

Play Pit Wall. There's no sign-up. Pick a three-letter driver code and race.

The intro

Pick a Grand Prix and a tyre

The start lights before lap 1

A question, with keyboard hints and team-colour helmets

Results: F1 points and a review of every lap

Code

github.com/hempun10/pit-wall. The README covers setup from an empty Sanity project.

How I Used Sanity

How Pit Wall fits together

The race data

A Python script converts the F1DB SQLite release (CC BY 4.0) into 6,254 Sanity documents. They cover 2014 to round 15 of 2026.

One raceResult is one driver in one race. It references its race, driver and team, so a single query answers "who finished on the podium in Azerbaijan?"

*[_type == "raceResult" && year == 2026 && round == 15 && position <= 3]
  | order(position asc) {position, "driver": driver->name, "team": team->name}
Enter fullscreen mode Exit fullscreen mode

The agent's instructions warn about three traps in the data.

  • points counts Grand Prix points only. Sprint points live in the standings documents.
  • Pole is polePosition == true. gridPosition == 1 is wrong after a grid penalty.
  • pitStops: 0 in a dry race means missing data. Every driver has to use two tyre compounds.

Two Context endpoints

Endpoint Source Answers
pit-wall-data the dataset, GROQ mode facts and numbers
pit-wall-rules a Knowledge Base what F1 words mean

An endpoint with both sources ignores the Knowledge Base without an error, so Pit Wall uses two. Both expose a tool named initial_context, so the agent gets each set with a prefix.

The data endpoint needs a deployed Studio. Run sanity deploy first, or every call fails.

The Knowledge Base

Nine Wikipedia articles became 15 entries. I added one stale article on purpose, a June 2023 revision that still gives a bonus point for the fastest lap. F1 dropped that rule in 2025.

The build flagged it. One entry said the point was gone, and the glossary said it still existed. The race data settled it. Here is what a winner who also set the fastest lap scored:

Season Points
2018 25
2019 26
2024 26
2025 25
2026 25

Two Knowledge Base entries disagreed about the fastest-lap point; the race data settled it

I resolved it to "abolished from 2025". The build flagged three more conflicts. I resolved two from the sources and dismissed one that misread a date range.

How a question gets made

How a question gets made, in six steps

  1. The agent (gpt-5.4-mini on the Vercel AI SDK) gets the round, the tyre and the questions already asked.
  2. It explores the data with groq_query and writes the query that answers its question.
  3. It reads one Knowledge Base entry to explain the F1 term.
  4. It calls submit_question.
  5. The server runs the query. If the result isn't a single string or number, the agent gets one retry with the error.
  6. The server encrypts the answer (AES-GCM) into the question. The browser can't read it, and the server stores nothing per player.

Every answer screen has a "How we checked this" drawer with the exact query that graded you.

The question bank

Writing a question live took 20 to 50 seconds and up to 55,000 input tokens. Now the server saves every verified question as a quizQuestion document and serves it from Sanity in about a second.

Serving a question: bank first, the agent only when the bank runs dry

The agent writes a new question only when you've seen every stored one for that round and tyre. It stops at six per round and tyre, which also caps the cost. The bank holds 222 questions.

Live questions also got cheaper. The server cancels the OpenAI request when you leave, the agent gets only the three tools it uses, and large query results get cut off so the agent narrows its query.

Team radio

After an answer, you can ask your race engineer a question. A live agent answers from both endpoints and lists the queries and rules entries it used.

Team radio: the engineer's reply, with the exact query it ran

When the data can't answer, it says so. Asked why a driver made one pit stop, it confirmed the count and said the data doesn't record the reason. You get two messages per lap.

The championship

A race scores like F1's top five. Five correct answers earn 25 points, then 18, 15, 12 and 10. Your best result per race and tyre counts, and ties go to whoever got there first. Each finished race is a quizRun document.

There are no accounts, only a three-letter code. Your score travels in an encrypted token, so the browser can't change it. Replaying a question, forging a token and finishing a race twice all fail.

What I'd do differently

  • Vary the questions. Hard often asks "most podiums after round X", and Antonelli has won 8 of 15 races, so the answer rarely changes.
  • Add optional sign-in. I appear twice as HEM because I played in two browsers.
  • Grow the Knowledge Base. Nine articles cover newcomer questions, not race strategy.

Built with

I built Pit Wall with Pi. The car at lights out is a recording by Geoff-Bremner-Audio (CC BY 4.0). The fonts are Departure Mono and Instrument Serif, and the flags come from country-flag-icons.

Sanity Project Details

  • Project ID: hptt7wjq
  • Dataset: production (public)
  • Example query: Azerbaijan 2026 podium
  • Document types: race, raceResult, driver, team, circuit, driverStanding, teamStanding, quizQuestion, quizRun
  • Context endpoints: pit-wall-data (GROQ mode), pit-wall-rules (Knowledge Base "F1 rules for newcomers")

Over to you

  • If you follow F1, play a race you watched and tell me which question felt wrong.
  • If you're new, which F1 term confused you first? I'll add it to the Knowledge Base so the race engineer can explain it.

Top comments (0)