144 lines
4.7 KiB
TypeScript
144 lines
4.7 KiB
TypeScript
import type { IDomainService } from '@core/shared/domain';
|
|
import type { IUserRatingRepository } from '../repositories/IUserRatingRepository';
|
|
import { UserRating } from '../value-objects/UserRating';
|
|
|
|
/**
|
|
* Domain Service: RatingUpdateService
|
|
*
|
|
* Handles updating user ratings based on various events and performance metrics.
|
|
* Centralizes rating calculation logic and ensures consistency across the system.
|
|
*/
|
|
export class RatingUpdateService implements IDomainService {
|
|
readonly serviceName = 'RatingUpdateService';
|
|
|
|
constructor(
|
|
private readonly userRatingRepository: IUserRatingRepository
|
|
) {}
|
|
|
|
/**
|
|
* Update driver ratings after race completion
|
|
*/
|
|
async updateDriverRatingsAfterRace(
|
|
driverResults: Array<{
|
|
driverId: string;
|
|
position: number;
|
|
totalDrivers: number;
|
|
incidents: number;
|
|
startPosition: number;
|
|
}>
|
|
): Promise<void> {
|
|
for (const result of driverResults) {
|
|
await this.updateDriverRating(result);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update individual driver rating based on race result
|
|
*/
|
|
private async updateDriverRating(result: {
|
|
driverId: string;
|
|
position: number;
|
|
totalDrivers: number;
|
|
incidents: number;
|
|
startPosition: number;
|
|
}): Promise<void> {
|
|
const { driverId, position, totalDrivers, incidents, startPosition } = result;
|
|
|
|
// Get or create user rating
|
|
let userRating = await this.userRatingRepository.findByUserId(driverId);
|
|
if (!userRating) {
|
|
userRating = UserRating.create(driverId);
|
|
}
|
|
|
|
// Calculate performance score (0-100)
|
|
const performanceScore = this.calculatePerformanceScore(position, totalDrivers, startPosition);
|
|
|
|
// Calculate fairness score based on incidents (lower incidents = higher fairness)
|
|
const fairnessScore = this.calculateFairnessScore(incidents, totalDrivers);
|
|
|
|
// Update ratings
|
|
const updatedRating = userRating
|
|
.updateDriverRating(performanceScore)
|
|
.updateFairnessScore(fairnessScore);
|
|
|
|
// Save updated rating
|
|
await this.userRatingRepository.save(updatedRating);
|
|
}
|
|
|
|
/**
|
|
* Calculate performance score based on finishing position and field strength
|
|
*/
|
|
private calculatePerformanceScore(
|
|
position: number,
|
|
totalDrivers: number,
|
|
startPosition: number
|
|
): number {
|
|
// Base score from finishing position (reverse percentile)
|
|
const positionScore = ((totalDrivers - position + 1) / totalDrivers) * 100;
|
|
|
|
// Bonus for positions gained
|
|
const positionsGained = startPosition - position;
|
|
const gainBonus = Math.max(0, positionsGained * 2); // 2 points per position gained
|
|
|
|
// Field strength adjustment (harder fields give higher scores for same position)
|
|
const fieldStrengthMultiplier = 0.8 + (totalDrivers / 50); // Max 1.0 for 30+ drivers
|
|
|
|
const rawScore = (positionScore + gainBonus) * fieldStrengthMultiplier;
|
|
|
|
// Clamp to 0-100 range
|
|
return Math.max(0, Math.min(100, rawScore));
|
|
}
|
|
|
|
/**
|
|
* Calculate fairness score based on incident involvement
|
|
*/
|
|
private calculateFairnessScore(incidents: number, totalDrivers: number): number {
|
|
// Base fairness score (100 = perfect, 0 = terrible)
|
|
let fairnessScore = 100;
|
|
|
|
// Deduct points for incidents
|
|
fairnessScore -= incidents * 15; // 15 points per incident
|
|
|
|
// Additional deduction for high incident rate relative to field
|
|
const incidentRate = incidents / totalDrivers;
|
|
if (incidentRate > 0.5) {
|
|
fairnessScore -= 20; // Heavy penalty for being involved in many incidents
|
|
}
|
|
|
|
// Clamp to 0-100 range
|
|
return Math.max(0, Math.min(100, fairnessScore));
|
|
}
|
|
|
|
/**
|
|
* Update trust score based on sportsmanship actions
|
|
*/
|
|
async updateTrustScore(driverId: string, trustChange: number): Promise<void> {
|
|
let userRating = await this.userRatingRepository.findByUserId(driverId);
|
|
if (!userRating) {
|
|
userRating = UserRating.create(driverId);
|
|
}
|
|
|
|
// Convert trust change (-50 to +50) to 0-100 scale
|
|
const currentTrust = userRating.trust.value;
|
|
const newTrustValue = Math.max(0, Math.min(100, currentTrust + trustChange));
|
|
|
|
const updatedRating = userRating.updateTrustScore(newTrustValue);
|
|
await this.userRatingRepository.save(updatedRating);
|
|
}
|
|
|
|
/**
|
|
* Update steward rating based on protest handling quality
|
|
*/
|
|
async updateStewardRating(stewardId: string, ratingChange: number): Promise<void> {
|
|
let userRating = await this.userRatingRepository.findByUserId(stewardId);
|
|
if (!userRating) {
|
|
userRating = UserRating.create(stewardId);
|
|
}
|
|
|
|
const currentRating = userRating.steward.value;
|
|
const newRatingValue = Math.max(0, Math.min(100, currentRating + ratingChange));
|
|
|
|
const updatedRating = userRating.updateStewardRating(newRatingValue);
|
|
await this.userRatingRepository.save(updatedRating);
|
|
}
|
|
} |