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

178 lines
4.5 KiB
TypeScript

/**
* Domain Entity: Standing
*
* Represents a championship standing 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 { Points } from '../value-objects/Points';
import { Position } from './championship/Position';
import { DriverId } from './DriverId';
import { LeagueId } from './LeagueId';
export class Standing extends Entity<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;
}) {
super(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,
});
}
static rehydrate(props: {
id: string;
leagueId: string;
driverId: string;
points: number;
wins: number;
position: number;
racesCompleted: number;
}): Standing {
if (!props.id || props.id.trim().length === 0) {
throw new RacingDomainValidationError('Standing ID is required');
}
return new Standing({
id: props.id,
leagueId: LeagueId.create(props.leagueId),
driverId: DriverId.create(props.driverId),
points: Points.create(props.points),
wins: props.wins,
position: Position.create(props.position),
racesCompleted: props.racesCompleted,
});
}
/**
* 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');
}
}
equals(other: Entity<string>): boolean {
if (!(other instanceof Standing)) {
return false;
}
return this.id === other.id;
}
/**
* Add points from a race result
*/
addRaceResult(position: number, pointsSystem: Record<number, number>): 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;
}
}