diff --git a/app/db.py b/app/db.py index cced8b9b1..394b66a00 100644 --- a/app/db.py +++ b/app/db.py @@ -1290,6 +1290,24 @@ def get_prediction_analysis() -> dict: suits = _suit_distribution(cards_data) winning_cards = _winning_card_patterns(cards_data) + # Bet rank analysis — how often the winning chair had high/mid/low bet + rank_result = client.query(""" + SELECT + countIf(winner_bet >= greatest(bet_a, bet_b, bet_c)) AS high, + countIf(winner_bet > least(bet_a, bet_b, bet_c) + AND winner_bet < greatest(bet_a, bet_b, bet_c)) AS mid, + countIf(winner_bet <= least(bet_a, bet_b, bet_c)) AS low + FROM ( + SELECT bet_a, bet_b, bet_c, + multiIf(winner = 3, bet_a, winner = 2, bet_b, bet_c) AS winner_bet + FROM games WHERE bet_a + bet_b + bet_c > 0 + ) + """) + bet_rank = {"high": 0, "mid": 0, "low": 0} + if rank_result.result_rows: + r = rank_result.result_rows[0] + bet_rank = {"high": r[0], "mid": r[1], "low": r[2]} + return { "total_games": len(winners), "last_winners": winners[-10:] if len(winners) >= 10 else winners, @@ -1302,6 +1320,7 @@ def get_prediction_analysis() -> dict: "chi_squared": chi_squared, "runs_test": runs_test, "backtest": backtest, + "bet_rank": bet_rank, "card_values": card_values, "face_cards": face_cards, "suits": suits, diff --git a/static/predictions.html b/static/predictions.html index ed3bb77cc..b0c276750 100644 --- a/static/predictions.html +++ b/static/predictions.html @@ -22,20 +22,13 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- } .header h1 { font-size: 15px; font-weight: 700; letter-spacing: -0.3px; } .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 { 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 { 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; } - .section { margin-bottom: 24px; } .section-title { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; @@ -45,22 +38,16 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- /* 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; + 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; -} +.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 { 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); } @@ -69,52 +56,91 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- .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: 20px; text-align: center; position: relative; transition: all 0.3s; + padding: 18px; text-align: center; position: relative; transition: all 0.3s; } -.pred-card.recommended { +.pred-card.best { + border-color: var(--green); + box-shadow: 0 0 20px rgba(16, 185, 129, 0.3), 0 0 40px rgba(16, 185, 129, 0.1); +} +.pred-card.second { border-color: var(--accent); - box-shadow: 0 0 20px rgba(108, 92, 231, 0.3), 0 0 40px rgba(108, 92, 231, 0.1); + box-shadow: 0 0 12px rgba(108, 92, 231, 0.2); } -.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: 6px; padding: 3px 10px; border-radius: 12px; - font-size: 11px; font-weight: 600; background: var(--accent); color: #fff; +.pred-card .chair-label { font-size: 14px; font-weight: 700; margin-bottom: 4px; } +.pred-card .prob { font-size: 36px; font-weight: 800; letter-spacing: -2px; } +.pred-card .rank-badge { + display: inline-block; margin-top: 4px; padding: 3px 10px; border-radius: 12px; + font-size: 11px; font-weight: 700; } +.rank-badge.best-badge { background: var(--green); color: #fff; } +.rank-badge.second-badge { 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); } +.pred-card .ev-line { font-size: 11px; margin-top: 6px; } +.ev-positive { color: var(--green); } +.ev-negative { color: var(--red); } + +/* Bet advisor */ +.bet-advisor { + background: var(--surface); border: 1px solid var(--border); border-radius: 10px; + padding: 18px; margin-bottom: 16px; +} +.advisor-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; } +.advisor-item { text-align: center; } +.advisor-item .adv-label { font-size: 11px; color: var(--text3); font-weight: 600; text-transform: uppercase; margin-bottom: 4px; } +.advisor-item .adv-value { font-size: 20px; font-weight: 800; } +.advisor-item .adv-note { font-size: 10px; color: var(--text3); margin-top: 2px; } + +/* Trend panels */ +.trends-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; } +.trend-panel { + background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px; +} +.trend-panel .panel-title { font-size: 12px; font-weight: 700; color: var(--text2); margin-bottom: 10px; text-transform: uppercase; } +.trend-row { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 13px; } +.trend-rank { font-size: 9px; font-weight: 700; padding: 1px 5px; border-radius: 3px; letter-spacing: 0.3px; } +.trend-rank-1 { background: #fbbf2430; color: #fbbf24; } +.trend-rank-2 { background: #94a3b830; color: #94a3b8; } +.trend-chair { font-weight: 900; font-size: 16px; min-width: 22px; text-align: center; } +.trend-bar-bg { flex: 1; height: 18px; border-radius: 4px; background: var(--surface3); overflow: hidden; } +.trend-bar-fill { height: 100%; border-radius: 4px; transition: width 0.4s ease; min-width: 2px; } +.trend-bar-fill.chair-A { background: linear-gradient(90deg, #3b82f680, #3b82f6); } +.trend-bar-fill.chair-B { background: linear-gradient(90deg, #ec489980, #ec4899); } +.trend-bar-fill.chair-C { background: linear-gradient(90deg, #f59e0b80, #f59e0b); } +.trend-pct { font-weight: 800; min-width: 40px; text-align: right; font-variant-numeric: tabular-nums; font-size: 12px; } +.trend-amt { font-size: 10px; color: var(--text3); min-width: 50px; text-align: right; font-variant-numeric: tabular-nums; } +.trend-note { font-size: 10px; color: var(--text3); margin-top: 4px; font-style: italic; } +.trend-empty { font-size: 11px; color: var(--text3); padding: 10px 0; } + +/* Historical crowd analysis */ +.crowd-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; } +.crowd-card { + background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px; text-align: center; +} +.crowd-card .cc-label { font-size: 11px; color: var(--text3); font-weight: 600; text-transform: uppercase; margin-bottom: 4px; } +.crowd-card .cc-value { font-size: 22px; font-weight: 800; } +.crowd-card .cc-note { font-size: 10px; color: var(--text3); margin-top: 2px; } .signal-table { width: 100%; border-collapse: collapse; font-size: 12px; } -.signal-table th, .signal-table td { - padding: 8px 12px; text-align: center; border-bottom: 1px solid var(--border); -} +.signal-table th, .signal-table td { padding: 8px 12px; text-align: center; border-bottom: 1px solid var(--border); } .signal-table th { color: var(--text2); font-weight: 600; text-transform: uppercase; font-size: 11px; } -/* Two-column layout */ .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } -.panel { - background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; -} +.panel { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px; } .panel-title { font-size: 12px; font-weight: 700; color: var(--text2); margin-bottom: 12px; text-transform: uppercase; } -/* Heatmap tables */ .heatmap { width: 100%; border-collapse: collapse; font-size: 12px; } .heatmap th, .heatmap td { padding: 6px 8px; text-align: center; border: 1px solid var(--border); } .heatmap th { background: var(--surface2); color: var(--text2); font-weight: 600; font-size: 11px; } -/* Stat cards */ .stat-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; } -.stat-card { - background: var(--surface); border: 1px solid var(--border); border-radius: 8px; - padding: 20px; text-align: center; -} +.stat-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 20px; text-align: center; } .stat-card .label { font-size: 12px; color: var(--text2); font-weight: 600; margin-bottom: 4px; } .stat-card .value { font-size: 28px; font-weight: 800; } -/* Test result panels */ .test-result { margin-bottom: 8px; } .test-result .test-label { font-size: 11px; color: var(--text3); font-weight: 600; text-transform: uppercase; } .test-result .test-value { font-size: 16px; font-weight: 700; } @@ -122,38 +148,28 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- .significant { color: var(--red); } .not-significant { color: var(--green); } -/* 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; } -.bt-card { - background: var(--surface); border: 1px solid var(--border); border-radius: 8px; - padding: 14px; text-align: center; -} +.bt-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px; text-align: center; } .bt-card .bt-name { font-size: 11px; color: var(--text2); font-weight: 600; text-transform: uppercase; margin-bottom: 4px; } .bt-card .bt-acc { font-size: 24px; font-weight: 800; } .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, .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; + 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); } @@ -161,7 +177,8 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans- @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; } + .pred-cards, .two-col, .stat-cards, .trends-grid, .crowd-stats { grid-template-columns: 1fr; } + .advisor-grid { grid-template-columns: 1fr; gap: 10px; } .backtest-grid { grid-template-columns: repeat(2, 1fr); } .pred-card .prob { font-size: 28px; } .live-bar { gap: 10px; } @@ -185,8 +202,8 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-