/** * Enhanced Result entity with detailed incident tracking */ import { RacingDomainValidationError } from '../errors/RacingDomainError'; import type { IEntity } from '@core/shared/domain'; import { RaceIncidents, type IncidentRecord } from '../value-objects/RaceIncidents'; import { RaceId } from './RaceId'; import { DriverId } from './DriverId'; import { Position } from './result/Position'; import { LapTime } from './result/LapTime'; export class ResultWithIncidents implements IEntity { 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, }); } /** * Create from legacy Result data (with incidents as 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, }; } }