///|
/// CSV tyre compounds supported by RaceDelta CSV v1.
///
/// The value is used by parsed records, pace profiles, and planned pit stops.
pub(all) enum Compound {
Soft
Medium
Hard
Intermediate
Wet
} derive(Eq, Debug)
///|
pub extend Compound with Eq::{not_equal, equal}
///|
pub extend Compound with @debug.Debug::{to_repr}
///|
/// Race-control state shared by every driver on a lap.
///
/// CSV v1 requires one shared status per lap; `SafetyCar` changes the modeled
/// pit-loss selection but does not compress observed input gaps.
pub(all) enum TrackStatus {
Green
SafetyCar
} derive(Eq, Debug)
///|
/// Track weather condition shared by every driver on a lap.
///
/// CSV v1 requires one shared weather value per lap and the pace model selects
/// its fresh-tyre delta from this value.
pub(all) enum Weather {
Dry
Damp
Wet
} derive(Eq, Debug)
///|
/// One validated row in CSV v1.
///
/// `lap_time_ms` is the complete positive lap duration in milliseconds; `pit`
/// means an end-of-lap stop whose loss is already included in that duration.
pub struct LapRecord {
lap : Int
driver : String
lap_time_ms : Int
compound : Compound
tyre_age_laps : Int
pit : Bool
track_status : TrackStatus
weather : Weather
} derive(Eq, Debug)
///|
/// Canonically ordered and validated race input.
///
/// `records` are sorted by ascending lap and then lexicographic driver name;
/// construct this through `parse_race_csv` to retain CSV v1 guarantees.
pub struct RaceData {
records : Array[LapRecord]
} derive(Eq, Debug)
///|
/// Stable categories for CSV v1 parse and validation errors.
///
/// Consumers can branch on these codes instead of parsing error messages.
pub(all) enum RaceErrorCode {
EmptyInput
InvalidHeader
FieldCount
UnsupportedCsvSyntax
InvalidInteger
InvalidValue
DuplicateDriverLap
MissingLap
InconsistentLaps
InconsistentTrackStatus
InconsistentWeather
InvalidTyreTransition
} derive(Eq, Debug)
///|
/// A user-facing CSV parse or validation error.
///
/// `line` is one-based; zero denotes a global input error. `field` and `message`
/// identify the rejected CSV value or cross-row constraint, while `code` is stable.
pub struct RaceError {
code : RaceErrorCode
line : Int
field : String
message : String
} derive(Eq, Debug)
///|
/// A driver's complete race state at the end of one lap.
///
/// All time and gap fields are integer milliseconds. `gap_to_leader_ms` and
/// `interval_to_ahead_ms` are non-negative; the leader has no interval to ahead.
pub struct DriverLapState {
lap : Int
driver : String
lap_time_ms : Int
cumulative_time_ms : Int
position : Int
gap_to_leader_ms : Int
interval_to_ahead_ms : Int?
compound : Compound
tyre_age_laps : Int
pit : Bool
track_status : TrackStatus
weather : Weather
} derive(Eq, Debug)
///|
/// A consecutive tyre run, closed by a pit stop or by the final lap.
///
/// Ages describe completed laps; `ended_by_pit` distinguishes a stop from the
/// end of the input race, and `total_time_ms` is the stint's observed duration.
pub struct Stint {
driver : String
stint_number : Int
start_lap : Int
end_lap : Int
compound : Compound
start_tyre_age_laps : Int
end_tyre_age_laps : Int
lap_count : Int
total_time_ms : Int
ended_by_pit : Bool
} derive(Eq, Debug)
///|
/// Details of an end-of-lap pit stop.
///
/// `outgoing_compound` was used on `lap`; `next_compound` is absent when no
/// following lap exists in the input.
pub struct PitStopEvent {
lap : Int
driver : String
outgoing_compound : Compound
next_compound : Compound?
} derive(Eq, Debug)
///|
/// A track-status change detected between two completed laps.
///
/// `lap` is the first completed lap using `current`.
pub struct TrackStatusChangeEvent {
lap : Int
previous : TrackStatus
current : TrackStatus
} derive(Eq, Debug)
///|
/// A weather change detected between two completed laps.
///
/// `lap` is the first completed lap using `current`.
pub struct WeatherChangeEvent {
lap : Int
previous : Weather
current : Weather
} derive(Eq, Debug)
///|
/// A driver's end-of-lap position change.
///
/// `lap` is the first completed lap with `current_position`.
pub struct PositionChangeEvent {
lap : Int
driver : String
previous_position : Int
current_position : Int
} derive(Eq, Debug)
///|
/// A deterministically ordered race event.
///
/// Events are produced by `analyze_race` in a stable order suitable for reports
/// and deterministic tests.
pub(all) enum RaceEvent {
PitStop(PitStopEvent)
TrackStatusChange(TrackStatusChangeEvent)
WeatherChange(WeatherChangeEvent)
PositionChange(PositionChangeEvent)
} derive(Eq, Debug)
///|
/// The complete deterministic reconstruction produced from validated CSV data.
///
/// `drivers` is lexicographically sorted; `states`, `stints`, and `events`
/// preserve documented stable order and use integer-millisecond timing.
pub struct RaceAnalysis {
drivers : Array[String]
lap_count : Int
states : Array[DriverLapState]
stints : Array[Stint]
events : Array[RaceEvent]
} derive(Eq, Debug)
///|
/// Demonstration fresh-tyre pace deltas by weather, in integer milliseconds.
///
/// Positive values make a lap slower than its neutral anchor; negative values
/// make it faster. These replaceable values are illustrative, not official F1 data.
pub(all) struct WeatherPaceDeltas {
dry_ms : Int
damp_ms : Int
wet_ms : Int
} derive(Eq, Debug)
///|
/// Configurable pace and degradation parameters for one tyre compound.
///
/// Degradation applies per completed lap after fresh age; cliff extra loss starts
/// after `cliff_age_laps`. Values are integer milliseconds and demonstration-only.
pub(all) struct TyreProfile {
compound : Compound
fresh_deltas : WeatherPaceDeltas
degradation_ms_per_lap : Int
cliff_age_laps : Int
cliff_extra_ms_per_lap : Int
} derive(Eq, Debug)
///|
/// Configurable modeled pit loss for each supported track status.
///
/// Values are non-negative integer milliseconds added only on a modeled pit lap.
pub(all) struct PitLossProfile {
green_pit_loss_ms : Int
safety_car_pit_loss_ms : Int
} derive(Eq, Debug)
///|
/// The complete replaceable configuration for the explanatory pace model.
///
/// It must contain one valid profile for every `Compound`; validate custom values
/// with `validate_pace_model_config` before simulating or explaining.
pub(all) struct PaceModelConfig {
tyre_profiles : Array[TyreProfile]
pit_loss_profile : PitLossProfile
} derive(Eq, Debug)
///|
/// Stable error categories for model configuration and calculations.
///
/// These cover invalid profiles, tyre ages, observed/neutral times, and results.
pub(all) enum ModelErrorCode {
MissingTyreProfile
DuplicateTyreProfile
NegativeLinearDegradation
InvalidCliffAge
NegativeCliffExtraDegradation
NegativeGreenPitLoss
NegativeSafetyCarPitLoss
InvalidTyreAge
InvalidLapTime
NonPositiveResult
} derive(Eq, Debug)
///|
/// A deterministic, user-facing model error suitable for future CLI output.
///
/// `code` is stable for programmatic handling and `message` explains the invalid
/// configuration or calculation input.
pub(all) struct ModelError {
code : ModelErrorCode
message : String
} derive(Eq, Debug)
///|
/// A planned end-of-lap pit stop and the compound used from the following lap.
///
/// `pit_lap` is one-based and the selected `next_compound` starts on the next lap.
pub(all) struct PlannedPitStop {
pit_lap : Int
next_compound : Compound
} derive(Eq, Debug)
///|
/// The target, opponent, and complete replacement pit plan for a replay.
///
/// The stop list replaces the target driver's observed stops; it does not append
/// to them. Both driver names must exist and refer to different drivers.
pub(all) struct StrategyRequest {
target_driver : String
opponent_driver : String
stops : Array[PlannedPitStop]
} derive(Eq, Debug)
///|
/// The target driver's simulated tyre state for one completed lap.
///
/// `pit` is true when the simulated stop occurs at this lap's end.
pub(all) struct SimulatedTyreState {
compound : Compound
tyre_age_laps : Int
pit : Bool
} derive(Eq, Debug)
///|
/// Actual and simulated end-of-lap facts for a target-driver strategy replay.
///
/// All time values are milliseconds. Signed gaps are target cumulative time minus
/// unchanged opponent cumulative time: positive means the target trails. Positive
/// `cumulative_time_gain_ms` means the simulated alternative is cumulatively faster.
pub(all) struct StrategyLapComparison {
lap : Int
neutral_lap_time_ms : Int
weather : Weather
track_status : TrackStatus
actual_lap_time_ms : Int
actual_cumulative_time_ms : Int
actual_compound : Compound
actual_tyre_age_laps : Int
actual_pit : Bool
actual_position : Int
actual_signed_gap_to_opponent_ms : Int
simulated_lap_time_ms : Int
simulated_cumulative_time_ms : Int
simulated_compound : Compound
simulated_tyre_age_laps : Int
simulated_pit : Bool
simulated_position : Int
simulated_signed_gap_to_opponent_ms : Int
cumulative_time_gain_ms : Int
} derive(Eq, Debug)
///|
/// Final comparison between actual and simulated target-driver outcomes.
///
/// Positive `time_gain_ms`, `positions_gained`, and `opponent_gap_gain_ms` mean
/// the alternative improved the target result; signed opponent gaps are positive
/// when the target trails the opponent.
pub(all) struct StrategySummary {
target_driver : String
opponent_driver : String
lap_count : Int
actual_stop_count : Int
simulated_stop_count : Int
actual_total_time_ms : Int
simulated_total_time_ms : Int
time_gain_ms : Int
actual_finish_position : Int
simulated_finish_position : Int
positions_gained : Int
actual_final_signed_gap_to_opponent_ms : Int
simulated_final_signed_gap_to_opponent_ms : Int
opponent_gap_gain_ms : Int
} derive(Eq, Debug)
///|
/// A complete deterministic counterfactual replay result.
///
/// `request` is the normalized replacement plan, `laps` are chronological, and
/// `summary` duplicates final-lap totals for convenient stable consumption.
pub(all) struct StrategySimulation {
request : StrategyRequest
laps : Array[StrategyLapComparison]
summary : StrategySummary
} derive(Eq, Debug)
///|
/// Stable categories for invalid replay requests and model failures.
///
/// These distinguish request validation, model errors, neutral-anchor failures,
/// estimation failures, and unexpectedly missing reconstructed states.
pub(all) enum StrategyErrorCode {
UnknownTargetDriver
UnknownOpponentDriver
SameDriver
InvalidPitLap
DuplicatePitLap
InvalidModel
NeutralLapFailure
EstimatedLapFailure
MissingLapState
} derive(Eq, Debug)
///|
/// A user-facing replay error; `lap` is populated for lap-specific failures.
///
/// `code` is stable and `message` provides a diagnostic without requiring callers
/// to inspect internal implementation details.
pub(all) struct StrategyError {
code : StrategyErrorCode
lap : Int?
message : String
} derive(Eq, Debug)
///|
/// Final time-based assessment of a counterfactual strategy.
///
/// `Improved` corresponds to positive final time gain, `Worsened` to negative,
/// and `Unchanged` to exactly zero.
pub(all) enum StrategyVerdict {
Improved
Worsened
Unchanged
} derive(Eq, Debug)
///|
/// The independent relationship represented by a crossover point.
///
/// Performance excludes pit loss, Strategic uses cumulative time gain, and
/// Opponent uses the simulated signed gap to the unchanged opponent.
pub(all) enum CrossoverKind {
Performance
Strategic
Opponent
} derive(Eq, Debug)
///|
/// The direction in which a crossover changes its relationship.
///
/// Each value names which side becomes favorable after a non-zero sign change.
pub(all) enum CrossoverDirection {
AlternativeBecomesFaster
ActualBecomesFaster
AlternativeBecomesBetter
ActualBecomesBetter
TargetMovesAhead
TargetFallsBehind
} derive(Eq, Debug)
///|
/// A deterministic sign-change point in a strategy comparison.
///
/// A crossover is emitted when a later non-zero relationship has the opposite
/// sign from the previous non-zero one; zero values neither emit nor reset it.
/// `previous_value_ms` and `current_value_ms` use the sign convention of `kind`.
pub(all) struct CrossoverPoint {
lap : Int
kind : CrossoverKind
direction : CrossoverDirection
previous_value_ms : Int
current_value_ms : Int
} derive(Eq, Debug)
///|
/// The actual-versus-simulated effect measured on one lap.
///
/// Positive `lap_time_gain_ms` means the simulated lap is faster. Positive
/// `cumulative_time_gain_ms` means the alternative is faster overall at that lap.
pub(all) struct LapImpact {
lap : Int
lap_time_gain_ms : Int
cumulative_time_gain_ms : Int
} derive(Eq, Debug)
///|
/// Stable categories for explainable strategy turning points.
///
/// Values cover input-state changes, actual/simulated stops, computed Safety Car
/// opportunities, crossover points, relative positions, and lap-time extremes.
pub(all) enum TurningPointKind {
TrackStatusChange
WeatherChange
ActualPitStop
SimulatedPitStop
SafetyCarPitOpportunity
PerformanceCrossover
StrategicCrossover
OpponentCrossover
RelativePositionGain
RelativePositionLoss
BestLapGain
WorstLapLoss
} derive(Eq, Debug)
///|
/// A concise, deterministic event used by the report and future CLI output.
///
/// `impact_ms` is optional and follows the originating gain or crossover sign
/// convention; points sort deterministically by lap then documented kind order.
pub(all) struct TurningPoint {
lap : Int
kind : TurningPointKind
impact_ms : Int?
message : String
} derive(Eq, Debug)
///|
/// A pit stop reconstructed from a completed strategy comparison.
///
/// `next_compound` is absent only when the stop occurs on the final available lap.
pub(all) struct StrategyPitStop {
lap : Int
next_compound : Compound?
} derive(Eq, Debug)
///|
/// A deterministic explanation derived from, but not mutating, a simulation.
///
/// It groups three crossover kinds, stable turning points, lap impacts, and the
/// original summary. Model conclusions are explanatory and use illustrative
/// parameters rather than official F1 data.
pub(all) struct StrategyExplanation {
verdict : StrategyVerdict
target_driver : String
opponent_driver : String
actual_stops : Array[StrategyPitStop]
simulated_stops : Array[StrategyPitStop]
performance_crossovers : Array[CrossoverPoint]
strategic_crossovers : Array[CrossoverPoint]
opponent_crossovers : Array[CrossoverPoint]
turning_points : Array[TurningPoint]
best_lap_gain : LapImpact?
worst_lap_loss : LapImpact?
summary : StrategySummary
laps : Array[StrategyLapComparison]
} derive(Eq, Debug)
///|
/// Stable categories for explanation and report-generation failures.
///
/// They distinguish invalid model input, tyre-delta failures, empty replay data,
/// and inconsistencies between a simulation's lap details and summary.
pub(all) enum ExplanationErrorCode {
InvalidModel
TyreDeltaFailure
EmptySimulation
InconsistentSimulation
} derive(Eq, Debug)
///|
/// A user-facing explanation error; `lap` identifies a lap-specific failure.
///
/// `code` is stable for callers and `message` describes the rejected model or
/// simulation invariant.
pub(all) struct ExplanationError {
code : ExplanationErrorCode
lap : Int?
message : String
} derive(Eq, Debug)
///|
pub extend TrackStatus with Eq::{not_equal, equal}
///|
pub extend TrackStatus with @debug.Debug::{to_repr}
///|
pub extend Weather with Eq::{not_equal, equal}
///|
pub extend Weather with @debug.Debug::{to_repr}
///|
pub extend LapRecord with Eq::{not_equal, equal}
///|
pub extend LapRecord with @debug.Debug::{to_repr}
///|
pub extend RaceData with Eq::{not_equal, equal}
///|
pub extend RaceData with @debug.Debug::{to_repr}
///|
pub extend RaceErrorCode with Eq::{not_equal, equal}
///|
pub extend RaceErrorCode with @debug.Debug::{to_repr}
///|
pub extend RaceError with Eq::{not_equal, equal}
///|
pub extend RaceError with @debug.Debug::{to_repr}
///|
pub extend DriverLapState with Eq::{not_equal, equal}
///|
pub extend DriverLapState with @debug.Debug::{to_repr}
///|
pub extend Stint with Eq::{not_equal, equal}
///|
pub extend Stint with @debug.Debug::{to_repr}
///|
pub extend PitStopEvent with Eq::{not_equal, equal}
///|
pub extend PitStopEvent with @debug.Debug::{to_repr}
///|
pub extend TrackStatusChangeEvent with Eq::{not_equal, equal}
///|
pub extend TrackStatusChangeEvent with @debug.Debug::{to_repr}
///|
pub extend WeatherChangeEvent with Eq::{not_equal, equal}
///|
pub extend WeatherChangeEvent with @debug.Debug::{to_repr}
///|
pub extend PositionChangeEvent with Eq::{not_equal, equal}
///|
pub extend PositionChangeEvent with @debug.Debug::{to_repr}
///|
pub extend RaceEvent with Eq::{not_equal, equal}
///|
pub extend RaceEvent with @debug.Debug::{to_repr}
///|
pub extend RaceAnalysis with Eq::{not_equal, equal}
///|
pub extend RaceAnalysis with @debug.Debug::{to_repr}
///|
pub extend WeatherPaceDeltas with Eq::{not_equal, equal}
///|
pub extend WeatherPaceDeltas with @debug.Debug::{to_repr}
///|
pub extend TyreProfile with Eq::{not_equal, equal}
///|
pub extend TyreProfile with @debug.Debug::{to_repr}
///|
pub extend PitLossProfile with Eq::{not_equal, equal}
///|
pub extend PitLossProfile with @debug.Debug::{to_repr}
///|
pub extend PaceModelConfig with Eq::{not_equal, equal}
///|
pub extend PaceModelConfig with @debug.Debug::{to_repr}
///|
pub extend ModelErrorCode with Eq::{not_equal, equal}
///|
pub extend ModelErrorCode with @debug.Debug::{to_repr}
///|
pub extend ModelError with Eq::{not_equal, equal}
///|
pub extend ModelError with @debug.Debug::{to_repr}
///|
pub extend PlannedPitStop with Eq::{not_equal, equal}
///|
pub extend PlannedPitStop with @debug.Debug::{to_repr}
///|
pub extend StrategyRequest with Eq::{not_equal, equal}
///|
pub extend StrategyRequest with @debug.Debug::{to_repr}
///|
pub extend SimulatedTyreState with Eq::{not_equal, equal}
///|
pub extend SimulatedTyreState with @debug.Debug::{to_repr}
///|
pub extend StrategyLapComparison with Eq::{not_equal, equal}
///|
pub extend StrategyLapComparison with @debug.Debug::{to_repr}
///|
pub extend StrategySummary with Eq::{not_equal, equal}
///|
pub extend StrategySummary with @debug.Debug::{to_repr}
///|
pub extend StrategySimulation with Eq::{not_equal, equal}
///|
pub extend StrategySimulation with @debug.Debug::{to_repr}
///|
pub extend StrategyErrorCode with Eq::{not_equal, equal}
///|
pub extend StrategyErrorCode with @debug.Debug::{to_repr}
///|
pub extend StrategyError with Eq::{not_equal, equal}
///|
pub extend StrategyError with @debug.Debug::{to_repr}
///|
pub extend StrategyVerdict with Eq::{not_equal, equal}
///|
pub extend StrategyVerdict with @debug.Debug::{to_repr}
///|
pub extend CrossoverKind with Eq::{not_equal, equal}
///|
pub extend CrossoverKind with @debug.Debug::{to_repr}
///|
pub extend CrossoverDirection with Eq::{not_equal, equal}
///|
pub extend CrossoverDirection with @debug.Debug::{to_repr}
///|
pub extend CrossoverPoint with Eq::{not_equal, equal}
///|
pub extend CrossoverPoint with @debug.Debug::{to_repr}
///|
pub extend LapImpact with Eq::{not_equal, equal}
///|
pub extend LapImpact with @debug.Debug::{to_repr}
///|
pub extend TurningPointKind with Eq::{not_equal, equal}
///|
pub extend TurningPointKind with @debug.Debug::{to_repr}
///|
pub extend TurningPoint with Eq::{not_equal, equal}
///|
pub extend TurningPoint with @debug.Debug::{to_repr}
///|
pub extend StrategyPitStop with Eq::{not_equal, equal}
///|
pub extend StrategyPitStop with @debug.Debug::{to_repr}
///|
pub extend StrategyExplanation with Eq::{not_equal, equal}
///|
pub extend StrategyExplanation with @debug.Debug::{to_repr}
///|
pub extend ExplanationErrorCode with Eq::{not_equal, equal}
///|
pub extend ExplanationErrorCode with @debug.Debug::{to_repr}
///|
pub extend ExplanationError with Eq::{not_equal, equal}
///|
pub extend ExplanationError with @debug.Debug::{to_repr}