feat: domain breakdown on dashboard — contributions by domain with top contributors
New _domain_breakdown() function cross-references merged PRs with contributor principals. Dashboard shows per-domain knowledge PR counts and top 3 contributors for each domain. API: GET /api/domains returns full breakdown. Pentagon-Agent: Epimetheus <3D35839A-7722-4740-B93D-51157F7D5E70>
This commit is contained in:
parent
ae1cce730c
commit
305445b164
1 changed files with 62 additions and 5 deletions
|
|
@ -454,6 +454,7 @@ async def handle_dashboard(request):
|
|||
vital_signs = _compute_vital_signs(conn)
|
||||
contributors_principal = _contributor_leaderboard(conn, limit=10, view="principal")
|
||||
contributors_agent = _contributor_leaderboard(conn, limit=10, view="agent")
|
||||
domain_breakdown = _domain_breakdown(conn)
|
||||
except sqlite3.Error as e:
|
||||
return web.Response(
|
||||
text=_render_error(f"Pipeline database unavailable: {e}"),
|
||||
|
|
@ -461,7 +462,7 @@ async def handle_dashboard(request):
|
|||
status=503,
|
||||
)
|
||||
now = datetime.now(timezone.utc)
|
||||
html = _render_dashboard(metrics, snapshots, changes, vital_signs, contributors_principal, contributors_agent, now)
|
||||
html = _render_dashboard(metrics, snapshots, changes, vital_signs, contributors_principal, contributors_agent, domain_breakdown, now)
|
||||
return web.Response(text=html, content_type="text/html")
|
||||
|
||||
|
||||
|
|
@ -502,11 +503,52 @@ async def handle_api_contributors(request):
|
|||
return web.json_response({"contributors": contributors, "view": view})
|
||||
|
||||
|
||||
def _domain_breakdown(conn) -> dict:
|
||||
"""Per-domain contribution breakdown: claims, contributors, sources, decisions."""
|
||||
# Claims per domain from merged knowledge PRs
|
||||
domain_stats = {}
|
||||
for r in conn.execute("""
|
||||
SELECT domain, count(*) as prs,
|
||||
SUM(CASE WHEN commit_type='knowledge' THEN 1 ELSE 0 END) as knowledge_prs
|
||||
FROM prs WHERE status='merged' AND domain IS NOT NULL
|
||||
GROUP BY domain ORDER BY prs DESC
|
||||
""").fetchall():
|
||||
domain_stats[r["domain"]] = {
|
||||
"total_prs": r["prs"],
|
||||
"knowledge_prs": r["knowledge_prs"] or 0,
|
||||
"contributors": [],
|
||||
}
|
||||
|
||||
# Top contributors per domain (from PR agent field + principal roll-up)
|
||||
has_principal = _has_column(conn, "contributors", "principal")
|
||||
for r in conn.execute("""
|
||||
SELECT p.domain,
|
||||
COALESCE(c.principal, p.agent, 'unknown') as contributor,
|
||||
count(*) as cnt
|
||||
FROM prs p
|
||||
LEFT JOIN contributors c ON LOWER(p.agent) = c.handle
|
||||
WHERE p.status='merged' AND p.commit_type='knowledge' AND p.domain IS NOT NULL
|
||||
GROUP BY p.domain, contributor
|
||||
ORDER BY p.domain, cnt DESC
|
||||
""").fetchall():
|
||||
domain = r["domain"]
|
||||
if domain in domain_stats:
|
||||
domain_stats[domain]["contributors"].append({
|
||||
"handle": r["contributor"],
|
||||
"claims": r["cnt"],
|
||||
})
|
||||
|
||||
return domain_stats
|
||||
|
||||
|
||||
async def handle_api_domains(request):
|
||||
"""GET /api/domains — per-domain health breakdown."""
|
||||
"""GET /api/domains — per-domain contribution breakdown.
|
||||
|
||||
Returns claims, contributors, and knowledge PR counts per domain.
|
||||
"""
|
||||
conn = _conn(request)
|
||||
metrics = _current_metrics(conn)
|
||||
return web.json_response({"domains": metrics["domains"]})
|
||||
breakdown = _domain_breakdown(conn)
|
||||
return web.json_response({"domains": breakdown})
|
||||
|
||||
|
||||
# ─── Dashboard HTML ──────────────────────────────────────────────────────────
|
||||
|
|
@ -521,7 +563,7 @@ def _render_error(message: str) -> str:
|
|||
</head><body><div class="err"><h1>Argus</h1><p>{message}</p><p>Check if <code>teleo-pipeline.service</code> is running and pipeline.db exists.</p></div></body></html>"""
|
||||
|
||||
|
||||
def _render_dashboard(metrics, snapshots, changes, vital_signs, contributors_principal, contributors_agent, now) -> str:
|
||||
def _render_dashboard(metrics, snapshots, changes, vital_signs, contributors_principal, contributors_agent, domain_breakdown, now) -> str:
|
||||
"""Render the full operational dashboard as HTML with Chart.js."""
|
||||
|
||||
# Prepare chart data
|
||||
|
|
@ -847,6 +889,21 @@ def _render_dashboard(metrics, snapshots, changes, vital_signs, contributors_pri
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Domain Breakdown -->
|
||||
<div class="section">
|
||||
<div class="section-title">Contributions by Domain</div>
|
||||
<div class="card">
|
||||
<table>
|
||||
<tr><th>Domain</th><th>Knowledge PRs</th><th>Top Contributors</th></tr>
|
||||
{"".join(f'''<tr>
|
||||
<td style="color:#58a6ff">{domain}</td>
|
||||
<td>{stats["knowledge_prs"]}</td>
|
||||
<td style="font-size:12px;color:#8b949e">{", ".join(f'{c["handle"]} ({c["claims"]})' for c in stats["contributors"][:3])}</td>
|
||||
</tr>''' for domain, stats in sorted(domain_breakdown.items(), key=lambda x: x[1]["knowledge_prs"], reverse=True) if stats["knowledge_prs"] > 0)}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stagnation Alerts -->
|
||||
{"" if not vital_signs["domain_activity"]["stagnant"] else f'''
|
||||
<div class="section">
|
||||
|
|
|
|||
Loading…
Reference in a new issue