Офлайн-replay стенд для прод-анализа

Приём для систем «сырые данные → анализ → число»: маленький офлайн-скрипт, который зовёт ТОТ ЖЕ анализ-код на сохранённых записях сырья. Если replay записи воспроизводит продовый результат байт-в-байт — стенду можно верить, и дальше все вопросы к анализу решаются без железа, расходников и времени на живые прогоны.

Как я это применял на калибровке 3D-принтера тензодатчиком (метод) — каждая цифра из реального дня:

1. Валидация стенда на эталонах

У автора чужого анализа в репозитории лежали 4 эталонные записи (.npz: массив сэмплов + JSON-мета) с задокументированными ответами (K≈0.033 PLA, ≈0.034 PETG). Мой replay выдал 0.0328 и 0.0335 — совпадение с паспортом. Ключ: стенд должен извлекать точку входа анализа из вызывающего кода, а не пересобирать по памяти — я скопировал сигнатуру вызова дословно из прод-модуля (какие поля меты, какая формула форса), иначе стенд врёт правдоподобно.

2. Оценка риска до железа: прореживание

Вопрос «хватит ли моего датчика 38.7 Гц, если эталоны писались на 488 Гц» закрылся за минуты: прореживаю эталонные записи bin-усреднением до целевых частот и смотрю дрейф ответа. Итог: 100–200 Гц — ошибка ≤9%, ниже 60 Гц — нестабильность ±20% с переменой знака, на стоковых порогах качества 38.7 Гц даёт 0/88 зачётных сегментов (ответ «None»). Это дало и решение (пороги в конфиг, ступени длиннее), и честную оценку остаточного риска — ДО первого грамма пластика.

Оговорка метода: прореживание не идентично реальному медленному датчику (bin-усреднение давит шум сильнее точечного опроса, а геометрия движения запечена в записи) — результат это нижняя оценка проблем, не гарантия.

3. Регрессия при правках чужого кода

Каждая моя правка анализа (пороги → параметры, новые дефолты) сопровождалась прогоном эталонов на стоковых значениях: ответ обязан не измениться (0.0328/0.0335 до и после). Это заменяет тесты, которых у чужого кода нет.

4. Replay живых записей

Прод сохраняет сырьё каждого прогона → любой живой результат воспроизводим на компе: мой replay четырёх живых записей дал 0.0328/0.0311/0.0407/0.0331 — байт-в-байт с числами, которые печатал принтер. Дальше подстройка весов/порогов крутится офлайн на уже собранных данных, а не новыми прогонами (у меня прогон = 3 минуты + 1 г пластика + занятый принтер).

Требования к реализации

  • Анализ должен быть импортируем без прод-окружения (чистые функции от массивов; у меня — numpy-модуль без klippy-зависимостей).
  • Записи — самоописываемые (сырьё + мета с параметрами прогона в одном файле; у меня .npz с JSON-строками меты).
  • Стенд поддерживает обе схемы меты, если формат эволюционировал (моя первая версия падала KeyError на новой схеме — чинить чтением вызывающего кода, не догадкой).
  • Изменяемые параметры анализа стенд принимает флагами (--downsample-hz, --min-rate, --min-samples) и честно предупреждает, если целевой код параметр ещё не поддерживает.

Родственное: Мутационная проверка тестов — та же идея «проверь проверяющего» для тестов.