/** * Domain Entity: Standing * * Represents a championship standing in the GridPilot platform. * Immutable entity with factory methods and domain validation. */ import { RacingDomainError, RacingDomainValidationError } from '../errors/RacingDomainError'; import type { IEntity } from '@gridpilot/shared/domain'; export class Standing implements IEntity { readonly id: string; readonly leagueId: string; readonly driverId: string; readonly points: number; readonly wins: number; readonly position: number; readonly racesCompleted: number; private constructor(props: { id: string; leagueId: string; driverId: string; points: number; wins: number; position: number; 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 { this.validate(props); const id = props.id && props.id.trim().length > 0 ? props.id : `${props.leagueId}:${props.driverId}`; return new Standing({ id, leagueId: props.leagueId, driverId: props.driverId, points: props.points ?? 0, wins: props.wins ?? 0, position: props.position ?? 0, 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; return new Standing({ id: this.id, leagueId: this.leagueId, driverId: this.driverId, points: this.points + racePoints, wins: this.wins + (isWin ? 1 : 0), position: this.position, racesCompleted: this.racesCompleted + 1, }); } /** * Update championship position */ updatePosition(position: number): Standing { if (!Number.isInteger(position) || position < 1) { throw new RacingDomainValidationError('Position must be a positive integer'); } return Standing.create({ id: this.id, leagueId: this.leagueId, driverId: this.driverId, points: this.points, wins: this.wins, position, racesCompleted: this.racesCompleted, }); } /** * Calculate average points per race */ getAveragePoints(): number { if (this.racesCompleted === 0) return 0; return this.points / this.racesCompleted; } /** * Calculate win percentage */ getWinPercentage(): number { if (this.racesCompleted === 0) return 0; return (this.wins / this.racesCompleted) * 100; } }