105 lines
3.7 KiB
TypeScript
105 lines
3.7 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/application/Result';
|
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase';
|
|
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
|
|
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
|
|
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
|
|
import type { DriverRatingProvider } from '../ports/DriverRatingProvider';
|
|
import {
|
|
AverageStrengthOfFieldCalculator,
|
|
type StrengthOfFieldCalculator,
|
|
} from '../../domain/services/StrengthOfFieldCalculator';
|
|
|
|
export interface GetRaceWithSOFQueryParams {
|
|
raceId: string;
|
|
}
|
|
|
|
export interface RaceWithSOFResultDTO {
|
|
raceId: string;
|
|
leagueId: string;
|
|
scheduledAt: Date;
|
|
track: string;
|
|
trackId: string;
|
|
car: string;
|
|
carId: string;
|
|
sessionType: string;
|
|
status: string;
|
|
strengthOfField: number | null;
|
|
registeredCount: number;
|
|
maxParticipants: number;
|
|
participantCount: number;
|
|
}
|
|
|
|
type GetRaceWithSOFErrorCode = 'RACE_NOT_FOUND';
|
|
|
|
export class GetRaceWithSOFUseCase implements AsyncUseCase<GetRaceWithSOFQueryParams, RaceWithSOFResultDTO, GetRaceWithSOFErrorCode> {
|
|
private readonly sofCalculator: StrengthOfFieldCalculator;
|
|
|
|
constructor(
|
|
private readonly raceRepository: IRaceRepository,
|
|
private readonly registrationRepository: IRaceRegistrationRepository,
|
|
private readonly resultRepository: IResultRepository,
|
|
private readonly driverRatingProvider: DriverRatingProvider,
|
|
sofCalculator?: StrengthOfFieldCalculator,
|
|
) {
|
|
this.sofCalculator = sofCalculator ?? new AverageStrengthOfFieldCalculator();
|
|
}
|
|
|
|
async execute(params: GetRaceWithSOFQueryParams): Promise<Result<RaceWithSOFResultDTO, ApplicationErrorCode<GetRaceWithSOFErrorCode>>> {
|
|
const { raceId } = params;
|
|
|
|
const race = await this.raceRepository.findById(raceId);
|
|
if (!race) {
|
|
return Result.err({ code: 'RACE_NOT_FOUND' });
|
|
}
|
|
|
|
// Get participant IDs based on race status
|
|
let participantIds: string[] = [];
|
|
|
|
if (race.status === 'completed') {
|
|
// For completed races, use results
|
|
const results = await this.resultRepository.findByRaceId(raceId);
|
|
participantIds = results.map(r => r.driverId);
|
|
} else {
|
|
// For upcoming/running races, use registrations
|
|
participantIds = await this.registrationRepository.getRegisteredDrivers(raceId);
|
|
}
|
|
|
|
// Use stored SOF if available, otherwise calculate
|
|
let strengthOfField = race.strengthOfField ?? null;
|
|
|
|
if (strengthOfField === null && participantIds.length > 0) {
|
|
const ratings = this.driverRatingProvider.getRatings(participantIds);
|
|
const driverRatings = participantIds
|
|
.filter(id => ratings.has(id))
|
|
.map(id => ({ driverId: id, rating: ratings.get(id)! }));
|
|
|
|
strengthOfField = this.sofCalculator.calculate(driverRatings);
|
|
}
|
|
|
|
const dto: RaceWithSOFResultDTO = {
|
|
raceId: race.id,
|
|
leagueId: race.leagueId,
|
|
scheduledAt: race.scheduledAt,
|
|
track: race.track ?? '',
|
|
trackId: race.trackId ?? '',
|
|
car: race.car ?? '',
|
|
carId: race.carId ?? '',
|
|
sessionType: race.sessionType.props,
|
|
status: race.status,
|
|
strengthOfField,
|
|
registeredCount: race.registeredCount ?? participantIds.length,
|
|
maxParticipants: race.maxParticipants ?? participantIds.length,
|
|
participantCount: participantIds.length,
|
|
};
|
|
|
|
return Result.ok(dto);
|
|
}
|
|
} |