AI Implementation feature(910): Scoreboard: Ranking, Matrix, Event Log and Score Graph 1.00 (#53)

This commit was merged in pull request #53.
This commit is contained in:
2026-07-23 05:42:17 +00:00
parent 7c4843d5cc
commit 4273662211
14 changed files with 129 additions and 179 deletions
@@ -364,6 +364,8 @@ describe('POST /api/v1/challenges/:id/solves', () => {
expect(latest).toMatchObject({
topic: 'solve',
challengeId: c.id,
challengeName: 'sse-emit',
categoryAbbreviation: 'CRY',
playerId: playerId,
position: 1,
awardedPoints: 400 + 15,
+6 -2
View File
@@ -46,6 +46,8 @@ describe('parseSolveEventIntoRanking', () => {
];
const next = parseSolveEventIntoRanking(rows, {
challengeId: 'c1',
challengeName: 'alpha',
categoryAbbreviation: 'CRY',
playerId: 'p2',
playerName: 'p2',
awardedPoints: 50,
@@ -90,7 +92,7 @@ describe('applySolveToGraph', () => {
};
const next = applySolveToGraph(
baseView,
{ challengeId: 'c1', playerId: 'p0', playerName: 'p0', awardedPoints: 30, rankBonus: 0, awardedAtUtc: '2025-01-01T06:00:00.000Z', position: 2 },
{ challengeId: 'c1', challengeName: 'alpha', categoryAbbreviation: 'CRY', playerId: 'p0', playerName: 'p0', awardedPoints: 30, rankBonus: 0, awardedAtUtc: '2025-01-01T06:00:00.000Z', position: 2 },
'2025-01-01T00:00:00.000Z',
'2025-01-02T00:00:00.000Z',
'2025-01-01T12:00:00.000Z',
@@ -105,7 +107,7 @@ describe('applySolveToGraph', () => {
it('returns empty when event is not configured', () => {
const next = applySolveToGraph(
null,
{ challengeId: 'c1', playerId: 'p0', playerName: 'p0', awardedPoints: 30, rankBonus: 0, awardedAtUtc: '2025-01-01T06:00:00.000Z', position: 1 },
{ challengeId: 'c1', challengeName: 'alpha', categoryAbbreviation: 'CRY', playerId: 'p0', playerName: 'p0', awardedPoints: 30, rankBonus: 0, awardedAtUtc: '2025-01-01T06:00:00.000Z', position: 1 },
null,
null,
'2025-01-01T12:00:00.000Z',
@@ -123,6 +125,8 @@ describe('mutateMatrixFromSolve', () => {
};
const next = mutateMatrixFromSolve(matrix, {
challengeId: 'c1',
challengeName: 'alpha',
categoryAbbreviation: 'CRY',
playerId: 'p2',
playerName: 'p2',
awardedPoints: 50,
+35
View File
@@ -133,6 +133,41 @@ describe('ScoreboardStore', () => {
});
});
it('propagates challenge name and category abbreviation from SSE solve frame to the Event Log row', () => {
const api = new FakeApi();
const destroyRef: any = { onDestroy: () => {} };
const store = new ScoreboardStore(api as any, destroyRef);
void store.loadAll();
return new Promise<void>((resolve) => {
const source = makeFakeSse((type, fn) => {
if (type === 'solve') {
setTimeout(() => {
fn(new MessageEvent('solve', { data: JSON.stringify({
challenge_id: 'c2',
challenge_name: 'Hardware Hello',
category_abbreviation: 'HW',
player_id: 'p3',
player_name: 'PlayerC',
awarded_points: 115,
rank_bonus: 15,
awarded_at_utc: '2026-07-23T05:25:37.309Z',
position: 1,
}) }));
setTimeout(() => {
const top = store.eventLog()?.[0];
expect(top?.challengeName).toBe('Hardware Hello');
expect(top?.categoryAbbreviation).toBe('HW');
expect(top?.playerName).toBe('PlayerC');
expect(top?.awardedPoints).toBe(115);
resolve();
}, 0);
}, 0);
}
});
store.wireSse(() => source);
});
});
it('reset clears all signals and stops SSE', () => {
const api = new FakeApi();
const destroyRef: any = { onDestroy: () => {} };