website refactor
This commit is contained in:
@@ -1,113 +1,26 @@
|
||||
/**
|
||||
* Application Use Case: DriverStatsUseCase
|
||||
* Application Use Case Interface: IDriverStatsUseCase
|
||||
*
|
||||
* Computes detailed driver statistics from race results and standings.
|
||||
* Orchestrates repositories to provide stats data to presentation layer.
|
||||
* Use case for computing detailed driver statistics from race results and standings.
|
||||
* This is an application layer concern that orchestrates domain data.
|
||||
*/
|
||||
|
||||
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 interface DriverStats {
|
||||
rating: number;
|
||||
safetyRating: number;
|
||||
sportsmanshipRating: number;
|
||||
totalRaces: number;
|
||||
wins: number;
|
||||
podiums: number;
|
||||
dnfs: number;
|
||||
avgFinish: number;
|
||||
bestFinish: number;
|
||||
worstFinish: number;
|
||||
consistency: number;
|
||||
experienceLevel: string;
|
||||
overallRank: number | null;
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
export interface DriverStatsUseCase {
|
||||
getDriverStats(driverId: string): Promise<DriverStats | null>;
|
||||
}
|
||||
Reference in New Issue
Block a user