> ## Documentation Index
> Fetch the complete documentation index at: https://playbook.smedleygroup.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Timing and Lap Splitting

> How TalentID timing data turns a continuous telemetry recording into laps, and where GKL API is involved in the conversion.

This page explains how a raw telemetry recording becomes a set of laps, why that depends on TalentID, and what GKL API contributes. For how a run reaches the converter see [DAQ Upload and Conversion](/software/developer/estate/systems/telemetry/daq-pipeline), and for when it goes wrong see [Troubleshooting](/software/developer/estate/systems/telemetry/troubleshooting).

## Why Splitting Is Needed

A DAQ unit records a continuous stream of signals for a whole run. It has no idea where one lap ends and the next begins, because nothing in the kart knows when it crossed the timing line. That information exists only in the timing system.

TalentID holds the timing. Lap splitting is therefore a join between two systems: telemetry supplies the signals, TalentID supplies the lap boundaries, and the converter cuts one against the other.

> [!NOTE]
>
> If no timing can be matched, the run still converts. It becomes a single lap covering the entire recording, flagged as both an out lap and an in lap, and it will not appear against a competitor in the Operator Dashboard.

## How A Run Is Matched To Timing

The join key is not a driver or a booking. It is the **transponder**, qualified by time and place:

| Field | Source |
| - | - |
| Transponder ID | Metadata in the uploaded run zip |
| Start and end time | Metadata in the uploaded run zip |
| Hub region | Metadata in the uploaded run zip |

TalentID takes those three and finds the competitor whose session overlaps that window in that region. From the competitor it can then reach the session, the event, the lap times, and the TalentID driver. That driver record carries the GKL driver ID, which is what makes the GKL calls below possible.

## The Conversion Flow

**1. The run reaches the converter**

A run zip is uploaded to the telemetry bucket and fans out through SNS and SQS to the converter Lambda. See [DAQ Upload and Conversion](/software/developer/estate/systems/telemetry/daq-pipeline).

**2. TalentID decides whether to convert**

The converter calls TalentID for conversion status, passing the metadata and the zip checksum. TalentID answers with `shouldConvert`, the matched competitor including its laps, and the session and event, or with no competitor if nothing matched.

**3. TalentID records the run**

The converter immediately upserts the telemetry record in TalentID, whether or not it goes on to convert. This is what makes the run visible in the Operator Dashboard even when conversion is pending or has failed.

**4. GKL API supplies event and driver context**

The converter asks GKL API whether the run happened on a race day, which decides where the output is filed, and what the driver's power level was at that time. See [Where GKL API Fits](#where-gkl-api-fits).

**5. The data is converted and split**

The binary is decoded against a DBC, normalised, forward filled, then split into laps using the timing. The result is exported as STL files.

**6. The result is written back**

The STL files are saved to S3 and their keys are posted back to TalentID. If anything threw along the way, the run is marked as a failed conversion instead.

## How The Split Is Calculated

Each timing lap gives a racing time (elapsed time from session start at the point the lap ended) and a lap time. The converter derives the boundaries from those two:

```
lap_end   = session_start_time + racing_time
lap_start = lap_end - lap_time
```

Telemetry rows falling in `[lap_start, lap_end)` become that lap. Three cases are handled beyond the simple one:

* **Telemetry before the first lap.** Anything recorded before lap 1 starts is emitted as a separate out lap, which is typically the drive out of the pits.
* **Telemetry after the last lap.** Anything after the final lap ends is emitted as an in lap.
* **Missing telemetry inside a lap.** If a lap has no telemetry at all, a NaN filled frame is generated across the lap at the resampling interval. If telemetry is merely short at either end, the gap is padded. Both cases log a warning naming the lap, competitor and session, so a lap that looks complete in the output may have been reconstructed.

> [!WARNING]
>
> Padded and NaN filled laps are only visible as warnings in the converter logs. Nothing surfaces them in the Operator Dashboard, so a lap can appear present and be substantially interpolated.

## Reconversion

Timing frequently arrives after the telemetry, so conversion cannot be a one shot operation. Two checksums drive the decision to redo it:

| Checksum | Meaning |
| - | - |
| Zip checksum | The uploaded run content changed |
| Competitor checksum | The timing for the matched competitor changed, or timing appeared where there was none |

When either differs from what TalentID has recorded, the run is converted again and the previous STL files are deleted until the new conversion completes.

### Where The Timing Comes From

AlphaTiming runs on the timing laptop at each hub region. Every time results are published, typically as each session finishes, it uploads the event timing JSON to the [ingestion bucket](https://eu-west-2.console.aws.amazon.com/s3/buckets/dev-talentid-talentidimportaea448bb-14fyrn4ajw9pc?region=eu-west-2). Nobody uploads it by hand.

That means timing for an event arrives in pieces across the day rather than once at the end, and each upload re-imports the event with more sessions in it than the last.

### What Happens On Import

TalentID imports the file and publishes an event imported notification. The reconversion checker Lambda subscribes to it, asks TalentID which zips in that event's region and time range now have a stale competitor checksum, and republishes each one onto the same ingest topic that a fresh upload uses, marked as coming from a timing import. The converter treats it the same way, except that it never forces conversion.

> [!NOTE]
>
> Because AlphaTiming publishes per session, this cycle runs repeatedly through an event day. A run uploaded before its session results were published converts with no timing, then converts again with laps a few minutes later, without anyone touching it.

> [!WARNING]
>
> A failed conversion is the exception. Nothing retries it automatically unless one of the two checksums moves, so it stays failed until someone forces a reconversion.

### Triggering A Reconversion Deliberately

There is no button for this and no manual entry point to the checker. Three routes exist:

| Route | Effect |
| - | - |
| Re-publish the timing | Re-importing the event fires the checker across every run in that event's region and time range whose competitor checksum has moved |
| Re-upload the zip with `Force` set | The zip metadata carries a `Force` flag. Re-uploading with it set converts regardless of either checksum |
| Change the content | Any change to the zip, or to the timing behind the competitor, moves a checksum and converts on the next trigger |

> [!WARNING]
>
> The timing import path overrides `Force` to false, deliberately. A reconversion that arrives via the checker will never force, so it will do nothing for a run whose checksums have not moved. Forcing is only possible by re-uploading the zip with the flag set in its metadata.

Re-uploading is best done from the track machine, because the run number is derived from local state. See [Running A Conversion Locally](/software/developer/estate/systems/telemetry/local-development#running-a-conversion-locally) for converting a zip on your own machine while investigating.

## Where GKL API Fits

GKL API is not involved in the timing at all. It is called twice during conversion, both times for context that only the booking platform holds:

| Call | Purpose |
| - | - |
| `v1/Events/isRaceDay/{timestamp}/{hubRegion}` | Whether the run happened on a race day. This selects the `Race` or `Test` segment of the STL output path, so it decides where engineers find the run |
| `v1/Admin/Drivers/byGklDriverId/{gklDriverId}` | The driver's power level at that timestamp, recorded against the split |

The second call is only possible because the TalentID competitor carries the GKL driver ID. A missing driver in GKL returns a 404, which the converter treats as an empty power level rather than a failure.

The telemetry converter authenticates to GKL API with a dedicated key held in Secrets Manager, separate from the keys used by the web applications.

## Key Files

| File | Role |
| - | - |
| `telemetry/telemetry-api/src/lambda_function.py` | Converter Lambda entry point, routes fresh uploads and reconversions |
| `telemetry/telemetry-api/src/offloaded/pipeline/zip_stl_converter.py` | Orchestrates the TalentID and GKL calls around the conversion |
| `telemetry/telemetry-api/src/offloaded/pipeline/telemetry_data_lap_splitter.py` | The splitting itself |
| `telemetry/telemetry-api/src/offloaded/services/lap_splitting/` | Missing and excess telemetry handling |
| `telemetry/telemetry-api/src/services/external/api/gkl_api_service.py` | The two GKL API calls |
| `telemetry/telemetry-api/src/services/external/api/tid_api_service.py` | The TalentID conversion status and write back calls |
| `telemetry/TalentId.To.Telemetry.ReconversionChecker/` | The Lambda that reacts to a TalentID timing import |
| `CdkBuild/lib/telemetry_cdk_stack.ts` | Topics, queues, Lambdas and their wiring |

## Related Documentation

| Page | Relevance |
| - | - |
| [DAQ Upload and Conversion](/software/developer/estate/systems/telemetry/daq-pipeline) | How a run reaches the converter and what conversion does |
| [Troubleshooting](/software/developer/estate/systems/telemetry/troubleshooting) | Failure modes, known issues and running a conversion locally |
| [Track Operations](/software/developer/estate/systems/telemetry/track-operations) | What runs at the track and how data gets uploaded |
| [TalentID in GKL Platform](/software/developer/estate/systems/gkl/talentid-in-gkl) | Where the Operator Dashboard surfaces TalentID data |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.