rename to core
This commit is contained in:
175
core/racing/domain/entities/ResultWithIncidents.ts
Normal file
175
core/racing/domain/entities/ResultWithIncidents.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Enhanced Result entity with detailed incident tracking
|
||||
*/
|
||||
|
||||
import { RacingDomainValidationError } from '../errors/RacingDomainError';
|
||||
import type { IEntity } from '@gridpilot/shared/domain';
|
||||
import { RaceIncidents, type IncidentRecord } from '../value-objects/RaceIncidents';
|
||||
|
||||
export class ResultWithIncidents implements IEntity<string> {
|
||||
readonly id: string;
|
||||
readonly raceId: string;
|
||||
readonly driverId: string;
|
||||
readonly position: number;
|
||||
readonly fastestLap: number;
|
||||
readonly incidents: RaceIncidents;
|
||||
readonly startPosition: number;
|
||||
|
||||
private constructor(props: {
|
||||
id: string;
|
||||
raceId: string;
|
||||
driverId: string;
|
||||
position: number;
|
||||
fastestLap: number;
|
||||
incidents: RaceIncidents;
|
||||
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: RaceIncidents;
|
||||
startPosition: number;
|
||||
}): ResultWithIncidents {
|
||||
ResultWithIncidents.validate(props);
|
||||
return new ResultWithIncidents(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 - this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if driver finished on podium
|
||||
*/
|
||||
isPodium(): boolean {
|
||||
return this.position <= 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user