wip
This commit is contained in:
128
packages/racing/domain/services/EventScoringService.ts
Normal file
128
packages/racing/domain/services/EventScoringService.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import type { ChampionshipConfig } from '../value-objects/ChampionshipConfig';
|
||||
import type { SessionType } from '../value-objects/SessionType';
|
||||
import type { ParticipantRef } from '../value-objects/ParticipantRef';
|
||||
import type { Result } from '../entities/Result';
|
||||
import type { Penalty } from '../entities/Penalty';
|
||||
import type { BonusRule } from '../value-objects/BonusRule';
|
||||
import type { ChampionshipType } from '../value-objects/ChampionshipType';
|
||||
|
||||
import type { PointsTable } from '../value-objects/PointsTable';
|
||||
|
||||
export interface ParticipantEventPoints {
|
||||
participant: ParticipantRef;
|
||||
basePoints: number;
|
||||
bonusPoints: number;
|
||||
penaltyPoints: number;
|
||||
totalPoints: number;
|
||||
}
|
||||
|
||||
function createDriverParticipant(driverId: string): ParticipantRef {
|
||||
return {
|
||||
type: 'driver' as ChampionshipType,
|
||||
id: driverId,
|
||||
};
|
||||
}
|
||||
|
||||
export class EventScoringService {
|
||||
scoreSession(params: {
|
||||
seasonId: string;
|
||||
championship: ChampionshipConfig;
|
||||
sessionType: SessionType;
|
||||
results: Result[];
|
||||
penalties: Penalty[];
|
||||
}): ParticipantEventPoints[] {
|
||||
const { championship, sessionType, results } = params;
|
||||
|
||||
const pointsTable = this.getPointsTableForSession(championship, sessionType);
|
||||
const bonusRules = this.getBonusRulesForSession(championship, sessionType);
|
||||
|
||||
const baseByDriver = new Map<string, number>();
|
||||
const bonusByDriver = new Map<string, number>();
|
||||
const penaltyByDriver = new Map<string, number>();
|
||||
|
||||
for (const result of results) {
|
||||
const driverId = result.driverId;
|
||||
const currentBase = baseByDriver.get(driverId) ?? 0;
|
||||
const added = pointsTable.getPointsForPosition(result.position);
|
||||
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<string>();
|
||||
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[sessionType] ?? [];
|
||||
}
|
||||
|
||||
private applyFastestLapBonus(
|
||||
rule: BonusRule,
|
||||
results: Result[],
|
||||
bonusByDriver: Map<string, number>,
|
||||
): void {
|
||||
if (results.length === 0) return;
|
||||
|
||||
const sortedByLap = [...results].sort((a, b) => a.fastestLap - b.fastestLap);
|
||||
const best = sortedByLap[0];
|
||||
|
||||
const requiresTop = rule.requiresFinishInTopN;
|
||||
if (typeof requiresTop === 'number') {
|
||||
if (best.position <= 0 || best.position > requiresTop) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const current = bonusByDriver.get(best.driverId) ?? 0;
|
||||
bonusByDriver.set(best.driverId, current + rule.points);
|
||||
}
|
||||
|
||||
private aggregatePenalties(penalties: Penalty[]): Map<string, number> {
|
||||
const map = new Map<string, number>();
|
||||
for (const penalty of penalties) {
|
||||
const current = map.get(penalty.driverId) ?? 0;
|
||||
map.set(penalty.driverId, current + penalty.pointsDelta);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user