Prometheus-shaped query

GET /api/bng/metrics/query_range?metric=&range=&step=&ip=   read_only

A Prometheus-compatible range query over a curated metric set. The response
envelope deliberately mimics Prometheus's /api/v1/query_range so that query and
charting code remains portable.

Parameter Rules
metric One of sessions_connected, sessions_captive, sessions_offering, subs_bps_up, subs_bps_dn. Otherwise 400.
range Same enum as /throughput/recent. Otherwise 400.
step Integer seconds, floored per range (5 s up to 1h, rising to 600 s for 1m).
ip Required when metric begins with subs_bps_; ignored otherwise. Missing gives 400.
{
  "status": "success",
  "data": {
    "resultType": "matrix",
    "result": [
      { "metric": { "__name__": "subs_bps_dn", "ip": "100.64.12.30" },
        "values": [[1753000000.0, "2411882.0000"]] }
    ],
    "range": { "start": 1753000000.0, "end": 1753003600.0, "step": 5 }
  }
}

result is an empty array when the series does not exist — an unknown subscriber
IP, or a series the sampler has not populated yet. data.range is a
QuantumTouch extension; real Prometheus does not return it.

Per-range step floors match the sampler's own cadence so that chart lag is bounded
by the five-second tick rather than by query-side aggregation.

Errors are 400 with {"status": "error", "error": …} and one of
"unknown metric", "unknown range", or
"ip required for per-subscriber metric".

Note the retention asymmetry for per-subscriber queries: those series live only in
the fast ring buffer, so a long range on subs_bps_* is bounded by that buffer's
window regardless of the range requested.