In-play win-probability timeline: the momentum curve on match pages

While a match runs, every poll records the in-play model probability
per game state (insert-if-new on minute+score) into inplay_history.
The match page draws it as a stacked area chart — home fills from the
bottom, away from the top, the gap is the draw — with minute ticks and
a 50% guide. Shows live (under the win-probability bar) and after full
time as its own card. EN/DE strings included.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 00:50:56 +02:00
parent acd8c3e75d
commit cbc5ff0468
6 changed files with 163 additions and 2 deletions
+38
View File
@@ -93,6 +93,18 @@ CREATE TABLE IF NOT EXISTS prediction_snapshots (
market_json TEXT
);
CREATE TABLE IF NOT EXISTS inplay_history (
fixture_num INTEGER NOT NULL,
minute INTEGER NOT NULL,
home_score INTEGER NOT NULL,
away_score INTEGER NOT NULL,
p_home REAL NOT NULL,
p_draw REAL NOT NULL,
p_away REAL NOT NULL,
at INTEGER NOT NULL,
PRIMARY KEY (fixture_num, minute, home_score, away_score)
);
CREATE TABLE IF NOT EXISTS ingest_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL,
@@ -135,6 +147,32 @@ export function cacheSet(key: string, value: string, ttl: number): void {
.run(key, value, Date.now(), ttl);
}
// ---- in-play win-probability history (the momentum curve on match pages) ----
export interface InplayPoint {
minute: number;
home_score: number;
away_score: number;
p_home: number;
p_draw: number;
p_away: number;
}
/** Insert-if-new keyed on (minute, score) — one point per game state. */
export function recordInplay(num: number, p: InplayPoint): void {
db()
.prepare(`INSERT OR IGNORE INTO inplay_history
(fixture_num, minute, home_score, away_score, p_home, p_draw, p_away, at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
.run(num, p.minute, p.home_score, p.away_score, p.p_home, p.p_draw, p.p_away, Date.now());
}
export function inplayHistory(num: number): InplayPoint[] {
return db()
.prepare(`SELECT minute, home_score, away_score, p_home, p_draw, p_away
FROM inplay_history WHERE fixture_num = ? ORDER BY minute, home_score + away_score`)
.all(num) as unknown as InplayPoint[];
}
/** Nightly consistent snapshot: VACUUM INTO a LOCAL temp file (SQLite needs
* real locking, which the CIFS-mounted archive cannot offer), then a plain
* sequential copy into the archive dir. Keeps the newest `keep` dated copies. */