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:
67
server.js
67
server.js
@@ -431,6 +431,47 @@ app.post("/api/state", (req, res) => {
|
||||
res.json({ ok: true, updatedAt: nowIso });
|
||||
});
|
||||
|
||||
// Writes one session's results into app_state and marks them "hot" (see
|
||||
// protectRecentSessionResults), so a stale full-state save can't revert them.
|
||||
function storeSessionResult(sessionId, sessionResult) {
|
||||
if (!sessionResult || typeof sessionResult !== "object") return false;
|
||||
const row = db.prepare("SELECT state_json FROM app_state WHERE id = 1").get();
|
||||
if (!row?.state_json) return false;
|
||||
try {
|
||||
const parsed = JSON.parse(row.state_json);
|
||||
if (!parsed.resultsBySession || typeof parsed.resultsBySession !== "object") {
|
||||
parsed.resultsBySession = {};
|
||||
}
|
||||
parsed.resultsBySession[String(sessionId)] = sessionResult;
|
||||
recentSessionResultWrites.set(String(sessionId), Date.now());
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO app_state (id, state_json, updated_at)
|
||||
VALUES (1, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
state_json = excluded.state_json,
|
||||
updated_at = excluded.updated_at
|
||||
`
|
||||
).run(JSON.stringify(parsed), new Date().toISOString());
|
||||
return true;
|
||||
} catch {
|
||||
// leave app_state untouched if the stored JSON is invalid
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// A deliberate correction (a lap removed, restored or approved, a penalty) on a session that
|
||||
// is getting live passings: the full-state save would be held back by the "hot results"
|
||||
// protection for up to RESULT_PROTECTION_WINDOW_MS, so the corrected result is sent here.
|
||||
app.post("/api/session-results", (req, res) => {
|
||||
const { sessionId, sessionResult } = req.body || {};
|
||||
if (!sessionId || !sessionResult || typeof sessionResult !== "object" || !Array.isArray(sessionResult.passings)) {
|
||||
res.status(400).json({ error: "Expected { sessionId, sessionResult }" });
|
||||
return;
|
||||
}
|
||||
res.json({ ok: storeSessionResult(sessionId, sessionResult) });
|
||||
});
|
||||
|
||||
app.post("/api/passings", (req, res) => {
|
||||
const { sessionId, passing, sessionResult } = req.body || {};
|
||||
if (!sessionId || !passing || typeof passing !== "object") {
|
||||
@@ -474,31 +515,7 @@ app.post("/api/passings", (req, res) => {
|
||||
|
||||
// Keep app_state hot for overlay clients that hydrate from backend instead of
|
||||
// having their own direct decoder socket.
|
||||
if (sessionResult && typeof sessionResult === "object") {
|
||||
const nowIso = new Date().toISOString();
|
||||
const row = db.prepare("SELECT state_json FROM app_state WHERE id = 1").get();
|
||||
if (row?.state_json) {
|
||||
try {
|
||||
const parsed = JSON.parse(row.state_json);
|
||||
if (!parsed.resultsBySession || typeof parsed.resultsBySession !== "object") {
|
||||
parsed.resultsBySession = {};
|
||||
}
|
||||
parsed.resultsBySession[String(sessionId)] = sessionResult;
|
||||
recentSessionResultWrites.set(String(sessionId), Date.now());
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO app_state (id, state_json, updated_at)
|
||||
VALUES (1, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
state_json = excluded.state_json,
|
||||
updated_at = excluded.updated_at
|
||||
`
|
||||
).run(JSON.stringify(parsed), nowIso);
|
||||
} catch {
|
||||
// leave app_state untouched if the stored JSON is invalid
|
||||
}
|
||||
}
|
||||
}
|
||||
storeSessionResult(sessionId, sessionResult);
|
||||
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user