Files
gridpilot.gg/core/racing/domain/services/TeamRatingSnapshotCalculator.test.ts
2025-12-30 12:25:45 +01:00

290 lines
11 KiB
TypeScript

import { TeamRatingSnapshotCalculator } from './TeamRatingSnapshotCalculator';
import { TeamRatingEvent } from '../entities/TeamRatingEvent';
import { TeamRatingEventId } from '../value-objects/TeamRatingEventId';
import { TeamRatingDimensionKey } from '../value-objects/TeamRatingDimensionKey';
import { TeamRatingDelta } from '../value-objects/TeamRatingDelta';
import { TeamRatingValue } from '../value-objects/TeamRatingValue';
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);
});
});
});