"""Stats / analytics page — aggregates over `burnin_jobs` for dashboards.""" from __future__ import annotations import aiosqlite from fastapi import APIRouter, Depends, Request from fastapi.responses import HTMLResponse from app import poller from app.database import get_db from app.renderer import templates from ._helpers import stale_context router = APIRouter() @router.get("/stats", response_class=HTMLResponse) async def stats_page( request: Request, db: aiosqlite.Connection = Depends(get_db), ): # Overall counts cur = await db.execute(""" SELECT COUNT(*) as total, SUM(CASE WHEN state='passed' THEN 1 ELSE 0 END) as passed, SUM(CASE WHEN state='failed' THEN 1 ELSE 0 END) as failed, SUM(CASE WHEN state='running' THEN 1 ELSE 0 END) as running, SUM(CASE WHEN state='cancelled' THEN 1 ELSE 0 END) as cancelled FROM burnin_jobs """) overall = dict(await cur.fetchone()) # Failure rate by drive model (only completed jobs) cur = await db.execute(""" SELECT COALESCE(d.model, 'Unknown') AS model, COUNT(*) AS total, SUM(CASE WHEN bj.state='passed' THEN 1 ELSE 0 END) AS passed, SUM(CASE WHEN bj.state='failed' THEN 1 ELSE 0 END) AS failed, ROUND(100.0 * SUM(CASE WHEN bj.state='passed' THEN 1 ELSE 0 END) / COUNT(*), 1) AS pass_rate FROM burnin_jobs bj JOIN drives d ON d.id = bj.drive_id WHERE bj.state IN ('passed', 'failed') GROUP BY COALESCE(d.model, 'Unknown') ORDER BY total DESC LIMIT 20 """) by_model = [dict(r) for r in await cur.fetchall()] # Activity last 14 days cur = await db.execute(""" SELECT date(created_at) AS day, COUNT(*) AS total, SUM(CASE WHEN state='passed' THEN 1 ELSE 0 END) AS passed, SUM(CASE WHEN state='failed' THEN 1 ELSE 0 END) AS failed FROM burnin_jobs WHERE created_at >= date('now', '-14 days') GROUP BY date(created_at) ORDER BY day DESC """) by_day = [dict(r) for r in await cur.fetchall()] # Average test duration by drive size (rounded to nearest TB) cur = await db.execute(""" SELECT CAST(ROUND(CAST(d.size_bytes AS REAL) / 1e12) AS INTEGER) AS size_tb, COUNT(*) AS total, ROUND(AVG( (julianday(bj.finished_at) - julianday(bj.started_at)) * 86400 / 3600.0 ), 1) AS avg_hours FROM burnin_jobs bj JOIN drives d ON d.id = bj.drive_id WHERE bj.state IN ('passed', 'failed') AND bj.started_at IS NOT NULL AND bj.finished_at IS NOT NULL GROUP BY size_tb ORDER BY size_tb """) by_size = [dict(r) for r in await cur.fetchall()] # Failure breakdown by stage (which stage caused the failure) cur = await db.execute(""" SELECT COALESCE(bj.stage_name, 'unknown') AS failed_stage, COUNT(*) AS count FROM burnin_jobs bj WHERE bj.state = 'failed' GROUP BY failed_stage ORDER BY count DESC """) by_failure_stage = [dict(r) for r in await cur.fetchall()] # Drives tracked cur = await db.execute("SELECT COUNT(*) FROM drives") drives_total = (await cur.fetchone())[0] ps = poller.get_state() return templates.TemplateResponse(request, "stats.html", { "request": request, "overall": overall, "by_model": by_model, "by_day": by_day, "by_size": by_size, "by_failure_stage": by_failure_stage, "drives_total": drives_total, "poller": ps, **stale_context(ps), })