feat(rulings): system_usage_events is read back — per-System counts and a telemetry block (#4769)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 50s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m44s
CI & Build / Build & push image (push) Successful in 36s

#4769 "Rulings are counted where someone will read them": milestone 444
step 4 wrote system_usage_events and nothing read it.

- retrieval_telemetry gains a `system_usage` block: surfacings and opens by
  source, distinct counts, and `by_system` naming the areas most shown.
  There is deliberately no pull-through ratio, because rulings travel in full
  in the line and opens are the exception.
- usage_for_systems (one GROUP BY) adds `usage` to the REST Systems list and
  detail, and to MCP get_system. MCP list_systems is unchanged.
- The Systems UI shows a "rulings shown N×" chip.
- rulings_pre_tool, rulings_write_path and mcp_get_system are now declared
  registry points; the registry guard covers their recorders.
- The Systems store merges a PATCH reply instead of replacing the row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 08:49:20 -04:00
co-authored by Claude Opus 5.5
parent 1b973ebd13
commit 0a1bb68808
12 changed files with 457 additions and 4 deletions
+6
View File
@@ -10,6 +10,7 @@ from scribe.routes.utils import not_found
from scribe.services import systems as systems_svc
from scribe.services.access import can_write_project
from scribe.services.projects import get_project_for_user
from scribe.services import system_usage as system_usage_svc
logger = logging.getLogger(__name__)
@@ -43,10 +44,14 @@ async def list_systems_route(project_id: int):
uid, project_id, include_archived=_truthy(request.args.get("include_archived")),
)
counts = await systems_svc.open_issue_counts_by_system(uid, project_id)
# How often each area's rulings reached a session (#4769) — one GROUP BY
# for the page, and a zero shape for an area nothing has touched.
usage = await system_usage_svc.usage_for_systems([s.id for s in systems])
out = []
for s in systems:
d = s.to_dict()
d["open_issue_count"] = counts.get(s.id, 0)
d["usage"] = usage.get(s.id, system_usage_svc.empty_system_usage())
out.append(d)
return jsonify({"systems": out})
@@ -114,6 +119,7 @@ async def get_system_route(project_id: int, system_id: int):
)
data = system.to_dict()
data["issues"], data["tasks"], data["notes"] = issues, tasks, notes
data["usage"] = (await system_usage_svc.usage_for_systems([system.id]))[system.id]
return jsonify(data)