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
@@ -373,6 +373,8 @@ export class ChallengesService {
this.publishSolveEvent({
challengeId,
challengeName: challenge.name,
categoryAbbreviation: challenge.abbreviation ?? '',
userId: currentUserId,
username: solvers.find((s) => s.userId === currentUserId)?.username ?? '',
awardedPoints,
@@ -405,6 +407,8 @@ export class ChallengesService {
private publishSolveEvent(input: {
challengeId: string;
challengeName: string;
categoryAbbreviation: string;
userId: string;
username: string;
awardedPoints: number;
@@ -422,6 +426,8 @@ export class ChallengesService {
this.hub.emitScoreboard({
topic: 'solve',
challengeId: input.challengeId,
challengeName: input.challengeName,
categoryAbbreviation: input.categoryAbbreviation,
userId: input.userId,
playerId: input.userId,
playerName: input.username,
@@ -81,6 +81,8 @@ export interface ChallengeDetailDto {
export interface SolveEventPayload {
challengeId: string;
challengeName: string;
categoryAbbreviation: string;
playerId: string;
playerName: string;
awardedPoints: number;
@@ -28,8 +28,9 @@ export class ChallengesEventsController {
* Emits NestJS MessageEvent objects with the proper `type` field so the
* wire format is `event: <type>\ndata: <JSON>\n\n`:
* - event: status, payload = { state, server_time_utc, event_start_utc, event_end_utc, seconds_to_start, seconds_to_end }
* - event: solve, payload = { challenge_id, player_id, player_name, awarded_points, rank_bonus, awarded_at_utc,
* position, live_points, solve_count, initial_points, minimum_points, decay_solves }
* - event: solve, payload = { challenge_id, challenge_name, category_abbreviation, player_id, player_name,
* awarded_points, rank_bonus, awarded_at_utc, position, live_points, solve_count,
* initial_points, minimum_points, decay_solves }
* Source of truth: every transition of the event window is broadcast by
* the admin general service through SseHubService.event$(). Solves are
* published by ChallengesService through SseHubService.scoreboard$().
@@ -78,6 +79,8 @@ export class ChallengesEventsController {
type: 'solve',
data: {
challenge_id: s?.challengeId,
challenge_name: s?.challengeName ?? '',
category_abbreviation: s?.categoryAbbreviation ?? '',
player_id: s?.playerId ?? s?.userId,
player_name: s?.playerName ?? '',
awarded_points: s?.awardedPoints ?? s?.pointsAwarded,