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 { 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 { ChampionshipStandingsDTO, ChampionshipStandingsRowDTO, } from '../dto/ChampionshipStandingsDTO'; import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; export interface RecalculateChampionshipStandingsParams { seasonId: string; championshipId: string; } type RecalculateChampionshipStandingsErrorCode = | 'SEASON_NOT_FOUND' | 'LEAGUE_SCORING_CONFIG_NOT_FOUND' | 'CHAMPIONSHIP_CONFIG_NOT_FOUND'; export class RecalculateChampionshipStandingsUseCase implements AsyncUseCase { 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: RecalculateChampionshipStandingsParams): Promise>> { const { seasonId, championshipId } = params; const season = await this.seasonRepository.findById(seasonId); if (!season) { return Result.err({ code: 'SEASON_NOT_FOUND', details: { message: `Season not found: ${seasonId}` } }); } const leagueScoringConfig = await this.leagueScoringConfigRepository.findBySeasonId(seasonId); if (!leagueScoringConfig) { return Result.err({ code: 'LEAGUE_SCORING_CONFIG_NOT_FOUND', details: { message: `League scoring config not found for season: ${seasonId}` } }); } const championship = leagueScoringConfig.championships.find((c) => c.id === championshipId); if (!championship) { return Result.err({ code: 'CHAMPIONSHIP_CONFIG_NOT_FOUND', details: { message: `Championship config not found: ${championshipId}` } }); } const races = await this.raceRepository.findByLeagueId(season.leagueId); const eventPointsByEventId: Record> = {}; 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.toNumber(), totalPoints: s.totalPoints.toNumber(), resultsCounted: s.resultsCounted.toNumber(), resultsDropped: s.resultsDropped.toNumber(), })); const dto: ChampionshipStandingsDTO = { seasonId, championshipId: championship.id, championshipName: championship.name, rows, }; return Result.ok(dto); } private mapRaceSessionType(sessionType: string): SessionType { if (sessionType === 'race') { return 'main'; } if ( sessionType === 'practice' || sessionType === 'qualifying' || sessionType === 'timeTrial' ) { return sessionType; } return 'main'; } }