feat(practice): flag suspiciously short laps and remove any lap in one click

Practice laps under 85% of the driver's own median (a likely shortcut)
are flagged: counted, but never a best lap, record or training stat
until approved. Timing and Race Day get a one-click Approve/Remove
panel, overlays show a warning mark, and any lap can be removed or
restored from Timing and Judging with undo.

Also sends judge corrections straight to the server so its hot-results
protection can no longer hold back a correction made right after a
passing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
larssand
2026-10-07 19:53:23 +02:00
parent 3cc4fa0845
commit 8601954ea2
24 changed files with 556 additions and 60 deletions

View File

@@ -9,6 +9,7 @@ import {
getManualCorrectionSummary,
invalidateCompetitorLastLap,
recalculateCompetitorFromPassings,
setCompetitorLapStatus,
restoreCompetitorLastInvalidLap,
undoJudgingAdjustment,
} from "../src/judging_logic.js";
@@ -86,6 +87,34 @@ test("recalculating after a judge correction keeps the best lap on the current t
assert.equal(harness.row.bestLapMs, 16000);
});
test("any lap can be removed, restored or (if suspect) approved - each logged and undoable", () => {
const harness = createHarness();
const act = (timestamp, action) => setCompetitorLapStatus(harness.session, "driver_1", timestamp, action, harness.deps);
// Remove the FIRST lap, not just the last one.
assert.equal(act(17000, "remove"), true);
assert.equal(harness.row.laps, 2);
assert.equal(harness.row.bestLapMs, 16500);
assert.equal(act(17000, "remove"), false, "already removed");
assert.equal(act(17000, "restore"), true);
assert.equal(harness.row.laps, 3);
assert.equal(harness.row.bestLapMs, 16000);
// A suspect lap doesn't count for best lap until approved; undo makes it suspect again.
harness.result.passings[2].lapMs = 12000;
harness.result.passings[2].suspect = true;
harness.deps.recalculateCompetitorFromPassings(harness.session, "driver_1");
assert.equal(harness.row.bestLapMs, 16000);
assert.equal(act(50500, "approve"), true);
assert.equal(harness.row.bestLapMs, 12000);
const approval = harness.result.adjustments.at(-1);
assert.equal(approval.category, "invalid");
assert.equal(undoJudgingAdjustment(harness.session, approval.id, { ...harness.deps, findPassingByUndoMarker: (session, key, ts) => harness.result.passings.find((passing) => passing.competitorKey === key && passing.timestamp === ts) }), true);
assert.equal(harness.result.passings[2].suspect, true);
assert.equal(harness.row.bestLapMs, 16000);
assert.equal(act(99999, "remove"), false, "unknown lap");
assert.equal(act(17000, "explode"), false, "unknown action");
});
test("manual corrections accumulate, reset, and undo to the prior state", () => {
const harness = createHarness();