import enum from datetime import datetime, timezone from typing import Any, List from sqlalchemy.orm import Mapped, mapped_column from app.extensions import db from app.models.activity import Activity from app.models.types import AwareDateTime class AlarmState(enum.Enum): UNKNOWN = 0 OK = 1 WARNING = 2 CRITICAL = 3 @property def emoji(self) -> str: if self.name == "OK": return "😅" if self.name == "WARNING": return "😟" if self.name == "CRITICAL": return "🚨" return "❓" class Alarm(db.Model): # type: ignore id: Mapped[int] = mapped_column(db.Integer, primary_key=True) target: Mapped[str] aspect: Mapped[str] alarm_state: Mapped[AlarmState] = mapped_column(default=AlarmState.UNKNOWN) state_changed: Mapped[datetime] = mapped_column(AwareDateTime()) last_updated: Mapped[datetime] = mapped_column(AwareDateTime()) text: Mapped[str] @classmethod def csv_header(cls) -> List[str]: return ["id", "target", "alarm_type", "alarm_state", "state_changed", "last_updated", "text"] def csv_row(self) -> List[Any]: return [getattr(self, x) for x in self.csv_header()] def update_state(self, state: AlarmState, text: str) -> None: if self.alarm_state != state or self.state_changed is None: self.state_changed = datetime.now(tz=timezone.utc) activity = Activity(activity_type="alarm_state", text=f"[{self.aspect}] {state.emoji} Alarm state changed from " f"{self.alarm_state.name} to {state.name} on {self.target}: {text}.") if (self.alarm_state.name in ["WARNING", "CRITICAL"] or state.name in ["WARNING", "CRITICAL"]): # Notifications are only sent on recovery from warning/critical state or on entry # to warning/critical states. This should reduce alert fatigue. activity.notify() db.session.add(activity) self.alarm_state = state self.text = text self.last_updated = datetime.now(tz=timezone.utc)