Organigramm: Lage je Abteilung in der Teamuebersicht (#688)
- TeamMember: Laeufe und Kosten heute, offene und blockierte Aufgaben je Agent (eine Abfrage fuer alle, nicht eine je Agent) - TeamUnit: dieselben Werte samt Untereinheiten, offene Vorgaenge aus der Einheit, heutige Kostenmeldung (#699) - team.js: Lagezeile auf jeder Karte und in der Seitenleiste, Instanz summiert; Spruenge zu Kosten je Einheit und zu den Vorgaengen; i18n en/de - Tests: TeamOverviewTests (+2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
888369698d
commit
f649f2fd7a
@@ -5,6 +5,7 @@ using ClawdDotNet.Core.Config;
|
||||
using ClawdDotNet.Core.Engine;
|
||||
using ClawdDotNet.Core.Organisation;
|
||||
using ClawdDotNet.Core.Staging;
|
||||
using ClawdDotNet.Core.Tasks;
|
||||
|
||||
namespace ClawdDotNet.App.Services;
|
||||
|
||||
@@ -43,7 +44,12 @@ public sealed record TeamMember(
|
||||
string? LastRunResult,
|
||||
long TypicalRunMs,
|
||||
string? UnitId = null,
|
||||
bool IsLead = false);
|
||||
bool IsLead = false,
|
||||
// #688: the agent's day — what a department's situation is added up from.
|
||||
int RunsToday = 0,
|
||||
decimal CostToday = 0,
|
||||
int OpenTasks = 0,
|
||||
int BlockedTasks = 0);
|
||||
|
||||
/// <summary>
|
||||
/// One unit in the team overview (#711, stage 1), with what happens inside it rolled up —
|
||||
@@ -60,7 +66,15 @@ public sealed record TeamUnit(
|
||||
int Agents,
|
||||
IReadOnlyDictionary<TeamState, int> States,
|
||||
int PendingApprovals,
|
||||
TeamState Worst);
|
||||
TeamState Worst,
|
||||
// #688: the department's situation, not the agent's — runs and cost today, tasks open and
|
||||
// blocked, cases raised inside it, and the cost alert of today, if any (#699).
|
||||
int RunsToday = 0,
|
||||
decimal CostToday = 0,
|
||||
int OpenTasks = 0,
|
||||
int BlockedTasks = 0,
|
||||
int OpenCases = 0,
|
||||
string? CostAlert = null);
|
||||
|
||||
/// <summary>The whole picture at one moment: every agent and unit, and who reached whom recently.</summary>
|
||||
public sealed record TeamSnapshot(
|
||||
@@ -89,7 +103,10 @@ public sealed class TeamOverview(
|
||||
IUsageRepository? usage,
|
||||
Func<IReadOnlyList<AgentConfig>> agents,
|
||||
TimeProvider? clock = null,
|
||||
Func<OrgChart>? organisation = null)
|
||||
Func<OrgChart>? organisation = null,
|
||||
TaskboardService? board = null,
|
||||
Core.Cases.ICaseRepository? cases = null,
|
||||
Func<CancellationToken, Task<IReadOnlyList<Core.Accounting.RaisedCostAlert>>>? costAlerts = null)
|
||||
{
|
||||
/// <summary>How far back the last receipt of each agent is looked for.</summary>
|
||||
public static readonly TimeSpan ReceiptWindow = TimeSpan.FromDays(3);
|
||||
@@ -140,6 +157,14 @@ public sealed class TeamOverview(
|
||||
|
||||
var org = organisation?.Invoke() ?? new OrgChart([], configured);
|
||||
|
||||
// #688: the day of each agent, in one query each — not one per agent.
|
||||
var today = DateOnly.FromDateTime(_clock.GetLocalNow().DateTime);
|
||||
var usageToday = usage is null ? [] : await usage.GetDailyByAgentAsync(today, today, ct);
|
||||
var openTasks = board is null ? [] : await board.ListAsync(new TaskQuery { Status = TaskItemStatus.Todo, Limit = 5_000 }, ct);
|
||||
var blockedTasks = board is null ? [] : await board.ListAsync(new TaskQuery { Status = TaskItemStatus.Blocked, Limit = 5_000 }, ct);
|
||||
int Count(IReadOnlyList<TaskItem> tasks, string agentId)
|
||||
=> tasks.Count(t => string.Equals(TaskAssignee.AgentId(t.Assignee), agentId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var members = new List<TeamMember>();
|
||||
foreach (var agent in configured)
|
||||
{
|
||||
@@ -155,7 +180,10 @@ public sealed class TeamOverview(
|
||||
members.Add(new TeamMember(
|
||||
agent.AgentId, agent.DisplayName, agent.Model, state, runs, approvals,
|
||||
last?.Status, last?.OccurredAt, last?.ResultRef, (long)typical.TotalMilliseconds,
|
||||
org.UnitOf(agent.AgentId)?.Id, org.LedBy(agent.AgentId) is not null));
|
||||
org.UnitOf(agent.AgentId)?.Id, org.LedBy(agent.AgentId) is not null,
|
||||
usageToday.Where(u => string.Equals(u.AgentId, agent.AgentId, StringComparison.OrdinalIgnoreCase)).Sum(u => u.Runs),
|
||||
usageToday.Where(u => string.Equals(u.AgentId, agent.AgentId, StringComparison.OrdinalIgnoreCase)).Sum(u => u.CostUsd),
|
||||
Count(openTasks, agent.AgentId), Count(blockedTasks, agent.AgentId)));
|
||||
}
|
||||
|
||||
// #720: files where agents keep getting in each other's way — for a human to decide
|
||||
@@ -165,7 +193,19 @@ public sealed class TeamOverview(
|
||||
.Select(h => h with { Path = Readable(h.Path, configured) }).ToList()
|
||||
: [];
|
||||
|
||||
return new TeamSnapshot(members, links, now, RollUp(org, members), hotspots);
|
||||
var openCases = cases is null ? [] : await cases.ListOpenAsync(ct);
|
||||
var alerts = costAlerts is null ? [] : (await costAlerts(ct)).Where(a => a.Period == today.ToString("yyyy-MM-dd")).ToList();
|
||||
var units = RollUp(org, members)
|
||||
.Select(u => u with
|
||||
{
|
||||
OpenCases = openCases.Count(c => c.OpenedUnitId is { } id
|
||||
&& (string.Equals(id, u.Id, StringComparison.OrdinalIgnoreCase)
|
||||
|| org.Ancestors(id).Any(a => string.Equals(a.Id, u.Id, StringComparison.OrdinalIgnoreCase)))),
|
||||
CostAlert = alerts.FirstOrDefault(a => string.Equals(a.Scope, u.Id, StringComparison.OrdinalIgnoreCase))?.Kind.ToString()
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new TeamSnapshot(members, links, now, units, hotspots);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -222,7 +262,8 @@ public sealed class TeamOverview(
|
||||
|
||||
result.Add(new TeamUnit(
|
||||
unit.Id, unit.Name, unit.ParentId, org.PathOf(unit.Id), org.LeaderOf(unit.Id)?.AgentId, unit.Goal,
|
||||
inside.Count, states, inside.Sum(m => m.PendingApprovals), worst));
|
||||
inside.Count, states, inside.Sum(m => m.PendingApprovals), worst,
|
||||
inside.Sum(m => m.RunsToday), inside.Sum(m => m.CostToday), inside.Sum(m => m.OpenTasks), inside.Sum(m => m.BlockedTasks)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user