О проекте
Команда: First time
Кейс HackAlem.ai: Инновации — Самрук-Казына
Стадия: Доведённый продукт
Задача кейса: «Хаттама»: протокол и поручения по аудиозаписи совещания (каз./рус./смешанная речь)
Суть
Система, которая по аудио/видео совещания строит стенограмму с разделением говорящих, извлекает поручения (кто/что/срок) с проверенными цитатами и выгружает протокол в DOCX.
Как устроено
ffmpeg → WAV 16 кГц → локальный Vosk (vosk-model-ru и vosk-model-kz, проба языка, для шала-казахского слияние двух моделей на уровне слов) → диаризация pyannote + titanet через sherpa-onnx → разбор стенограммы Claude или локальной Ollama qwen2.5 в JSON (what/who/due/priority/quote/confidence) → код проверяет дословные цитаты, имена и превращает сроки словами в даты со статусами «просрочено/горит». Провайдеры STT и анализа спрятаны за единый контракт: три контура (полностью локальный, смешанный, облачный с Soniox).
Ключевые решения
- Vosk STT
- слияние ru/kz моделей на уровне слов
- pyannote
- titanet
- sherpa-onnx
- диаризация
- проверка цитат кодом
- восстановление имён говорящих из диалога
- парсинг русских формулировок сроков
- DOCX export
Метрики
WER 4.49 на корпусе KSC для vosk-model-kz; эталонный демо-сценарий: 3 решения и 4 поручения
Особенности
Принцип «отказ вместо выдумки»: не названные ответственный/срок остаются пустыми с низкой уверенностью; цитату ищет код, а не модель; клик по поручению подсвечивает момент в записи. Один код с тремя контурами развёртывания (закрытый/смешанный/облачный) переключается двумя строками .env.
Стек
Python · http.server · ffmpeg (imageio-ffmpeg) · HTML · sherpa-onnx
Модели
Claude claude-opus-5 · Ollama qwen2.5 · Vosk vosk-model-ru · Vosk vosk-model-kz · Soniox
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-557bd04d-first-time.
README
README.mdПротокол совещаний с фиксацией поручений
HackAlem AI 2026 · трек 08 «Инновации» · команда First time
Совещание закончилось, все разошлись — и через неделю выясняется, что задачу никто не сделал, потому что каждый запомнил договорённость по-своему. Система слушает запись и достаёт из неё поручения: кто, что и к какому сроку.
Вот что происходит на самом деле. Из записи:
[00:54] Марат Отлично. Тимур, по выгрузке — посмотри, пожалуйста, что там
с таймаутом, и если это на стороне банка, напиши им в поддержку.
Успеешь до среды?
[01:08] Тимур До среды успею.
Получается:
Разобраться с таймаутом выгрузки отчётов Тимур · срок: среда ·
00:54· ✓ цитата проверена · уверенность 93%
А через минуту в том же разговоре:
[01:20] Айдана Кстати, надо бы ещё посмотреть логи за прошлую неделю,
там было несколько странных ошибок ночью.
Получается:
Посмотреть логи за прошлую неделю ответственный не назначен · срок не назван ·
01:20· уверенность 45%
Второе поручение — то, ради чего всё и построено. Модель вполне могла бы уверенно вписать сюда Айдану и «до пятницы»: звучит правдоподобно, проверять никто не станет. Она этого не делает. Имени не прозвучало — значит поля пустые.
Так же и с цитатами: каждое поручение привязано к дословному фрагменту стенограммы, и фрагмент ищет код, а не модель. Не нашёл — поручение помечено неподтверждённым. Клик по карточке подсвечивает момент в записи, откуда оно взято. Протокол, в котором можно ткнуть в любую строку и услышать, откуда она, — это совсем другой уровень доверия, чем красивый пересказ.
Дальше: сводка, решения, статусы сроков («просрочено», «горит»), выгрузка в DOCX. Распознавание речи, разделение говорящих и, при желании, разбор текста работают полностью на машине — аудио не уходит никуда.
Быстрый старт
Нужен Python 3.8 или новее.
pip install -r requirements.txt
python scripts/get_models.py
ANALYSIS_PROVIDER=demo python app.py
Откройте http://localhost:8000 и нажмите «Разобрать пример».
Проверка основного сценария за 2 минуты
Ключи не нужны. ANALYSIS_PROVIDER=demo фиксирует эталонный режим, чтобы результат
совпал в точности: без него проект возьмёт то, что доступно на машине.
Ожидаемый результат — протокол планёрки, 3 решения и 4 поручения:
| Что | Кто | Срок | Уверенность |
|---|---|---|---|
| Макет экрана оплаты | Айдана | четверг | 95% |
| Таймаут выгрузки отчётов | Тимур | среда | 93% |
| Список клиентов для пилота | Марат | пятница | 90% |
| Посмотреть логи | не назначен | не назван | 45% |
Дальше:
- Нажмите на карточку поручения — в стенограмме справа подсветится момент, из которого оно извлечено.
- Нажмите «Подтвердить» — статус сохранится в
storage/meetings/<id>.json. - Нажмите «Скачать DOCX» — протокол выгрузится файлом.
Четвёртое поручение — главное в этой проверке. В записи прозвучало «надо бы посмотреть логи», ответственный и срок названы не были. Система их не выдумывает, а честно помечает низкой уверенностью.
Своя запись. Кнопка «Загрузить запись…», любой аудио- или видеофайл.
Для неё нужны локальные модели (python scripts/get_models.py) — без них система
скажет об этом прямо, а не подставит демо-пример. Для разбора нужен ключ, см.
раздел «Настройки».
Без браузера:
curl -s http://localhost:8000/api/health
curl -s -X POST http://localhost:8000/api/analyze -H "Content-Type: application/json" -d "{}"
Соответствие обязательному минимуму
| # | Требование задания | Как закрыто |
|---|---|---|
| 1 | Распознавание речи участников | Vosk локально, meeting/stt_local.py |
| 2 | Понимание русского языка | vosk-model-ru, проверено на записях задания |
| 3 | Понимание казахского языка | vosk-model-kz, WER 4.49 на корпусе KSC |
| 4 | Понимание шала-казахского | стенограмма собирается из двух моделей: русская даёт основу, казахские слова подставляются из казахской |
| 5 | Поручения с ответственным и сроком | meeting/pipeline.py, извлечение и проверка цитат |
| 6 | Диаризация с привязкой к человеку | pyannote + titanet локально, meeting/diarize.py |
| 7 | Экспорт протокола в файл | DOCX, meeting/export_docx.py |
Сценарий 2 — контроль сроков — закрыт в meeting/deadlines.py. Срок, названный
словами («до пятницы», «к пятнадцатому октября»), превращается в дату, а поручение
получает статус «просрочено», «срок сегодня», «горит» или «в работе». Сводка видна
в шапке совещания и попадает в DOCX. Отсчёт идёт от даты совещания, статус — от
сегодняшнего дня.
Что отличает решение
Проверяемость вместо доверия модели. Каждое поручение привязано к точной цитате и таймкоду: клик по карточке подсвечивает момент, где это прозвучало. Цитату проверяет код, а не модель — если дословный фрагмент в стенограмме не найден, поручение помечается неподтверждённым и его уверенность снижается.
Отказ вместо выдумки. Ответственный и срок, которых не было в разговоре, не додумываются: поле остаётся пустым. То же с именами говорящих и с формулировками сроков, которые не удалось разобрать.
Человек принимает решение. Каждое поручение подтверждается или отклоняется.
Кто есть кто. Распознавание различает голоса, но имён не знает. Имена восстанавливаются из самого разговора: если к человеку обратились «Тимур, сможешь…», отвечающий следующим — Тимур. Имя принимается, только если оно действительно прозвучало в записи. Того, чьё имя не назвали ни разу, система показывает меткой «Спикер 1» и даёт назвать его в один клик — имя сразу расходится по стенограмме и по ответственным.
Как это работает
запись (аудио или видео, любой формат)
│ ffmpeg -> WAV 16 кГц моно
▼
[ Vosk, локально ] проба языка по началу записи; на смешанной
│ речи казахские слова подставляются в русскую основу
▼
[ pyannote + titanet ] границы речи, смена говорящего, кластеризация голосов
▼ сегменты: время + говорящий + текст
[ разбор совещания ] Claude или локальная модель
│ JSON: speakers + what / who / due / priority / quote / confidence
▼
[ проверка кодом ] цитата ищется в стенограмме, таймкод берётся оттуда же;
│ имя принимается, только если звучало; срок превращается
│ в дату и статус контроля
▼
[ интерфейс ] карточки поручений ↔ стенограмма, подтверждение,
│ именование говорящих, экспорт DOCX
▼
storage/meetings/<id>.json
Технологии
| Слой | Чем сделано |
|---|---|
| Бэкенд | Python 3.8+, стандартная библиотека (http.server) |
| Распознавание речи | Vosk локально: vosk-model-ru, vosk-model-kz |
| Разделение говорящих | pyannote + titanet через sherpa-onnx, локально |
| Аудио и видео | ffmpeg из пакета imageio-ffmpeg, отдельная установка не нужна |
| Разбор совещания | Anthropic Claude claude-opus-5 |
| Разбор без облака | Ollama (qwen2.5), включается ANALYSIS_PROVIDER=local |
| Сроки и контроль | разбор русских формулировок даты, meeting/deadlines.py |
| Экспорт | DOCX собирается стандартной библиотекой |
| Интерфейс | один HTML-файл без сборки и фреймворков |
Структура
app.py HTTP-сервер и маршруты
meeting/stt_local.py локальное распознавание, проба языка, слияние моделей
meeting/diarize.py разделение на говорящих
meeting/pipeline.py выбор провайдеров, извлечение поручений, проверка цитат
meeting/deadlines.py сроки словами -> даты и статусы контроля
meeting/analyze_local.py разбор локальной моделью через Ollama
meeting/export_docx.py сборка DOCX
meeting/store.py хранилище совещаний
web/index.html интерфейс
scripts/get_models.py загрузка локальных моделей
samples/ демо-стенограмма и сценарий тестовой записи
Закрытый контур
Задание запрещает передавать аудио во внешние облачные API и требует возможности развернуть решение у заказчика.
- Распознавание речи и разделение говорящих работают полностью локально.
Модели Vosk (Apache 2.0) лежат в
models/, аудио не покидает машину; интернет для этой части не нужен вообще. - Разбор текста тоже может остаться на машине:
ANALYSIS_PROVIDER=localвключает локальную модель через Ollama. Качество ниже — замеры приведены ниже, мы их не прячем. - Загруженный файл живёт во временном файле и удаляется сразу после разбора.
Результаты лежат в
storage/и не версионируются. Ключи — только в.env.
Один код — три контура
Локальный и облачный режимы — это не две версии продукта и не ветки в репозитории.
Это один и тот же код с разной настройкой, и переключение занимает столько,
сколько нужно на правку двух строк в .env.
| Контур | STT_PROVIDER | ANALYSIS_PROVIDER | Что это значит для заказчика |
|---|---|---|---|
| Закрытый | local | local | Наружу не уходит ничего. Нужно железо и 6 ГБ моделей |
| Смешанный | local | claude | Аудио не покидает периметр, наружу идёт только текст |
| Облачный | soniox | claude | Максимум качества, железо не нужно |
Средняя строка — самая практичная. Голос — это биометрия, и запрет обычно касается именно аудиозаписи; обезличенный текст многие организации выпускать наружу готовы. Такой контур даёт качество разбора, близкое к облачному, не вынося записи совещаний за периметр.
Работает это потому, что провайдеры спрятаны за один контракт: распознавание — это «дай файл, получи сегменты с временем, говорящим и текстом», разбор — «дай стенограмму, получи JSON с поручениями». Всё, что выше по конвейеру — проверка цитат, восстановление имён, сроки, экспорт, интерфейс — не знает, кто именно считал, и не меняется при переключении. Новый провайдер — это один модуль на пару десятков строк.
Практический смысл:
- Продавать можно и туда, и туда. Госорган и банк получают локальную сборку, небольшой бизнес — облачную, без отдельной разработки под каждого.
- Пилот не упирается в закупку железа. Начинают на облаке, переезжают в закрытый контур, когда служба безопасности согласует, — переезд ничего не переписывает.
- Поставщик заменяем. Кончились кредиты, изменились цены, появилась модель получше — меняется модуль, а не продукт.
- Провал одного пути не роняет систему. Недоступен провайдер — режимы честно показываются в интерфейсе, а демо-режим работает всегда.
Настройки
Скопируйте .env.example в .env. Всё необязательно: без настроек проект работает
локально, а без моделей — в демо-режиме.
| Переменная | Назначение |
|---|---|
ANALYSIS_PROVIDER | demo, local (Ollama) или claude. Без неё: есть ключ Anthropic — облако, иначе локальная модель, иначе демо |
ANTHROPIC_API_KEY | ключ для облачного разбора |
STT_PROVIDER | local (по умолчанию) или soniox |
SONIOX_API_KEY | нужен только при STT_PROVIDER=soniox |
STT_MIXED | 0 отключает разбор двумя моделями, ускоряет примерно вдвое |
STT_LANGUAGE | зафиксировать язык (ru/kz) и пропустить пробу |
DIARIZATION_THRESHOLD | порог склейки голосов, по умолчанию 0.70 |
DIARIZATION_SPEAKERS | сколько участников ожидать; 0 — определить само |
LOCAL_ANALYSIS_MODEL | модель Ollama, по умолчанию qwen2.5:3b-instruct |
PORT | порт сервера, по умолчанию 8000 |
Модели
python scripts/get_models.py скачивает компактный набор (~180 МБ) и модели
разделения говорящих. Качество распознавания напрямую зависит от размера модели:
компактной хватает на чистой записи, на телефонной записи в шумном помещении — нет.
python scripts/get_models.py --full
Добавляет большие модели (~3 ГБ). Скрипт сам предпочитает большую, если она есть.
API
| Метод | Путь | Назначение |
|---|---|---|
GET | /api/health | статус и текущие режимы работы |
POST | /api/analyze | {} — демо-пример; {filename, audio_b64, title} — своя запись |
GET | /api/meetings | список разобранных совещаний |
GET | /api/meetings/<id> | одно совещание целиком |
GET | /api/meetings/<id>/export.docx | протокол файлом DOCX |
POST | /api/meetings/<id>/items/<item_id> | {"status": "confirmed"|"rejected"|"pending"} |
POST | /api/meetings/<id>/speakers | {"from": "Спикер 1", "to": "Марат"} |
У поручения в ответе есть due_date и deadline (код статуса, подпись, дней до
срока), у совещания — deadline_summary.
Замеры
Все цифры получены на записях, приложенных к заданию, и на собственной тестовой записи команды: телефон, шумное помещение, три говорящих.
Скорость
Двухминутная запись, модели в памяти:
| время | |
|---|---|
| Первый разбор после старта сервера | 197 с |
| Каждый следующий | 67 с |
Разница — загрузка трёхгигабайтной модели с диска. Поэтому модели греются в фоне при старте, а язык определяется по первым 45 секундам лёгкими моделями.
Размер модели
| Модель | Уверенность | Распознано говорящих |
|---|---|---|
vosk-model-small-ru-0.22, 88 МБ | 0.860 | 1 |
vosk-model-ru-0.42, 3 ГБ | 0.924 | 4 |
На чистой записи из задания компактная модель даёт 0.913 — разница проявляется именно на сложном звуке.
Разделение говорящих
Сначала мы брали вектор голоса, который Vosk отдаёт на целую реплику. Это не работает: реплика кончается на длинной паузе, а в живом совещании внутри неё успевают высказаться двое — вектор усредняется, и все сливаются в одного.
| вектор голоса Vosk | pyannote + titanet | |
|---|---|---|
| Распознано участников (их трое) | 1 | 3 |
| Реплик в стенограмме | 7 | 25 |
Смешанная речь
Соединять результаты двух моделей по уверенности нельзя: Vosk уверенно ошибается на чужом языке. Фраза «жарайды келісемін» превратилась у русской модели в «жара и до семин» — с уверенностью 0.896, выше, чем у казахской на том же куске.
Признак надёжнее: в казахском есть буквы (ә, ғ, қ, ң, ө, ұ, ү, һ, і), которых в русском алфавите нет, и русская модель выдать их физически не может. Слово с такими буквами — якорь казахской речи. Слияние идёт на уровне слов: русская стенограмма остаётся основой, на месте якорей встают слова казахской модели, если они перекрывают русское слово по времени больше чем наполовину и распознаны увереннее.
| стенограмма | |
|---|---|
| Только русская модель | «…до следующего цикла жара и до семин тогда…» |
| После слияния | «…до следующего цикла жара и до келісемін тогда…» |
Заменено одно слово; «сейчас», которое казахская модель услышала как «сияқты», осталось на месте — проверка уверенности отсекла подмену.
Разбор: локально против облака
Эталон — размеченная вручную планёрка, 4 поручения с известными ответственными:
| qwen2.5:3b локально | claude-opus-5 | |
|---|---|---|
| Время | 12 с | 19 с |
| Найдено верно | 2 из 4 | 4 из 4 |
| Выдумано лишнего | 1 | 0 |
Вывод честный: в закрытом контуре разбор работает, но качество падает, и маленькая модель склонна додумывать ответственного — ровно то, против чего построена проверка цитат. Поэтому провайдер вынесен в настройку.
Воспроизводимость
Репозиторий клонирован в чистую папку, создано новое виртуальное окружение,
установлены зависимости из requirements.txt. Всё встало, проект запустился,
контрольный сценарий совпал с описанным выше, DOCX выгрузился и открылся.
Ограничения
Пишем прямо, чтобы не было сюрпризов на проверке.
- Качество упирается в запись. На чистых записях из задания локальные модели дают связную стенограмму и верные поручения. На телефонной записи в шумном зале распознавание заметно хуже, и это тянет за собой имена и ответственных.
- Якорем казахской речи служит только слово с казахскими буквами. Казахские слова, которые пишутся русскими буквами («жарайды»), так не ловятся. На участке, где обе модели слышат мусор, слияние может подставить одно непонятное слово вместо другого.
- Локальный разбор слабее облачного — цифры выше.
- Разбор двумя моделями примерно вдвое дольше. Отключается
STT_MIXED=0. - Подключение к Teams, Zoom и Google Meet не реализовано: принимается готовая запись.
Что дальше
- Подключение к Teams, Zoom и Meet как участник и живая расшифровка по ходу встречи: у Vosk есть потоковый режим, интерфейс провайдера к этому готов.
- Автоматическая рассылка напоминаний ответственным при приближении срока.
- Дашборд статусов поручений и интеграция с СЭД.
- Словарь терминов организации, чтобы имена и отраслевые слова распознавались точнее.
- Локальная модель побольше для разбора — закрывает контур без потери качества.
- Развёртывание в закрытом контуре «под ключ»: образ со всеми моделями, чтобы установка у заказчика не требовала интернета вообще.
Сторонние и заранее подготовленные компоненты
В соответствии с пунктами 5.4.4 и 5.4.4.2 Положения:
- Модели: Vosk
vosk-model-ru,vosk-model-kz,vosk-model-spk(Apache 2.0); pyannote segmentation и NeMo titanet в ONNX из релизов sherpa-onnx. - Библиотеки:
vosk(Apache 2.0),sherpa-onnx(Apache 2.0),imageio-ffmpeg(BSD, включает ffmpeg под LGPL),numpy(BSD),anthropic(MIT). Остальное — стандартная библиотека Python. - Внешние сервисы: Anthropic Claude для разбора текста, Soniox как опциональный провайдер распознавания. Оба по умолчанию выключены.
- Заранее подготовленное: у команды есть личный проект расшифровки аудио, из опыта которого взяты подходы к работе с распознаванием речи и к деградации на заглушки при недоступности внешнего сервиса. Доменная логика этого проекта — протокол совещания, поручения с ответственным и сроком, проверка цитат, восстановление имён, контроль сроков, экспорт — разработана в рамках соревновательной части хакатона.
- AI-инструменты: разработка велась с использованием AI-ассистентов, что разрешено пунктом 5.4.12 Положения.
Данные
Демо-запись samples/planerka.json — постановочная, составлена командой.
Персональных данных и записей реальных людей проект не содержит.
Авторы коммитов
История коммитов
- 2026-09-23 17:43.env.example приведён в соответствие с README и кодомA11ert
- 2026-09-23 17:40Сроки периодами словами: «за две недели», «на этой неделе», «через месяц»A11ert
- 2026-09-23 17:31README: раздел про переключение между локальным и облачным контуромA11ert
- 2026-09-23 17:28README: начинается с живого примера вместо описанияA11ert
- 2026-09-23 17:24Честная ошибка вместо подмены: загруженная запись без моделейA11ert
- 2026-09-23 17:20README переписан: быстрый старт в начале, замеры собраны в один разделA11ert
- 2026-09-23 17:18Слияние моделей включено по умолчанию, порог разрыва убранA11ert
- 2026-09-23 17:07Шала-казахский: слияние двух моделей на уровне словA11ert
Все коммиты (23)
- 2026-09-23 16:43Детерминированный контрольный сценарий: явный выбор провайдера разбораA11ert
- 2026-09-23 16:41Контроль сроков поручений: второй сценарий заданияA11ert
- 2026-09-23 16:29Ускорение распознавания: проба языка и прогрев моделейA11ert
- 2026-09-23 16:06Локальный разбор совещания через Ollama как провайдерA11ert
- 2026-09-23 15:52Список прошлых разборов в интерфейсе и приём видеофайловA11ert
- 2026-09-23 15:45Отдельная модель диаризации вместо векторов VoskA11ert
- 2026-09-23 15:30README: замеры качества распознавания на компактной и большой моделяхA11ert
- 2026-09-23 15:26Чистка стенограммы: фильтр нераспознанных слов и разбиение по паузамA11ert
- 2026-09-23 15:18Экспорт протокола в DOCX и загрузчик локальных моделейA11ert
- 2026-09-23 15:14Локальное распознавание речи: Vosk + локальная диаризацияA11ert
- 2026-09-23 14:44Восстановление имён говорящих и ручное именованиеA11ert
- 2026-09-23 14:14Сценарий тестовой записи: восемь проверочных ситуаций и ожидаемый результатA11ert
- 2026-09-23 13:56Конвейер: стенограмма, извлечение поручений, проверка цитат по текстуA11ert
- 2026-09-23 13:56Веб-интерфейс: карточки поручений, связь со стенограммой, подтверждениеA11ert
- 2026-09-23 13:56Каркас проекта: README, зависимости, шаблон окруженияA11ert
Структура репозитория (19 файлов)
.claude/launch.json .env.example .gitignore README.md app.py meeting/__init__.py meeting/analyze_local.py meeting/deadlines.py meeting/diarize.py meeting/export_docx.py meeting/pipeline.py meeting/store.py meeting/stt_local.py requirements.txt samples/planerka.analysis.json samples/planerka.json samples/scenario.md scripts/get_models.py web/index.html
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.