Kosten je Einheit aufrechnen und Abweichungen melden (#699)
- IUsageRepository.GetDailyByAgentAsync: Tagessummen je Agent, Abo-Tokens getrennt - UnitCosts: je Einheit samt Untereinheiten heute/7/30 Tage, Monat, Vormonat, Durchschnittstag (28 Tage, ab 7 Tagen Verlauf), Maximum = Summe der Agentengrenzen - Meldungen: Abweichung vom eigenen Verlauf, Naehe zur Grenzsumme, Monatsschwellen der Instanz; CostWatch alle 15 min, je Einheit/Tag einmal (Tabelle CostAlerts), als Aufgabe an den Menschen; schaltet nichts ab - API /usage/units, /usage/cost-alerts; Zeitreihe gruppiert nach Einheit - WebUI: Karte "Kosten je Einheit" mit Meldungen in der Verbrauchsansicht; i18n en/de - Einstellungen instance.json costWatch - Tests: UnitCostsTests (8) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7eb211fd06
commit
da34b215f7
@@ -58,9 +58,39 @@ public static class AnalyticsEndpoints
|
||||
groupBy is "model" or "source" or "budget" ? groupBy : "agent",
|
||||
TimeSpan.FromMinutes(Math.Clamp(tzOffset, -14 * 60, 14 * 60)),
|
||||
ct);
|
||||
|
||||
// #699: "What does marketing cost me?" — the agents' series, added up by their unit as it is now.
|
||||
if (groupBy == "unit")
|
||||
{
|
||||
var org = host.Organisation.Current();
|
||||
return Results.Ok(points
|
||||
.GroupBy(p => (p.BucketStart, Unit: org.UnitOf(p.Group) is { } u ? org.PathOf(u.Id) : "—"))
|
||||
.Select(g => new SeriesPointDto(g.Key.BucketStart, g.Key.Unit, g.Sum(p => p.Runs), g.Sum(p => p.Tokens), g.Sum(p => p.CostUsd)))
|
||||
.OrderBy(p => p.BucketStart)
|
||||
.ToList());
|
||||
}
|
||||
return Results.Ok(points.Select(p => new SeriesPointDto(p.BucketStart, p.Group, p.Runs, p.Tokens, p.CostUsd)).ToList());
|
||||
});
|
||||
|
||||
// #699: Costs per unit — itself and everything below it — with the alerts raised on them.
|
||||
api.MapGet("/usage/units", async (AppHost host, CancellationToken ct) =>
|
||||
{
|
||||
if (host.CostWatch is not { } watch)
|
||||
return ApiEndpoints.Unavailable("No usage data: agents are not active.");
|
||||
var lines = await watch.ReportAsync(ct);
|
||||
return Results.Ok(lines.Select(l => new UnitCostDto(
|
||||
l.UnitId, l.Name, l.Path, l.Depth, l.Agents, l.Today, l.Last7Days, l.Last30Days, l.ThisMonth, l.PreviousMonth,
|
||||
l.MaxPerDay, l.AverageDay, l.ShareOfMaxToday, l.SubscriptionTokensToday, l.SubscriptionTokens30Days)).ToList());
|
||||
});
|
||||
|
||||
api.MapGet("/usage/cost-alerts", async (AppHost host, CancellationToken ct, int days = 30) =>
|
||||
{
|
||||
if (host.CostWatch is not { } watch)
|
||||
return Results.Ok(Array.Empty<CostAlertDto>());
|
||||
return Results.Ok((await watch.RecentAsync(days, ct))
|
||||
.Select(a => new CostAlertDto(a.Period, a.Scope, a.Kind.ToString(), a.Message, a.RaisedAt)).ToList());
|
||||
});
|
||||
|
||||
api.MapGet("/usage/events", async (AppHost host, HttpRequest request, CancellationToken ct) =>
|
||||
{
|
||||
if (host.Insights is not { } insights)
|
||||
|
||||
Reference in New Issue
Block a user