/** * 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/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { IRaceRepository } from '../../domain/repositories/IRaceRepository'; import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository'; import type { IResultRepository } from '../../domain/repositories/IResultRepository'; import { AverageStrengthOfFieldCalculator, type StrengthOfFieldCalculator, } from '../../domain/services/StrengthOfFieldCalculator'; import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort'; import type { Race } from '../../domain/entities/Race'; 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: IRaceRepository, private readonly registrationRepository: IRaceRegistrationRepository, private readonly resultRepository: IResultRepository, private readonly getDriverRating: GetDriverRating, private readonly output: UseCaseOutputPort, sofCalculator?: StrengthOfFieldCalculator, ) { this.sofCalculator = sofCalculator ?? new AverageStrengthOfFieldCalculator(); } async execute( params: GetRaceWithSOFInput, ): Promise>> { 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, }; this.output.present(result); return Result.ok(undefined); } catch (error) { const message = (error as Error)?.message ?? 'Failed to load race with SOF'; return Result.err({ code: 'REPOSITORY_ERROR', details: { message }, }); } } }