- 11 записей типа goal закрыты с причиной, называющей задачи-наследники; ROADMAP.md удалён, индекс остался один — BACKLOG.md; - 33 записи переписаны: ссылка «Двигает пункты N «Завершения» цели» уступила место прямому утверждению — без целей номера пунктов вели в никуда; - шапка BACKLOG.md размечена парой <!-- стадия -->, порядок строк теперь объявлен зависимостью, а не важностью.
42 lines
3.4 KiB
Markdown
42 lines
3.4 KiB
Markdown
# ✨ Узнавать уже загруженный файл по хеш-сумме
|
||
|
||
- **Тип:** feature
|
||
- **Категория:** Очередь — Дедупликация ищет совпадение в пределах пользователя — то есть после владельца записи, и экономит деньги с первого дня приложения.
|
||
- **Зачем:** Один и тот же файл, отправленный дважды, распознаётся дважды и оплачивается дважды: приём не смотрит на содержимое вовсе.
|
||
|
||
Повторная отправка того же файла возвращает прежнюю запись вместо второй задачи.
|
||
|
||
Совпадение ищется **в пределах одного пользователя**: чужая расшифровка по
|
||
совпадению хеш-суммы не отдаётся и о её существовании отправитель не узнаёт.
|
||
|
||
## Затрагивает
|
||
|
||
- `TranscribeService.createTranscribeJob` — единая точка приёма, через неё идут
|
||
оба входа;
|
||
- таблица `files`: колонка хеш-суммы, её миграция и индекс по паре «владелец,
|
||
хеш-сумма»;
|
||
- контракт `POST /api/audio`: ответ на повторный файл;
|
||
- ответ бота на повторно присланное голосовое;
|
||
- `docs/database.md` — представление данных.
|
||
|
||
## Критерии приёмки
|
||
|
||
- Повторная отправка того же файла тем же пользователем возвращает
|
||
идентификатор прежней задачи, второй задачи в базе не появляется. Оракул —
|
||
тест приёма: два вызова одним содержимым, в репозитории одна задача.
|
||
- Тот же файл от другого пользователя заводит свою задачу и своё распознавание.
|
||
Оракул — тест приёма с двумя владельцами: две задачи, тексты не разделяются.
|
||
- Повторный файл не остаётся вторым экземпляром в каталоге хранения. Оракул —
|
||
тест: после второго вызова в каталоге один файл.
|
||
- Незавершённая задача тоже узнаётся: повторная отправка отдаёт её состояние, а
|
||
не заводит соседнюю. Оракул — тест на задаче в состоянии `created`.
|
||
|
||
## Рамки
|
||
|
||
Владелец записи приходит из `record-ownership` — до неё дедупликация опирается
|
||
на того владельца, который уже есть. Хеш-сумма считается на сервере: подсчёт на
|
||
стороне приложения относится к `chunked-upload-choice`. Колонка добавляется во
|
||
всех четырёх местах пакета хранилища `internal/adapter/repo/pocketbase` —
|
||
`applyToRecord`, `recordToJob`, `acquireColumns`, `acquiredRow` с её `toJob`, —
|
||
плюс шаг схемы (инвариант `CLAUDE.md`).
|