Files
gridpilot.gg/core/racing/application/use-cases/GetRaceWithSOFUseCase.ts
2026-01-16 19:46:49 +01:00

116 lines
4.0 KiB
TypeScript

/**
* Use Case: GetRaceWithSOFUseCase
*
* Returns race details enriched with calculated Strength of Field (SOF).
* SOF is calculated from participant ratings if not already stored on the race.
*/
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Race } from '../../domain/entities/Race';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import {
AverageStrengthOfFieldCalculator,
type StrengthOfFieldCalculator,
} from '../../domain/services/StrengthOfFieldCalculator';
export interface GetRaceWithSOFInput {
raceId: string;
}
export type GetRaceWithSOFErrorCode = 'RACE_NOT_FOUND' | 'REPOSITORY_ERROR';
export type GetRaceWithSOFResult = {
race: Race;
strengthOfField: number | null;
participantCount: number;
registeredCount: number;
maxParticipants: number;
};
type GetDriverRating = (input: { driverId: string }) => Promise<{ rating: number | null }>;
export class GetRaceWithSOFUseCase {
private readonly sofCalculator: StrengthOfFieldCalculator;
constructor(
private readonly raceRepository: RaceRepository,
private readonly registrationRepository: RaceRegistrationRepository,
private readonly resultRepository: ResultRepository,
private readonly getDriverRating: GetDriverRating,
sofCalculator?: StrengthOfFieldCalculator,
) {
this.sofCalculator = sofCalculator ?? new AverageStrengthOfFieldCalculator();
}
async execute(
params: GetRaceWithSOFInput,
): Promise<Result<GetRaceWithSOFResult, ApplicationErrorCode<GetRaceWithSOFErrorCode, { message: string }>>> {
const { raceId } = params;
try {
const race = await this.raceRepository.findById(raceId);
if (!race) {
return Result.err({
code: 'RACE_NOT_FOUND',
details: { message: `Race with id ${raceId} not found` },
});
}
// Get participant IDs based on race status
let participantIds: string[] = [];
if (race.status.isCompleted()) {
// For completed races, use results
const results = await this.resultRepository.findByRaceId(raceId);
participantIds = results.map(r => r.driverId.toString());
} else {
// For upcoming/running races, use registrations
participantIds = await this.registrationRepository.getRegisteredDrivers(raceId);
}
// Use stored SOF if available, otherwise calculate
let strengthOfField = race.strengthOfField?.toNumber() ?? null;
if (strengthOfField === null && participantIds.length > 0) {
// Get ratings for all participants using clean ports
const ratingPromises = participantIds.map(driverId =>
this.getDriverRating({ driverId }),
);
const ratingResults = await Promise.all(ratingPromises);
const driverRatings = participantIds.reduce<{ driverId: string; rating: number }[]>(
(acc, driverId, index) => {
const ratingResult = ratingResults[index];
if (ratingResult && ratingResult.rating !== null) {
acc.push({ driverId, rating: ratingResult.rating });
}
return acc;
},
[],
);
strengthOfField = this.sofCalculator.calculate(driverRatings);
}
const result: GetRaceWithSOFResult = {
race,
strengthOfField,
registeredCount: race.registeredCount?.toNumber() ?? participantIds.length,
maxParticipants: race.maxParticipants?.toNumber() ?? participantIds.length,
participantCount: participantIds.length,
};
return Result.ok(result);
} catch (error) {
const message =
(error as Error)?.message ?? 'Failed to load race with SOF';
return Result.err({
code: 'REPOSITORY_ERROR',
details: { message },
});
}
}
}