Files
gridpilot.gg/core/racing/application/use-cases/GetLeagueDriverSeasonStatsUseCase.ts
2025-12-16 11:52:26 +01:00

109 lines
3.7 KiB
TypeScript

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 '@core/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<void> {
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<string, { baseDelta: number; bonusDelta: number }>();
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<string, { rating: number | null; ratingChange: number | null }>();
for (const standing of standings) {
const ratingInfo = this.driverRatingPort.getRating(standing.driverId);
driverRatings.set(standing.driverId, ratingInfo);
}
// Collect driver results
const driverResults = new Map<string, Array<{ position: number }>>();
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);
}
}