add whale/public picks to prediction history and new API endpoint
- Add _compute_whale_public_picks() to reconstruct whale/public picks from historical bets - Merge whale_pick, public_pick, whale_hit, public_hit into last_20_predictions - Add get_prediction_history(limit) for lightweight prediction+accuracy data - Add /api/prediction-history endpoint (default 100, max 500) - Add Whale and Public columns with HIT/MISS to Last 20 table in frontend
This commit is contained in:
@@ -262,7 +262,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
|
||||
<div class="section-title">Last 20 Predictions vs Actual</div>
|
||||
<div class="panel" style="max-height:500px;overflow-y:auto">
|
||||
<table class="pred-history" id="pred-history">
|
||||
<thead><tr><th>Game</th><th>Predicted</th><th>2nd Pick</th><th>P(A)</th><th>P(B)</th><th>P(C)</th><th>Actual</th><th>Result</th></tr></thead>
|
||||
<thead><tr><th>Game</th><th>Predicted</th><th>2nd Pick</th><th>P(A)</th><th>P(B)</th><th>P(C)</th><th>Whale</th><th>Public</th><th>Actual</th><th>Result</th></tr></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -666,16 +666,46 @@ function renderCrowdStats(data) {
|
||||
function renderLast20(predictions) {
|
||||
const tbody = $('pred-history').querySelector('tbody');
|
||||
if (!predictions || predictions.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" style="color:var(--text3)">Not enough data</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="10" style="color:var(--text3)">Not enough data</td></tr>';
|
||||
return;
|
||||
}
|
||||
const fullHits = predictions.filter(p => p.correct).length;
|
||||
const semiHits = predictions.filter(p => p.semi_correct).length;
|
||||
const score = fullHits + semiHits * 0.5;
|
||||
|
||||
// Whale/public accuracy
|
||||
const whaleEntries = predictions.filter(p => p.whale_pick != null);
|
||||
const whaleHits = whaleEntries.filter(p => p.whale_hit).length;
|
||||
const publicEntries = predictions.filter(p => p.public_pick != null);
|
||||
const publicHits = publicEntries.filter(p => p.public_hit).length;
|
||||
const whalePct = whaleEntries.length > 0 ? (whaleHits / whaleEntries.length * 100).toFixed(1) : '--';
|
||||
const publicPct = publicEntries.length > 0 ? (publicHits / publicEntries.length * 100).toFixed(1) : '--';
|
||||
|
||||
tbody.innerHTML = predictions.slice().reverse().map(p => {
|
||||
const maxProb = Math.max(p.probs.A, p.probs.B, p.probs.C);
|
||||
const resultClass = p.correct ? 'correct' : (p.semi_correct ? 'semi' : 'wrong');
|
||||
const resultLabel = p.correct ? 'HIT' : (p.semi_correct ? 'SEMI' : 'MISS');
|
||||
|
||||
// Whale cell
|
||||
let whaleCell;
|
||||
if (p.whale_pick) {
|
||||
const whCls = p.whale_hit ? 'correct' : 'wrong';
|
||||
const whLabel = p.whale_hit ? 'HIT' : 'MISS';
|
||||
whaleCell = `<td><span style="color:${CHAIR_COLORS[p.whale_pick]};font-weight:700">${p.whale_pick}</span> <span class="${whCls}" style="font-size:10px">${whLabel}</span></td>`;
|
||||
} else {
|
||||
whaleCell = '<td style="color:var(--text3)">--</td>';
|
||||
}
|
||||
|
||||
// Public cell
|
||||
let pubCell;
|
||||
if (p.public_pick) {
|
||||
const puCls = p.public_hit ? 'correct' : 'wrong';
|
||||
const puLabel = p.public_hit ? 'HIT' : 'MISS';
|
||||
pubCell = `<td><span style="color:${CHAIR_COLORS[p.public_pick]};font-weight:700">${p.public_pick}</span> <span class="${puCls}" style="font-size:10px">${puLabel}</span></td>`;
|
||||
} else {
|
||||
pubCell = '<td style="color:var(--text3)">--</td>';
|
||||
}
|
||||
|
||||
return `<tr>
|
||||
<td style="font-weight:700">#${p.game_no}</td>
|
||||
<td class="winner-cell" style="color:${CHAIR_COLORS[p.predicted]}">${p.predicted}</td>
|
||||
@@ -684,14 +714,18 @@ function renderLast20(predictions) {
|
||||
const w = p.probs[c] / maxProb * 40;
|
||||
return `<td><span class="prob-bar" style="width:${w}px;background:${CHAIR_COLORS[c]}"></span> ${pct(p.probs[c])}</td>`;
|
||||
}).join('')}
|
||||
${whaleCell}
|
||||
${pubCell}
|
||||
<td class="winner-cell" style="color:${CHAIR_COLORS[p.actual]}">${p.actual}</td>
|
||||
<td class="${resultClass}" style="font-weight:700">${resultLabel}</td>
|
||||
</tr>`;
|
||||
}).join('') +
|
||||
`<tr style="border-top:2px solid var(--border);font-weight:700">
|
||||
<td colspan="6" style="text-align:right;color:var(--text2)">Accuracy (last ${predictions.length})</td>
|
||||
<td style="color:${whalePct !== '--' && parseFloat(whalePct) > 33.3 ? 'var(--green)' : 'var(--text2)'}">${whalePct !== '--' ? whalePct + '%' : '--'}</td>
|
||||
<td style="color:${publicPct !== '--' && parseFloat(publicPct) > 33.3 ? 'var(--green)' : 'var(--text2)'}">${publicPct !== '--' ? publicPct + '%' : '--'}</td>
|
||||
<td colspan="2" style="color:${score/predictions.length > 1/3 ? 'var(--green)' : 'var(--red)'}">
|
||||
${(score/predictions.length*100).toFixed(1)}% (${score}/${predictions.length})
|
||||
Model: ${(score/predictions.length*100).toFixed(1)}%
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user