wip
This commit is contained in:
99
packages/racing/application/use-cases/GetLeagueStatsQuery.ts
Normal file
99
packages/racing/application/use-cases/GetLeagueStatsQuery.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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<LeagueStatsDTO | null> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user