/** * Domain Value Object: StrengthOfField * * Represents the strength of field (SOF) rating for a race or league. * Enforces valid range and provides domain-specific operations. */ import type { IValueObject } from '@core/shared/domain'; import { RacingDomainValidationError } from '../errors/RacingDomainError'; export interface StrengthOfFieldProps { value: number; } export class StrengthOfField implements IValueObject { readonly value: number; private constructor(value: number) { this.value = value; } static create(value: number): StrengthOfField { if (!Number.isInteger(value)) { throw new RacingDomainValidationError('Strength of field must be an integer'); } if (value < 0 || value > 100) { throw new RacingDomainValidationError('Strength of field must be between 0 and 100'); } return new StrengthOfField(value); } /** * Get the strength category */ getCategory(): 'beginner' | 'intermediate' | 'advanced' | 'expert' { if (this.value < 25) return 'beginner'; if (this.value < 50) return 'intermediate'; if (this.value < 75) return 'advanced'; return 'expert'; } /** * Check if this SOF is suitable for the given participant count */ isSuitableForParticipants(count: number): boolean { // Higher SOF should generally have more participants const minExpected = Math.floor(this.value / 10); return count >= minExpected; } /** * Calculate difference from another SOF */ differenceFrom(other: StrengthOfField): number { return Math.abs(this.value - other.value); } get props(): StrengthOfFieldProps { return { value: this.value }; } toNumber(): number { return this.value; } equals(other: IValueObject): boolean { return this.value === other.props.value; } toString(): string { return this.value.toString(); } }