96 行
3.7 KiB
TypeScript
96 行
3.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildParticipantSettlement, deriveSuggestedMatchState } from "./match";
|
|
|
|
describe("deriveSuggestedMatchState", () => {
|
|
it("aggregates point, game and set events into a suggested scoreboard", () => {
|
|
const result = deriveSuggestedMatchState([
|
|
{ eventIndex: 1, eventType: "point", source: "camera_a", winnerSlot: "player_a", confidence: 0.6, payload: { rallyCount: 4, isWinner: true } },
|
|
{ eventIndex: 2, eventType: "point", source: "camera_b", winnerSlot: "player_a", confidence: 0.7, payload: { rallyCount: 7, isAce: true, firstServeIn: true, firstServeAttempt: true } },
|
|
{ eventIndex: 3, eventType: "game", source: "camera_a", winnerSlot: "player_a", confidence: 0.65 },
|
|
{ eventIndex: 4, eventType: "set", source: "camera_a", winnerSlot: "player_a", confidence: 0.8 },
|
|
]);
|
|
|
|
expect(result.score.sets.player_a).toBe(1);
|
|
expect(result.score.games.player_a).toBe(1);
|
|
expect(result.score.points.player_a).toBe(0);
|
|
expect(result.score.winnerSlot).toBe("player_a");
|
|
expect(result.metrics.players.player_a.pointsWon).toBe(2);
|
|
expect(result.metrics.players.player_a.aces).toBe(1);
|
|
expect(result.metrics.players.player_a.firstServePct).toBe(100);
|
|
expect(result.metrics.longestRally).toBe(7);
|
|
expect(result.sourceCount).toBe(2);
|
|
});
|
|
|
|
it("prefers later reviewed score snapshots while keeping metric snapshots", () => {
|
|
const result = deriveSuggestedMatchState([
|
|
{
|
|
eventIndex: 1,
|
|
eventType: "score_suggestion",
|
|
source: "system",
|
|
confidence: 0.78,
|
|
payload: {
|
|
score: {
|
|
sets: { player_a: 1, player_b: 0 },
|
|
games: { player_a: 6, player_b: 4 },
|
|
points: { player_a: 0, player_b: 0 },
|
|
},
|
|
metrics: {
|
|
player_a: { winners: 14, pointsWon: 32 },
|
|
player_b: { winners: 10, pointsWon: 25 },
|
|
totalRallies: 18,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
eventIndex: 2,
|
|
eventType: "review_adjustment",
|
|
source: "admin",
|
|
confidence: 1,
|
|
payload: {
|
|
score: {
|
|
sets: { player_a: 2, player_b: 0 },
|
|
games: { player_a: 12, player_b: 6 },
|
|
points: { player_a: 68, player_b: 53 },
|
|
winnerSlot: "player_a",
|
|
},
|
|
},
|
|
},
|
|
]);
|
|
|
|
expect(result.score.sets.player_a).toBe(2);
|
|
expect(result.score.games.player_b).toBe(6);
|
|
expect(result.score.points.player_a).toBe(68);
|
|
expect(result.score.winnerSlot).toBe("player_a");
|
|
expect(result.metrics.players.player_a.winners).toBe(14);
|
|
expect(result.metrics.totalRallies).toBe(18);
|
|
});
|
|
});
|
|
|
|
describe("buildParticipantSettlement", () => {
|
|
it("projects scoreboard and metrics into per-player settlement rows", () => {
|
|
const settlement = buildParticipantSettlement(
|
|
{
|
|
sets: { player_a: 2, player_b: 1 },
|
|
games: { player_a: 13, player_b: 11 },
|
|
points: { player_a: 74, player_b: 68 },
|
|
winnerSlot: "player_a",
|
|
},
|
|
{
|
|
players: {
|
|
player_a: { aces: 6, firstServeIn: 32, firstServeAttempts: 40, pointsWon: 74 },
|
|
player_b: { aces: 4, firstServeIn: 28, firstServeAttempts: 41, pointsWon: 68 },
|
|
},
|
|
totalRallies: 22,
|
|
longestRally: 11,
|
|
},
|
|
);
|
|
|
|
expect(settlement.winnerSlot).toBe("player_a");
|
|
expect(settlement.players.player_a.isWinner).toBe(true);
|
|
expect(settlement.players.player_a.gamesWon).toBe(13);
|
|
expect(settlement.players.player_a.stats.firstServePct).toBe(80);
|
|
expect(settlement.players.player_b.stats.firstServePct).toBeCloseTo(68.3, 1);
|
|
expect(settlement.summary.longestRally).toBe(11);
|
|
});
|
|
});
|