Files
gridpilot.gg/core/racing/domain/entities/ResultWithIncidents.ts
2025-12-23 16:16:12 +01:00

192 lines
4.9 KiB
TypeScript

/**
* Enhanced Result entity with detailed incident tracking
*/
import type { IEntity } from '@core/shared/domain';
import { RacingDomainValidationError } from '../errors/RacingDomainError';
import { RaceIncidents, type IncidentRecord } from '../value-objects/RaceIncidents';
import { DriverId } from './DriverId';
import { RaceId } from './RaceId';
import { LapTime } from './result/LapTime';
import { Position } from './result/Position';
export class ResultWithIncidents implements IEntity<string> {
readonly id: string;
readonly raceId: RaceId;
readonly driverId: DriverId;
readonly position: Position;
readonly fastestLap: LapTime;
readonly incidents: RaceIncidents;
readonly startPosition: Position;
private constructor(props: {
id: string;
raceId: RaceId;
driverId: DriverId;
position: Position;
fastestLap: LapTime;
incidents: RaceIncidents;
startPosition: Position;
}) {
this.id = props.id;
this.raceId = props.raceId;
this.driverId = props.driverId;
this.position = props.position;
this.fastestLap = props.fastestLap;
this.incidents = props.incidents;
this.startPosition = props.startPosition;
}
/**
* Factory method to create a new Result entity
*/
static create(props: {
id: string;
raceId: string;
driverId: string;
position: number;
fastestLap: number;
incidents: RaceIncidents;
startPosition: number;
}): ResultWithIncidents {
this.validate(props);
const raceId = RaceId.create(props.raceId);
const driverId = DriverId.create(props.driverId);
const position = Position.create(props.position);
const fastestLap = LapTime.create(props.fastestLap);
const startPosition = Position.create(props.startPosition);
return new ResultWithIncidents({
id: props.id,
raceId,
driverId,
position,
fastestLap,
incidents: props.incidents,
startPosition,
});
}
/**
* Legacy interop: create from Result data where incidents are stored as a number.
*/
static fromLegacy(props: {
id: string;
raceId: string;
driverId: string;
position: number;
fastestLap: number;
incidents: number;
startPosition: number;
}): ResultWithIncidents {
const raceIncidents = RaceIncidents.fromLegacyIncidentsCount(props.incidents);
return ResultWithIncidents.create({
...props,
incidents: raceIncidents,
});
}
/**
* Domain validation logic
*/
private static validate(props: {
id: string;
raceId: string;
driverId: string;
position: number;
fastestLap: number;
incidents: RaceIncidents;
startPosition: number;
}): void {
if (!props.id || props.id.trim().length === 0) {
throw new RacingDomainValidationError('Result ID is required');
}
if (!props.raceId || props.raceId.trim().length === 0) {
throw new RacingDomainValidationError('Race ID is required');
}
if (!props.driverId || props.driverId.trim().length === 0) {
throw new RacingDomainValidationError('Driver ID is required');
}
if (!Number.isInteger(props.position) || props.position < 1) {
throw new RacingDomainValidationError('Position must be a positive integer');
}
if (props.fastestLap < 0) {
throw new RacingDomainValidationError('Fastest lap cannot be negative');
}
if (!Number.isInteger(props.startPosition) || props.startPosition < 1) {
throw new RacingDomainValidationError('Start position must be a positive integer');
}
}
/**
* Calculate positions gained/lost
*/
getPositionChange(): number {
return this.startPosition.toNumber() - this.position.toNumber();
}
/**
* Check if driver finished on podium
*/
isPodium(): boolean {
return this.position.toNumber() <= 3;
}
/**
* Check if driver had a clean race (no incidents)
*/
isClean(): boolean {
return this.incidents.isClean();
}
/**
* Get total incident count (for backward compatibility)
*/
getTotalIncidents(): number {
return this.incidents.getTotalCount();
}
/**
* Get incident severity score
*/
getIncidentSeverityScore(): number {
return this.incidents.getSeverityScore();
}
/**
* Get human-readable incident summary
*/
getIncidentSummary(): string {
return this.incidents.getSummary();
}
/**
* Add an incident to this result
*/
addIncident(incident: IncidentRecord): ResultWithIncidents {
const updatedIncidents = this.incidents.addIncident(incident);
return new ResultWithIncidents({
...this,
incidents: updatedIncidents,
});
}
/**
* Convert to legacy format (for backward compatibility)
*/
toLegacyFormat() {
return {
id: this.id,
raceId: this.raceId,
driverId: this.driverId,
position: this.position,
fastestLap: this.fastestLap,
incidents: this.getTotalIncidents(),
startPosition: this.startPosition,
};
}
}