From 4903b6943a5ad39d650f48b3326dbe4db40266b2 Mon Sep 17 00:00:00 2001 From: Junaid Saeed Uppal Date: Wed, 25 Feb 2026 23:25:49 +0500 Subject: [PATCH] add real-time game data, prediction history, and fix winning cards chart - WebSocket connection shows live game state (round #, phase, bets per chair, pot) in a persistent bar at the top of predictions page - Prediction cards now display current bet amounts per chair - Round results flash HIT/MISS against the Bayesian prediction - New "Last 20 Predictions vs Actual" table with per-game probabilities, predicted vs actual winner, and running accuracy - Predictions auto-refresh after each round ends - Fix winning cards chart: use taller container (480px) and dedicated scales config for horizontal bar rendering - Add _last_n_predictions() helper to db.py for detailed per-game prediction history with game numbers --- app/db.py | 39 +++++- static/predictions.html | 288 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 302 insertions(+), 25 deletions(-) diff --git a/app/db.py b/app/db.py index e73dd7d12..cced8b9b1 100644 --- a/app/db.py +++ b/app/db.py @@ -1217,14 +1217,39 @@ def _backtest_theories(winners): } +def _last_n_predictions(winners, n=20): + """Get detailed prediction vs actual for the last N games.""" + warmup = 30 + if len(winners) <= warmup: + return [] + start = max(warmup, len(winners) - n) + results = [] + for i in range(start, len(winners)): + history = winners[:i] + actual = winners[i] + m1, _ = _markov_matrix_1(history) + m2, _ = _markov_matrix_2(history) + pred, _ = _bayesian_prediction(history, m1, m2) + predicted = max(CHAIR_LABELS, key=lambda c: pred[c]) + results.append({ + "index": i, + "predicted": predicted, + "actual": actual, + "correct": predicted == actual, + "probs": {c: round(pred[c], 4) for c in CHAIR_LABELS}, + }) + return results + + @_with_lock def get_prediction_analysis() -> dict: """Run all prediction/game-theory analysis and return results.""" client = get_client() - # Query 1: Full winner sequence - result = client.query("SELECT winner FROM games ORDER BY game_no ASC") - winners = [config.CHAIRS.get(r[0], "?") for r in result.result_rows] + # Query 1: Full winner sequence with game numbers + result = client.query("SELECT game_no, winner FROM games ORDER BY game_no ASC") + game_nos = [r[0] for r in result.result_rows if config.CHAIRS.get(r[1], "?") in CHAIR_LABELS] + winners = [config.CHAIRS.get(r[1], "?") for r in result.result_rows] winners = [w for w in winners if w in CHAIR_LABELS] # filter unknowns # Query 2: Card data for last 500 games @@ -1252,6 +1277,13 @@ def get_prediction_analysis() -> dict: # Backtesting backtest = _backtest_theories(winners) + # Last 20 prediction vs actual + last_20_raw = _last_n_predictions(winners, 20) + # Attach game_nos to last_20 + for entry in last_20_raw: + idx = entry["index"] + entry["game_no"] = game_nos[idx] if idx < len(game_nos) else 0 + # Card analysis card_values = _card_value_distribution(cards_data) face_cards = _face_card_frequency(cards_data) @@ -1261,6 +1293,7 @@ def get_prediction_analysis() -> dict: return { "total_games": len(winners), "last_winners": winners[-10:] if len(winners) >= 10 else winners, + "last_20_predictions": last_20_raw, "prediction": prediction, "signals": signals, "markov1": {"matrix": markov1, "counts": {k: dict(v) for k, v in markov1_counts.items()}}, diff --git a/static/predictions.html b/static/predictions.html index 49e504e14..ed3bb77cc 100644 --- a/static/predictions.html +++ b/static/predictions.html @@ -21,12 +21,17 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- padding: 10px 20px; background: var(--surface); border-bottom: 1px solid var(--border); } .header h1 { font-size: 15px; font-weight: 700; letter-spacing: -0.3px; } -.nav-links { display: flex; gap: 14px; } +.nav-links { display: flex; gap: 14px; align-items: center; } .nav-link { font-size: 12px; color: var(--accent); text-decoration: none; font-weight: 600; transition: color 0.2s; } .nav-link:hover { color: #a78bfa; } +.ws-dot { + width: 7px; height: 7px; border-radius: 50%; background: var(--red); + display: inline-block; margin-left: 6px; +} +.ws-dot.connected { background: var(--green); } .content { padding: 16px 20px; max-width: 1400px; margin: 0 auto; } .loading { text-align: center; padding: 60px; color: var(--text2); font-size: 14px; } @@ -37,22 +42,50 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- color: var(--text2); margin-bottom: 12px; padding-bottom: 6px; border-bottom: 1px solid var(--border); } +/* Live game bar */ +.live-bar { + background: var(--surface); border: 1px solid var(--border); border-radius: 8px; + padding: 12px 20px; margin-bottom: 16px; display: flex; align-items: center; gap: 20px; + flex-wrap: wrap; +} +.live-bar .game-no { font-size: 16px; font-weight: 800; } +.live-bar .phase { + padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 700; + text-transform: uppercase; +} +.phase-BETTING { background: #10b981; color: #fff; } +.phase-REVEALING { background: #f59e0b; color: #000; } +.phase-ENDED { background: #ef4444; color: #fff; } +.phase-NEW { background: #6c5ce7; color: #fff; } +.live-bar .bet-pills { display: flex; gap: 12px; } +.bet-pill { + display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; +} +.bet-pill .dot { width: 8px; height: 8px; border-radius: 50%; } +.live-bar .timer { font-size: 14px; font-weight: 700; color: var(--green); } +.live-bar .pot { font-size: 13px; color: var(--text2); } + /* Hero prediction cards */ .pred-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 16px; } .pred-card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; - padding: 24px; text-align: center; position: relative; transition: all 0.3s; + padding: 20px; text-align: center; position: relative; transition: all 0.3s; } .pred-card.recommended { border-color: var(--accent); box-shadow: 0 0 20px rgba(108, 92, 231, 0.3), 0 0 40px rgba(108, 92, 231, 0.1); } -.pred-card .chair-label { font-size: 14px; font-weight: 700; margin-bottom: 8px; } -.pred-card .prob { font-size: 42px; font-weight: 800; letter-spacing: -2px; } +.pred-card .chair-label { font-size: 14px; font-weight: 700; margin-bottom: 6px; } +.pred-card .prob { font-size: 38px; font-weight: 800; letter-spacing: -2px; } .pred-card .badge { - display: inline-block; margin-top: 8px; padding: 3px 10px; border-radius: 12px; + display: inline-block; margin-top: 6px; padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; background: var(--accent); color: #fff; } +.pred-card .bet-info { + margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); + font-size: 12px; color: var(--text2); +} +.pred-card .bet-val { font-size: 18px; font-weight: 700; color: var(--text); } .signal-table { width: 100%; border-collapse: collapse; font-size: 12px; } .signal-table th, .signal-table td { @@ -92,6 +125,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- /* Chart containers */ .chart-container { position: relative; height: 300px; } .chart-container-sm { position: relative; height: 250px; } +.chart-container-tall { position: relative; height: 480px; } /* Backtest */ .backtest-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 16px; } @@ -104,10 +138,34 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- .bt-card .bt-acc.above { color: var(--green); } .bt-card .bt-acc.below { color: var(--red); } +/* Last 20 predictions table */ +.pred-history { width: 100%; border-collapse: collapse; font-size: 12px; } +.pred-history th, .pred-history td { + padding: 7px 10px; text-align: center; border-bottom: 1px solid var(--border); +} +.pred-history th { color: var(--text2); font-weight: 600; text-transform: uppercase; font-size: 11px; background: var(--surface2); position: sticky; top: 0; } +.pred-history .correct { color: var(--green); } +.pred-history .wrong { color: var(--red); } +.pred-history .winner-cell { font-weight: 800; } +.prob-bar { display: inline-block; height: 6px; border-radius: 3px; vertical-align: middle; } + +/* Result flash */ +.result-flash { + display: none; padding: 10px 16px; border-radius: 8px; margin-bottom: 16px; + font-weight: 700; font-size: 14px; text-align: center; + animation: flashIn 0.3s ease-out; +} +.result-flash.show { display: block; } +.result-flash.win { background: rgba(16,185,129,0.15); border: 1px solid var(--green); color: var(--green); } +.result-flash.loss { background: rgba(239,68,68,0.15); border: 1px solid var(--red); color: var(--red); } +@keyframes flashIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } + @media (max-width: 768px) { .pred-cards, .two-col, .stat-cards { grid-template-columns: 1fr; } .backtest-grid { grid-template-columns: repeat(2, 1fr); } - .pred-card .prob { font-size: 32px; } + .pred-card .prob { font-size: 28px; } + .live-bar { gap: 10px; } + .live-bar .bet-pills { width: 100%; justify-content: space-between; } } @@ -118,15 +176,32 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- Live Dashboard → Analytics → Patterns → +
Loading prediction data...