Язык (backend): Python Язык (frontend): TypeScript ML/DL: PyTorch, XGBoost, pandas Вычисления: NumPy, SciPy Backend: FastAPI Frontend: React База данных: PostgreSQL Инструменты автоматизации: Ollama
Промышленные предприятия вынуждены проводить плановое техническое обслуживание оборудования по жёсткому графику, даже когда оборудование находится в исправном состоянии. Это приводит к избыточным затратам на человеческие ресурсы и простоям производства. При этом отказ от планового обслуживания в пользу обслуживания «по факту поломки» несёт риск внезапных аварий. В результате предприятия вынуждены выбирать между переплатой за избыточное обслуживание и риском пропустить развивающийся дефект.
-
Как инженер по эксплуатации, я хочу видеть текущий прогноз остаточного ресурса каждой единицы оборудования, чтобы планировать техобслуживание по факту приближающегося дефекта, а не по жёсткому графику.
-
Как ответственный за производство, я хочу получать уведомление, когда состояние оборудования становится критическим, чтобы оперативно среагировать до внеплановой остановки или аварии.
-
Как инженер по эксплуатации, я хочу видеть историю показаний датчиков и отклонений от физически ожидаемого поведения оборудования, чтобы понимать, как развивался дефект, и точнее диагностировать его причину.
Обучающие данные — NASA C-MAPSS (симуляция деградации турбовентиляторных двигателей), файл train_FD001.txt: 100 двигателей, 20 631 строка, жизнь двигателя от 128 до 362 циклов (в среднем 206), один режим работы, пропусков нет. Каждая строка — один цикл двигателя: unit, cycle, 3 операционные настройки, 21 датчик.
RUL (остаточный ресурс, в циклах) размечается как rul = последний_цикл_двигателя − текущий_цикл; в последнем цикле rul = 0 (отказ). Для обучения дополнительно используется rul_clipped — RUL с потолком 125 циклов (на раннем этапе жизни износ по датчикам не виден). Разметка: ml/prepare_rul.py.
Датчики. В работе остаются 14 из 21: s2, s3, s4, s7, s8, s9, s11, s12, s13, s14, s15, s17, s20, s21. Исключены 7 датчиков без информации об износе: s1, s5, s10, s16, s18, s19 (константы) и s6 (почти константа). Операционные настройки op1–op3 в FD001 — шум/константа, в модель не идут. Для FD002–FD004 (6 режимов работы) датчики по константности не удаляются — требуется нормализация по режимам.
| Датчик | Что измеряет | Ед. | Связь с RUL (Спирмен) |
|---|---|---|---|
| s2 | T24 — температура на выходе КНД | °R | −0,63 |
| s3 | T30 — температура на выходе КВД | °R | −0,61 |
| s4 | T50 — температура на выходе ТНД | °R | −0,70 |
| s7 | P30 — давление на выходе КВД | psia | +0,68 |
| s8 | Nf — физическая скорость вентилятора | об/мин | −0,57 |
| s9 | Nc — физическая скорость ядра | об/мин | −0,32 |
| s11 | Ps30 — статическое давление на выходе КВД | psia | −0,72 |
| s12 | phi — расход топлива / Ps30 | pps/psia | +0,69 |
| s13 | NRf — приведённая скорость вентилятора | об/мин | −0,57 |
| s14 | NRc — приведённая скорость ядра | об/мин | −0,20 |
| s15 | BPR — степень двухконтурности | — | −0,67 |
| s17 | htBleed — энтальпия отбора | — | −0,63 |
| s20 | W31 — охлаждающий отбор ТВД | lbm/s | +0,65 |
| s21 | W32 — охлаждающий отбор ТНД | lbm/s | +0,66 |
Подготовка признаков: нормализация (параметры сохраняются для инференса), сглаживание (скользящее среднее/EWMA), скользящие признаки (среднее, std, наклон за 5–30 циклов) для XGBoost, окна по 30 циклов для нейросети на PyTorch. Разбиение train/val — по unit, не по строкам, чтобы не было утечки.
Статусы оборудования по прогнозу RUL: normal — более 75 циклов, warning — от 31 до 75, critical — 30 циклов и меньше.
Целевая аудитория системы - инженеры по эксплуатации и ответственные за производство на промышленных предприятиях. При оценке масштаба в 10 000 уникальных пользователей в сутки предполагается, что система обслуживает порядка N предприятий с M единицами оборудования на каждом, где каждый пользователь в среднем совершает K запросов в день (просмотр дашборда, проверка истории, получение алертов). Основная нагрузка - read-запросы (просмотр данных), запись происходит асинхронно от симулятора/датчиков оборудования, а не от действий пользователей напрямую.
При недоступности Telegram Bot API система применяет паттерн Circuit Breaker: после 3 неудачных попыток отправки подряд прекращает попытки на 60 секунд, чтобы не тратить ресурсы на заведомо неудачные запросы. Алерт при этом всё равно сохраняется в базу данных и отображается в интерфейсе.
При недоступности Open-Meteo API физическая модель использует последнее закэшированное значение погодных условий (кэш на 1 час). Если кэш пуст (например, при первом запуске) - модель работает без погодного фактора.
Исходные допущения:
| Параметр | Значение | Почему |
|---|---|---|
| Пользователей в сутки | 10 000 | — |
| Единиц оборудования в системе | 1 000 | условно |
| Частота показаний датчика | 1 раз / 30 сек (1 цикл) | типично для промышленного мониторинга |
| Частота инференса модели | 1 раз / 5 мин на единицу | нет смысла считать прогноз чаще, чем меняются данные |
| Запросов пользователя за сессию | ~5-10 (дашборд + история + алерты) | из прошлой оценки RPS |
WRITE в БД идет не от пользователей, а от фонового simulator + ml / physics predictions: 1 000 / 30 + 1 000 / 300 + 1 000 / 300 ~40 записей в сек READ 10 000 * 8 запросов ~ 80 000, правило 80%/20%, 20% от 24 часов = ~5, 80% от 80 000 = 64 000, 64 000 / (5 * 3600) ~ 3.5 RPS в пик
Средний размер JSON-ответа: ~10 КБ Запросов в сутки: 10 000 польз. × 8 запросов ≈ 80 000 запросов/сутки Трафик: 80 000 × 10 КБ ≈ 800 МБ/сутки ≈ ~24 ГБ/месяц
Показания датчиков (таблица sensor_readings, широкая строка: 14 датчиков × 4 байта (REAL) = 56 Б + id 8 Б + equipment_id 4 Б + cycle 4 Б + rul 4 Б + created_at 8 Б + служебный заголовок строки PostgreSQL ~24 Б ≈ 110 байт):
33 записи/сек × 86 400 сек = ~2.88 млн строк/сутки
2.88 млн × 110 байт ≈ 320 МБ/сутки → ~115 ГБ/год → ~580 ГБ за 5 лет (без индексов; индекс (equipment_id, cycle) добавляет порядка 30–40%).
Предсказания модели: 6.6 записи/сек × 86 400 = ~570 тыс. строк/сутки. 570 тыс. × 50 байт ≈ 28.5 МБ/сутки → ~10 ГБ/год → ~52 ГБ за 5 лет.
api/v1 (эндпоинты, валидация) → services (бизнес-логика) → repositories (доступ к данным)
└→ predictor (модель RUL: заглушка → XGBoost/PyTorch)
Репозиторий — интерфейс EquipmentRepository. Сейчас работает MockRepository (данные в памяти, похожие на FD001), позже — PostgresRepository. Переключение: OFFTECH_USE_MOCK_REPOSITORY. Эндпоинты и фронтенд при этом не меняются.
Все эндпоинты — с префиксом /api/v1. Документация OpenAPI: /docs.
| Метод | Эндпоинт | Описание | Ожидаемый latency |
|---|---|---|---|
| GET | /equipment |
Список всех единиц оборудования (с текущим циклом, статусом и прогнозом RUL) | < 200 мс |
| GET | /equipment/{id} |
Информация об одной единице | < 100 мс |
| GET | /equipment/{id}/status |
Текущее состояние: статус, RUL, вероятность дефекта, невязка | < 150 мс |
| GET | /equipment/{id}/history |
История показаний датчиков (from_cycle, to_cycle, limit) |
< 500 мс |
| GET | /equipment/{id}/forecast |
Прогноз деградации: RUL по будущим циклам с доверительным интервалом | < 500 мс |
| Метод | Эндпоинт | Описание | Ожидаемый latency |
|---|---|---|---|
| GET | /alerts |
Список последних алертов | < 200 мс |
| GET | /alerts/{equipment_id} |
Алерты по конкретному оборудованию | < 200 мс |
| Метод | Эндпоинт | Описание | Ожидаемый latency |
|---|---|---|---|
| POST | /predict |
Ручной запрос инференса модели для конкретного оборудования | < 2000 мс |
| Метод | Эндпоинт | Описание | Ожидаемый latency |
|---|---|---|---|
| GET | /sensors |
Каталог датчиков (21, с признаком is_used) |
< 100 мс |
| GET | /health |
Проверка работоспособности (без префикса) | < 50 мс |
Таблицы привязаны к структуре датасета C-MAPSS: одна единица оборудования = один двигатель (unit), одна запись показаний = один цикл (cycle). Обучающая выборка (train_FD00X.txt с разметкой RUL) хранится файлами в data/, а не в БД.
erDiagram
equipment ||--o{ sensor_readings : "показания"
equipment ||--o{ physics_predictions : "физ. прогнозы"
equipment ||--o{ ml_predictions : "ML прогнозы"
equipment ||--o{ alerts : "алерты"
ml_predictions ||--o{ alerts : "порождают"
equipment {
int id PK
varchar name
varchar type
varchar dataset "FD001..FD004"
int unit_number "unit из датасета"
date install_date
date last_maintenance_date
}
sensor_catalog {
varchar code PK "s1..s21"
varchar name
varchar unit
boolean is_used "false для s1 s5 s6 s10 s16 s18 s19"
}
sensor_readings {
bigint id PK
int equipment_id FK
int cycle
real s2
real s3
real s4
real s7
real s8
real s9
real s11
real s12
real s13
real s14
real s15
real s17
real s20
real s21
int rul
timestamp created_at
}
physics_predictions {
bigint id PK
int equipment_id FK
int cycle
float predicted_degradation
timestamp created_at
}
ml_predictions {
bigint id PK
int equipment_id FK
int cycle
float predicted_rul
float defect_probability
float residual
varchar model_version
timestamp created_at
}
alerts {
bigint id PK
int equipment_id FK
bigint ml_prediction_id FK
int cycle
timestamp triggered_at
varchar severity "info warning critical"
text message
}
Ограничения и индексы: UNIQUE (dataset, unit_number) в equipment; UNIQUE (equipment_id, cycle) в sensor_readings, physics_predictions, ml_predictions; индекс alerts (equipment_id, triggered_at DESC). Показания хранятся широкой таблицей: набор из 14 датчиков фиксирован, так запросы истории не требуют PIVOT. Для других наборов (FD002–FD004) добавляются колонки миграцией; полный список датчиков и признак is_used лежат в sensor_catalog.
При записи (40 записей/сек) все вставки идут в разные таблицы от фоновых процессов. 40 INSERT/сек — незначительная нагрузка для PostgreSQL. При чтении (~10-20 RPS) PostgreSQL будет находить строки за O(log n). Проблем не возникнет.
При 100 000 — запускаем несколько экземпляров за балансировщиком нагрузки.
Основная нагрузка — чтение (дашборд, история, алерты). Добавляем 1-2 read-реплики, куда направляем все GET-запросы. Запись идёт только в primary
Часто запрашиваемые данные кэшируем в Redis, чтобы не ходить в Postgres на каждый запрос
Open-Meteo API: Погодные данные меняются медленно - нет смысла запрашивать чаще раза в час. Кэшируем ответ в Redis.
Telegram Bot API: Алертов немного, rate limit маловероятен.
- Запускаем несколько воркеров ML-engine параллельно,
каждый обрабатывает свой диапазон
equipment_id - Если CPU не хватает - батчинг: вместо инференса по одной единице за раз, модель обрабатывает пачку, что сильно эффективнее.

