Files
gridpilot.gg/core/racing/domain/entities/result/Result.ts
2026-01-16 16:46:57 +01:00

151 lines
3.8 KiB
TypeScript

/**
* Domain Entity: Result
*
* Represents a race result in the GridPilot platform.
* Immutable entity with factory methods and domain validation.
*/
import { Entity } from '@core/shared/domain/Entity';
import { RacingDomainValidationError } from '../../errors/RacingDomainError';
import { DriverId } from '../DriverId';
import { RaceId } from '../RaceId';
import { IncidentCount } from './IncidentCount';
import { LapTime } from './LapTime';
import { Position } from './Position';
export class Result extends Entity<string> {
readonly raceId: RaceId;
readonly driverId: DriverId;
readonly position: Position;
readonly fastestLap: LapTime;
readonly incidents: IncidentCount;
readonly startPosition: Position;
private constructor(props: {
id: string;
raceId: RaceId;
driverId: DriverId;
position: Position;
fastestLap: LapTime;
incidents: IncidentCount;
startPosition: Position;
}) {
super(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: number;
startPosition: number;
}): Result {
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 incidents = IncidentCount.create(props.incidents);
const startPosition = Position.create(props.startPosition);
return new Result({
id: props.id,
raceId,
driverId,
position,
fastestLap,
incidents,
startPosition,
});
}
static rehydrate(props: {
id: string;
raceId: string;
driverId: string;
position: number;
fastestLap: number;
incidents: number;
startPosition: number;
}): Result {
if (!props.id || props.id.trim().length === 0) {
throw new RacingDomainValidationError('Result ID is required');
}
return new Result({
id: props.id,
raceId: RaceId.create(props.raceId),
driverId: DriverId.create(props.driverId),
position: Position.create(props.position),
fastestLap: LapTime.create(props.fastestLap),
incidents: IncidentCount.create(props.incidents),
startPosition: Position.create(props.startPosition),
});
}
/**
* Domain validation logic
*/
private static validate(props: {
id: string;
raceId: string;
driverId: string;
position: number;
fastestLap: number;
incidents: number;
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');
}
}
equals(other: Entity<string>): boolean {
if (!(other instanceof Result)) {
return false;
}
return this.id === other.id;
}
/**
* 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 (0 incidents)
*/
isClean(): boolean {
return this.incidents.toNumber() === 0;
}
}