diff --git a/diagnostics/app.py b/diagnostics/app.py index f677287..0234f9f 100644 --- a/diagnostics/app.py +++ b/diagnostics/app.py @@ -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:

Argus

{message}

Check if teleo-pipeline.service is running and pipeline.db exists.

""" -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 + +
+
Contributions by Domain
+
+ + + {"".join(f''' + + + + ''' for domain, stats in sorted(domain_breakdown.items(), key=lambda x: x[1]["knowledge_prs"], reverse=True) if stats["knowledge_prs"] > 0)} +
DomainKnowledge PRsTop Contributors
{domain}{stats["knowledge_prs"]}{", ".join(f'{c["handle"]} ({c["claims"]})' for c in stats["contributors"][:3])}
+
+
+ {"" if not vital_signs["domain_activity"]["stagnant"] else f'''