Перейти к содержимому

Сейчас: внедряю системы ИИ-мониторинга в Steady Control

Daniil Dushin

Fridge Alert Bot

Тревога в Telegram, когда холодильник в ресторане слишком тёплый, — и тишина, когда кто-то просто открыл дверь.

Роль
Личный проект — проектирование, код, тесты
Год
2026
Категория
Боты · Инфраструктура
Стек
Python 3.12, FastAPI, aiogram 3, SQLite
Обложка Fridge Alert Bot — линия температуры пересекает лимит 5 °C

Проблема

Холодильник, который нагрелся за ночь, — это испорченные продукты утром. Пробел в журнале температур — это проблемы на санитарной проверке. Сотрудники смотрят на термометры несколько раз в день и пропускают большинство поломок.

Дешёвые датчики температуры есть. Не хватает того, что следит за ними и зовёт человека в нужный момент — и не поднимает ложную тревогу каждый раз, когда кто-то открыл дверь. В Steady Control я работаю с датчиками температуры холодильников в отелях и ресторанах, поэтому сделал ту часть, которая решает, когда будить человека.

Решение

Датчики отправляют показания в HTTP API. Небольшой конечный автомат решает, что значит каждое показание:

  • Холодильник должен быть выше лимита 5 минут, прежде чем придёт тревога. Дверь, открытая на минуту, тревогу не вызывает.
  • Одна тревога на инцидент, потом сообщение «снова в норме» с длительностью и пиковой температурой.
  • Для возврата к норме температура должна опуститься на 0,5 °C ниже лимита — так холодильник, который «висит» на границе, не засыпает чат сообщениями.
  • Опоздавшие или повторные показания от датчика, который был офлайн, попадают в историю, но не откатывают состояние назад. Инциденты старше 15 минут записываются, но не объявляются.

В Telegram команда кухни пользуется /status, /history <sensor> (график PNG), /setlimit и /report, а каждое утро приходит сводка за сутки.

Демо

Промо-ролик, собранный в Remotion.

Скриншоты

График температуры kitchen-fridge-1 за шесть часов — короткие всплески от открывания двери и одна поломка компрессора выше лимита 5 °C
График /history из демо. Открывания двери остаются короткими, поломка компрессора закрашена красным.

Технологии

  • Python 3.12
  • FastAPI
  • aiogram 3
  • SQLite
  • matplotlib
  • pytest
  • Docker

Python 3.12 с аннотациями типов, FastAPI для API, aiogram 3 для бота, SQLite для хранения и matplotlib для графиков. API, бот и планировщик отчётов работают в одном asyncio-процессе и делят один сервисный объект, поэтому они никогда не расходятся во мнении о состоянии холодильника.

Фрагмент кода

Правило тревоги — чистая функция: без базы данных и без Telegram, только «состояние + показание → новое состояние и, возможно, событие». Поэтому каждый пограничный случай проверяется тестом в три строки.

python
def evaluate(
    current: AlertState,
    *,
    sensor_id: str,
    temperature: float,
    at: datetime,
    limit: float,
    rules: AlertRules,
) -> tuple[AlertState, AlertEvent | None]:
    """Return the next state and, if something worth telling people happened, an event."""
    above = temperature > limit

    if current.state is SensorState.OK:
        if above:
            return AlertState(SensorState.PENDING, at, temperature), None
        return current, None

    started = current.breach_started_at
    peak = (
        temperature
        if current.peak_temperature is None
        else max(current.peak_temperature, temperature)
    )

    if current.state is SensorState.PENDING:
        if not above:
            return AlertState(), None
        if at - started >= rules.delay:
            event = AlertEvent(EventKind.ALERT, sensor_id, temperature, limit, started, peak, at)
            return AlertState(SensorState.ALERT, started, peak), event
        return replace(current, peak_temperature=peak), None

    # ALERT
    if temperature <= limit - rules.hysteresis:
        event = AlertEvent(EventKind.RECOVERED, sensor_id, temperature, limit, started, peak, at)
        return AlertState(), event
    return replace(current, peak_temperature=peak), None

Баг, который я нашёл и исправил

Симулятор поднимал ложные тревоги при обычном открывании двери

Что сломалось

Тест прогоняет смоделированный день открываний двери через настоящие правила тревоги. Он упал: 18 сообщений о тревоге и возврате к норме — без единой поломки оборудования. На 30 случайных seed'ах демо давало 8,2 ложной тревоги в сутки. Демо, которое постоянно «кричит волк», выглядит как сломанный продукт.

Причина

Симулятор считал, что при открытой двери датчик стремится к комнатной температуре (24 °C). Так ведёт себя голый датчик воздуха. Датчик внутри холодильника стоит среди продуктов и видит смешанный воздух. Два открывания подряд держали показания выше 5 °C дольше, чем задержка тревоги.

Исправление

Теперь открытая дверь поднимает цель на несколько градусов выше уставки, а компрессор возвращает холод быстрее. Ложных тревог стало 0,13 в сутки вместо 8,2 на тех же seed'ах. Размытый тест превратился в два точных: одно открывание двери никогда не даёт тревогу, а оживлённая неделя — максимум две.

До
door_tau_s: float = 900.0
cool_tau_s: float = 300.0

elif self.mode is Mode.DOOR_OPEN:
    target, tau = p.ambient_c, p.door_tau_s
После
door_delta_c: float = 5.0
door_tau_s: float = 300.0
cool_tau_s: float = 180.0

elif self.mode is Mode.DOOR_OPEN:
    target, tau = p.setpoint_c + p.door_delta_c, p.door_tau_s

Результат

  • 83 автоматических теста и ноль предупреждений линтера; GitHub Actions запускает их при каждом push.
  • Работает от начала до конца без оборудования: симулятор создаёт открывания двери и поломки компрессора, а без токена бота тревоги печатаются в консоль.
  • В живом запуске демо принудительная поломка компрессора дала ровно одну тревогу, а открывания двери — ни одной.