116 lines
4.0 KiB
TypeScript
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 },
|
|
});
|
|
}
|
|
}
|
|
} |