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| // // 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| 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) }