import type { ChampionshipConfig } from '../types/ChampionshipConfig'; import type { SessionType } from '../types/SessionType'; import type { ParticipantRef } from '../types/ParticipantRef'; import type { Result } from '../entities/result/Result'; import type { Penalty } from '../entities/Penalty'; import type { BonusRule } from '../types/BonusRule'; import type { ChampionshipType } from '../types/ChampionshipType'; import type { PointsTable } from '../value-objects/PointsTable'; import type { IDomainCalculationService } from '@core/shared/domain'; export interface ParticipantEventPoints { participant: ParticipantRef; basePoints: number; bonusPoints: number; penaltyPoints: number; totalPoints: number; } export interface EventScoringInput { seasonId: string; championship: ChampionshipConfig; sessionType: SessionType; results: Result[]; penalties: Penalty[]; } function createDriverParticipant(driverId: string): ParticipantRef { return { type: 'driver' as ChampionshipType, id: driverId, }; } export class EventScoringService implements IDomainCalculationService { calculate(input: EventScoringInput): ParticipantEventPoints[] { return this.scoreSession(input); } scoreSession(params: EventScoringInput): ParticipantEventPoints[] { const { championship, sessionType, results } = params; const pointsTable = this.getPointsTableForSession(championship, sessionType); const bonusRules = this.getBonusRulesForSession(championship, sessionType); const baseByDriver = new Map(); const bonusByDriver = new Map(); const penaltyByDriver = new Map(); for (const result of results) { const driverId = result.driverId.toString(); const currentBase = baseByDriver.get(driverId) ?? 0; const added = pointsTable.getPointsForPosition(result.position.toNumber()); baseByDriver.set(driverId, currentBase + added); } const fastestLapRule = bonusRules.find((r) => r.type === 'fastestLap'); if (fastestLapRule) { this.applyFastestLapBonus(fastestLapRule, results, bonusByDriver); } const penaltyMap = this.aggregatePenalties(params.penalties); for (const [driverId, value] of penaltyMap.entries()) { penaltyByDriver.set(driverId, value); } const allDriverIds = new Set(); for (const id of baseByDriver.keys()) allDriverIds.add(id); for (const id of bonusByDriver.keys()) allDriverIds.add(id); for (const id of penaltyByDriver.keys()) allDriverIds.add(id); const participants: ParticipantEventPoints[] = []; for (const driverId of allDriverIds) { const basePoints = baseByDriver.get(driverId) ?? 0; const bonusPoints = bonusByDriver.get(driverId) ?? 0; const penaltyPoints = penaltyByDriver.get(driverId) ?? 0; const totalPoints = basePoints + bonusPoints - penaltyPoints; participants.push({ participant: createDriverParticipant(driverId), basePoints, bonusPoints, penaltyPoints, totalPoints, }); } return participants; } private getPointsTableForSession( championship: ChampionshipConfig, sessionType: SessionType, ): PointsTable { return championship.pointsTableBySessionType[sessionType]; } private getBonusRulesForSession( championship: ChampionshipConfig, sessionType: SessionType, ): BonusRule[] { const all = championship.bonusRulesBySessionType ?? {}; return (all as Record)[sessionType] ?? []; } private applyFastestLapBonus( rule: BonusRule, results: Result[], bonusByDriver: Map, ): void { if (results.length === 0) return; const sortedByLap = [...results].sort((a, b) => a.fastestLap.toNumber() - b.fastestLap.toNumber()); const best = sortedByLap[0]; if (!best) { return; } const requiresTop = rule.requiresFinishInTopN; if (typeof requiresTop === 'number') { if (best.position.toNumber() <= 0 || best.position.toNumber() > requiresTop) { return; } } const current = bonusByDriver.get(best.driverId.toString()) ?? 0; bonusByDriver.set(best.driverId.toString(), current + rule.points); } private aggregatePenalties(penalties: Penalty[]): Map { const map = new Map(); for (const penalty of penalties) { // Only count applied points_deduction penalties if (penalty.status !== 'applied' || penalty.type !== 'points_deduction') { continue; } const current = map.get(penalty.driverId) ?? 0; const delta = penalty.value ?? 0; map.set(penalty.driverId, current + delta); } return map; } }