/** * Application Use Case: DriverStatsUseCase * * Computes detailed driver statistics from race results and standings. * Orchestrates repositories to provide stats data to presentation layer. */ import type { Logger } from '@core/shared/application'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import type { IStandingRepository } from '../../domain/repositories/IStandingRepository'; import type { IDriverStatsUseCase, DriverStats } from './IDriverStatsUseCase'; export class DriverStatsUseCase implements IDriverStatsUseCase { constructor( private readonly resultRepository: IResultRepository, private readonly standingRepository: IStandingRepository, private readonly logger: Logger ) { this.logger.info('[DriverStatsUseCase] Initialized with real data repositories'); } async getDriverStats(driverId: string): Promise { this.logger.debug(`[DriverStatsUseCase] Computing stats for driver: ${driverId}`); try { // Get all results for this driver const results = await this.resultRepository.findByDriverId(driverId); if (results.length === 0) { this.logger.warn(`[DriverStatsUseCase] No results found for driver: ${driverId}`); return null; } // Get standings for context const standings = await this.standingRepository.findAll(); const driverStanding = standings.find(s => s.driverId.toString() === driverId); // Calculate basic stats from results const wins = results.filter(r => r.position.toNumber() === 1).length; const podiums = results.filter(r => r.position.toNumber() <= 3).length; const dnfs = results.filter(r => r.position.toNumber() > 20).length; const totalRaces = results.length; const positions = results.map(r => r.position.toNumber()); const avgFinish = positions.reduce((sum, pos) => sum + pos, 0) / totalRaces; const bestFinish = Math.min(...positions); const worstFinish = Math.max(...positions); // Calculate rating based on performance let rating = 1000; if (driverStanding) { // Use standing-based rating const pointsBonus = driverStanding.points.toNumber() * 2; const positionBonus = Math.max(0, 50 - (driverStanding.position.toNumber() * 2)); const winBonus = driverStanding.wins * 100; rating = Math.round(1000 + pointsBonus + positionBonus + winBonus); } else { // Calculate from results if no standing const performanceBonus = ((totalRaces - wins) * 5) + ((totalRaces - podiums) * 2); rating = Math.round(1000 + (wins * 100) + (podiums * 50) - performanceBonus); } // Calculate consistency (inverse of position variance) const avgPosition = avgFinish; const variance = positions.reduce((sum, pos) => sum + Math.pow(pos - avgPosition, 2), 0) / totalRaces; const consistency = Math.round(Math.max(0, 100 - (variance * 2))); // Safety rating (simplified - based on incidents) const totalIncidents = results.reduce((sum, r) => sum + r.incidents.toNumber(), 0); const safetyRating = Math.round(Math.max(0, 100 - (totalIncidents / totalRaces))); // Sportsmanship rating (placeholder - could be based on penalties/protests) const sportsmanshipRating = 4.5; // Experience level const experienceLevel = this.determineExperienceLevel(totalRaces); // Overall rank const overallRank = driverStanding ? driverStanding.position.toNumber() : null; const stats: DriverStats = { rating, safetyRating, sportsmanshipRating, totalRaces, wins, podiums, dnfs, avgFinish: Math.round(avgFinish * 10) / 10, bestFinish, worstFinish, consistency, experienceLevel, overallRank }; this.logger.debug(`[DriverStatsUseCase] Computed stats for driver ${driverId}: rating=${stats.rating}, wins=${stats.wins}`); return stats; } catch (error) { this.logger.error(`[DriverStatsUseCase] Error computing stats for driver ${driverId}:`, error instanceof Error ? error : new Error(String(error))); throw error; } } private determineExperienceLevel(totalRaces: number): string { if (totalRaces >= 100) return 'Veteran'; if (totalRaces >= 50) return 'Experienced'; if (totalRaces >= 20) return 'Intermediate'; if (totalRaces >= 10) return 'Rookie'; return 'Beginner'; } }