113 lines
4.4 KiB
TypeScript
113 lines
4.4 KiB
TypeScript
/**
|
|
* 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<DriverStats | null> {
|
|
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';
|
|
}
|
|
} |