> ## 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.

# Ticket Eligibility

> How the platform decides which ticket types a driver may buy, how close matches are scored, and why a ticket can be blocked.

Vivenu can sell a ticket to anyone. It has no idea whether a nine year old weighing 32kg is allowed to race a Cadet Light class. Answering that is the value GKL Platform adds, and this page describes how it is decided.

## The Question Being Answered

For one driver and one event, which of the event's ticket types may that driver buy?

The answer is not a yes or no per ticket. It is two lists: **perfect matches**, where the driver satisfies every requirement, and **close candidates**, where they do not but are near enough to be worth showing, ordered by how near. The booking website presents both, which is why a customer sees tickets they cannot currently buy along with the reason.

## Race Age

Everything starts with the driver's **race age**, computed against the **event end date** rather than today.

It is a calendar year age: the event year minus the birth year. A driver who turns twelve in December races as twelve all season, which is the racing convention rather than a bug.

> [!NOTE]
>
> Race age is `null` if the driver has no date of birth, or if their age falls outside the range spanned by every configured class. A null race age means no class can match, and the driver gets no tickets at all unless restrictions are waived.

## What A Class Requires

Classes are configured per hub region, and each carries the limits a driver must satisfy:

| Requirement | Notes |
| - | - |
| Minimum age | Inclusive |
| Maximum age | **Exclusive** |
| Minimum and maximum height | Rounded before comparison |
| Minimum weight | Compared against race weight, not bare weight |
| Allowed power levels | Which performance tiers may enter this class |
| Ballast and racewear offset | Used in the weight calculation and in scoring |

**Race weight** is the driver's weight plus the class's racewear offset, rounded up. A driver is compared on what they will weigh in kit, not what they weigh on the scales.

## Perfect Matches

A class is a perfect match when **all** of these hold:

* Race age is at or above the minimum, and below the maximum
* Height is within the minimum and maximum
* Race weight is at or above the minimum
* The driver's power level is allowed for this class and ticket type

A ticket type is a perfect match if **any** of its eligible classes is.

## Close Candidates And The Score

Anything not perfect gets a score, and lower is nearer. The score is a squared distance from the centre of the class in three dimensions:

```
score = (raceWeight - centroidWeight)²
      + (height     - centroidHeight)²
      + (raceAge    - centroidAge)²
      + powerLevelPenalty
      + maxHeightPenalty
```

The centroid is the midpoint of the class in each dimension. On top of that sit two flat penalties: **10,000** if the driver's power level is not allowed, and **1,000** if they exceed the class's maximum height. Those dominate the geometric part, so a driver who is simply the wrong size sorts above one who is barred outright.

A perfect match scores `0`. A driver whose race age falls outside the class entirely scores the maximum and effectively disappears.

> [!NOTE]
>
> The penalties encode a judgement about which mismatches are recoverable. Being slightly under weight is a near miss worth showing. Being at the wrong power level is a hard no, and sorts to the bottom.

## Conflicts With Tickets Already Owned

Eligibility is not only about the driver. A candidate ticket is flagged as conflicting if the driver already owns a ticket that either:

* Is the **same ticket type**, or
* Shares **any eligible part** with the candidate

Eligible parts are the tags on a ticket type describing what it grants, such as a race or a specific practice session. Overlap means the driver would be buying the same track time twice.

A conflicting candidate is still returned, with a link to the already purchased ticket, so the customer can see what they already have rather than simply being blocked.

## Sold Out

Stock is Vivenu's, not ours. A ticket type counts as sold out if its Vivenu category is missing or has no volume, or the ticket itself is missing, has no volume, or is inactive. That check runs against live Vivenu event info at the time of the request.

## Race Level Eligibility

A second layer restricts which power levels may enter which kind of race, expressed as event and ticket type: `Unrestricted`, `RegRace` or `ProRace`.

> [!WARNING]
>
> This configuration is **held in code, not in the database**, unlike every other configuration described here. It also has two different tables: one applied at checkout and one applied in the admin path. Operators can therefore issue combinations a customer cannot buy for themselves, deliberately.

## The Restrictions Override

The whole evaluation accepts a `noRestrictions` flag. With it set, class filtering is skipped and a driver with no race age still gets candidates. This is what lets operators issue a ticket the rules would otherwise refuse, and it is the reason the admin path and the customer path can disagree.

## Related Documentation

| Page | Relevance |
| - | - |
| [How A Sale Happens](/software/developer/estate/systems/gkl/how-a-sale-happens) | Where the availability tree is used |
| [The Domain Model](/software/developer/estate/systems/gkl/domain-model) | Class codes, power levels and eligible parts as concepts |
| [Hub Regions and Configuration](/software/developer/estate/systems/gkl/hub-regions-and-configuration) | Where the class and power level configuration lives |


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