/** * Use Case for retrieving league statistics. * Orchestrates domain logic and delegates presentation to the presenter. */ import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { DriverRatingProvider } from '../ports/DriverRatingProvider'; import type { ILeagueStatsPresenter } from '../presenters/ILeagueStatsPresenter'; import type { AsyncUseCase } from '@gridpilot/shared/application'; import { AverageStrengthOfFieldCalculator, type StrengthOfFieldCalculator, } from '../../domain/services/StrengthOfFieldCalculator'; export interface GetLeagueStatsUseCaseParams { leagueId: string; } /** * Use Case for retrieving league statistics including average SOF across completed races. */ export class GetLeagueStatsUseCase implements AsyncUseCase { private readonly sofCalculator: StrengthOfFieldCalculator; constructor( private readonly leagueRepository: ILeagueRepository, private readonly raceRepository: IRaceRepository, private readonly resultRepository: IResultRepository, private readonly driverRatingProvider: DriverRatingProvider, public readonly presenter: ILeagueStatsPresenter, sofCalculator?: StrengthOfFieldCalculator, ) { this.sofCalculator = sofCalculator ?? new AverageStrengthOfFieldCalculator(); } async execute(params: GetLeagueStatsUseCaseParams): Promise { const { leagueId } = params; const league = await this.leagueRepository.findById(leagueId); if (!league) { throw new Error(`League ${leagueId} not found`); } const races = await this.raceRepository.findByLeagueId(leagueId); const completedRaces = races.filter(r => r.status === 'completed'); const scheduledRaces = races.filter(r => r.status === 'scheduled'); // Calculate SOF for each completed race const sofValues: number[] = []; for (const race of completedRaces) { // Use stored SOF if available if (race.strengthOfField) { sofValues.push(race.strengthOfField); continue; } // Otherwise calculate from results const results = await this.resultRepository.findByRaceId(race.id); if (results.length === 0) continue; const driverIds = results.map(r => r.driverId); const ratings = this.driverRatingProvider.getRatings(driverIds); const driverRatings = driverIds .filter(id => ratings.has(id)) .map(id => ({ driverId: id, rating: ratings.get(id)! })); const sof = this.sofCalculator.calculate(driverRatings); if (sof !== null) { sofValues.push(sof); } } this.presenter.present( leagueId, races.length, completedRaces.length, scheduledRaces.length, sofValues ); } }