import { TeamRatingEvent } from '../entities/TeamRatingEvent'; import { TeamRatingDelta } from '../value-objects/TeamRatingDelta'; import { TeamRatingDimensionKey } from '../value-objects/TeamRatingDimensionKey'; import { TeamRatingEventId } from '../value-objects/TeamRatingEventId'; import { TeamRatingValue } from '../value-objects/TeamRatingValue'; import { TeamRatingSnapshotCalculator } from './TeamRatingSnapshotCalculator'; describe('TeamRatingSnapshotCalculator', () => { describe('calculate', () => { it('should return default ratings for empty events', () => { const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', []); expect(snapshot.teamId).toBe('team-123'); expect(snapshot.driving.value).toBe(50); expect(snapshot.adminTrust.value).toBe(50); expect(snapshot.overall).toBe(50); expect(snapshot.eventCount).toBe(0); }); it('should calculate single dimension rating', () => { const events = [ TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(10), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-1' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }), ]; const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events); expect(snapshot.driving.value).toBe(60); // 50 + 10 expect(snapshot.adminTrust.value).toBe(50); // Default expect(snapshot.overall).toBeCloseTo(57, 1); // 60 * 0.7 + 50 * 0.3 = 57 }); it('should calculate multiple events with weights', () => { const events = [ TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(10), weight: 1, occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-1' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }), TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(-5), weight: 2, occurredAt: new Date('2024-01-01T11:00:00Z'), createdAt: new Date('2024-01-01T11:00:00Z'), source: { type: 'race', id: 'race-2' }, reason: { code: 'RACE_PENALTY', description: 'Incident penalty' }, visibility: { public: true }, version: 1, }), ]; const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events); // Weighted average: (10*1 + (-5)*2) / (1+2) = 0/3 = 0 // So driving = 50 + 0 = 50 expect(snapshot.driving.value).toBe(50); }); it('should calculate mixed dimensions', () => { const events = [ TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(15), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-1' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }), TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('adminTrust'), delta: TeamRatingDelta.create(5), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'adminAction', id: 'action-1' }, reason: { code: 'ADMIN_BONUS', description: 'Helpful admin work' }, visibility: { public: true }, version: 1, }), ]; const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events); expect(snapshot.driving.value).toBe(65); // 50 + 15 expect(snapshot.adminTrust.value).toBe(55); // 50 + 5 expect(snapshot.overall).toBeCloseTo(62, 1); // 65 * 0.7 + 55 * 0.3 = 62 }); it('should clamp values between 0 and 100', () => { const events = [ TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(60), // Would make it 110 occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-1' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }), ]; const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events); expect(snapshot.driving.value).toBe(100); // Clamped }); it('should track last updated date', () => { const events = [ TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(5), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-1' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }), TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(3), occurredAt: new Date('2024-01-02T10:00:00Z'), createdAt: new Date('2024-01-02T10:00:00Z'), source: { type: 'race', id: 'race-2' }, reason: { code: 'RACE_FINISH', description: 'Finished 2nd' }, visibility: { public: true }, version: 1, }), ]; const snapshot = TeamRatingSnapshotCalculator.calculate('team-123', events); expect(snapshot.lastUpdated).toEqual(new Date('2024-01-02T10:00:00Z')); expect(snapshot.eventCount).toBe(2); }); }); describe('calculateDimensionChange', () => { it('should calculate net change for a dimension', () => { const events = [ TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(10), weight: 1, occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-1' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }), TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(-5), weight: 2, occurredAt: new Date('2024-01-01T11:00:00Z'), createdAt: new Date('2024-01-01T11:00:00Z'), source: { type: 'race', id: 'race-2' }, reason: { code: 'RACE_PENALTY', description: 'Incident penalty' }, visibility: { public: true }, version: 1, }), ]; const change = TeamRatingSnapshotCalculator.calculateDimensionChange( TeamRatingDimensionKey.create('driving'), events ); // (10*1 + (-5)*2) / (1+2) = 0/3 = 0 expect(change).toBe(0); }); it('should return 0 for no events', () => { const change = TeamRatingSnapshotCalculator.calculateDimensionChange( TeamRatingDimensionKey.create('driving'), [] ); expect(change).toBe(0); }); }); describe('calculateOverWindow', () => { it('should calculate ratings for a time window', () => { const allEvents = [ TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(10), occurredAt: new Date('2024-01-01T10:00:00Z'), createdAt: new Date('2024-01-01T10:00:00Z'), source: { type: 'race', id: 'race-1' }, reason: { code: 'RACE_FINISH', description: 'Finished 1st' }, visibility: { public: true }, version: 1, }), TeamRatingEvent.create({ id: TeamRatingEventId.generate(), teamId: 'team-123', dimension: TeamRatingDimensionKey.create('driving'), delta: TeamRatingDelta.create(5), occurredAt: new Date('2024-01-02T10:00:00Z'), createdAt: new Date('2024-01-02T10:00:00Z'), source: { type: 'race', id: 'race-2' }, reason: { code: 'RACE_FINISH', description: 'Finished 2nd' }, visibility: { public: true }, version: 1, }), ]; const snapshot = TeamRatingSnapshotCalculator.calculateOverWindow( 'team-123', allEvents, new Date('2024-01-01T00:00:00Z'), new Date('2024-01-01T23:59:59Z') ); // Only first event in window expect(snapshot.driving.value).toBe(60); // 50 + 10 expect(snapshot.eventCount).toBe(1); }); }); describe('calculateDelta', () => { it('should calculate differences between snapshots', () => { const before = { teamId: 'team-123', driving: TeamRatingValue.create(50), adminTrust: TeamRatingValue.create(50), overall: 50, lastUpdated: new Date('2024-01-01'), eventCount: 10, }; const after = { teamId: 'team-123', driving: TeamRatingValue.create(65), adminTrust: TeamRatingValue.create(55), overall: 62, lastUpdated: new Date('2024-01-02'), eventCount: 15, }; const delta = TeamRatingSnapshotCalculator.calculateDelta(before, after); expect(delta.driving).toBe(15); expect(delta.adminTrust).toBe(5); expect(delta.overall).toBe(12); }); }); });