/** * Domain Entity: Standing * * Represents a championship standing in the GridPilot platform. * Immutable entity with factory methods and domain validation. */ import type { IEntity } from '@core/shared/domain'; import { RacingDomainValidationError } from '../errors/RacingDomainError'; import { LeagueId } from './LeagueId'; import { DriverId } from './DriverId'; import { Points } from '../value-objects/Points'; import { Position } from './championship/Position'; export class Standing implements IEntity { readonly id: string; readonly leagueId: LeagueId; readonly driverId: DriverId; readonly points: Points; readonly wins: number; readonly position: Position; readonly racesCompleted: number; private constructor(props: { id: string; leagueId: LeagueId; driverId: DriverId; points: Points; wins: number; position: Position; racesCompleted: number; }) { this.id = props.id; this.leagueId = props.leagueId; this.driverId = props.driverId; this.points = props.points; this.wins = props.wins; this.position = props.position; this.racesCompleted = props.racesCompleted; } /** * Factory method to create a new Standing entity */ static create(props: { id?: string; leagueId: string; driverId: string; points?: number; wins?: number; position?: number; racesCompleted?: number; }): Standing { Standing.validate(props); const id = props.id && props.id.trim().length > 0 ? props.id : `${props.leagueId}:${props.driverId}`; const leagueId = LeagueId.create(props.leagueId); const driverId = DriverId.create(props.driverId); const points = Points.create(props.points ?? 0); const position = Position.create(props.position ?? 1); // Default to 1 for position return new Standing({ id, leagueId, driverId, points, wins: props.wins ?? 0, position, racesCompleted: props.racesCompleted ?? 0, }); } /** * Domain validation logic */ private static validate(props: { id?: string; leagueId: string; driverId: string; }): void { if (!props.leagueId || props.leagueId.trim().length === 0) { throw new RacingDomainValidationError('League ID is required'); } if (!props.driverId || props.driverId.trim().length === 0) { throw new RacingDomainValidationError('Driver ID is required'); } } /** * Add points from a race result */ addRaceResult(position: number, pointsSystem: Record): Standing { const racePoints = pointsSystem[position] ?? 0; const isWin = position === 1; const newPoints = Points.create(this.points.toNumber() + racePoints); const newPosition = this.position; // Position might be updated separately return new Standing({ id: this.id, leagueId: this.leagueId, driverId: this.driverId, points: newPoints, wins: this.wins + (isWin ? 1 : 0), position: newPosition, racesCompleted: this.racesCompleted + 1, }); } /** * Update championship position */ updatePosition(position: number): Standing { const newPosition = Position.create(position); return new Standing({ id: this.id, leagueId: this.leagueId, driverId: this.driverId, points: this.points, wins: this.wins, position: newPosition, racesCompleted: this.racesCompleted, }); } /** * Calculate average points per race */ getAveragePoints(): number { if (this.racesCompleted === 0) return 0; return this.points.toNumber() / this.racesCompleted; } /** * Calculate win percentage */ getWinPercentage(): number { if (this.racesCompleted === 0) return 0; return (this.wins / this.racesCompleted) * 100; } }