SPLap

SPLap 2.1.1 · engineering notes

Data sources, providers and attribution

SPLap 2.1 ("Motorsport Telemetry Lab") replays and analyses real Formula 1 sessions. This document says where the data comes from, what each adapter actually implements, what is observed versus derived versus modeled, and what the licensing and credential rules are. Field-level detail lives in docs/DATA_SCHEMA.md; the import walkthrough is docs/IMPORTING_A_RACE.md.

1. Provider adapter architecture

OpenF1 REST ----> openf1.py / importer.js --\
FastF1 (opt.) --> providers_extra.py --------+--> canonical events (splap.canonical/1)
Jolpica -------> providers_extra.py ---------/        |
                                          track.derive -> geometry (splap.track/1)
                                          frames.build -> replay frames (splap.replay.frames/1)
                                                       |
                              bundle.write (offline) / ingest.py (index) / fixture (lookups)

The canonical schema is the only contract the app depends on. Adapters are thin: they rename provider fields, attach provenance and a stable source_record_id, and count what they found into the capability object. Nothing downstream reads a provider payload.

1.1 OpenF1 (primary)

1.2 FastF1 (optional, Python only)

providers_extra.fastf1_available() reports whether the fastf1 package imports; fastf1_fetch(year, gp, session_name) loads a session through FastF1 (which reads F1 live timing) and produces canonical lap, location, car_data and weather events plus a driver list and capability object. What it does not do today: it emits no session, meeting, driver, position, interval, pit, stint or race_control events; t1 is fixed at t0 + 3 h and date_end is null; lap events lack the team fields; brake_state is 0/1 rather than OpenF1's 0/100. It has not been used to build any shipped asset and there is no CLI subcommand wired to it. Treat it as a proof that a second telemetry provider fits the schema, not as a supported path.

1.3 Jolpica (calendar and results metadata)

jolpica_calendar(season) and jolpica_results(season, round) call https://api.jolpi.ca/ergast/f1 (the community continuation of Ergast) and return plain dicts labelled source_provider: "jolpica", provenance: "observed". They exist to label meetings and cross-check results; nothing in the shipped app or the Monza bundle depends on them.

1.4 TUMFTM racetrack database (reference only)

The TUM racetrack database (centerlines and widths for many circuits, published under an open licence) is a candidate fallback geometry when a session has no usable location data. It is not used anywhere in the code today: there is no adapter, no bundled file and no reference in the build. The shipped Monza geometry (splap/appserver/static/sessions/monza_2025/track.json, bundles/monza_2025_window/track.geojson) is derived entirely from OpenF1 location samples of the session itself (track.derive; reference lap and sample counts are recorded under reference). If TUMFTM were adopted it would enter as a provenance: "observed" centerline in a different coordinate frame that must be registered to the venue frame; width_m would then become observed instead of the current modeled 12 m constant.

1.5 Bundled sample sessions

2. Capability object and "unavailable"

Every session carries capabilities (see DATA_SCHEMA.md section 4). Each channel is {available, count, provenance}. The UI reads it before querying:

"Unavailable" therefore means "the provider did not give it for this session/window", not "zero". The same word is used as a provenance value on geometry parts (pit_lane.provenance, sectors.provenance, drs_provenance) when derivation had nothing to work with.

3. Observed, derived, modeled in the shipped Monza session

Item Provenance How
Laps, sectors, speed traps, stints, pits, positions, intervals, race control, weather, overtakes, team radio URLs, location, car_data observed OpenF1 rows, renamed
Driver identity, team name, team primary colour observed OpenF1 /drivers
Circuit centerline, cumulative distance, heading, curvature, start/finish, corner numbering, sector boundaries derived From the session's own location samples and the reference lap's sector times
Pit lane path, entry/exit distance, box derived From location samples during the pit event window with the most off-line points
DRS zones derived From car_data.drs in {10,12,14} projected onto the centerline
Track width (12 m) modeled Constant, width_provenance: "modeled"
Replay frames (interpolated positions, held telemetry, tyre age, pit progress) derived frames.build_frames
Running order when no position row exists derived Race distance ranking, flagged position_provenance: "derived" in the row form
Team secondary/accent colours curated (provenance: "curated") or derived from the provider colour palettes.py, team_palettes.json
SC/VSC pit-loss impact (Race Engineer rule sc_vsc_strategic_impact) modeled A fixed percentage band (SC 50–60%, VSC 35–45% of the median lane time); an estimate, not measured
Battle groups, lap delta, flag state per sector derived Computed in the browser from frames and race control

4. Licensing and attribution

The attribution text shipped with the data is bundles/monza_2025_window/ATTRIBUTION.md; the same text is embedded in the static session.json and shown in the UI footer. Summary:

5. Credentials policy

6. Offline bundle

bundle.write produces a directory (optionally zipped) that splap_import.py ingest --bundle can load with no network access:

File Contents
manifest.json splap.bundle/1: import_id, provider, session/meeting keys, season, circuit, window, event counts, frame count and step, capabilities, file list, offline_importable: true
session.json, drivers.json Session record (with t0/t1, capabilities, counts) and driver list
laps.ndjson.gz lap, stint, pit events
telemetry.ndjson.gz car_data
location.ndjson.gz location
race_events.ndjson.gz race_control, position, interval, overtake, weather, team_radio, meeting, session, driver
replay_frames.json.gz Compact frame payload
track.geojson GeoJSON FeatureCollection (centerline, start/finish, corners, pit lane, DRS zones) plus the full splap_track object
team_palettes.json Palette per team for this season
ATTRIBUTION.md Attribution text
validation.json Validator results
checksums.json SHA-256 and byte size of every other file; bundle.read refuses a bundle with a mismatch

Ingest adds the circuit event, expands frames into replay_frame events and stamps import_id on every row. See docs/DATA_SCHEMA.md for field definitions and docs/IMPORTING_A_RACE.md for the step-by-step import procedure.