Files
gridpilot.gg/apps/website/lib/presenters/LeagueDriverSeasonStatsPresenter.ts
2025-12-11 21:06:25 +01:00

78 lines
2.6 KiB
TypeScript

import type {
ILeagueDriverSeasonStatsPresenter,
LeagueDriverSeasonStatsItemViewModel,
LeagueDriverSeasonStatsViewModel,
} from '@gridpilot/racing/application/presenters/ILeagueDriverSeasonStatsPresenter';
export class LeagueDriverSeasonStatsPresenter implements ILeagueDriverSeasonStatsPresenter {
private viewModel: LeagueDriverSeasonStatsViewModel | null = null;
present(
leagueId: string,
standings: Array<{
driverId: string;
position: number;
points: number;
racesCompleted: number;
}>,
penalties: Map<string, { baseDelta: number; bonusDelta: number }>,
driverResults: Map<string, Array<{ position: number }>>,
driverRatings: Map<string, { rating: number | null; ratingChange: number | null }>
): LeagueDriverSeasonStatsViewModel {
const stats: LeagueDriverSeasonStatsItemViewModel[] = standings.map((standing) => {
const penalty = penalties.get(standing.driverId) ?? { baseDelta: 0, bonusDelta: 0 };
const totalPenaltyPoints = penalty.baseDelta;
const bonusPoints = penalty.bonusDelta;
const racesCompleted = standing.racesCompleted;
const pointsPerRace = racesCompleted > 0 ? standing.points / racesCompleted : 0;
const ratingInfo = driverRatings.get(standing.driverId) ?? { rating: null, ratingChange: null };
const results = driverResults.get(standing.driverId) ?? [];
let avgFinish: number | null = null;
if (results.length > 0) {
const totalPositions = results.reduce((sum, r) => sum + r.position, 0);
const avg = totalPositions / results.length;
avgFinish = Number.isFinite(avg) ? Number(avg.toFixed(2)) : null;
}
return {
leagueId,
driverId: standing.driverId,
position: standing.position,
driverName: '',
teamId: '',
teamName: '',
totalPoints: standing.points + totalPenaltyPoints + bonusPoints,
basePoints: standing.points,
penaltyPoints: Math.abs(totalPenaltyPoints),
bonusPoints,
pointsPerRace,
racesStarted: results.length,
racesFinished: results.length,
dnfs: 0,
noShows: 0,
avgFinish,
rating: ratingInfo.rating,
ratingChange: ratingInfo.ratingChange,
};
});
stats.sort((a, b) => a.position - b.position);
this.viewModel = {
leagueId,
stats,
};
return this.viewModel;
}
getViewModel(): LeagueDriverSeasonStatsViewModel {
if (!this.viewModel) {
throw new Error('Presenter has not been called yet');
}
return this.viewModel;
}
}