/** * Application Query: GetLeagueStatsQuery * * Returns league statistics including average SOF across completed races. */ 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 { AverageStrengthOfFieldCalculator, type StrengthOfFieldCalculator, } from '../../domain/services/StrengthOfFieldCalculator'; export interface GetLeagueStatsQueryParams { leagueId: string; } export interface LeagueStatsDTO { leagueId: string; totalRaces: number; completedRaces: number; scheduledRaces: number; averageSOF: number | null; highestSOF: number | null; lowestSOF: number | null; } export class GetLeagueStatsQuery { private readonly sofCalculator: StrengthOfFieldCalculator; constructor( private readonly leagueRepository: ILeagueRepository, private readonly raceRepository: IRaceRepository, private readonly resultRepository: IResultRepository, private readonly driverRatingProvider: DriverRatingProvider, sofCalculator?: StrengthOfFieldCalculator, ) { this.sofCalculator = sofCalculator ?? new AverageStrengthOfFieldCalculator(); } async execute(params: GetLeagueStatsQueryParams): Promise { const { leagueId } = params; const league = await this.leagueRepository.findById(leagueId); if (!league) { return null; } 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); } } // Calculate aggregate stats const averageSOF = sofValues.length > 0 ? Math.round(sofValues.reduce((a, b) => a + b, 0) / sofValues.length) : null; const highestSOF = sofValues.length > 0 ? Math.max(...sofValues) : null; const lowestSOF = sofValues.length > 0 ? Math.min(...sofValues) : null; return { leagueId, totalRaces: races.length, completedRaces: completedRaces.length, scheduledRaces: scheduledRaces.length, averageSOF, highestSOF, lowestSOF, }; } }