tasks: упразднены цели и роадмап, объявлена стадия build

- 11 записей типа goal закрыты с причиной, называющей задачи-наследники;
  ROADMAP.md удалён, индекс остался один — BACKLOG.md;
- 33 записи переписаны: ссылка «Двигает пункты N «Завершения» цели» уступила
  место прямому утверждению — без целей номера пунктов вели в никуда;
- шапка BACKLOG.md размечена парой <!-- стадия -->, порядок строк теперь
  объявлен зависимостью, а не важностью.
This commit is contained in:
av
2026-08-13 16:00:14 +03:00
parent 00148bcfb5
commit 5501384cdc
50 changed files with 118 additions and 485 deletions
+2 -1
View File
@@ -1,7 +1,7 @@
# Раскладка av-dev в этом проекте: версия и настройки проверок.
# Файл ведут скиллы плагина, править руками можно — комментарии свои.
version = 1 # версия раскладки; обратной совместимости нет, есть «приведён» и «нет»
version = 3 # версия раскладки; обратной совместимости нет, есть «приведён» и «нет»
[docs]
# каталог миграций: по нему docs.py сверяет схему с database.md
@@ -10,3 +10,4 @@ migrations = "internal/adapter/repo/pocketbase/migrations"
[tasks]
# каталог задач от корня репозитория; имена частей — умолчания скрипта
dir = "tasks"
stage = "build"
+20 -16
View File
@@ -1,23 +1,27 @@
# Беклог
Что **можно взять**. Одна задача = один файл `items/<slug>.md`
+ строка здесь. Целей тут нет — они в [ROADMAP.md](ROADMAP.md): беклог — то, что берут,
роадмап — то, подо что берут. **Порядок строк значим:**
это очередь, и первая строка — то, что делают следующим. Порядок
назначает человек на груминге, машина его не выводит. Одно исключение
производно от типа — сырьё (`research` без раздела «Вопрос»)
стоит в конце: его не берут. Ведётся скиллом `av-dev:task-track`.
+ строка здесь. Ведётся скиллом `av-dev:task-track`.
Секция одна — полок домена у проекта нет, и делить очередь на две
значило бы держать два порядка вместо одного.
<!-- стадия -->
Стадия проекта — **стройка** (`[tasks] stage = "build"`).
**Порядок строк — зависимость:** это план стройки от базы к деталям,
и строка выше сделана раньше не потому, что важнее, а потому, что
иначе нельзя. Секция здесь **одна**: разложенный по полкам план
перестаёт быть планом. Список пишется вперёд целиком — это не
гниение беклога, а замысел. Пустой беклог значит, что стройка
окончена: дальше `tasks.py stage support`.
<!-- /стадия -->
**Чем очередь упорядочена на этом этапе — от базы к деталям.**
Сначала то, на чём стоит остальное: проверки, которым можно верить,
владелец записи, единый контракт API, покрытый тестами конвейер, — и
только потом экраны и возможности поверх них. Порядок расставлен на
груминге 2026-08-12 и держится, пока сервис не собран целиком:
задача, взятая раньше своего основания, стоит дважды — сперва её
пишут, потом переписывают под появившееся основание.
**Чем основание отличается от детали в этом проекте.** Сначала идёт то,
на чём стоит остальное: проверки, которым можно верить, владелец записи,
единый контракт API, покрытый тестами конвейер, — и только потом экраны
и возможности поверх них. Этот порядок расставили 2026-08-12.
Задача, взятая раньше своего основания, стоит дважды: сперва её пишут,
потом переписывают под появившееся основание.
Одно место в очереди назначено не человеком, а типом: сырьё
(`research` без раздела «Вопрос») стоит в конце секции — его не берут.
Отсюда правило для **новых** записей. Заведённая по ходу работы —
интейком, урожаем ревью, разбором находок — задача встаёт в конец
@@ -76,7 +80,7 @@
- [✨ Считать только те уровни текста, что включены у владельца записи](items/settings-applied-in-pipeline.md) — Дом настроек есть, а конвейер их не читает: выключенный уровень всё равно уходит платной модели, и настройка ничего не экономит.
- [✨ Отправлять готовый текст через apprise и ntfy](items/ntfy-delivery.md) — Пользователь веба узнаёт о готовности только опросом с открытого экрана.
- [✨ Слать готовый текст на почту из учётной записи](items/email-notification.md) — Адрес почты приходит вместе с входом через OIDC, но почтового отправителя в сервисе нет.
- [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается.
- [🔬 Потолки SpeechKit по длине записи и по формату](items/speechkit-limits.md) — Потолок длины записи и перечень принимаемых форматов неизвестны, а работа над долгими записями без них не начинается.
- [🔬 Потолки приёма, конвертации и заливки по длине записи](items/intake-limits-measure.md) — Из пяти звеньев задача speechkit-limits замерила только модель распознавания: где отваливается шестичасовая запись до неё, неизвестно.
- [✨ Отклонять на приёме запись сверх потолка](items/reject-oversized-recording.md) — Запись сверх потолка принимается молча и висит в конвейере до истечения часового захвата, а человек всё это время ждёт текста.
- [✨ Резать длинную запись на фрагменты и продолжать с места остановки](items/long-audio-chunking.md) — Шаг конвейера повторяется целиком: перезапуск на пятом часу шестичасовой записи начинает распознавание заново и оплачивает его второй раз.
+11
View File
@@ -6,3 +6,14 @@
<!-- - ГГГГ-ММ-ДД `slug` — Заголовок. Причина: … Была секция: … -->
- 2026-08-12 `gate-go-version-sync` — 🧹 Сверять версию Go в образе с директивой go.mod. Причина: слита в go-1-26-upgrade 2026-08-12: сверка версии и само обновление правят одни и те же строки go.mod и Dockerfile, и порознь заводят расхождение заново. Была секция: Очередь.
- 2026-08-13 `any-audio-source` — 🎯 Принимается запись любого формата, включая дорожку из видео. Причина: Зонтик над разобранной работой: перечень форматов меряет audio-format-coverage-measure, дорожку из видео берёт video-audio-track-intake. Тип goal упразднён раскладкой av-dev 3. Была секция: Направления.
- 2026-08-13 `data-ownership` — 🎯 Человек убирает свою запись из архива вместе со всеми текстами. Причина: Зонтик над разобранной работой: удаление записи со всеми уровнями текста делает delete-record. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано.
- 2026-08-13 `long-recordings` — 🎯 Запись длиной до шести часов доходит до текста. Причина: Зонтик над разобранной работой: потолки меряют speechkit-limits и intake-limits-measure, дальше идут reject-oversized-recording, long-audio-chunking и long-text-delivery. Тип goal упразднён раскладкой av-dev 3. Была секция: Направления.
- 2026-08-13 `multi-user` — 🎯 Сервисом пользуются несколько человек, и записи одного не видны другому. Причина: Зонтик над разобранной работой: вход сделан задачей oidc-login, дальше идут record-ownership, telegram-account-link и api-tokens. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано.
- 2026-08-13 `ready-notification` — 🎯 Пользователь узнаёт о готовности текста, не держа приложение открытым. Причина: Зонтик над разобранной работой: доставку делают ntfy-delivery и email-notification. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано.
- 2026-08-13 `service-observability` — 🎯 Состояние сервиса видно без чтения логов. Причина: Зонтик над разобранной работой: словарь метрик выбирает opentelemetry-fit, дальше идут stalled-pipeline-metric, external-service-metrics, job-path-by-request и owner-alerting. Тип goal упразднён раскладкой av-dev 3. Была секция: Сопровождение.
- 2026-08-13 `text-insights` — 🎯 Приложение показывает, о чём запись, не читая её целиком. Причина: Зонтик над разобранной работой: уровни текста считает llm-insights-adapter, вычитку даёт literary-text-level, показывает их insights-visible-in-list. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано.
- 2026-08-13 `upload-reliability` — 🎯 Загрузка большого файла доходит до сервиса и не повторяется впустую. Причина: Зонтик над разобранной работой: дедупликацию делает dedup-by-content-hash, пачку файлов multi-file-upload, ход загрузки upload-progress, уборку за обрывом orphan-file-on-failed-intake. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано.
- 2026-08-13 `usage-stats` — 🎯 Владелец видит, кто сколько загрузил и во что это обошлось. Причина: Зонтик над разобранной работой: учёт ведёт usage-accounting, показывает его admin-stats-screen. Тип goal упразднён раскладкой av-dev 3. Была секция: Сопровождение.
- 2026-08-13 `user-settings` — 🎯 Пользователь настраивает, что сервис делает с его записями. Причина: Зонтик над разобранной работой: дом настроек заводит settings-screen, читает их в конвейере settings-applied-in-pipeline. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано.
- 2026-08-13 `web-access` — 🎯 Записи загружаются и читаются в приложении, которое ставится на телефон. Причина: Зонтик над разобранной работой: каркас даёт spa-skeleton, контракт json-api-for-spa, экраны upload-and-status-screen, records-list-screen, play-recording-in-app, установку на телефон installable-pwa. Тип goal упразднён раскладкой av-dev 3. Была секция: Запланировано.
-46
View File
@@ -1,46 +0,0 @@
# Роадмап
Состояние проекта: что приложение **уже умеет** и чего ещё не умеет.
Цель — возможность приложения: файл типа `goal` (🎯) в
`items/`. Её задачи здесь **не перечисляются** — перечень даёт
`tasks.py list --goal <слаг>`.
- **Запланировано** — очередь значима и обосновывается прозой;
- **Направления** — очереди нет, тянутся долго;
- **Сопровождение** — чем держат проект: инструмент,
процесс, эксплуатация. Не возможности приложения, и отдельно —
чтобы не читаться как обещание продукта;
- **Готово** — достигнутое: строку пишет
`tasks.py close <цель> --implemented`, ссылки на файл в ней нет —
файл удаляется, поведение живёт в спеках. Стоит последней: копится.
Секции **канонические** и переименованию проектом не подлежат:
у каждой свой смысл, и в достигнутое пишет сам `close`. Порядок
тоже канонический. Английский
вариант — Planned | Directions | Operations | Done, один язык на весь
индекс.
## Запланировано
- [🎯 Сервисом пользуются несколько человек, и записи одного не видны другому](items/multi-user.md) — У задачи нет владельца, а HTTP API открыт наружу без аутентификации: пригласить второго человека сейчас значит открыть ему чужие расшифровки.
- [🎯 Записи загружаются и читаются в приложении, которое ставится на телефон](items/web-access.md) — Сегодня записи принимает только бот и голый HTTP API без интерфейса: отдать сервис человеку, у которого нет Telegram, нечем.
- [🎯 Загрузка большого файла доходит до сервиса и не повторяется впустую](items/upload-reliability.md) — Приём рассчитан на голосовое в пару мегабайт: обрыв на середине гигабайтного файла начинает загрузку заново, а один и тот же файл распознаётся повторно за наши деньги.
- [🎯 Человек убирает свою запись из архива вместе со всеми текстами](items/data-ownership.md) — Хранение бессрочное, а способа убрать запись нет ни одного: ошибочно загруженный файл и разговор, который человек не хочет держать у нас, остаются навсегда.
- [🎯 Пользователь настраивает, что сервис делает с его записями](items/user-settings.md) — Уровни текста считает платная модель, а уведомления приходят одним общим способом: отказаться от лишнего и выбрать свой канал пользователю нечем.
- [🎯 Приложение показывает, о чём запись, не читая её целиком](items/text-insights.md) — Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем.
- [🎯 Пользователь узнаёт о готовности текста, не держа приложение открытым](items/ready-notification.md) — Расшифровка занимает минуты, и всё это время человек либо смотрит на экран с опросом статуса, либо забывает вернуться.
## Направления
- [🎯 Запись длиной до шести часов доходит до текста](items/long-recordings.md) — Потолок не замерен ни на одном звене: Telegram не отдаёт больше 20 МиБ, границы модели deferred-general неизвестны, а перезапуск на середине начинает распознавание заново.
- [🎯 Принимается запись любого формата, включая дорожку из видео](items/any-audio-source.md) — Конвертер вызывается одной командой ffmpeg, проверенной на голосовых Telegram; что он берёт помимо них, никто не мерил.
## Сопровождение
- [🎯 Состояние сервиса видно без чтения логов](items/service-observability.md) — Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера.
- [🎯 Владелец видит, кто сколько загрузил и во что это обошлось](items/usage-stats.md) — Распознавание и языковая модель оплачиваются по факту, а счёт приходит одной суммой: кто её набрал, из сервиса не выясняется.
## Готово
- 2025-08-14 `telegram-transcription` — Голосовое сообщение из Telegram возвращается текстом. Первый вход сервиса: бот принимает голосовое, аудиофайл и документ с аудио и отвечает расшифровкой.
- 2025-08-08 `api-transcription` — Запись, отданная по HTTP, возвращается текстом. Программный вход: файл отдаётся формой, готовность и текст забираются опросом статуса задачи.
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Страница показывает собранный учёт: без учёта показывать нечего.
- **Зачем:** Собранный учёт читается только запросом к базе руками: ни страницы, ни признака владельца в приложении нет.
- **Теги:** goal:usage-stats
Двигает пункты 1, 2 и 3 «Завершения» цели: расход по каждому пользователю виден
на странице, и открывается она только владельцу сервиса.
Расход по каждому пользователю виден на странице, и открывается она только
владельцу сервиса.
## Затрагивает
-20
View File
@@ -1,20 +0,0 @@
# 🎯 Принимается запись любого формата, включая дорожку из видео
- **Тип:** goal
- **Секция:** Направления — Перечень форматов не замерен, и потолок длины у видео тот же, что у долгих записей: тянется следом за ними.
- **Зачем:** Конвертер вызывается одной командой ffmpeg, проверенной на голосовых Telegram; что он берёт помимо них, никто не мерил.
- **Теги:** decomposed
Человек отдаёт файл, не думая о том, что внутри: аудио любого распространённого
контейнера или видео, из которого нужна только речь. Подготовка на стороне
пользователя не требуется.
## Завершение
1. Перечень принимаемых форматов замерен и записан в `research/`, а не выведен
из документации ffmpeg.
2. Видеофайл принимается, и из него берётся звуковая дорожка.
3. Формат, который принять нельзя, отклоняется на приёме — с текстом, из
которого понятно почему, а не отказом на конвертации через минуту.
4. Расхождение ogg/vorbis против заявленного SpeechKit `OGG_OPUS` разобрано:
либо устранено, либо записано как проверенно безвредное.
+2 -4
View File
@@ -3,11 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Второй способ представиться ставится на готовые владельца и контракт, иначе форма ошибки переписывается дважды.
- **Зачем:** Вход через OIDC закрывает API целиком, а скрипту браузерная сессия недоступна: автоматизировать загрузку станет нечем.
- **Теги:** goal:multi-user
Двигает пункты 1 и 6 «Завершения» цели: запрос без токена не проходит (пункт 1),
а скрипт ходит в API по токену, выпущенному пользователем, и видит ровно его
записи (пункт 6).
Запрос без токена не проходит, а скрипт ходит в API по токену, выпущенному
пользователем, и видит ровно его записи.
Токен принадлежит учётной записи и даёт ровно её права: записи, заведённые по
токену, видны владельцу в приложении, и наоборот.
+2 -4
View File
@@ -3,11 +3,9 @@
- **Тип:** research
- **Категория:** Очередь — Форматы: сначала замер того, что конвейер берёт на самом деле.
- **Зачем:** Команда ffmpeg проверена на голосовых Telegram, а что она берёт помимо них, не мерил никто: перечень выведен из документации, а не из прогона.
- **Теги:** goal:any-audio-source
Двигает пункты 1 и 4 «Завершения» цели: перечень принимаемых форматов замерен и
записан, а расхождение `ogg/vorbis` против заявленного SpeechKit `OGG_OPUS`
разобрано.
Замер даёт перечень принимаемых форматов и разбирает расхождение `ogg/vorbis`
против заявленного SpeechKit `OGG_OPUS`.
## Вопрос
-27
View File
@@ -1,27 +0,0 @@
# 🎯 Человек убирает свою запись из архива вместе со всеми текстами
- **Тип:** goal
- **Секция:** Запланировано — Очередь у цели появилась: удаление записи стоит 32-й строкой и трогает конвейер, файлы и колонку дедупликации, которые к тому месту готовы.
- **Зачем:** Хранение бессрочное, а способа убрать запись нет ни одного: ошибочно загруженный файл и разговор, который человек не хочет держать у нас, остаются навсегда.
- **Теги:** decomposed
Сервис объявлен архивом 2026-08-11, и с тем же решением у человека появляется
обратное право: сказать «убери это» и убедиться, что убрано. Речь в записи
принадлежит тем, кто говорил, а не хранилищу.
Стирается всё, что породила запись: сам файл, его фрагменты, объект в Object
Storage и все уровни текста. **Учёт расхода при этом остаётся** — деньги уже
потрачены, и сводка владельца задним числом не переписывается; строки
потребления несут идентификаторы и числа, не текст.
## Завершение
1. Своя запись убирается одним действием, и после него не остаётся ни файла, ни
объекта в хранилище, ни одного из уровней текста.
2. Убранное не возвращается: восстановления нет, и человек предупреждён об этом
до подтверждения.
3. Чужую запись убрать нельзя — ни по идентификатору, ни по токену.
4. Сводка расхода после удаления не меняется: потраченное остаётся видно
владельцу сервиса.
5. Тот же файл, загруженный снова, обрабатывается как новая запись, а не
узнаётся дедупликацией удалённой.
+1 -3
View File
@@ -3,10 +3,8 @@
- **Тип:** feature
- **Категория:** Очередь — Дедупликация ищет совпадение в пределах пользователя — то есть после владельца записи, и экономит деньги с первого дня приложения.
- **Зачем:** Один и тот же файл, отправленный дважды, распознаётся дважды и оплачивается дважды: приём не смотрит на содержимое вовсе.
- **Теги:** goal:upload-reliability
Двигает пункт 1 «Завершения» цели: повторная отправка того же файла возвращает
прежнюю запись вместо второй задачи.
Повторная отправка того же файла возвращает прежнюю запись вместо второй задачи.
Совпадение ищется **в пределах одного пользователя**: чужая расшифровка по
совпадению хеш-суммы не отдаётся и о её существовании отправитель не узнаёт.
+2 -4
View File
@@ -3,11 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Удаление трогает конвейер, файлы, объект хранилища и колонку дедупликации — всё это к этому месту уже готово.
- **Зачем:** Ни файлы, ни расшифровки не удаляются вовсе: убрать запись сегодня можно только руками в базе и в каталоге на сервере.
- **Теги:** goal:data-ownership
Двигает все пять пунктов «Завершения» цели: запись убирается одним действием
вместе с файлом, объектом в хранилище и всеми уровнями текста. Чужую запись
убрать нельзя. Учёт расхода остаётся.
Запись убирается одним действием вместе с файлом, объектом в хранилище и всеми
уровнями текста. Чужую запись убрать нельзя. Учёт расхода остаётся.
Удаление необратимо и потому спрашивает подтверждения. Задача, которая ещё в
работе, тоже убирается: конвейер обязан заметить исчезнувшую запись и не
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Второй канал на той же доставке.
- **Зачем:** Адрес почты приходит вместе с входом через OIDC, но почтового отправителя в сервисе нет.
- **Теги:** goal:ready-notification
Двигает пункты 1, 2 и 3 «Завершения» цели: готовый текст и отказ доходят
письмом, а адрес берётся у учётной записи, а не из общего конфига.
Готовый текст и отказ доходят письмом, а адрес берётся у учётной записи, а не из
общего конфига.
Почта — второй канал рядом с тем, что заводит `ntfy-delivery`; выбор канала
остаётся в той же единой точке, что и сейчас.
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Метрики внешних сервисов пишутся в выбранном словаре, а не переписываются потом.
- **Зачем:** Ни у Telegram, ни у Object Storage, ни у SpeechKit нет ни одной метрики: отказ внешнего сервиса виден только строкой в журнале контейнера.
- **Теги:** goal:service-observability
Двигает пункты 2 и 5 «Завершения» цели: у каждого внешнего сервиса появляются
вызовы, отказы и длительность, а расход на платные сервисы виден числом.
У каждого внешнего сервиса появляются вызовы, отказы и длительность, а расход на
платные сервисы становится виден числом.
Внешних сервисов сегодня четыре — Telegram, Object Storage, SpeechKit и
`ffmpeg`/`ffprobe` как внешний процесс; пятым станет языковая модель. Метрика
+5 -6
View File
@@ -1,14 +1,13 @@
# ✨ Показывать заголовок в списке, отбирать список по темам и считать токены
- **Тип:** feature
- **Категория:** Очередь — Три пункта «Завершения» цели не закрывала ни одна задача: показывать и отбирать можно, когда заголовки и темы уже считаются.
- **Категория:** Очередь — Показывать и отбирать можно, когда заголовки и темы уже считаются.
- **Зачем:** Заголовок, темы и пересказ считаются, но список по-прежнему показывает первые слова расшифровки и не отбирается ничем, а расход на модель не виден числом.
- **Теги:** goal:text-insights
Двигает пункты 1, 4 и 6 «Завершения» цели — те три, где выводы из текста
становятся видны человеку и владельцу: заголовок в списке (1), отбор по темам
(4), стоимость числом (6). Сами уровни считает `llm-insights-adapter`, показать
их некому: экран списка написан раньше и знает только первые слова расшифровки.
Выводы из текста становятся видны человеку и владельцу: заголовок в списке,
отбор по темам, стоимость числом. Сами уровни считает `llm-insights-adapter`,
показать их некому: экран списка написан раньше и знает только первые слова
расшифровки.
Берётся после `llm-insights-adapter`: пока заголовков и тем нет, показывать и
отбирать нечего.
+4 -6
View File
@@ -3,11 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Ставить на телефон есть смысл, когда есть что ставить.
- **Зачем:** Приложение, живущее вкладкой браузера, теряется среди прочих: ярлыка на экране у него нет.
- **Теги:** goal:web-access
Двигает пункты 5 и 6 «Завершения» цели: приложение ставится с телефона и
запускается с ярлыка без адресной строки, а открытое без сети показывает это
состоянием.
Приложение ставится с телефона и запускается с ярлыка без адресной строки, а
открытое без сети показывает это состоянием.
Берётся после того, как есть что ставить, — то есть после
`records-list-screen`.
@@ -41,5 +39,5 @@
## Рамки
Офлайн-чтения готовых расшифровок и очереди отправки без сети **не делаем**
это за границей цели. Web Push не делаем: уведомления идут через apprise и ntfy,
цель `ready-notification`.
это за границей из паспорта. Web Push не делаем: уведомления идут через apprise
и ntfy — задача `ntfy-delivery`.
+3 -4
View File
@@ -3,11 +3,10 @@
- **Тип:** research
- **Категория:** Очередь — Второй замер — остальные четыре звена.
- **Зачем:** Из пяти звеньев задача speechkit-limits замерила только модель распознавания: где отваливается шестичасовая запись до неё, неизвестно.
- **Теги:** goal:long-recordings
Пункт 1 «Завершения» цели требует замера пяти звеньев, а разведка
`speechkit-limits` меряет одно — модель `deferred-general`. Остальные четыре
дешевле: они не требуют боевых ключей и считаются локально, кроме заливки.
Звеньев пять, а разведка `speechkit-limits` меряет одно — модель
`deferred-general`. Остальные четыре дешевле: они не требуют боевых ключей и
считаются локально, кроме заливки.
Числа нужны раньше кода: они назначают потолок, который проверяет приём, и длину
фрагмента, на которые режет `long-audio-chunking`.
+2 -4
View File
@@ -1,12 +1,10 @@
# ✨ Собирать путь одной записи по конвейеру запросом
- **Тип:** feature
- **Категория:** Очередь — Пункт 3 «Завершения» цели не закрывала ни одна задача; применяет словарь, который выберет разведка строкой выше.
- **Категория:** Очередь — Применяет словарь метрик, который выберет разведка строкой выше.
- **Зачем:** Звенья пути связаны только идентификатором задачи в строках журнала: чтобы понять, где запись провела минуты, владелец читает логи контейнера глазами.
- **Теги:** goal:service-observability
Двигает пункт 3 «Завершения» цели: путь одной записи по конвейеру собирается
запросом, а не чтением логов глазами.
Путь одной записи по конвейеру собирается запросом, а не чтением логов глазами.
Путь длиной в минуты идёт через четыре внешних сервиса и три воркера. Сегодня
его звенья связывает `job_id` в строках журнала, и собирает их человек.
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Единая точка трансляции доменной ошибки — база и для экранов, и для токенов; список своих записей заводится после владельца, а не до.
- **Зачем:** Сегодняшний API отвечает 404 на любую ошибку чтения и 500 на любую ошибку приёма: строить на нём экраны нечем.
- **Теги:** goal:web-access
Двигает пункты 1, 2 и 4 «Завершения» цели: экраны заводят задачу, видят её
состояние и листают список — всё через один контракт.
Экраны заводят задачу, видят её состояние и листают список — всё через один
контракт.
Обработчик `GET /api/status/:id` сегодня отвечает `404` на **любую** ошибку
чтения, включая сбой базы, а `POST /api/audio``500` на любую ошибку заведения,
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Вычитанный текст считается тем же адаптером.
- **Зачем:** Сырая расшифровка идёт без знаков препинания, с повторами и словами-паразитами: читать её подряд тяжело, а другого уровня текста нет.
- **Теги:** goal:text-insights
Двигает пункт «Завершения» цели про литературный текст: у записи появляется
второй уровень — тот же разговор, вычитанный до читаемого вида.
У записи появляется второй уровень текста — тот же разговор, вычитанный до
читаемого вида.
Вычитку считает та же внешняя модель, что заголовок и темы. Сырой текст
остаётся и не переписывается: уровни лежат рядом, а не поверх друг друга.
+2 -4
View File
@@ -3,11 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Уровни текста: сюда приходит пятая внешняя зависимость, и конвейер к этому моменту покрыт тестами.
- **Зачем:** Расшифровка доходит стеной текста: ни заголовка, ни тем, ни пересказа сервис не считает, и клиента языковой модели в нём нет.
- **Теги:** goal:text-insights
Двигает пункты 1, 3, 4 и 5 «Завершения» цели: у готовой записи появляются
заголовок (1), пересказ (3) и темы (4), а отказ и молчание модели не роняют
задачу (5).
У готовой записи появляются заголовок, пересказ и темы, а отказ и молчание
модели не роняют задачу.
Здесь появляется пятая внешняя зависимость — языковая модель с
OpenAI-совместимым интерфейсом за шлюзом bifrost, — и текст расшифровки уходит
+3 -4
View File
@@ -3,11 +3,10 @@
- **Тип:** feature
- **Категория:** Очередь — Резка на фрагменты — самая глубокая переделка конвейера, и она идёт по замеренным числам.
- **Зачем:** Шаг конвейера повторяется целиком: перезапуск на пятом часу шестичасовой записи начинает распознавание заново и оплачивает его второй раз.
- **Теги:** goal:long-recordings
Двигает пункты 3, 5 и 6 «Завершения» цели: запись в пределах потолка доходит до
текста целиком, долгая задача не занимает воркер на часы, а перезапуск на
середине продолжает работу с первого неотмеченного фрагмента.
Запись в пределах потолка доходит до текста целиком, долгая задача не занимает
воркер на часы, а перезапуск на середине продолжает работу с первого
неотмеченного фрагмента.
Запись делится на фрагменты, каждый распознаётся отдельно, готовый фрагмент
отмечается в базе. После перезапуска работа продолжается с первого неотмеченного, а
-28
View File
@@ -1,28 +0,0 @@
# 🎯 Запись длиной до шести часов доходит до текста
- **Тип:** goal
- **Секция:** Направления — Цель начинается с двух замеров и кончается резкой на фрагменты — самой глубокой переделкой конвейера: очереди внутри нет, пока числа не получены.
- **Зачем:** Потолок не замерен ни на одном звене: Telegram не отдаёт больше 20 МиБ, границы модели deferred-general неизвестны, а перезапуск на середине начинает распознавание заново.
- **Теги:** decomposed
Лекция, созвон, интервью и диктофонная запись из семейного архива целиком
превращаются в текст. Сегодня неизвестно даже, на каком звене такая запись
отваливается, — цель начинается с замера, а не с переделки.
Расчётный потолок — **шесть часов**: он взят с запасом под диктофонные записи и
дорожки из видео, и замер проверяет, каким звеном он ограничен на самом деле.
## Завершение
1. Потолки каждого звена замерены и записаны в `research/` с командой замера:
приём из Telegram, приём по HTTP, конвертация, заливка в Object Storage,
модель `deferred-general`.
2. Запись, превышающая потолок, отклоняется на приёме понятным текстом, а не
висит в конвейере до истечения захвата.
3. Запись в пределах потолка доходит до текста и не теряет его хвост.
4. Текст в несколько сотен килобайт доходит до получателя: и в браузере, и в
Telegram, где предел сообщения — 4000 символов.
5. Долгая задача не блокирует короткие: запись на три часа не останавливает
конвейер для голосового на десять секунд.
6. Перезапуск сервиса на середине долгой расшифровки не начинает её заново:
работа продолжается с места остановки.
+1 -3
View File
@@ -3,10 +3,8 @@
- **Тип:** feature
- **Категория:** Очередь — Сотни килобайт текста появляются только после долгих записей.
- **Зачем:** Отправитель Telegram режет текст по 4000 знаков: расшифровка шестичасовой записи придёт сотней сообщений подряд.
- **Теги:** goal:long-recordings
Двигает пункт 4 «Завершения» цели: текст в несколько сотен килобайт доходит и в
Telegram, и в браузере.
Текст в несколько сотен килобайт доходит и в Telegram, и в браузере.
Деление по словам (`internal/adapter/telegram/split.go`) остаётся для обычной
расшифровки; сверх названного числа частей вместо потока сообщений уходит один
+1 -3
View File
@@ -3,10 +3,8 @@
- **Тип:** feature
- **Категория:** Очередь — Пачка файлов заводится на готовом экране загрузки и готовой дедупликации.
- **Зачем:** Приём берёт один файл в запросе, а с телефона выбирают пачку сразу: десять записей значат десять заходов на экран загрузки.
- **Теги:** goal:upload-reliability
Двигает пункт 2 «Завершения» цели: пачка файлов уходит одной загрузкой, и отказ
одного не отменяет остальные.
Пачка файлов уходит одной загрузкой, и отказ одного не отменяет остальные.
## Затрагивает
-24
View File
@@ -1,24 +0,0 @@
# 🎯 Сервисом пользуются несколько человек, и записи одного не видны другому
- **Тип:** goal
- **Секция:** Запланировано — Владелец записи — фундамент, на котором стоят список своих записей, дедупликация, удаление, учёт расхода и квота: пока его нет, остальные цели строятся на песке.
- **Зачем:** У задачи нет владельца, а HTTP API открыт наружу без аутентификации: пригласить второго человека сейчас значит открыть ему чужие расшифровки.
- **Теги:** decomposed
Приложение узнаёт, кто к нему пришёл, и показывает каждому только его записи.
Учётные записи заводит и проверяет внешний провайдер — Authelia по OIDC; своей
регистрации и своих паролей не делаем, это граница из
[паспорта](../../docs/passport.md).
## Завершение
1. Неаутентифицированный запрос к записям не проходит: ни к странице, ни к API.
2. У задачи и файла есть владелец, и выборка чужой записи по её
идентификатору возвращает «не найдено», а не содержимое.
3. Вход идёт через OIDC у Authelia; выход из сессии работает.
4. Пользователь Telegram сопоставлен с учётной записью, и записи, пришедшие
ботом, видны ему же в браузере.
5. Белый список Telegram перестаёт быть отдельным механизмом: право писать боту
выводится из учётной записи.
6. Скрипт ходит в API по токену, выпущенному пользователем, и видит ровно его
записи.
+8 -5
View File
@@ -3,12 +3,10 @@
- **Тип:** feature
- **Категория:** Очередь — Канал уведомлений выбирается в настройках, которые уже есть.
- **Зачем:** Пользователь веба узнаёт о готовности только опросом с открытого экрана.
- **Теги:** goal:ready-notification
Двигает пункты 1, 2, 4 и 5 «Завершения» цели: готовый текст и отказ доходят до
пользователя веба без открытого приложения, отказ канала задачу не роняет, а
пользователь Telegram получает ответ по-прежнему ботом. Выбор канала самим
пользователем (пункт 3) заводит `settings-screen`.
Готовый текст и отказ доходят до пользователя веба без открытого приложения,
отказ канала задачу не роняет, а пользователь Telegram получает ответ
по-прежнему ботом. Выбор канала самим пользователем заводит `settings-screen`.
Сегодня `completeJob` и `failJob` отвечают только источнику `telegram`;
источник `api` не получает ничего. Здесь появляется второй способ доставки, и
@@ -46,3 +44,8 @@
Web Push с VAPID и своим хранением подписок не делаем. Своего сервера ntfy не
поднимаем — адрес приходит конфигом. Текст расшифровки уходит на внешний сервис,
и это сдвиг периметра: строка в `docs/security.md` обязательна.
Отказ от Web Push сегодня живёт открытым вопросом `docs/architecture.md`,
«Уведомления», и своего ADR не имеет: заводить его не из чего, пока нет
`design.md` этой задачи. Решение промоутится из него, когда задача пойдёт в
работу.
-1
View File
@@ -3,7 +3,6 @@
- **Тип:** research
- **Категория:** Очередь — Сопровождение: словарь метрик выбирается до того, как метрик станет втрое больше.
- **Зачем:** Метрик одиннадцать штук на пять счётчиков, трассировки нет вовсе: путь одной записи по конвейеру собирается только чтением логов глазами.
- **Теги:** goal:service-observability
Эндпоинт `/metrics` остаётся и развивается — это решено. Вопрос в том, чем его
развивать: дописывать счётчики в `internal/metrics` напрямую через
+1 -1
View File
@@ -3,7 +3,7 @@
- **Тип:** fix
- **Категория:** Очередь — Осиротевший файл — тот же отказ на середине, и чинится тем же местом приёма.
- **Зачем:** Отказ чтения метаданных и отказ записи на диск оставляют файл в каталоге хранения без задачи и без учёта: сопоставить его не с чем, удалять приходится руками.
- **Теги:** review-2026-08-11, goal:upload-reliability
- **Теги:** review-2026-08-11
Приём пишет файл на диск, потом спрашивает у источника метаданных длительность,
потом заводит запись в учёте и задачу. Уборка при отказе есть **только на
+2 -3
View File
@@ -3,9 +3,8 @@
- **Тип:** feature
- **Категория:** Очередь — Правило оповещения ставится на метрику, которой до этого нет.
- **Зачем:** Об отказе владелец узнаёт от пользователя: правил оповещения нет ни на одной метрике, а метрики читают глазами.
- **Теги:** goal:service-observability
Двигает пункт 4 «Завершения» цели: владелец узнаёт об отказе сам.
Владелец узнаёт об отказе сам.
Берётся после `external-service-metrics` и `stalled-pipeline-metric`: правило
оповещения ставится на метрику, а метрик, на которых его ставить, сегодня нет.
@@ -35,4 +34,4 @@
Выкладку правил запускает человек — `inv pl -- transcriber` из
`pet-project-server`. Пользовательские уведомления о готовности здесь не
трогаем: это цель `ready-notification`.
трогаем: их доставку заводят `ntfy-delivery` и `email-notification`.
+2 -4
View File
@@ -3,10 +3,8 @@
- **Тип:** feature
- **Категория:** Очередь — Проигрывание живёт на экране записи, которого до списка нет.
- **Зачем:** Послушать загруженное приложение не даёт, а самой копии для этого у задачи нет: указатель на файл перезаписывается на каждом шаге конвейера и у готовой задачи ведёт на объект в Object Storage.
- **Теги:** goal:web-access
Двигает пункт 7 «Завершения» цели: запись слушается на её экране, а не
скачивается файлом.
Запись слушается на её экране, а не скачивается файлом.
Заметка владельца от 2026-08-12. Копии на диске остаются все три — исходник,
ogg после конвертации и запись об объекте, — но `job.FileID` на каждом шаге
@@ -47,6 +45,6 @@ ogg после конвертации и запись об объекте, —
## Рамки
Скачивания файла, обрезки и синхронизации текста со звуком не делаем. Записи
звука в приложении не делаем — это граница цели. Формат хранения и раскладку
звука в приложении не делаем — это граница из паспорта. Формат хранения и раскладку
каталога данных не трогаем: они объявлены необратимыми, а шаг схемы, уехавший на
сервер, не переписывается — изменение только новым файлом шага.
-28
View File
@@ -1,28 +0,0 @@
# 🎯 Пользователь узнаёт о готовности текста, не держа приложение открытым
- **Тип:** goal
- **Секция:** Запланировано — Канал уведомлений выбирается в настройках, которые к этому месту уже есть.
- **Зачем:** Расшифровка занимает минуты, и всё это время человек либо смотрит на экран с опросом статуса, либо забывает вернуться.
- **Теги:** decomposed
Задача уходит в работу — человек закрывает приложение и получает сообщение,
когда текст готов. Пользователь Telegram это уже имеет: бот отвечает сам.
Пользователь веба — нет.
Каналов два: почта, адрес которой приходит вместе с входом через OIDC, и
Telegram для того, кто связал свою учётную запись с ботом. Доставку берём
внешнюю: apprise как отправитель, ntfy как один из каналов. Web Push с VAPID и
собственным хранением подписок за целью **не стоит** — это отдельная
инфраструктура ради того же результата.
## Завершение
1. Готовый текст доходит до пользователя веба сообщением, без открытого
приложения.
2. Отказ задачи доходит тем же путём и тем же человекочитаемым текстом, что
видит пользователь Telegram.
3. Канал и адрес уведомлений задаёт пользователь, а не общий конфиг: у каждого
свой, и от уведомлений можно отказаться.
4. Недоступность канала уведомлений не роняет задачу и не мешает ей завершиться:
текст остаётся в приложении.
5. Пользователь Telegram получает ответ по-прежнему ботом, а не вторым каналом.
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Фундамент домена: без владельца знание UUID и есть право читать чужое, и на владельце стоят список, дедуп, удаление, учёт и квота.
- **Зачем:** У задачи и файла нет владельца, поэтому знание UUID задачи и есть право её читать.
- **Теги:** goal:multi-user
Двигает пункт 2 «Завершения» цели: выборка чужой записи по её идентификатору
возвращает «не найдено», а не содержимое.
Выборка чужой записи по её идентификатору возвращает «не найдено», а не
содержимое.
Берётся после `oidc-login`: до входа неизвестно, кто владелец.
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Список и чтение текста берутся после экрана загрузки — так записано в самой задаче.
- **Зачем:** Расшифровка сегодня доходит одним сообщением и теряется в переписке; вернуться к ней через неделю нечем.
- **Теги:** goal:web-access
Двигает пункты 3 и 4 «Завершения» цели: список своих записей открывается и
листается, а готовый текст читается и копируется целиком.
Список своих записей открывается и листается, а готовый текст читается и
копируется целиком.
Берётся после `upload-and-status-screen`.
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Отклонять сверх потолка можно, когда потолок замерен.
- **Зачем:** Запись сверх потолка принимается молча и висит в конвейере до истечения часового захвата, а человек всё это время ждёт текста.
- **Теги:** goal:long-recordings
Двигает пункт 2 «Завершения» цели: запись длиннее потолка отклоняется на приёме
понятным текстом, а не отказом через час.
Запись длиннее потолка отклоняется на приёме понятным текстом, а не отказом
через час.
Потолок задаётся конфигурацией, а не зашивается в код: числа приходят из
разведок `intake-limits-measure` и `speechkit-limits`, и меняются они без сборки.
-26
View File
@@ -1,26 +0,0 @@
# 🎯 Состояние сервиса видно без чтения логов
- **Тип:** goal
- **Секция:** Сопровождение — Словарь метрик выбирается разведкой до того, как метрик станет втрое больше; наблюдаем мы, а не пользователь сервиса.
- **Зачем:** Отказ замечает пользователь, а не владелец: оповещения нет, а путь записи по конвейеру собирается глазами по логам контейнера.
- **Теги:** decomposed
Наблюдаем **мы**, а не пользователь сервиса, — поэтому цель стоит в
сопровождении, а не среди возможностей приложения. Пользовательская половина
той же темы живёт отдельно: о готовности своей записи человек узнаёт по цели
[ready-notification](ready-notification.md).
Эндпоинт `/metrics` остаётся и развивается. Чем именно развивать — голым
`client_golang` или OpenTelemetry с трассировкой — решает разведка
`opentelemetry-fit`.
## Завершение
1. По метрикам видно, что конвейер встал: задача висит в состоянии дольше
обычного, и это отличимо от «работы нет».
2. Каждый внешний сервис имеет метрику вызовов, отказов и длительности —
сегодня их нет ни у одного.
3. Путь одной записи по конвейеру собирается запросом, а не чтением логов
глазами.
4. Владелец узнаёт об отказе сам, а не от пользователя.
5. Стоимость обращений к платным сервисам видна числом.
+2 -4
View File
@@ -3,11 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Выключать нечего, пока уровней нет; настройка ставится сразу за ними, чтобы платная модель не считала лишнее.
- **Зачем:** Дом настроек есть, а конвейер их не читает: выключенный уровень всё равно уходит платной модели, и настройка ничего не экономит.
- **Теги:** goal:user-settings
Двигает пункты 1 и 4 «Завершения» цели: выключенный уровень не запрашивается у
языковой модели вовсе, а запись обрабатывается по той настройке, которая
действовала на приёме.
Выключенный уровень не запрашивается у языковой модели вовсе, а запись
обрабатывается по той настройке, которая действовала на приёме.
Берётся после `settings-screen` (настройки негде хранить) и после
`llm-insights-adapter` (нечего выключать).
+2 -4
View File
@@ -3,11 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Дом настроек нужен раньше уровней текста и раньше выбора канала уведомлений.
- **Зачем:** Настроек у пользователя нет вовсе: уровни текста и канал уведомлений задаются общим конфигом сервиса.
- **Теги:** goal:user-settings
Двигает пункты 1, 2 и 3 «Завершения» цели: у каждого пользователя свои
переключатели уровней текста и свой канал уведомлений, и они переживают
повторный вход.
У каждого пользователя свои переключатели уровней текста и свой канал
уведомлений, и они переживают повторный вход.
Здесь появляется дом настроек — таблица и эндпоинты; применяют их задачи
уровней текста и доставки уведомлений.
+4 -5
View File
@@ -3,12 +3,11 @@
- **Тип:** feature
- **Категория:** Очередь — Каркас приложения: экранов нет и собирать их нечем, а на экране стоят настройки, удаление, уровни текста и статистика.
- **Зачем:** Экранов нет и собирать их нечем: ни сборки фронтенда, ни раздачи статики в проекте не существует.
- **Теги:** goal:web-access
Ни одной строки «Завершения» цели каркас не закрывает: он готовит четыре
экранные — 1, 2, 3 и 4, — но сам по себе пользователю ничего не даёт. Видимое от
него одно: приложение открывается и достаёт данные с живого сервера, а не отдаёт
пустую страницу.
Каркас готовит четыре экрана — загрузку, состояние задачи, чтение текста и
список, — но сам по себе пользователю ничего не даёт. Видимое от него одно:
приложение открывается и достаёт данные с живого сервера, а не отдаёт пустую
страницу.
Фреймворк выбран 2026-08-11 — Vue 3 с роутером пятой версии и сборкой Vite
([ADR](../../docs/adr/ADR-2026-08-11-spa-on-vue.md)); правила кода приложения
+3 -4
View File
@@ -2,11 +2,10 @@
- **Тип:** research
- **Категория:** Очередь — Долгие записи начинаются с замера: без потолков ни отказ на приёме, ни резка не проектируются.
- **Зачем:** Потолок длины записи и перечень принимаемых форматов неизвестны, а цель про долгие записи без них не начинается.
- **Теги:** goal:long-recordings
- **Зачем:** Потолок длины записи и перечень принимаемых форматов неизвестны, а работа над долгими записями без них не начинается.
Цель «запись длиной в несколько часов доходит до текста» упирается в то, что
неизвестно, на каком звене такая запись отваливается. Начинать с переделки
Запись длиной в несколько часов упирается в то, что неизвестно, на каком звене
она отваливается. Начинать с переделки
приёма или с деления записи на куски — разные работы, и выбор между ними
определяет замер, а не рассуждение.
+2 -4
View File
@@ -3,10 +3,8 @@
- **Тип:** feature
- **Категория:** Очередь — Признак вставшего конвейера — вторая половина той же работы.
- **Зачем:** Вставший конвейер неотличим от простоя: возраст задачи в состоянии не считается, и очередь без движения выглядит как отсутствие работы.
- **Теги:** goal:service-observability
Двигает пункт 1 «Завершения» цели: по метрикам видно, что конвейер встал, и это
отличимо от «работы нет».
По метрикам видно, что конвейер встал, и это отличимо от «работы нет».
Сегодня метрики считают события — принято, распознано, отказано, — а состояние
очереди не считает ничто. Пустая очередь и очередь, где десять задач висят третий
@@ -34,4 +32,4 @@
Правило оповещения на этой метрике заводит задача `owner-alerting` — здесь
только число. Порог «сколько считается застреванием» здесь не решается: он
зависит от замеров цели `long-recordings`.
зависит от замеров `speechkit-limits` и `intake-limits-measure`.
+2 -4
View File
@@ -3,11 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Связывать чат с учётной записью не с чем, пока у записи нет владельца.
- **Зачем:** Белый список сверяется с именем пользователя Telegram, которое владелец меняет в любой момент, а записи из бота ни с кем не связаны.
- **Теги:** goal:multi-user
Двигает пункты 4 и 5 «Завершения» цели: записи из бота видны тому же человеку в
приложении, а белый список перестаёт быть отдельным механизмом — право писать
боту выводится из учётной записи.
Записи из бота видны тому же человеку в приложении, а белый список перестаёт
быть отдельным механизмом — право писать боту выводится из учётной записи.
Берётся после `record-ownership`: связывать не с чем, пока у записи нет
владельца.
-36
View File
@@ -1,36 +0,0 @@
# 🎯 Приложение показывает, о чём запись, не читая её целиком
- **Тип:** goal
- **Секция:** Запланировано — Очередь у цели появилась: три задачи подряд после настроек, и прибавка видна даже в боте.
- **Зачем:** Расшифровка часового разговора — это стена текста: найти в списке нужную запись и вспомнить, о чём она, сегодня нечем.
- **Теги:** decomposed
Текст записи выдаётся уровнями: сырая расшифровка, вычитанный литературный
текст, заголовок в одну строку, темы-теги и пересказ в два-три предложения.
Считает их внешний сервис с OpenAI-совместимым интерфейсом за шлюзом bifrost —
своей модели не держим, как не держим и модели распознавания.
Каждый уровень сверх сырого пользователь выключает у себя — это цель
[user-settings](user-settings.md).
**Это сдвиг границы паспорта, и сдвигали её дважды.** До 2026-08-10 «понимание
сказанного» было записано как то, чем проект не является: «мы отдаём текст, а не
выводы из него». 2026-08-11 к выводам добавился литературный текст — машинная
вычитка расшифровки, прежде запрещённая строкой про редактор.
От обоих запретов остались более узкие: ответов на вопросы по записи и поиска по
смыслу не делаем, правку текста руками человеку не даём.
## Завершение
1. У готовой записи есть заголовок в одну строку, и он виден в списке вместо
первых слов расшифровки.
2. Рядом с сырой расшифровкой лежит вычитанный текст: со знаками препинания, без
повторов и слов-паразитов.
3. У записи есть пересказ в два-три предложения, который читается быстрее самой
расшифровки.
4. У записи есть темы, и по ним список отбирается.
5. Отказ или молчание внешнего сервиса не роняют задачу: расшифровка доходит до
человека без выводов из неё.
6. Стоимость обращения к сервису видна метрикой: сколько записей обработано и
сколько это стоило по числу токенов.
+2 -3
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Первое, ради чего приложение открывают; требует каркаса и контракта, оба выше.
- **Зачем:** Первое, ради чего приложение открывают: отдать файл и увидеть, что с ним происходит.
- **Теги:** goal:web-access
Двигает пункты 1 и 2 «Завершения» цели: экран принимает файл и заводит задачу, а
её состояние обновляется само, пока задача не дошла до `done` или `failed`.
Экран принимает файл и заводит задачу, а её состояние обновляется само, пока
задача не дошла до `done` или `failed`.
Берётся после `spa-skeleton` и `json-api-for-spa`.
+1 -3
View File
@@ -3,10 +3,8 @@
- **Тип:** feature
- **Категория:** Очередь — Ход загрузки — доработка того же экрана.
- **Зачем:** Гигабайтный файл уходит на сервер молча: до ответа сервера экран не отличает идущую загрузку от зависшей.
- **Теги:** goal:upload-reliability
Двигает пункт 3 «Завершения» цели: ход загрузки виден числом, а не одним
ожиданием.
Ход загрузки виден числом, а не одним ожиданием.
Загрузка шестичасовой записи по сотовой сети идёт минутами. Пока сервер не
ответил, экран показывает долю отправленного и позволяет её отменить.
-25
View File
@@ -1,25 +0,0 @@
# 🎯 Загрузка большого файла доходит до сервиса и не повторяется впустую
- **Тип:** goal
- **Секция:** Запланировано — Дедупликация и пачка файлов доводятся на готовом экране загрузки и экономят деньги с первого дня приложения.
- **Зачем:** Приём рассчитан на голосовое в пару мегабайт: обрыв на середине гигабайтного файла начинает загрузку заново, а один и тот же файл распознаётся повторно за наши деньги.
- **Теги:** decomposed
Человек отдаёт диктофонную запись или видео из семейного архива с телефона, по
сотовой сети, и загрузка либо доходит, либо честно говорит, что не дошла.
Отданное однажды второй раз не грузится и второй раз не распознаётся.
Дедупликация ищет совпадение **в пределах одного пользователя**: чужая
расшифровка не достаётся по совпадению хеш-суммы, даже когда файл тот же.
Загрузка частями за целью пока **не стоит**: сначала один запрос, а протокол
докачки разбирает разведка.
## Завершение
1. Файл, уже загруженный этим пользователем, узнаётся по хеш-сумме: сервис
возвращает прежнюю запись и не заводит вторую задачу.
2. До десяти файлов уходят одной загрузкой, и отказ одного не отменяет
остальные.
3. Ход загрузки виден на экране числом, а не одним ожиданием.
4. Оборванная загрузка не оставляет ни файла в хранилище, ни задачи в очереди.
+5 -6
View File
@@ -3,10 +3,9 @@
- **Тип:** feature
- **Категория:** Очередь — Учёт по пользователям требует владельца записи и метрик расхода — оба выше.
- **Зачем:** Ни объём, ни длительность, ни обращения к платным сервисам никуда не записываются: восстановить расход задним числом не из чего.
- **Теги:** goal:usage-stats
Двигает пункты 1, 2 и 4 «Завершения» цели: по каждому пользователю копятся
объём, минуты и расход на внешние сервисы, и повтор шага не удваивает счёт.
По каждому пользователю копятся объём, минуты и расход на внешние сервисы, и
повтор шага не удваивает счёт.
Учёт ведётся записями о потреблении, а не счётчиком в строке пользователя:
счётчик, увеличенный дважды при повторе шага, обратно не отматывается.
@@ -38,10 +37,10 @@
## Рамки
Снаружи эта задача не видна сама по себе: ни экрана, ни эндпоинта она не
заводит — их строит `admin-stats-screen`. Тип оставлен `feature` сознательно,
как шаг цели.
заводит — их строит `admin-stats-screen`. Тип оставлен `feature` сознательно:
это первая половина работы, которую вторая делает видимой.
Потолков и отказов по исчерпании квоты не заводим — цель показывает, а не
Потолков и отказов по исчерпании квоты не заводим — учёт показывает, а не
ограничивает. Пересчёт расхода в деньги здесь не делается: копятся минуты и
токены, цена прайс-листа живёт вне сервиса. Данные о потреблении содержат
идентификаторы, но не текст записи и не имя файла (инвариант приватности).
-25
View File
@@ -1,25 +0,0 @@
# 🎯 Владелец видит, кто сколько загрузил и во что это обошлось
- **Тип:** goal
- **Секция:** Сопровождение — Учёт по пользователям требует владельца записи и метрик расхода — оба появляются раньше.
- **Зачем:** Распознавание и языковая модель оплачиваются по факту, а счёт приходит одной суммой: кто её набрал, из сервиса не выясняется.
- **Теги:** decomposed
Владелец открывает страницу и видит по каждому пользователю объём загруженного,
длительность записей в минутах и расход на внешние сервисы. Приглашая человека,
он понимает, во что это обойдётся.
Цель **показывает, но не ограничивает**: потолков и отказов по исчерпании квоты
здесь нет — перебравшего останавливает разговор или отзыв доступа в Authelia.
Секция — сопровождение, потому что наблюдает владелец сервиса, а не его
пользователь.
## Завершение
1. По каждому пользователю видны объём загруженных файлов и длительность записей
в минутах, накопительно и за период.
2. Видно, во что обошлись внешние сервисы: минуты распознавания и число токенов
языковой модели.
3. Страница открывается только владельцу, обычному пользователю она недоступна.
4. Учёт переживает перезапуск и не считает одну запись дважды при повторе шага.
-22
View File
@@ -1,22 +0,0 @@
# 🎯 Пользователь настраивает, что сервис делает с его записями
- **Тип:** goal
- **Секция:** Запланировано — Дом настроек нужен раньше уровней текста и раньше выбора канала уведомлений.
- **Зачем:** Уровни текста считает платная модель, а уведомления приходят одним общим способом: отказаться от лишнего и выбрать свой канал пользователю нечем.
- **Теги:** decomposed
У каждого своя мера: одному нужна только сырая расшифровка, другому — все пять
уровней текста. Настройки принадлежат человеку, а не общему конфигу сервиса, и
переживают выход и повторный вход.
Выключенный уровень **не считается вовсе**: настройка экономит деньги, а не
прячет готовое.
## Завершение
1. Каждый уровень текста сверх сырого включается и выключается отдельно, и
выключенный не запрашивается у языковой модели.
2. Канал уведомлений выбирает сам пользователь, а не общий конфиг.
3. Настройки переживают выход и повторный вход, и у каждого пользователя свои.
4. Запись, заведённая до правки настроек, обрабатывается по той настройке,
которая действовала на приёме.
+5 -6
View File
@@ -3,11 +3,10 @@
- **Тип:** feature
- **Категория:** Очередь — Приём видео и отказ неизвестному формату ставятся на замеренный перечень.
- **Зачем:** Запись семейного архива приходит видеофайлом, а приём смотрит на аудио: человеку приходится доставать дорожку самому.
- **Теги:** goal:any-audio-source
Двигает пункты 2 и 3 «Завершения» цели: видеофайл принимается и из него берётся
звуковая дорожка, а формат, который принять нельзя, отклоняется на приёме
понятным текстом, а не отказом на конвертации через минуту.
Видеофайл принимается и из него берётся звуковая дорожка, а формат, который
принять нельзя, отклоняется на приёме понятным текстом, а не отказом на
конвертации через минуту.
Берётся после `audio-format-coverage-measure`: чем отклонять неизвестное, пока
неизвестно, что конвейер берёт.
@@ -38,5 +37,5 @@
## Рамки
Видео с несколькими звуковыми дорожками берём первую, выбор дорожки человеком за
задачей не стоит. Потолок длины остаётся тем, что назначит цель
`long-recordings`.
задачей не стоит. Потолок длины остаётся тем, что назначат замеры
`speechkit-limits` и `intake-limits-measure`.
-31
View File
@@ -1,31 +0,0 @@
# 🎯 Записи загружаются и читаются в приложении, которое ставится на телефон
- **Тип:** goal
- **Секция:** Запланировано — Экраны приложения нужны раньше настроек, удаления, уровней текста и статистики: всем им негде показаться.
- **Зачем:** Сегодня записи принимает только бот и голый HTTP API без интерфейса: отдать сервис человеку, у которого нет Telegram, нечем.
- **Теги:** decomposed
Приложение получает поверхность, на которой запись загружают и забирают текст,
не открывая Telegram и не вызывая API руками. Ставится оно на телефон и
открывается с ярлыка, как обычное приложение. Конвейер обработки при этом
остаётся прежним — меняется вход и способ показать результат.
**Приложение — основной вход сервиса**, бот остаётся дополнением для голосовых
сообщений. Экраны рисуются сначала под телефон, потом под широкий экран.
Границы взяты уже: запись звука в самом приложении и работа без сети за целью
**не стоят** — файл выбирают в системном диалоге, а без сети приложение
показывает, что связи нет.
## Завершение
1. Экран принимает файл и заводит задачу — ту же, что заводит бот.
2. Состояние задачи видно на экране и обновляется само, пока задача не дошла до
`done` или `failed`; отказ показывается человекочитаемым текстом.
3. Готовый текст читается и копируется с экрана целиком, без деления на части.
4. Список своих записей открывается и листается.
5. Приложение ставится на телефон из браузера и запускается с ярлыка на
отдельном экране, без адресной строки.
6. Открытое без сети, приложение показывает это состоянием, а не пустой
страницей и не ошибкой браузера.
7. Загруженная запись слушается на своём экране, а не скачивается файлом.