Получен формальный документ заказчика (Департамент по делам ГО, ЧС и ПБ
города Москвы, ГБУ «Система 112»), двадцать страниц. То, по чему строилась
вся документация, было коротким описанием с лендинга — примерно десятая
часть требований.
Текст ТЗ положен в docs/spec/TZ-FULL.md под версию, соответствие
реализации разобрано в docs/spec/GAP.md по коду, а не по памяти.
Закрыто уже многое, включая главное требование — работу без доступа
к внешним сетям. Но появились требования, которых в коротком описании
не было: аутентификация и роли с аудитом, администратор системы, VoIP
через локальный SIP, двадцать одновременных сессий, второй режим
занятия с потоком карточек и вводом текста, проверка грамматики,
настраиваемые преподавателем тайминги, экспорт отчётов.
Два расхождения требуют решения людей, а не разработчика: делать ли SIP
(в стеке записано обратное) и сколько одновременных голосовых занятий
должен держать стенд — двадцать это другой класс железа.
Что можно сказать, решает слот-автомат, а не модель: в промпт попадают
только раскрытые факты. Иначе LLM услужливо назовёт адрес, которого не
спрашивали. Промпты лежат файлами — их правит тот, кто ведёт занятия.
Отказ провайдера не роняет занятие: звонящий откатывается на заготовки.
Молчащий звонящий хуже шаблонной фразы.
Офлайн — таблица, а не локальная модель. Ветвление уже делает слот-автомат,
поэтому таблица индексируется парой «событие × настроение» и выходит
небольшой: для пожарного сценария 62 реплики, около 36 секунд генерации.
Формулировки в ней от облачной модели, а задержка на занятии нулевая —
локальная 3–4B дала бы формулировки хуже и за 2.5 секунды.
Найдено на бесплатной модели OpenRouter: рассуждающие модели тратят
сотни токенов на размышление и при малом бюджете возвращают пустой
content с finish_reason=length. Пустой ответ теперь честный отказ,
бюджет токенов поднят, рассуждение из ответа исключается.
Живые запросы к LLM вынесены из общего прогона: они требуют сети,
а рассуждающая модель отвечает десятками секунд.
Контракт в коде: КИО по нормативу, 43 события WS по шести союзам направлений,
таймеры ГОСТ, таксономия E1–E6, шесть компетенций радара. make types гоняет
их через JSON Schema в generated.ts, тест ловит забытую регенерацию.
Стенд: docker compose postgres + backend, health отвечает, база досягаема
из контейнера бэкенда. Makefile не врёт — нереализованные цели падают
и называют карточку, которая их закроет.
frontend/.npmrc: под WSL2 npm install зависает на дефолтных 15 сокетах.