summaryrefslogtreecommitdiff
path: root/internal/api/dashboard.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api/dashboard.go')
-rw-r--r--internal/api/dashboard.go75
1 files changed, 75 insertions, 0 deletions
diff --git a/internal/api/dashboard.go b/internal/api/dashboard.go
new file mode 100644
index 0000000..1238295
--- /dev/null
+++ b/internal/api/dashboard.go
@@ -0,0 +1,75 @@
+package api
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/thepeterstone/claudomator/internal/storage"
+)
+
+// parseWindow resolves a ?window= query param to a lookback duration,
+// defaulting to def when absent/unrecognized. Mirrors
+// handleGetDashboardStats' "24h vs 7d" special-casing, generalized with a
+// few friendly aliases plus a time.ParseDuration fallback for anything else
+// (e.g. "2h", "90m").
+func parseWindow(v string, def time.Duration) time.Duration {
+ switch v {
+ case "":
+ return def
+ case "5h":
+ return 5 * time.Hour
+ case "24h":
+ return 24 * time.Hour
+ case "7d":
+ return 7 * 24 * time.Hour
+ default:
+ if d, err := time.ParseDuration(v); err == nil && d > 0 {
+ return d
+ }
+ return def
+ }
+}
+
+// handleGetEscalationFunnel returns the (escalation_rung, agent) aggregate
+// that drives the budget dashboard's escalation funnel — the harness's core
+// "resolve locally, escalate only when needed" story made visible.
+// GET /api/escalation-funnel?window=24h|5h|7d|<Go duration>
+//
+// Defaults to a 24h window: wider than the budget accountant's 5h rolling
+// spend window (which exists to gate live dispatch), since this is a
+// historical/observability view, not a live gate — 24h gives a more
+// representative sample of rung distribution without requiring the caller
+// to know the accountant's configured window.
+func (s *Server) handleGetEscalationFunnel(w http.ResponseWriter, r *http.Request) {
+ window := parseWindow(r.URL.Query().Get("window"), 24*time.Hour)
+ since := time.Now().Add(-window)
+
+ buckets, err := s.store.QueryEscalationFunnel(since)
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ if buckets == nil {
+ buckets = []storage.EscalationBucket{}
+ }
+ writeJSON(w, http.StatusOK, buckets)
+}
+
+// handleGetSpendTimeseries returns cost-per-provider bucketed over time —
+// hourly for windows of a day or less, daily otherwise (mirroring
+// QueryDashboardStats' own hourly-throughput/daily-billing split).
+// GET /api/spend-timeseries?window=24h|5h|7d|<Go duration>
+func (s *Server) handleGetSpendTimeseries(w http.ResponseWriter, r *http.Request) {
+ window := parseWindow(r.URL.Query().Get("window"), 24*time.Hour)
+ since := time.Now().Add(-window)
+
+ points, err := s.store.QuerySpendTimeseries(since, window <= 26*time.Hour)
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ if points == nil {
+ points = []storage.SpendPoint{}
+ }
+ writeJSON(w, http.StatusOK, points)
+}