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 { IDriverRepository } from '../../domain/repositories/IDriverRepository'; import type { ITeamRepository } from '../../domain/repositories/ITeamRepository'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import type { DriverRatingPort } from '../ports/DriverRatingPort'; export type DriverSeasonStats = { leagueId: string; driverId: string; position: number; driverName: string; teamId: string | undefined; teamName: string | undefined; totalPoints: number; basePoints: number; penaltyPoints: number; bonusPoints: number; pointsPerRace: number; racesStarted: number; racesFinished: number; dnfs: number; noShows: number; avgFinish: number | null; rating: number | null; ratingChange: number | null; }; export type GetLeagueDriverSeasonStatsInput = { leagueId: string; }; export type GetLeagueDriverSeasonStatsResult = { leagueId: string; stats: DriverSeasonStats[]; }; export type GetLeagueDriverSeasonStatsErrorCode = | 'LEAGUE_NOT_FOUND' | 'SEASON_NOT_FOUND' | 'DRIVER_NOT_FOUND' | 'REPOSITORY_ERROR'; /** * Use Case for retrieving league driver season statistics. * Orchestrates domain logic and returns the result. */ export class GetLeagueDriverSeasonStatsUseCase { constructor( private readonly standingRepository: IStandingRepository, private readonly resultRepository: IResultRepository, private readonly penaltyRepository: IPenaltyRepository, private readonly raceRepository: IRaceRepository, private readonly driverRepository: IDriverRepository, private readonly teamRepository: ITeamRepository, private readonly driverRatingPort: DriverRatingPort, private readonly output: UseCaseOutputPort, ) {} async execute( input: GetLeagueDriverSeasonStatsInput, ): Promise< Result> > { try { const { leagueId } = input; const [standings, races] = await Promise.all([ this.standingRepository.findByLeagueId(leagueId), this.raceRepository.findByLeagueId(leagueId), ]); if (!standings || standings.length === 0) { return Result.err({ code: 'LEAGUE_NOT_FOUND', details: { message: 'League not found' }, }); } const penaltiesArrays = await Promise.all( races.map(race => this.penaltyRepository.findByRaceId(race.id)), ); const penaltiesForLeague = penaltiesArrays.flat(); const penaltiesByDriver = new Map(); for (const p of penaltiesForLeague) { if (p.status !== 'applied') continue; const current = penaltiesByDriver.get(p.driverId) ?? { baseDelta: 0, bonusDelta: 0 }; if (p.type === 'points_deduction' && p.value) { current.baseDelta -= p.value; } penaltiesByDriver.set(p.driverId, current); } const driverRatings = new Map(); for (const standing of standings) { const driverId = String(standing.driverId); const rating = await this.driverRatingPort.getDriverRating(driverId); driverRatings.set(driverId, { rating, ratingChange: null }); } const driverResults = new Map>(); for (const standing of standings) { const driverId = String(standing.driverId); const results = await this.resultRepository.findByDriverIdAndLeagueId(driverId, leagueId); driverResults.set( driverId, results.map(result => ({ position: Number((result as any).position) })), ); } const driverIds = standings.map(s => String(s.driverId)); const drivers = await Promise.all(driverIds.map(id => this.driverRepository.findById(id))); const driversMap = new Map(drivers.filter(d => d).map(d => [String(d!.id), d!])); const teamIds = Array.from( new Set( drivers .filter(d => (d as any)?.teamId) .map(d => (d as any).teamId as string), ), ); const teams = await Promise.all(teamIds.map(id => this.teamRepository.findById(id))); const teamsMap = new Map(teams.filter(t => t).map(t => [String(t!.id), t!])); const stats: DriverSeasonStats[] = standings.map(standing => { const driverId = String(standing.driverId); const driver = driversMap.get(driverId) as any; const teamId = driver?.teamId as string | undefined; const team = teamId ? teamsMap.get(String(teamId)) : undefined; const penalties = penaltiesByDriver.get(driverId) ?? { baseDelta: 0, bonusDelta: 0 }; const results = driverResults.get(driverId) ?? []; const rating = driverRatings.get(driverId); const racesStarted = results.length; const racesFinished = results.filter(r => r.position > 0).length; const dnfs = results.filter(r => r.position === 0).length; const noShows = races.length - racesStarted; const avgFinish = results.length > 0 ? results.reduce((sum, r) => sum + r.position, 0) / results.length : null; const totalPoints = Number(standing.points); const pointsPerRace = racesStarted > 0 ? totalPoints / racesStarted : 0; return { leagueId, driverId, position: Number(standing.position), driverName: String(driver?.name ?? ''), teamId, teamName: (team as any)?.name as string | undefined, totalPoints, basePoints: totalPoints - penalties.baseDelta, penaltyPoints: penalties.baseDelta, bonusPoints: penalties.bonusDelta, pointsPerRace, racesStarted, racesFinished, dnfs, noShows, avgFinish, rating: rating?.rating ?? null, ratingChange: rating?.ratingChange ?? null, }; }); const result: GetLeagueDriverSeasonStatsResult = { leagueId, stats, }; this.output.present(result); return Result.ok(undefined); } catch (error) { const message = error instanceof Error && error.message ? error.message : 'Failed to fetch league driver season stats'; return Result.err({ code: 'REPOSITORY_ERROR', details: { message }, }); } } }