"""Actor workload read-models — tenant-scoped aggregation.""" from __future__ import annotations from typing import Any from psycopg2.extras import RealDictCursor from db import get_connection from tenant_context import TenantContext def get_actor_workload(ctx: TenantContext) -> list[dict[str, Any]]: """Open/blocked/in_progress action counts per active actor in tenant.""" conn = get_connection() try: with conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute( """ SELECT a.id AS actor_id, a.name AS display_name, a.actor_type, COUNT(*) FILTER (WHERE act.status = 'open') AS open_actions, COUNT(*) FILTER (WHERE act.status = 'blocked') AS blocked_actions, COUNT(*) FILTER (WHERE act.status = 'in_progress') AS in_progress_actions FROM actors a LEFT JOIN action_assignments aa ON aa.actor_id = a.id AND aa.tenant_id = a.tenant_id LEFT JOIN actions act ON act.id = aa.action_id AND act.tenant_id = aa.tenant_id AND act.status IN ('open', 'in_progress', 'blocked') WHERE a.tenant_id = %s AND a.is_active = TRUE GROUP BY a.id, a.name, a.actor_type ORDER BY CASE a.actor_type WHEN 'human' THEN 0 WHEN 'working_group' THEN 1 WHEN 'agent' THEN 2 ELSE 3 END, a.name """, (ctx.tenant_id,), ) result = [] for row in cur.fetchall(): result.append( { "actor_id": str(row["actor_id"]), "display_name": row["display_name"], "actor_type": row["actor_type"], "open_actions": int(row["open_actions"] or 0), "blocked_actions": int(row["blocked_actions"] or 0), "in_progress_actions": int(row["in_progress_actions"] or 0), } ) return result finally: conn.close()