Skip to main content

f1verse

The story layer for Formula 1 data. Data libraries fetch and tidy — f1verse tells you what happened: lead changes, laps led, event timelines, stint strategy, race pace, and a live championship projection.

Zero dependencies. Standard library only. Seasons 2023 onward; historic records reach back to 1950.

pip install f1verse
import f1verse

race = f1verse.load(2026, 12)        # year, round — no other library needed

race.laps_led()      # {'ANT': 32, 'NOR': 31, 'HAM': 9}
race.leader_runs()   # [{'abbr': 'NOR', 'from': 1, 'to': 4}, ...]
race.results()[7]    # {'abbr': 'HUL', 'gap': '+1 LAP', ...}
race.race_pace()     # median pace — pit/SC/VSC laps excluded by default
race.story()         # one call, whole story, plain JSON

race.championship_prediction()   # per-lap "if it ended now" title projection
race.team_radio()                # timestamped clip URLs (nothing downloaded)

Beyond a single race

f1verse.career("max_verstappen")
# {'starts': 245, 'wins': 71, 'podiums': 131, 'poles': 48, ...}  1950-present

f1verse.milestones("max_verstappen")
# [{'stat': 'poles', 'current': 48, 'target': 50, 'remaining': 2}]

f1verse.circuit_profile(2026, 13)
# corners, marshal sectors, track outline, and pit loss split by track state
# {'normal': 25.43, 'sc': 16.11, 'vsc': 18.4}  <- what an undercut costs here
# plus historic record: 75 races held, pole-to-win rate 0.30

f1verse.head_to_head(2026)
# teammate quali/race scores per constructor

f1verse.standings(2026)

Running this on a schedule

f1verse.status(2026)
# {'latest_race': {'round': 12, 'meeting': 'Dutch Grand Prix'},
#  'next': {'round': 13, 'session': 'Practice 1'}, 'next_in_hours': 125.7}

f1verse.due(2026, processed=[...session keys you already handled...])
# sessions finished, settled (45 min past the flag) and not yet processed —
# nothing published twice, nothing missed after downtime

Caching is policy-driven, not blanket: completed-session data is immutable and cached forever, while schedules expire every few hours — a calendar cached for a season would hide a cancelled round for the rest of the year. f1verse.cache_info() and f1verse.clear_cache(older_than=...) are there for operators.

Telemetry, track position, conditions

f1verse.lap_telemetry(race, "NOR", 40)
# per-sample speed, throttle, brake, gear, RPM and DRS state for one lap

f1verse.lap_trace(race, "NOR", 40)      # x/y/z coordinates of that lap
f1verse.top_speeds(race)                # fastest reading per driver

f1verse.weather_summary(race)
# {'track_c': {'min': 25.8, 'max': 38.2}, 'rain': True, 'samples': 191}

Telemetry is high-frequency, so these take a bounded window and filter server-side rather than downloading a session and trimming it locally.

Grounded narration

facts = f1verse.race_facts(race)       # all numbers computed and formatted here
f1verse.brief(race)                    # deterministic text, no model required

result = f1verse.narrate(
    race,
    generate=lambda prompt: my_model(prompt),
    cache_dir=".cache/narration",
)
# {'text': '...', 'source': 'generated' | 'cache' | 'template', ...}

narrate accepts any text-generation callback; f1verse has no model SDK dependency. Drafts are checked against the structured fact sheet. Unknown numbers and driver codes are rejected, generation is retried at most twice, and a deterministic summary is returned if verification still fails. The optional cache is exact-match only and stores verified text.

Predictions, pit-stop verdicts, official documents

f1verse.win_probabilities({"NOR": 1, "ANT": 2, "RUS": 3},
                          year=2026, upto_round=12, circuit_id="monza")
# every probability ships with its own evidence:
#   grid base rate measured over 233 real races (pole wins 54.1%)
#   blended with that circuit's pole-to-win conversion (Monza: 0.30)
#   scaled by recent form (average finish over the last 5 rounds)

f1verse.pit_exchanges(race, pit_loss_s=22.74)
# [{'lap': 17, 'driver': 'RUS', 'rival': 'PIA', 'verdict': 'worked',
#   'gain_s': 3.54}, ...]
# neutralised laps (red flag / SC / VSC) and same-lap covering stops are
# excluded — calling those undercuts would be wrong

f1verse.fia_documents(2026)          # stewards' decisions, classified
f1verse.power_unit_documents(2026)   # "who changed which engine part"

Why this exists

Raw timing data needs a lot of domain knowledge before it means anything:

  • Classified gaps are not comparable across lapped cars. A car one lap down can show a smaller number than one that finished ahead on the lead lap. → format_gap applies the broadcast convention (+1 LAP).
  • "Who led the race" has to be derived. Lead changes, laps led and the moments they happened are not published as such. → leader_runs, laps_led, timeline.
  • Race pace needs rules, not just a threshold: in/out laps and laps run under SC/VSC have to go, or the number is meaningless. → race_pace applies them by default.
  • Web and video pipelines need plain JSON. Every f1verse output is JSON-safe Python, ready to serialise.
  • Data quality should be checkable in code, not read from logs. → crosscheck and integrity_report return structured verdicts.

Additional live-timing feeds

The official live-timing archive publishes several feeds that are rarely surfaced. f1verse parses three of them, with the same caching and rate-limit etiquette as the rest of the library:

f1verse.championship_prediction(session)
# per-lap "if the race ended now" projection of both championships,
# including the moments the projected champion changed

f1verse.team_radio(session)
# timestamped team-radio clips: [{'t', 'utc', 'driver_number', 'url'}]
# URLs only — nothing is downloaded or redistributed

f1verse.timing_stats(session)
# personal bests, best sectors, speed-trap figures

Running continuously

See OPERATIONS.md for caching policy, rate limits and scheduling.

Tests

pip install -e ".[test]" && pytest -q

Sources

Layer What it gives
Race data laps, stints, pits, positions, results, overtakes
Live-timing archive championship projection, team radio, timing stats
Historic records careers, circuit records, standings — 1950 onward
Circuit geometry track outline, corners, marshal sectors, pit loss

All are public endpoints, read at runtime. See src/f1verse/sources/ for the exact hosts and LICENSE notes where attribution applies.

Design rules

  1. Zero required dependencies. The native loader reads public REST endpoints and the official live-timing archive directly, with its own on-disk cache and polite pacing.
  2. Everything returned is plain JSON-safe Python.
  3. F1 domain rules are defaults, not options.
  4. Cross-checked where possible — lapped-car gaps, for instance, are computed by convention and confirmed against a second source.
  5. Code only. No timing data, media, or images are bundled or redistributed; data is fetched by the end user.

Roadmap

  • Broader cross-validation coverage
  • Additional session types (qualifying, sprint)
  • Localisation packages

Unofficial fan project. Not affiliated with, endorsed by, or associated with Formula 1, FIA, FOM, or any F1 team. F1, FORMULA 1 and related marks are trademarks of Formula One Licensing BV. This library contains code only — no timing data, media, or images are included or redistributed; data is fetched by the end user from publicly accessible endpoints, subject to the respective providers' terms.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

f1verse-0.9.1.tar.gz (39.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

f1verse-0.9.1-py3-none-any.whl (38.9 kB view details)

Uploaded Python 3

File details

Details for the file f1verse-0.9.1.tar.gz.

File metadata

  • Download URL: f1verse-0.9.1.tar.gz
  • Upload date:
  • Size: 39.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for f1verse-0.9.1.tar.gz
Algorithm Hash digest
SHA256 4bdc298e3b675a107eab3a1784f7ca3fea6ef8ee1068543874692f9ee339d304
MD5 61a41b94bf47cef6dfd16d86e883d787
BLAKE2b-256 ac1dd8e4411a3da4b6a0f8c5c563141f7912d4688d329235e5762947c18b85b6

See more details on using hashes here.

Provenance

The following attestation bundles were made for f1verse-0.9.1.tar.gz:

Publisher: publish.yml on jinsim/f1verse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file f1verse-0.9.1-py3-none-any.whl.

File metadata

  • Download URL: f1verse-0.9.1-py3-none-any.whl
  • Upload date:
  • Size: 38.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for f1verse-0.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c2a1d9eb84e3d8479b780a6ccd575b5bdb2fe042707fb05013683869c398edea
MD5 4ecb76b979492303fc428fc1e14e97ee
BLAKE2b-256 5960189fe000e4d9122017c9476407cbd235231d3381e6a1c3b336d0264401cd

See more details on using hashes here.

Provenance

The following attestation bundles were made for f1verse-0.9.1-py3-none-any.whl:

Publisher: publish.yml on jinsim/f1verse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.11.1

2 files

0.11.0

2 files

0.10.0

2 files

This release

0.9.1 This release

2 files

0.9.0

2 files

0.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page