import type { IStandingRepository } from '../../domain/repositories/IStandingRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { ILeagueDriverSeasonStatsPresenter, LeagueDriverSeasonStatsResultDTO, LeagueDriverSeasonStatsViewModel, } from '../presenters/ILeagueDriverSeasonStatsPresenter'; import type { UseCase } from '@gridpilot/shared/application/UseCase'; export interface DriverRatingPort { getRating(driverId: string): { rating: number | null; ratingChange: number | null }; } export interface GetLeagueDriverSeasonStatsUseCaseParams { leagueId: string; } /** * Use Case for retrieving league driver season statistics. * Orchestrates domain logic and delegates presentation to the presenter. */ export class GetLeagueDriverSeasonStatsUseCase implements UseCase< GetLeagueDriverSeasonStatsUseCaseParams, LeagueDriverSeasonStatsResultDTO, LeagueDriverSeasonStatsViewModel, ILeagueDriverSeasonStatsPresenter > { constructor( private readonly standingRepository: IStandingRepository, private readonly resultRepository: IResultRepository, private readonly penaltyRepository: IPenaltyRepository, private readonly raceRepository: IRaceRepository, private readonly driverRatingPort: DriverRatingPort, ) {} async execute( params: GetLeagueDriverSeasonStatsUseCaseParams, presenter: ILeagueDriverSeasonStatsPresenter, ): Promise { presenter.reset(); const { leagueId } = params; // Get standings and races for the league const [standings, races] = await Promise.all([ this.standingRepository.findByLeagueId(leagueId), this.raceRepository.findByLeagueId(leagueId), ]); // Fetch all penalties for all races in the league const penaltiesArrays = await Promise.all( races.map(race => this.penaltyRepository.findByRaceId(race.id)) ); const penaltiesForLeague = penaltiesArrays.flat(); // Group penalties by driver for quick lookup const penaltiesByDriver = new Map(); for (const p of penaltiesForLeague) { // Only count applied penalties if (p.status !== 'applied') continue; const current = penaltiesByDriver.get(p.driverId) ?? { baseDelta: 0, bonusDelta: 0 }; // Convert penalty to points delta based on type if (p.type === 'points_deduction' && p.value) { // Points deductions are negative current.baseDelta -= p.value; } penaltiesByDriver.set(p.driverId, current); } // Collect driver ratings const driverRatings = new Map(); for (const standing of standings) { const ratingInfo = this.driverRatingPort.getRating(standing.driverId); driverRatings.set(standing.driverId, ratingInfo); } // Collect driver results const driverResults = new Map>(); for (const standing of standings) { const results = await this.resultRepository.findByDriverIdAndLeagueId( standing.driverId, leagueId, ); driverResults.set(standing.driverId, results); } const dto: LeagueDriverSeasonStatsResultDTO = { leagueId, standings: standings.map(standing => ({ driverId: standing.driverId, position: standing.position, points: standing.points, racesCompleted: standing.racesCompleted, })), penalties: penaltiesByDriver, driverResults, driverRatings, }; presenter.present(dto); } }