Files
gridpilot.gg/core/identity/domain/services/RatingUpdateService.ts
2025-12-23 11:25:08 +01:00

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);
}
}