From 9a54afba60da49de24a6c6208ae7c60084d7f9b8 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Tue, 11 Aug 2026 10:28:56 +0300 Subject: [PATCH] =?UTF-8?q?tasks:=20=D0=B7=D0=B0=D0=B2=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=82=D1=80=D0=B8=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B8=20=D0=BF=D0=BE=D0=B4=20=D0=BD=D0=B5=D0=B7=D0=B0?= =?UTF-8?q?=D0=BA=D1=80=D1=8B=D1=82=D1=8B=D0=B5=20=D0=BF=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=82=D1=8B=20=D1=86=D0=B5=D0=BB=D0=B8=20=D0=BE=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BB=D0=B3=D0=B8=D1=85=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8F?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - intake-limits-measure меряет четыре звена, которых не берёт speechkit-limits: приём из Telegram и по HTTP, конвертацию и заливку в Object Storage; - reject-oversized-recording отклоняет запись сверх потолка на приёме, long-text-delivery отдаёт текст в сотни килобайт файлом вместо сотни сообщений Telegram; - все шесть пунктов «Завершения» цели теперь закрыты задачами. --- tasks/BACKLOG.md | 3 ++ tasks/items/intake-limits-measure.md | 32 ++++++++++++++++++ tasks/items/long-text-delivery.md | 41 +++++++++++++++++++++++ tasks/items/reject-oversized-recording.md | 41 +++++++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 tasks/items/intake-limits-measure.md create mode 100644 tasks/items/long-text-delivery.md create mode 100644 tasks/items/reject-oversized-recording.md diff --git a/tasks/BACKLOG.md b/tasks/BACKLOG.md index 9bcf28c..0bc2205 100644 --- a/tasks/BACKLOG.md +++ b/tasks/BACKLOG.md @@ -47,7 +47,10 @@ - [✨ Считать объём, минуты и расход по каждому пользователю](items/usage-accounting.md) — Ни объём, ни длительность, ни обращения к платным сервисам никуда не записываются: восстановить расход задним числом не из чего. - [✨ Сделать страницу статистики для владельца](items/admin-stats-screen.md) — Собранный учёт читается только запросом к базе руками: ни страницы, ни признака владельца в приложении нет. - [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается. +- [🔬 Потолки приёма, конвертации и заливки по длине записи](items/intake-limits-measure.md) — Из пяти звеньев задача speechkit-limits замерила только модель распознавания: где отваливается шестичасовая запись до неё, неизвестно. +- [✨ Отклонять на приёме запись сверх потолка](items/reject-oversized-recording.md) — Запись сверх потолка принимается молча и висит в конвейере до истечения часового захвата, а человек всё это время ждёт текста. - [✨ Резать длинную запись на фрагменты и продолжать с места остановки](items/long-audio-chunking.md) — Шаг конвейера повторяется целиком: перезапуск на пятом часу шестичасовой записи начинает распознавание заново и оплачивает его второй раз. +- [✨ Отдавать текст в сотни килобайт файлом, а не сотней сообщений](items/long-text-delivery.md) — Отправитель Telegram режет текст по 4000 знаков: расшифровка шестичасовой записи придёт сотней сообщений подряд. - [🔬 Загрузка большого файла частями](items/chunked-upload-choice.md) — Гигабайтный файл едет одним запросом, и обрыв на девяноста процентах начинает его заново. - [🧹 Сравнивать доменные ошибки через errors.As](items/errors-as-instead-of-typecast.md) — NoopJobError и JobNotFoundError проверяются приведением типа: первая же обёртка %w между слоями сломает проверку молча. - [🧹 Покрыть тестами разбор вывода ffprobe](items/metaviewer-adapter-tests.md) — Проверки приёма перестали звать настоящий ffprobe 2026-08-11, а своего теста у адаптера метаданных нет: разбор JSON и отличие «программы нет в PATH» от «обработка отказала» не проверяет ничто. diff --git a/tasks/items/intake-limits-measure.md b/tasks/items/intake-limits-measure.md new file mode 100644 index 0000000..4fe5229 --- /dev/null +++ b/tasks/items/intake-limits-measure.md @@ -0,0 +1,32 @@ +# 🔬 Потолки приёма, конвертации и заливки по длине записи + +- **Тип:** research +- **Категория:** Очередь +- **Зачем:** Из пяти звеньев задача speechkit-limits замерила только модель распознавания: где отваливается шестичасовая запись до неё, неизвестно. +- **Теги:** goal:long-recordings + +Пункт 1 «Завершения» цели требует замера пяти звеньев, а разведка +`speechkit-limits` меряет одно — модель `deferred-general`. Остальные четыре +дешевле: они не требуют боевых ключей и считаются локально, кроме заливки. + +Числа нужны раньше кода: они назначают потолок, который проверяет приём, и длину +фрагмента, на которые режет `long-audio-chunking`. + +## Вопрос + +На какой длине и на каком весе записи отказывает каждое звено до распознавания — +приём из Telegram, приём по HTTP вместе с обратным прокси, конвертация `ffmpeg` +и заливка в Object Storage, — и сколько времени и места на диске занимает +шестичасовая запись на каждом из них. + +## Куда ляжет ответ + +`docs/research/intake-limits.md` — числами и с командой замера по каждому звену. +Оттуда их берут задачи `reject-oversized-recording` и `long-audio-chunking`. + +## Рамки + +Заливка в Object Storage оплачивается по факту — число прогонов назначает +человек, пробные файлы после замера удаляются. Прочие звенья меряются локально, +на файлах, созданных `ffmpeg`, а не на записях пользователей. Потолок самой +модели меряет `speechkit-limits`, здесь он не трогается. diff --git a/tasks/items/long-text-delivery.md b/tasks/items/long-text-delivery.md new file mode 100644 index 0000000..824670d --- /dev/null +++ b/tasks/items/long-text-delivery.md @@ -0,0 +1,41 @@ +# ✨ Отдавать текст в сотни килобайт файлом, а не сотней сообщений + +- **Тип:** feature +- **Категория:** Очередь +- **Зачем:** Отправитель Telegram режет текст по 4000 знаков: расшифровка шестичасовой записи придёт сотней сообщений подряд. +- **Теги:** goal:long-recordings + +Двигает пункт 4 «Завершения» цели: текст в несколько сотен килобайт доходит и в +Telegram, и в браузере. + +Деление по словам (`internal/adapter/telegram/split.go`) остаётся для обычной +расшифровки; сверх названного числа частей вместо потока сообщений уходит один +файл. + +## Затрагивает + +- `internal/adapter/telegram` — отправка документа помимо текстовых сообщений; +- порог, за которым текст уходит файлом, и его место в конфигурации; +- формат отправляемого файла и его имя, которое не раскрывает имени, данного + пользователем; +- экран чтения записи: показ текста в сотни килобайт и его выгрузка файлом; +- `docs/database.md` — порог числом. + +## Критерии приёмки + +- Текст, разбиваемый более чем на названное число частей, уходит в Telegram одним + файлом, а не потоком сообщений. Оракул — тест отправителя на тексте в 300 КиБ: + вызов отправки документа один, вызовов отправки сообщения нет. +- Обычная расшифровка по-прежнему приходит сообщениями, разбитыми по словам. + Оракул — существующий тест деления плюс тест на тексте в две части. +- Имя отправляемого файла не содержит имени, данного пользователем, и текста + записи. Оракул — тест: имя собрано из идентификатора задачи. +- Экран чтения открывает текст в сотни килобайт целиком и даёт выгрузить его + файлом. Оракул — тест экрана на подставном API с текстом в 300 КиБ. + +## Рамки + +Порог считается в частях, а не в байтах: он производен от предела сообщения +Telegram, который задаёт не наш сервис. Формат файла — простой текст, разметки +не добавляем. Инвариант приватности: имя файла пользователя в журнал и в имя +отправляемого документа не попадает. diff --git a/tasks/items/reject-oversized-recording.md b/tasks/items/reject-oversized-recording.md new file mode 100644 index 0000000..3187a8d --- /dev/null +++ b/tasks/items/reject-oversized-recording.md @@ -0,0 +1,41 @@ +# ✨ Отклонять на приёме запись сверх потолка + +- **Тип:** feature +- **Категория:** Очередь +- **Зачем:** Запись сверх потолка принимается молча и висит в конвейере до истечения часового захвата, а человек всё это время ждёт текста. +- **Теги:** goal:long-recordings + +Двигает пункт 2 «Завершения» цели: запись длиннее потолка отклоняется на приёме +понятным текстом, а не отказом через час. + +Потолок задаётся конфигурацией, а не зашивается в код: числа приходят из +разведок `intake-limits-measure` и `speechkit-limits`, и меняются они без сборки. + +## Затрагивает + +- `TranscribeService.createTranscribeJob` — проверка длительности и веса до + записи файла на диск; +- контракт `POST /api/audio`: код и тело ответа на запись сверх потолка; +- ответ бота на слишком длинную запись; +- секция конфигурации: потолок длительности и потолок веса; +- `docs/database.md` — настройки с числовым значением. + +## Критерии приёмки + +- Запись длиннее потолка отклоняется на приёме: задача не заводится, файл на + диске не остаётся. Оракул — тест приёма на записи сверх потолка: задач ноль, + каталог хранения пуст. +- Отправитель видит человекочитаемый текст с названным пределом, а не код + ошибки. Оракул — тест: в теле ответа предел числом, текста внутренней ошибки + нет. +- Запись в пределах потолка принимается по-прежнему. Оракул — существующие + тесты приёма по HTTP. +- Потолок меняется конфигурацией без пересборки. Оракул — тест разбора + конфигурации с двумя разными значениями. + +## Рамки + +Берётся после `intake-limits-measure`: до неё потолок брать неоткуда. Длительность +известна из `ffprobe` — значит, проверка идёт после чтения метаданных, но до +записи в базу. Предел веса на стороне сервера уже есть +(`router.MaxMultipartMemory`), и он не отменяется, а дополняется.