feat(evolution, web): add live match counter per plan §16.18
- Add matches_today and active_bots fields to LiveData Totals (evolver) - Query matches table for COUNT(*) WHERE completed_at >= today - Query bots table for COUNT(*) WHERE status = 'active' - Add fields to index builder EvolutionMeta struct - Update homepage to render "X matches today · Y bots active · Gen #Z evolving" - Add CSS styling for .home-live-stats section Closes: bf-4m8mo
This commit is contained in:
parent
db54067f56
commit
f35477dd96
4 changed files with 72 additions and 2 deletions
|
|
@ -133,6 +133,8 @@ type Totals struct {
|
|||
HighestEvolvedRating int `json:"highest_evolved_rating"`
|
||||
EvolvedInTop10 int `json:"evolved_in_top_10"`
|
||||
MutationsPerHour float64 `json:"mutations_per_hour"`
|
||||
MatchesToday int `json:"matches_today"` // plan §16.18: matches completed today
|
||||
ActiveBots int `json:"active_bots"` // plan §16.18: active bot count
|
||||
}
|
||||
|
||||
// LiveData is the full evolution dashboard payload written to live.json (plan §14 format).
|
||||
|
|
@ -312,6 +314,24 @@ func fillTotals(ctx context.Context, db *sql.DB, data *LiveData) error {
|
|||
mutationsLastHour = 0
|
||||
}
|
||||
|
||||
// Matches today (plan §16.18: completed matches since midnight UTC)
|
||||
var matchesToday int
|
||||
err = db.QueryRowContext(ctx, `
|
||||
SELECT COUNT(*) FROM matches
|
||||
WHERE completed_at >= $1::date`, today).Scan(&matchesToday)
|
||||
if err != nil {
|
||||
matchesToday = 0
|
||||
}
|
||||
|
||||
// Active bots (plan §16.18: bots with status = 'active')
|
||||
var activeBots int
|
||||
err = db.QueryRowContext(ctx, `
|
||||
SELECT COUNT(*) FROM bots
|
||||
WHERE status = 'active'`).Scan(&activeBots)
|
||||
if err != nil {
|
||||
activeBots = 0
|
||||
}
|
||||
|
||||
data.Totals = Totals{
|
||||
GenerationsTotal: maxGen,
|
||||
CandidatesToday: candidatesToday,
|
||||
|
|
@ -320,6 +340,8 @@ func fillTotals(ctx context.Context, db *sql.DB, data *LiveData) error {
|
|||
HighestEvolvedRating: highestRating,
|
||||
EvolvedInTop10: top10Count,
|
||||
MutationsPerHour: round3(float64(mutationsLastHour)),
|
||||
MatchesToday: matchesToday,
|
||||
ActiveBots: activeBots,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1035,6 +1035,8 @@ type EvolutionMeta struct {
|
|||
TotalPromoted int `json:"total_promoted"` // all-time promoted count
|
||||
PromotionRate float64 `json:"promotion_rate"` // promoted/total
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
MatchesToday int `json:"matches_today"` // plan §16.18: matches completed today
|
||||
ActiveBots int `json:"active_bots"` // plan §16.18: active bot count
|
||||
}
|
||||
|
||||
// EvolvedBotRating represents an evolved bot's rating info
|
||||
|
|
@ -1091,6 +1093,8 @@ func fetchEvolutionMeta(ctx context.Context, db *sql.DB) (*EvolutionMeta, error)
|
|||
TotalPromoted: 0,
|
||||
PromotionRate: 0,
|
||||
UpdatedAt: updatedAt,
|
||||
MatchesToday: 0,
|
||||
ActiveBots: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -1140,6 +1144,28 @@ func fetchEvolutionMeta(ctx context.Context, db *sql.DB) (*EvolutionMeta, error)
|
|||
// Count evolved bots in top 10
|
||||
meta.Top10Count = len(meta.BestRatings)
|
||||
|
||||
// Fetch matches today (plan §16.18: completed matches since midnight UTC)
|
||||
var matchesToday int
|
||||
matchErr := db.QueryRowContext(ctx, `
|
||||
SELECT COUNT(*) FROM matches
|
||||
WHERE completed_at >= CURRENT_DATE
|
||||
`).Scan(&matchesToday)
|
||||
if matchErr != nil {
|
||||
matchesToday = 0
|
||||
}
|
||||
meta.MatchesToday = matchesToday
|
||||
|
||||
// Fetch active bots count (plan §16.18: bots with status = 'active')
|
||||
var activeBots int
|
||||
botErr := db.QueryRowContext(ctx, `
|
||||
SELECT COUNT(*) FROM bots
|
||||
WHERE status = 'active'
|
||||
`).Scan(&activeBots)
|
||||
if botErr != nil {
|
||||
activeBots = 0
|
||||
}
|
||||
meta.ActiveBots = activeBots
|
||||
|
||||
meta.UpdatedAt = updatedAt
|
||||
return &meta, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,13 +449,16 @@ export interface EvolutionMeta {
|
|||
promoted_today: number;
|
||||
top_10_count: number;
|
||||
updated_at: string;
|
||||
matches_today?: number; // plan §16.18: matches completed today
|
||||
active_bots?: number; // plan §16.18: active bot count
|
||||
}
|
||||
|
||||
export async function fetchEvolutionMeta(): Promise<EvolutionMeta> {
|
||||
return swr('evolution-meta', async () => {
|
||||
const response = await fetch('/data/evolution/meta.json');
|
||||
if (!response.ok) {
|
||||
return { generation: 0, promoted_today: 0, top_10_count: 0, updated_at: '' };
|
||||
const fallback: EvolutionMeta = { generation: 0, promoted_today: 0, top_10_count: 0, updated_at: '', matches_today: 0, active_bots: 0 };
|
||||
return fallback;
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ export async function renderHomePage(): Promise<void> {
|
|||
promoted_today: 0,
|
||||
top_10_count: 0,
|
||||
updated_at: '',
|
||||
matches_today: 0,
|
||||
active_bots: 0,
|
||||
})),
|
||||
fetchSeasonIndex().catch(() => ({
|
||||
updated_at: '',
|
||||
|
|
@ -200,7 +202,8 @@ export async function renderHomePage(): Promise<void> {
|
|||
|
||||
const evoHtml = lazySection(
|
||||
'home-evo',
|
||||
`<section class="home-evo"><div class="home-evo-info"><span class="home-evo-icon">🧬</span><span class="home-evo-text"><strong>Evolution Observatory</strong> — Gen #${evolutionMeta.generation}${evolutionMeta.promoted_today > 0 ? ` · ${evolutionMeta.promoted_today} promoted today` : ''}${evolutionMeta.top_10_count > 0 ? ` · ${evolutionMeta.top_10_count} in top 10` : ''}</span></div><a href="#/evolution" class="btn small secondary">Watch evolution live →</a></section>`,
|
||||
`<section class="home-evo"><div class="home-evo-info"><span class="home-evo-icon">🧬</span><span class="home-evo-text"><strong>Evolution Observatory</strong> — Gen #${evolutionMeta.generation}${evolutionMeta.promoted_today > 0 ? ` · ${evolutionMeta.promoted_today} promoted today` : ''}${evolutionMeta.top_10_count > 0 ? ` · ${evolutionMeta.top_10_count} in top 10` : ''}</span></div><a href="#/evolution" class="btn small secondary">Watch evolution live →</a></section>
|
||||
<div class="home-live-stats"><span class="home-live-icon">⚔</span><span class="home-live-text">${(evolutionMeta.matches_today ?? 0).toLocaleString()} matches today · ${(evolutionMeta.active_bots ?? 0).toLocaleString()} bots active · Gen #${evolutionMeta.generation} evolving</span></div>`,
|
||||
{ placeholder: '<div class="lazy-placeholder" style="min-height:60px"></div>' }
|
||||
);
|
||||
|
||||
|
|
@ -535,6 +538,22 @@ export async function renderHomePage(): Promise<void> {
|
|||
}
|
||||
.home-evo-text strong { color: var(--text-primary); }
|
||||
|
||||
/* Live stats bar (plan §16.18) */
|
||||
.home-live-stats {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.home-live-icon { font-size: 1rem; }
|
||||
.home-live-text {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.home-empty {
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue