
Я сделал Telegram-бота, который по короткому фрагменту детского плача (в MVP беру 5 секунд) пытается угадать причину из 6 классов и быстро вернуть ответ. Это не диагностика: бот может ошибаться, а шум и контекст ломают предсказание сильнее, чем хочется.
Что именно предсказываем
Сейчас бот возвращает один наиболее вероятный класс:
Боль в животике
Колики
Дискомфорт
Усталость
Голод
Страх
Честная оговорка: в реальности причины могут накладываться, а некоторые классы пересекаются по смыслу (например, «колики» vs «боль в животике»), поэтому это классификация по разметке, а не “понимание ребёнка”.
Архитектура без воркера: очередь в Postgres + cron
Я не держу постоянно работающий воркер. Задачи на обработку копятся в Postgres‑очереди, а по расписанию их разгребает cron прямо в базе: достаёт пачку задач, отправляет их в Flask-инференс, сохраняет результат и помечает задачу выполненной.
Telegram -> бот | кладём задачу {voice_url, request_id, ...} v Supabase Postgres - очередь pgmq: cry_jobs - таблицы: requests / predictions / errors ^ | pg_cron по расписанию: read batch -> HTTP -> write result | Flask inference API - скачать голосовое - препроцессинг (5s, mel) - CNN predict - ответ + запись результата
Supabase Cron работает на расширении pg_cron: расписание хранится в cron.job, а история прогонов — в cron.job_run_details. Очередь — это pgmq: чтение сообщений задаёт visibility timeout (пока задача “в работе” она невидима), а после успеха сообщение нужно удалить (pgmq.delete) или архивировать (pgmq.archive), иначе оно вернётся в очередь после истечения окна.
Как я разгребаю очередь (вот что реально важно)
Суть тут не в “красивом SQL”, а в трёх правилах, без которых ты быстро утонешь:
Пачка: cron‑задача читает ограниченное число сообщений через pgmq.read(queue, vt, qty) и обрабатывает их за один запуск, чтобы не упираться в таймауты и не выстрелить себе в ногу.
Повторы: если инференс/сеть упали и вы не сделали delete/archive, сообщение снова станет видимым после vt — значит, дубликаты будут.
Идемпотентность: результат пишется по уникальному request_id (UPSERT/“записать один раз”), иначе повторная доставка начнёт плодить мусор и ломать статистику.
Если в системе есть только одна вещь “как у взрослых” — пусть это будет идемпотентность.
Где Supabase, а где модель
Supabase у меня — это Postgres и два расширения вокруг фоновых задач: очередь (pgmq) и планировщик (pg_cron). Модель живёт отдельно в Flask: сервис принимает ссылку на голосовое, скачивает её, делает препроцессинг и отдаёт вероятности по 6 классам, а я сохраняю результат в базу и отвечаю пользователю.
ML: baseline mel + CNN (почему так)
Я сделал максимально прямолинейный baseline, чтобы быстро выйти в “работает end‑to‑end”:
Аудио привожу к SAMPLE_RATE = 22050, беру фиксированное окно DURATION = 5 секунд (короткое дополняю нулями).
Строю mel‑спектрограмму (N_MELS=128), перевожу в dB через librosa.power_to_db(..., ref=np.max), и привожу временную ось к MAX_TIME=200.
Дальше CNN: три блока Conv+Pool (32/64/128) → Dense(128) + Dropout(0.5) → Softmax.
5 секунд я выбрал как компромисс между UX и стабильностью входа: проще стандартизировать форму тензора и быстрее отвечать пользователю.
Данные и ответственность
Сейчас я не храню аудио пользователей: файл нужен только на время обработки и ответа. Дальше я хочу улучшать модель, но сбор аудио для дообучения возможен только как отдельный opt‑in с понятными сроками хранения и удалением по запросу — иначе доверия не будет.
И ещё: если у ребёнка есть тревожные симптомы (температура, вялость, отказ от еды и т.п.), бот не должен быть точкой принятия решения — это надо проговаривать в тексте прямо, иначе вы выглядите безответственно.
Где это ломается (и что буду чинить первым)
Шум/эхо и агрессивная обработка микрофоном телефона меняют спектрограмму сильнее, чем кажется.
В реальности “причина” может быть не одна, но модель всегда выбирает один класс.
Пересечение классов делает ошибки неизбежными — поэтому нужен режим “не уверен” и предложение перезаписать в тишине.
Вопрос к читателям (выберите A/B/C)
Мне нужен совет по двум решениям — выберите вариант в комментариях:
Дообучение и данные:
A) Только opt‑in + пользователь выбирает “что было на самом деле” (дороже по UX, но лучшая разметка).
B) Только opt‑in + кнопки “угадал/не угадал” (хуже разметка, выше конверсия).
C) Не собирать аудио вообще, улучшать только препроцессинг и правила отказа.
Что первым даст прирост качества:
A) VAD/проверка качества входа (шум, клиппинг, доля плача) + “не уверен”.
B) Аугментации под шум/микрофоны.
C) Менять модель (CRNN/Transformer) раньше, чем чинить данные.
Если вы делали аудио‑ML в проде: где я гарантированно наступлю на грабли?
Если хотите смотреть, как это всё развивается (и где оно снова развалится), я веду Telegram‑канал про разработку/продукт и этот проект: t.me/debug_leg. Туда же кидаю мелкие апдейты
Комментарии (2)

fronik
05.02.2026 08:56Почти каждое предложение пестрит фразами нейронки. Текст статьи целиком был сгенерирован?
outlingo
Вы профессионально деформированный (чтобы не обидеть просточеловеческим "больной на голову")? Там же надо смотреть на поведение ребенка, оно не менее (а может и более) важно чем плач - это я вам как родитель с двойным стажем ответственно заявляю.