rheinpegel-python / app /models.py
Archaeo's picture
Upload 38 files
15d848a verified
from __future__ import annotations
from datetime import datetime
from enum import Enum, IntEnum
from typing import Final
from pydantic import BaseModel, ConfigDict, Field
class Trend(IntEnum):
FALLING = -1
STABLE = 0
RISING = 1
@property
def symbol(self) -> str:
return {self.FALLING: "↓", self.STABLE: "→", self.RISING: "↑"}[self]
@property
def color(self) -> str:
return {self.FALLING: "badge-falling", self.STABLE: "badge-stable", self.RISING: "badge-rising"}[self]
class WarningLevel(str, Enum):
NORMAL = "normal"
ATTENTION = "attention"
WARNING = "warning"
ALARM = "alarm"
@property
def label(self) -> str:
return {
self.NORMAL: "Normal",
self.ATTENTION: "Aufmerksamkeit",
self.WARNING: "Warnung",
self.ALARM: "Alarm",
}[self]
@property
def color(self) -> str:
return {
self.NORMAL: "badge-normal",
self.ATTENTION: "badge-attention",
self.WARNING: "badge-warning",
self.ALARM: "badge-alarm",
}[self]
WARN_THRESHOLDS: Final[tuple[tuple[int, WarningLevel], ...]] = (
(400, WarningLevel.NORMAL),
(600, WarningLevel.ATTENTION),
(800, WarningLevel.WARNING),
)
def determine_warning(level_cm: int) -> WarningLevel:
for threshold, level in WARN_THRESHOLDS:
if level_cm < threshold:
return level
return WarningLevel.ALARM
class Measurement(BaseModel):
model_config = ConfigDict(frozen=True)
level_cm: int = Field(ge=0, description="Water level in centimeters")
timestamp: datetime = Field(description="Timestamp of the measurement with tz info")
trend: Trend = Field(description="Trend indicator: -1 falling, 0 stable, 1 rising")
is_demo: bool = False
@property
def warning(self) -> WarningLevel:
return determine_warning(self.level_cm)
class HistoryResponse(BaseModel):
data: list[Measurement]
demo_mode: bool = False
class LatestResponse(BaseModel):
measurement: Measurement
history: list[Measurement] = Field(default_factory=list)
latency_ms: float | None = None
error: str | None = None
demo_mode: bool = False
def resolve_trend(
current_level: int,
provided_trend: int | None,
previous: Measurement | None,
) -> Trend:
if provided_trend in (-1, 0, 1):
return Trend(provided_trend)
if previous is None:
return Trend.STABLE
delta = current_level - previous.level_cm
if delta > 2:
return Trend.RISING
if delta < -2:
return Trend.FALLING
return Trend.STABLE