From 0bec4c46488ecaf7573c1fd39cdeca9a29c198e9 Mon Sep 17 00:00:00 2001 From: bonamin Date: Mon, 21 Sep 2026 18:27:35 +0300 Subject: [PATCH] feat(devices): add DeviceHealthSettings model for per-device alert thresholds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two-tier (warning/critical) threshold config per device — restarts/week, RSSI floor, free-heap floor, CPU temp ceiling, plus an offline timeout used for both a client-computed health status icon and the Health tab chart's gap detection. Purely advisory for now: no server-side email/push alerting infra exists yet, so email_on_threshold and push_on_crash_boot are placeholders for that future work. Co-Authored-By: Claude Sonnet 5 --- backend/devices/models.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/backend/devices/models.py b/backend/devices/models.py index 032124a..0b39744 100644 --- a/backend/devices/models.py +++ b/backend/devices/models.py @@ -98,6 +98,35 @@ class DeviceStatistics(BaseModel): maintainancePeriod: int = 0 +class HealthThresholdTier(BaseModel): + """One severity tier's worth of metric thresholds. Two tiers (warning, + critical) make up DeviceHealthSettings — see there for why.""" + restarts_per_week_threshold: int + rssi_floor_dbm: int + free_heap_floor_kb: int + cpu_temp_ceiling_c: int + + +class DeviceHealthSettings(BaseModel): + """Per-device alert thresholds shown/edited on the Health tab's Settings + sub-tab. Purely advisory today — thresholds drive a client-computed + ok/warning/critical status icon, not any server-side alerting yet (no + email/push infra exists). Two severity tiers per metric (warning fires + first, critical is the harder breach) so the header status icon can show + orange vs. red rather than a single degraded/healthy binary. + offline_timeout_sec also feeds the Health tab's chart gap detection + (LineChart's gapThresholdMs) so both share one source of truth.""" + warning: HealthThresholdTier = HealthThresholdTier( + restarts_per_week_threshold=6, rssi_floor_dbm=-80, free_heap_floor_kb=30, cpu_temp_ceiling_c=65, + ) + critical: HealthThresholdTier = HealthThresholdTier( + restarts_per_week_threshold=12, rssi_floor_dbm=-90, free_heap_floor_kb=15, cpu_temp_ceiling_c=80, + ) + offline_timeout_sec: int = 90 + email_on_threshold: bool = False + push_on_crash_boot: bool = False + + class MelodyMainItem(BaseModel): """Mirrors the Melody schema used in the melodies collection.""" information: dict = {} @@ -118,6 +147,7 @@ class DeviceCreate(BaseModel): device_attributes: DeviceAttributes = DeviceAttributes() device_subscription: DeviceSubInformation = DeviceSubInformation() device_stats: DeviceStatistics = DeviceStatistics() + device_health_settings: DeviceHealthSettings = DeviceHealthSettings() events_on: bool = False device_location_coordinates: Any = None # GeoPoint dict {lat, lng} or legacy str device_melodies_all: List[MelodyMainItem] = [] @@ -143,6 +173,7 @@ class DeviceUpdate(BaseModel): device_attributes: Optional[Dict[str, Any]] = None device_subscription: Optional[Dict[str, Any]] = None device_stats: Optional[Dict[str, Any]] = None + device_health_settings: Optional[Dict[str, Any]] = None events_on: Optional[bool] = None device_location_coordinates: Optional[Any] = None # dict {lat, lng} or legacy str device_melodies_all: Optional[List[MelodyMainItem]] = None