Files
gridpilot.gg/packages/racing/domain/entities/Result.ts
2025-12-11 11:25:22 +01:00

115 lines
2.8 KiB
TypeScript

/**
* Domain Entity: Result
*
* Represents a race result in the GridPilot platform.
* Immutable entity with factory methods and domain validation.
*/
import { RacingDomainValidationError } from '../errors/RacingDomainError';
export class Result {
readonly id: string;
readonly raceId: string;
readonly driverId: string;
readonly position: number;
readonly fastestLap: number;
readonly incidents: number;
readonly startPosition: number;
private constructor(props: {
id: string;
raceId: string;
driverId: string;
position: number;
fastestLap: number;
incidents: number;
startPosition: number;
}) {
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: number;
startPosition: number;
}): Result {
this.validate(props);
return new Result(props);
}
/**
* 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');
}
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.incidents) || props.incidents < 0) {
throw new RacingDomainValidationError('Incidents must be a non-negative integer');
}
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 - this.position;
}
/**
* Check if driver finished on podium
*/
isPodium(): boolean {
return this.position <= 3;
}
/**
* Check if driver had a clean race (0 incidents)
*/
isClean(): boolean {
return this.incidents === 0;
}
}