# steward/core/time_range.py """Shared time-range utilities for scoping queries and sparkline data.""" from __future__ import annotations from datetime import datetime, timedelta, timezone from typing import TypeVar RANGES: dict[str, timedelta] = { "1h": timedelta(hours=1), "6h": timedelta(hours=6), "24h": timedelta(hours=24), "7d": timedelta(days=7), "30d": timedelta(days=30), } RANGE_OPTIONS: list[str] = list(RANGES.keys()) DEFAULT_RANGE = "24h" def parse_range(range_str: str | None) -> tuple[datetime, str]: """Return (since_datetime, range_key) from a query-param string like '24h'.""" key = range_str if range_str in RANGES else DEFAULT_RANGE since = datetime.now(timezone.utc) - RANGES[key] return since, key def bucket_seconds(since: datetime, target_points: int = 80) -> int: """Return bucket width in seconds so that (now - since) / width ≈ target_points. Minimum is 60s (one poll interval) so raw data is never re-averaged below the collection resolution. """ range_secs = (datetime.now(timezone.utc) - since).total_seconds() return max(60, int(range_secs / target_points)) T = TypeVar("T") def subsample(seq: list[T], n: int = 80) -> list[T]: """Return at most n evenly-distributed elements from seq (preserves order).""" if len(seq) <= n: return seq indices = sorted({int(round(i * (len(seq) - 1) / (n - 1))) for i in range(n)}) return [seq[i] for i in indices]