feat(portal): show the client their active alert limits
New scoped GET /portal/api/location/{id}/thresholds returns the enabled alert
rules (scrubbed: name/metric/comparison/threshold/duration/schedule — no cooldown
or hysteresis internals). Location page renders an "Alert limits" panel above the
history, e.g. "Night noise · Leq above 65 dB for 60s · 22:00–07:00", hidden when
no limits are set. Gives the breach history context.
Verified: portal.py compiles; location script balances; template parses.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -242,6 +242,34 @@ async def portal_location_events(location_id: str, limit: int = 20,
|
||||
return {"status": "ok", "events": events, "active": sum(1 for e in events if e.get("status") == "active")}
|
||||
|
||||
|
||||
# Whitelist of alert-rule fields shown to a client (the active limits, no cooldown/
|
||||
# hysteresis internals).
|
||||
_PORTAL_RULE_FIELDS = ("name", "metric", "comparison", "threshold_db", "duration_s",
|
||||
"schedule_start", "schedule_end", "schedule_days")
|
||||
|
||||
|
||||
@router.get("/api/location/{location_id}/thresholds")
|
||||
async def portal_location_thresholds(location_id: str,
|
||||
client: Client = Depends(get_current_client),
|
||||
db: Session = Depends(get_db)):
|
||||
"""The active alert limits for a location the client owns (enabled rules only),
|
||||
so the client can see what they're being alerted on. Read-only, scrubbed."""
|
||||
resolve_client_location(client, location_id, db)
|
||||
unit_id = active_unit_for_location(location_id, db)
|
||||
if not unit_id:
|
||||
return {"status": "ok", "rules": []}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0) as hc:
|
||||
r = await hc.get(f"{SLMM_BASE_URL}/api/nl43/{unit_id}/alerts/rules")
|
||||
except Exception:
|
||||
return {"status": "ok", "rules": []}
|
||||
if r.status_code != 200:
|
||||
return {"status": "ok", "rules": []}
|
||||
raw = (r.json() or {}).get("rules", [])
|
||||
rules = [{k: x.get(k) for k in _PORTAL_RULE_FIELDS} for x in raw if x.get("enabled")]
|
||||
return {"status": "ok", "rules": rules}
|
||||
|
||||
|
||||
# -- live stream (fan-out feed, scoped + scrubbed) ---------------------------
|
||||
|
||||
def _scrub_frame(raw: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user