Назначение и данные
Создайте REST-сервис телеметрии. Каждое измерение содержит device_id, event_id, event_time, received_at, metric, value и unit. Время события передаётся как RFC 3339 UTC; серверное время получения назначается сервером. Уникальна пара device_id/event_id. Допустимый дрейф часов составляет от received_at минус 24 часа до received_at плюс 5 минут; измерение вне диапазона получает статус rejected с причиной invalid_event_time. Устройство должно существовать и иметь статус active. Значение должно быть конечным числом, единица должна совпадать с единицей метрики в регистрации устройства. Один пакет содержит не более 500 событий и не более 1 MiB JSON.
REST-контракт
Локальный seed создаёт telemetry_admin с фиксированным токеном из Authorization header для управления правилами оповещений. POST /api/v1/devices/{device_id}/events принимает пакет и возвращает статус для каждого event_id: accepted, duplicate или rejected. Повтор ID с тем же нормализованным содержимым возвращает duplicate. Тот же ID с другим временем, метрикой, единицей или значением возвращает conflict, не меняя исходную запись. GET /api/v1/devices/{device_id}/measurements?metric=...&from=...&to=...&window=... возвращает агрегаты для полуоткрытого диапазона [from,to). Максимальный диапазон — 31 день, допустимые окна — 1m, 5m, 1h, 1d. Значения среднего и количества считаются по event_time; пустые окна не выдаются. Окно фиксируется по UTC. Максимум 1000 строк на страницу.
Поздние события и правила
Для открытого окна событие принимается, пока event_time не старше 7 дней. Более старое событие получает late и сохраняется в таблице карантина, не изменяя закрытые агрегаты. Окно становится закрытым через 10 минут после своего конца; события внутри допустимого 7-дневного срока пересчитывают затронутое окно только пока оно не закрыто. POST /api/v1/devices/{device_id}/alerts создаёт правило по metric, operator (gt или lt), threshold и cooldown_seconds от 60 до 86400. Порог проверяется по каждому принятому измерению в event_time-порядке. Одна запись оповещения создаётся на переход из нормального состояния в тревожное; повтор события не дублирует её. Нормальным состояние становится после наблюдения по другую сторону порога.
Структура программы
Приём отдельно проверяет события; предметный пакет считает окна и переходы оповещений; хранилище сохраняет события и очередь, фоновый исполнитель агрегирует их. main.go только загружает конфигурацию, связывает зависимости и управляет запуском. Пакеты без циклов, общих utils и интерфейсов без потребителя. README показывает зависимости; тесты ядра без HTTP/БД, интеграционные отдельно.
Хранение, отказы и приёмка
Используйте Go, PostgreSQL, SQL без ORM и миграции. Пакет и уникальные события фиксируются транзакционно. Очередь агрегатора ограничена 10 000 ожидающих событий; при достижении предела новый пакет получает 503 до подтверждения сохранения. Подтверждённые записи сохраняются после перезапуска. Исполнитель обрабатывает партии (batch) по 250 событий и атомарно фиксирует агрегаты и контрольную позицию, использует контекстный таймаут SQL в 3 секунды и продолжает после перезапуска с контрольной позицией. При конфликте события оно остаётся в истории без повторного применения. Метрики показывают backlog, возраст самого старого события, число late и rejected записей.
Офлайн-тесты используют фиксированные часы и PostgreSQL. Они проверяют дубликат и конфликт, 1-минутное окно, границы полуоткрытого диапазона, сортировку переставленных событий, 7-дневную границу, закрытое окно, порог и cooldown, заполнение очереди и рестарт после сохранения события до обработки. Критерии готовности: ответ accepted выдаётся только после фиксации; каждая пара device/event встречается в истории не более одного раза; агрегат совпадает с ручной суммой контрольных событий; после перезапуска подтверждённая очередь обработана без двойного счёта; полная очередь возвращает 503 и не теряет подтверждённый пакет.