import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeasonRepository'; import type { ILeagueScoringConfigRepository } from '@core/racing/domain/repositories/ILeagueScoringConfigRepository'; import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository'; import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository'; import type { IPenaltyRepository } from '@core/racing/domain/repositories/IPenaltyRepository'; import type { IChampionshipStandingRepository } from '@core/racing/domain/repositories/IChampionshipStandingRepository'; import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository'; import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig'; import type { SessionType } from '@core/racing/domain/types/SessionType'; import type { ChampionshipStanding } from '@core/racing/domain/entities/championship/ChampionshipStanding'; import { EventScoringService } from '@core/racing/domain/services/EventScoringService'; import { ChampionshipAggregator } from '@core/racing/domain/services/ChampionshipAggregator'; import type { UseCaseOutputPort, Logger } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; export type RecalculateChampionshipStandingsInput = { leagueId: string; seasonId: string; }; export type ChampionshipStandingsEntry = { driverId: string | null; teamId: string | null; position: number; points: number; }; export type RecalculateChampionshipStandingsResult = { leagueId: string; seasonId: string; entries: ChampionshipStandingsEntry[]; }; export type RecalculateChampionshipStandingsErrorCode = | 'LEAGUE_NOT_FOUND' | 'SEASON_NOT_FOUND' | 'REPOSITORY_ERROR'; export class RecalculateChampionshipStandingsUseCase { constructor( private readonly leagueRepository: ILeagueRepository, 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, private readonly logger: Logger, private readonly output: UseCaseOutputPort, ) {} async execute( input: RecalculateChampionshipStandingsInput, ): Promise< Result< void, ApplicationErrorCode > > { const { leagueId, seasonId } = input; try { const league = await this.leagueRepository.findById(leagueId); if (!league) { return Result.err({ code: 'LEAGUE_NOT_FOUND', details: { message: `League not found: ${leagueId}` }, }); } const season = await this.seasonRepository.findById(seasonId); if (!season || season.leagueId !== leagueId) { return Result.err({ code: 'SEASON_NOT_FOUND', details: { message: `Season not found for league: leagueId=${leagueId}, seasonId=${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); const races = await this.raceRepository.findByLeagueId(leagueId); const eventPointsByEventId: Record> = {}; for (const race of races) { const sessionType = this.mapRaceSessionType(String(race.sessionType)); if (!championship.sessionTypes.includes(sessionType)) { continue; } const results = await this.resultRepository.findByRaceId(race.id); 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 result: RecalculateChampionshipStandingsResult = { leagueId, seasonId, entries: standings.map((standing) => ({ driverId: standing.participant?.id ?? null, teamId: null, position: standing.position.toNumber(), points: standing.totalPoints.toNumber(), })), }; this.output.present(result); return Result.ok(undefined); } catch (error) { const err = error as Error; this.logger.error('Failed to recalculate championship standings', err, { leagueId, seasonId, }); return Result.err({ code: 'REPOSITORY_ERROR', details: { message: err.message || 'Failed to recalculate championship standings', }, }); } } private findChampionshipConfig(championships: ChampionshipConfig[]): ChampionshipConfig { if (!championships || championships.length === 0) { throw new Error('No championship configurations found'); } return championships[0]!; } private mapRaceSessionType(sessionType: SessionType | string): SessionType { if (sessionType === 'race') { return 'main'; } if ( sessionType === 'practice' || sessionType === 'qualifying' || sessionType === 'timeTrial' ) { return sessionType; } return 'main'; } }