Files
gridpilot.gg/packages/racing/application/use-cases/RecalculateChampionshipStandingsUseCase.ts
2025-12-11 13:50:38 +01:00

131 lines
4.6 KiB
TypeScript

import type { ISeasonRepository } from '@gridpilot/racing/domain/repositories/ISeasonRepository';
import type { ILeagueScoringConfigRepository } from '@gridpilot/racing/domain/repositories/ILeagueScoringConfigRepository';
import type { IRaceRepository } from '@gridpilot/racing/domain/repositories/IRaceRepository';
import type { IResultRepository } from '@gridpilot/racing/domain/repositories/IResultRepository';
import type { IPenaltyRepository } from '@gridpilot/racing/domain/repositories/IPenaltyRepository';
import type { IChampionshipStandingRepository } from '@gridpilot/racing/domain/repositories/IChampionshipStandingRepository';
import type { ChampionshipConfig } from '@gridpilot/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@gridpilot/racing/domain/types/SessionType';
import type { ChampionshipStanding } from '@gridpilot/racing/domain/entities/ChampionshipStanding';
import { EventScoringService } from '@gridpilot/racing/domain/services/EventScoringService';
import { ChampionshipAggregator } from '@gridpilot/racing/domain/services/ChampionshipAggregator';
import type {
ChampionshipStandingsDTO,
ChampionshipStandingsRowDTO,
} from '../dto/ChampionshipStandingsDTO';
export class RecalculateChampionshipStandingsUseCase {
constructor(
private readonly seasonRepository: ISeasonRepository,
private readonly leagueScoringConfigRepository: ILeagueScoringConfigRepository,
private readonly raceRepository: IRaceRepository,
private readonly resultRepository: IResultRepository,
private readonly penaltyRepository: IPenaltyRepository,
private readonly championshipStandingRepository: IChampionshipStandingRepository,
private readonly eventScoringService: EventScoringService,
private readonly championshipAggregator: ChampionshipAggregator,
) {}
async execute(params: {
seasonId: string;
championshipId: string;
}): Promise<ChampionshipStandingsDTO> {
const { seasonId, championshipId } = params;
const season = await this.seasonRepository.findById(seasonId);
if (!season) {
throw new Error(`Season not found: ${seasonId}`);
}
const leagueScoringConfig =
await this.leagueScoringConfigRepository.findBySeasonId(seasonId);
if (!leagueScoringConfig) {
throw new Error(`League scoring config not found for season: ${seasonId}`);
}
const championship = this.findChampionshipConfig(
leagueScoringConfig.championships,
championshipId,
);
const races = await this.raceRepository.findByLeagueId(season.leagueId);
const eventPointsByEventId: Record<string, ReturnType<EventScoringService['scoreSession']>> =
{};
for (const race of races) {
// Map existing Race.sessionType into scoring SessionType where possible.
const sessionType = this.mapRaceSessionType(race.sessionType);
if (!championship.sessionTypes.includes(sessionType)) {
continue;
}
const results = await this.resultRepository.findByRaceId(race.id);
// Fetch penalties for this specific race
const penalties = await this.penaltyRepository.findByRaceId(race.id);
const participantPoints = this.eventScoringService.scoreSession({
seasonId,
championship,
sessionType,
results,
penalties,
});
eventPointsByEventId[race.id] = participantPoints;
}
const standings: ChampionshipStanding[] = this.championshipAggregator.aggregate({
seasonId,
championship,
eventPointsByEventId,
});
await this.championshipStandingRepository.saveAll(standings);
const rows: ChampionshipStandingsRowDTO[] = standings.map((s) => ({
participant: s.participant,
position: s.position,
totalPoints: s.totalPoints,
resultsCounted: s.resultsCounted,
resultsDropped: s.resultsDropped,
}));
const dto: ChampionshipStandingsDTO = {
seasonId,
championshipId: championship.id,
championshipName: championship.name,
rows,
};
return dto;
}
private findChampionshipConfig(
configs: ChampionshipConfig[],
championshipId: string,
): ChampionshipConfig {
const found = configs.find((c) => c.id === championshipId);
if (!found) {
throw new Error(`Championship config not found: ${championshipId}`);
}
return found;
}
private mapRaceSessionType(sessionType: string): SessionType {
if (sessionType === 'race') {
return 'main';
}
if (
sessionType === 'practice' ||
sessionType === 'qualifying' ||
sessionType === 'timeTrial'
) {
return sessionType;
}
return 'main';
}
}