Архив OpenSpec retry-recognition-and-edit-osv-header (PR #161) #176
@ -179,3 +179,37 @@ credit_account_ids, cells}`; для `PRESET` в `after_value` SHALL быть д
|
||||
#### Scenario: Нет счёта статьи
|
||||
- **WHEN** выгрузка отвечает 422 `RESULT_ACCOUNTS_MISSING`
|
||||
- **THEN** событие `EXPORT` не записано
|
||||
|
||||
### Requirement: Аудит ручной повторной обработки файла
|
||||
Каждая принятая ручная попытка SHALL писать одно audit_event с action = FILE_REPROCESS,
|
||||
section = PRIMARY_DOCUMENTS, object_type = stored_file, object_id = file_id, user_id инициатора,
|
||||
IP и User-Agent запроса. before_value SHALL содержать прежние job_id, attempt, status задания, error_code;
|
||||
after_value — новые job_id, attempt, а при пакетном повторе также consolidation_id. Событие SHALL фиксироваться в транзакции создания попытки.
|
||||
|
||||
#### Scenario: Пакетный повтор
|
||||
- **WHEN** пакет принял два файла ОСВ РБ и пропустил третий
|
||||
- **THEN** два FILE_REPROCESS с разными object_id и прежними/новыми job_id, с consolidation_id в after_value;
|
||||
before_value.status — статус прежнего задания (timeout или dlq), error_code — TIMEOUT или NO_ACK; для skipped события нет
|
||||
|
||||
#### Scenario: Отказ или откат
|
||||
- **WHEN** повтор отклонён либо транзакция создания задания откатывается
|
||||
- **THEN** FILE_REPROCESS для него отсутствует
|
||||
|
||||
#### Scenario: Автоматическая повторная публикация
|
||||
- **WHEN** outbox повторно публикует прежнее задание без ack, не создавая новую попытку
|
||||
- **THEN** FILE_REPROCESS не пишется
|
||||
|
||||
### Requirement: Аудит уточнения шапки ОСВ
|
||||
Фактический PATCH шапки ОСВ SHALL писать одно RECOGNIZED_DATA_EDIT раздела RECOGNITION,
|
||||
object_type = document, object_id документа, user_id инициатора. before/after SHALL содержать изменённые
|
||||
поля и при сбросе подтверждения status, confirmed_by/at; after_value.unbound_version_ids SHALL содержать
|
||||
версии снятых привязок. Запрос без изменений MUST NOT писать аудит.
|
||||
|
||||
#### Scenario: Подтверждённая ОСВ исправлена
|
||||
- **WHEN** меняется счёт CONFIRMED ОСВ РБ, привязанной к версии DATA_CORRECTION
|
||||
- **THEN** одно RECOGNIZED_DATA_EDIT с прежним/новым счётом и подтверждением, after_value содержит
|
||||
unbound_version_ids, пользователь, IP и User-Agent сохранены
|
||||
|
||||
#### Scenario: Возврат к исходному
|
||||
- **WHEN** аналитик возвращает исходные значения после предыдущей правки
|
||||
- **THEN** новое RECOGNIZED_DATA_EDIT отражает эту правку; старые события не меняются
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Схема БД равна DBML v5.19
|
||||
ORM-метаданные SHALL содержать ровно 44 таблицы и 33 enum'а из `../docs/model/data-model-v5.19.dbml` в схеме PostgreSQL
|
||||
### Requirement: Схема БД равна DBML v5.21
|
||||
ORM-метаданные SHALL содержать ровно 44 таблицы и 33 enum'а из `../docs/model/data-model-v5.21.dbml` в схеме PostgreSQL
|
||||
из настройки `DB_SCHEMA` (по умолчанию `dkb`; в контуре — `OPENBAO__APP__DB_SCHEMA`; простой идентификатор
|
||||
`[a-z_][a-z0-9_]{0,62}`, не начинающийся с `pg_`, иначе приложение не стартует); сверка с DBML идёт в эталонной схеме `dkb`;
|
||||
имена таблиц, колонок, enum'ов и их значений SHALL совпадать с DBML буквально; nullability, PK, UNIQUE, FK, значения
|
||||
@ -31,9 +31,12 @@ SHALL добавлять обе колонки (существующие зап
|
||||
(год, затем квартал); файлы без привязок остаются `NULL`; откат SHALL удалять индекс и колонку. Поля, отсутствующие в
|
||||
DBML, MUST NOT существовать.
|
||||
Схема v5.19 (RSHB/arm-ka-docs#116) SHALL добавлять: таблицу `document_issue` и enum'ы `statement_line_status`, `statement_form_variant`, `document_issue_kind` и прочие enum'ы DBML v5.19; колонки `document.file_ordinal` (уникальна в паре `(stored_file_id, file_ordinal)`), `recognition_job.statements_version`, `related_group.crm_id` (`varchar`, nullable, уникальна), в `financial_statement_meta` — `form_variant`, `needs_review_fields`, `header_source`, `organization_name_original_value`, `organization_name_modified`; в `financial_statement_column` — `period_kind`, `label_confidence`; в `financial_statement_line` — `row_index`, `source_ref`, `line_status`, `line_code_text`, `parent_row_index`, `needs_review`, `is_confirmed`; в `financial_statement_value` — `source_text`, `needs_review`, `is_confirmed` (с `confirmed_by/at`). Уникальность `(meta_id, line_code)` снимается, добавляется частичная уникальность `(meta_id, source_ref)` WHERE `source_ref IS NOT NULL`; уникальность документа `(stored_file_id, document_type, osv_account_code)` становится частичной (только для типов, кроме `BALANCE` / `INCOME`). Состав и порядок колонок — буквально по DBML v5.19.
|
||||
Схема v5.20 SHALL добавлять в конец `document` колонки `is_modified` (`boolean NOT NULL DEFAULT false`), `original_values`
|
||||
(`jsonb`, nullable), `modified_by` (`uuid`, nullable, FK → `app_user.id`) и `modified_at` (`timestamptz`, nullable); схема v5.21 SHALL
|
||||
добавлять значение `FILE_REPROCESS` в enum `audit_action`. Состав и порядок колонок — буквально по DBML v5.21.
|
||||
|
||||
#### Scenario: Сверка ORM с DBML
|
||||
- **WHEN** выполняется `python scripts/check_dbml_vs_orm.py ../../docs/model/data-model-v5.19.dbml` (`make dbml-check`)
|
||||
- **WHEN** выполняется `python scripts/check_dbml_vs_orm.py ../../docs/model/data-model-v5.21.dbml` (`make dbml-check`)
|
||||
- **THEN** вывод пуст и код возврата 0
|
||||
|
||||
#### Scenario: Лишняя колонка в ORM
|
||||
@ -64,7 +67,7 @@ DBML, MUST NOT существовать.
|
||||
`stored_file`; `alembic downgrade 0005_dbml_v5_17` удаляет индекс и колонку, повторный `upgrade` их возвращает
|
||||
|
||||
#### Scenario: Сверка с DBML v5.19
|
||||
- **WHEN** выполняется `make dbml-check` по `../docs/model/data-model-v5.19.dbml`
|
||||
- **WHEN** выполняется `make dbml-check` по `../docs/model/data-model-v5.21.dbml`
|
||||
- **THEN** таблицы, колонки, enum'ы, ключи и индексы ORM совпадают с DBML без расхождений
|
||||
|
||||
### Requirement: Миграции применяются с нуля и откатываются
|
||||
@ -99,11 +102,11 @@ enum'ы, таблицы, ограничения, индексы и послед
|
||||
- **THEN** сгенерированная ревизия не содержит операций (пустые `upgrade`/`downgrade`)
|
||||
|
||||
### Requirement: Версия DBML зафиксирована в коде
|
||||
Пакет моделей SHALL объявлять константу версии DBML (`DBML_VERSION = "5.19"`); скрипт сверки SHALL сравнивать её с версией в шапке файла
|
||||
Пакет моделей SHALL объявлять константу версии DBML (`DBML_VERSION = "5.21"`); скрипт сверки SHALL сравнивать её с версией в шапке файла
|
||||
DBML и падать при расхождении — смена модели без осознанного обновления ORM невозможна.
|
||||
|
||||
#### Scenario: Новая версия DBML без обновления ORM
|
||||
- **WHEN** в `docs/model/` появляется `data-model-v5.20.dbml`, а константа в ORM — `5.19`
|
||||
- **WHEN** в `docs/model/` появляется `data-model-v5.22.dbml`, а константа в ORM — `5.21`
|
||||
- **THEN** сверка против нового файла падает с сообщением о версии до сравнения таблиц
|
||||
|
||||
### Requirement: Деньги и проценты — точные типы
|
||||
@ -161,3 +164,35 @@ organization_name`, `document.file_ordinal` — по `(created_at, id)` внут
|
||||
#### Scenario: Откат после подтверждений
|
||||
- **WHEN** в БД нет несовместимых данных, но у ячеек есть `is_confirmed = true` и записи `document_issue`
|
||||
- **THEN** `downgrade` проходит, удаляет колонки и таблицу и пишет в журнал число удалённых подтверждений и записей
|
||||
|
||||
### Requirement: Миграция DBML v5.20
|
||||
Одна добавочная миграция после 0007_dbml_v5_19 SHALL добавить четыре колонки document по DBML v5.20; существующие записи получают is_modified = false и остальные поля NULL. Миграция MUST NOT менять документы, их строки, подтверждения и привязки. Downgrade SHALL до любых изменений проверить отсутствие document.is_modified = true и при наличии остановиться с ошибкой и числом записей.
|
||||
|
||||
#### Scenario: Обновление существующих ОСВ
|
||||
- **WHEN** миграция применяется к базе с подтверждёнными ОСВ РБ
|
||||
- **THEN** добавлены четыре поля, данные ОСВ, id, file_ordinal, подтверждения и привязки сохранены; сверка с DBML v5.20 проходит
|
||||
|
||||
#### Scenario: Защита от потери исправлений
|
||||
- **WHEN** хотя бы один документ имеет is_modified = true и запрошен downgrade до v5.19
|
||||
- **THEN** откат прекращается до DDL с причиной и числом записей, схема и данные не меняются
|
||||
|
||||
#### Scenario: Пустая база и нестандартная схема
|
||||
- **WHEN** выполняется upgrade head, downgrade base, upgrade head на пустой базе с настроенным DB_SCHEMA
|
||||
- **THEN** цикл успешен, ORM соответствует DBML v5.20, autogenerate пуст
|
||||
|
||||
### Requirement: Миграция DBML v5.21 и действие аудита повторной обработки
|
||||
Отдельная миграция v5.21 после миграции v5.20 SHALL добавить только FILE_REPROCESS в enum audit_action
|
||||
по DBML v5.21. Миграция SHALL сохранять все существующие значения и события. Downgrade MUST NOT удалять либо
|
||||
переписывать audit_event; при наличии FILE_REPROCESS он SHALL завершаться ошибкой до изменения схемы.
|
||||
|
||||
#### Scenario: Схема после дополнения модели
|
||||
- **WHEN** миграция v5.21 применена к БД v5.20
|
||||
- **THEN** событие нового типа записывается, прежние события читаются без изменений, сверка DBML успешна
|
||||
|
||||
#### Scenario: Откат после ручного повтора
|
||||
- **WHEN** существует audit_event с FILE_REPROCESS и выполняется downgrade с v5.21 к v5.20
|
||||
- **THEN** ошибка с причиной и количеством несовместимых событий до DDL, события и схема сохранены
|
||||
|
||||
#### Scenario: Независимый откат enum
|
||||
- **WHEN** в БД v5.21 нет FILE_REPROCESS и выполняется downgrade к v5.20
|
||||
- **THEN** удалено только новое значение enum; четыре поля document, исходные значения и привязки сохранены
|
||||
|
||||
@ -568,3 +568,112 @@ locator?}` (`fileWarning` контракта): у `2.0` — как пришли,
|
||||
#### Scenario: Нарушение с пустой целью в ответе
|
||||
- **WHEN** у формы запись `relation` по `1700 = 1300 + 1400 + 1500` с `expected = NULL`
|
||||
- **THEN** элемент `checks.failed` имеет `expected = null`, `actual` — сумма членов
|
||||
|
||||
### Requirement: Уточнение шапки ОСВ
|
||||
`PATCH /api/v1/documents/{id}/osv-header` SHALL требовать `DOCUMENT_EDIT` и видимость ЮЛ, принимать частичный набор
|
||||
`period_year`, `period_quarter`, `is_cumulative`, `osv_account_code` и возвращать карточку документа с
|
||||
`is_modified`, `original_values`, `modified_by`, `modified_at`. Отсутствующие поля SHALL сохраняться.
|
||||
Правила — §9.5 п.12. Чужой и несуществующий документ SHALL возвращать одинаковый 404.
|
||||
|
||||
#### Scenario: Изменение только счёта
|
||||
- **WHEN** аналитик меняет счёт ОСВ РБ с 60 на 62, не передавая период
|
||||
- **THEN** меняется только счёт; даже неопределённый квартал остаётся прежним
|
||||
|
||||
#### Scenario: Нет доступа
|
||||
- **WHEN** пользователь без DOCUMENT_EDIT правит шапку либо документ находится вне видимости ЮЛ
|
||||
- **THEN** без права возвращается 403, при наличии права и отсутствии видимости — 404; данные не меняются
|
||||
|
||||
### Requirement: Проверки правки шапки ОСВ
|
||||
После проверки типов тела запроса Pydantic PATCH SHALL проверять последовательно: тип OSV (иначе 409 DOCUMENT_NOT_OSV), файл не SUPERSEDED (иначе 409
|
||||
FILE_SUPERSEDED), корректность периода (422 INVALID_PERIOD), счёта (422 INVALID_ACCOUNT_CODE), отсутствие
|
||||
другой ОСВ с тем же нормализованным счётом в файле (409 DUPLICATE_ACCOUNT с detail.document_id),
|
||||
редактируемость привязанных версий (409 VERSION_NOT_EDITABLE с detail.version_ids). Все проверки SHALL
|
||||
предшествовать записи.
|
||||
|
||||
#### Scenario: Частичная правка периода
|
||||
- **WHEN** передано хотя бы одно поле периода
|
||||
- **THEN** итоговые год, квартал и is_cumulative обязательны: год 1900–2099, квартал Q1–Q4, признак boolean;
|
||||
для Q1 признак true; иначе 422 INVALID_PERIOD без изменений
|
||||
|
||||
#### Scenario: Нормализация и коллизия
|
||||
- **WHEN** передано 60.1, а у другой ОСВ этого файла уже счёт 60.01
|
||||
- **THEN** нормализация даёт 60.01, возвращается 409 DUPLICATE_ACCOUNT с id другого документа
|
||||
|
||||
#### Scenario: Пустой счёт
|
||||
- **WHEN** передан пустой счёт
|
||||
- **THEN** 422 INVALID_ACCOUNT_CODE, прежний счёт сохранён
|
||||
|
||||
#### Scenario: Порядок конфликтов
|
||||
- **WHEN** у заменённого файла документ BALANCE и некорректный период в теле
|
||||
- **THEN** возвращается DOCUMENT_NOT_OSV; у OSV того же файла — FILE_SUPERSEDED
|
||||
|
||||
### Requirement: Исходная шапка и признак исправления
|
||||
При первой фактической правке ядро SHALL сохранить прежние значения всех четырёх полей в document.original_values;
|
||||
при последующих правках снимок MUST NOT меняться. Новые значения SHALL записываться в ту же строку document,
|
||||
is_modified SHALL отражать отличие от снимка, modified_by/at — последнюю правку. document.id, file_ordinal,
|
||||
строки osv_entry, файл и JSON результата в S3 MUST NOT изменяться.
|
||||
|
||||
#### Scenario: Две правки
|
||||
- **WHEN** счёт ОСВ РБ меняется 60 → 62 → 76
|
||||
- **THEN** текущий счёт 76, original_values.osv_account_code = 60, в original_values также исходный период;
|
||||
is_modified = true, id и количество документов и строк ОСВ прежние
|
||||
|
||||
#### Scenario: Возврат к исходному
|
||||
- **WHEN** все четыре поля возвращены к значениям original_values
|
||||
- **THEN** is_modified = false, original_values остаётся заполненным, последняя правка атрибутирована
|
||||
|
||||
#### Scenario: Запрос без изменений
|
||||
- **WHEN** нормализованная итоговая шапка совпадает с текущей
|
||||
- **THEN** 200 с карточкой без аудита, создания original_values, сброса подтверждения и изменения modified_by/at
|
||||
|
||||
### Requirement: Снятие подтверждения ОСВ после уточнения
|
||||
Фактическая правка CONFIRMED ОСВ SHALL установить NEEDS_CHECK, очистить confirmed_by/at и снять привязки
|
||||
consolidation_document из всех версий «в работе», сохранив привязки CANCELLED и статусы версий.
|
||||
Привязка к иной версии SHALL запретить правку. Неподтверждённый документ SHALL сохранить статус.
|
||||
consolidation_lock MUST NOT требоваться; автоподтверждение после PATCH MUST NOT запускаться.
|
||||
|
||||
#### Scenario: Исправление автоподтверждённой ОСВ
|
||||
- **WHEN** ОСВ РБ привязана к версии DATA_CORRECTION, аналитик исправляет период
|
||||
- **THEN** NEEDS_CHECK, confirmed_by/at = null, привязка снята, версия остаётся DATA_CORRECTION;
|
||||
повторное ручное подтверждение привязывает документ по новому периоду и счёту
|
||||
|
||||
#### Scenario: Завершённая версия
|
||||
- **WHEN** документ привязан к VERIFIED, даже если одновременно привязан к версии «в работе»
|
||||
- **THEN** 409 VERSION_NOT_EDITABLE со списком запрещающих version_ids, ни одна привязка и шапка не меняются
|
||||
|
||||
#### Scenario: Отменённая версия
|
||||
- **WHEN** подтверждённый документ связан только с CANCELLED и версиями «в работе»
|
||||
- **THEN** правка разрешена без consolidation_lock, привязки CANCELLED остаются, остальные снимаются
|
||||
|
||||
### Requirement: Производные данные после уточнения ОСВ
|
||||
Подходящесть версиям, чек-лист и колонка «ОСВ» SHALL отражать новую шапку при следующем чтении;
|
||||
period_check, counterparty_check и строки osv_entry SHALL сохраняться. Правка MUST NOT запускать автозамену
|
||||
другого файла; замещение привязки с тем же ключом происходит при повторном подтверждении по §3 п.1.
|
||||
Перезагрузка файла MUST NOT переносить уточнения старых документов. Флаг CONSOLIDATION_PERIOD_FROM_VERSION сохраняется.
|
||||
|
||||
#### Scenario: Смена счёта шапки
|
||||
- **WHEN** счёт ОСВ РБ исправлен с 60 на 62
|
||||
- **THEN** колонка «ОСВ» и подходящие слоты чек-листа учитывают 62; account_code/account_id/account_root
|
||||
строк ОСВ, period_check и counterparty_check не изменились, другой файл ОСВ 62 не заменён
|
||||
|
||||
#### Scenario: Перезагрузка
|
||||
- **WHEN** новый загруженный файл распознан с исходным счётом 60
|
||||
- **THEN** новые документы созданы из нового результата, исправление 62 предыдущего документа не переносится
|
||||
|
||||
### Requirement: Строгие типы шапки ОСВ
|
||||
PATCH SHALL принимать period_year как integer, period_quarter и osv_account_code как string,
|
||||
is_cumulative как boolean, без приведения типов. Отсутствующие поля не меняются; null SHALL передаваться
|
||||
в доменную проверку явно. Неверные типы SHALL отклоняться стандартным 422 валидации тела до доменных
|
||||
проверок. Границы года, допустимые кварталы и совместимость полей SHALL проверяться доменом.
|
||||
|
||||
#### Scenario: Неверный тип года
|
||||
- **WHEN** передан period_year как строка, дробное число, boolean, массив или объект
|
||||
- **THEN** стандартный 422 с detail-массивом ошибок валидации, доменная обработка не запускается
|
||||
|
||||
#### Scenario: Год вне диапазона
|
||||
- **WHEN** у доступного незаменённого документа OSV передан period_year = 2100
|
||||
- **THEN** тип integer принят, домен возвращает 422 INVALID_PERIOD
|
||||
|
||||
#### Scenario: Неизвестный квартал
|
||||
- **WHEN** у доступного незаменённого документа OSV передан period_quarter = "Q0"
|
||||
- **THEN** тип string принят, домен возвращает 422 INVALID_PERIOD
|
||||
|
||||
@ -60,7 +60,7 @@ Query-параметр `consolidation_version_id` MUST NOT приниматьс
|
||||
- **THEN** `rejected[0].code = DUPLICATE_FILE`, `rejected[0].file_id` — прежний файл; новых записей нет
|
||||
|
||||
### Requirement: Жизненный цикл статуса файла
|
||||
`stored_file.status` SHALL меняться только по заданию `current_recognition_job_id`: `UPLOADED` при загрузке;
|
||||
`stored_file.status` SHALL меняться только по заданию `current_recognition_job_id`: `UPLOADED` при загрузке и при принятом ручном повторе с новым текущим заданием;
|
||||
`PROCESSING` при ack сервиса; `ERROR` при финальной ошибке задания (`error`, `timeout`, `dlq`) и при невозможности
|
||||
маппинга; `PROCESSED` после успешного маппинга; `SUPERSEDED` при автозамене. Карточка файла SHALL показывать причину
|
||||
`ERROR` (`error_code`/`error_message` задания или ошибку маппинга).
|
||||
@ -238,3 +238,63 @@ advisory-блокировка привязок ГК → версии по `id`.
|
||||
#### Scenario: Очистка и повторная загрузка
|
||||
- **WHEN** очистка выполнена, ядро v5.19 выложено, аналитик загружает тот же PDF с ББ и ОФР
|
||||
- **THEN** дедуп не мешает (файла больше нет), формы создаются по мапперу 1.x или 2.0 — смотря что шлёт сервис
|
||||
|
||||
### Requirement: Ручной повтор обработки файла
|
||||
`POST /api/v1/files/{file_id}/retry` SHALL требовать DOCUMENT_EDIT и видимость ЮЛ. Повтор разрешён для
|
||||
текущего задания tabular в timeout/TIMEOUT или dlq/NO_ACK при attempt < max_attempts, отсутствии документов
|
||||
и файле ERROR. Успех SHALL вернуть 202 с карточкой файла и новым текущим заданием. Несуществующий или чужой файл — 404.
|
||||
|
||||
#### Scenario: После таймаута
|
||||
- **WHEN** табличный файл ОСВ РБ завершился timeout/TIMEOUT, attempt = 1, max_attempts = 3
|
||||
- **THEN** 202, создано новое задание attempt = 2, прежнее осталось, в карточке новое текущее задание
|
||||
|
||||
#### Scenario: Неподходящее состояние
|
||||
- **WHEN** повтор запрошен для OCR, done, needs_check, error сервиса, ошибки маппинга либо файла с документами
|
||||
- **THEN** 409 FILE_NOT_RETRYABLE без нового задания и аудита
|
||||
|
||||
#### Scenario: Лимит исчерпан
|
||||
- **WHEN** подходящее задание имеет attempt = max_attempts
|
||||
- **THEN** 409 MAX_ATTEMPTS_REACHED, состояние не меняется
|
||||
|
||||
### Requirement: Пакет повторной обработки версии
|
||||
`POST /api/v1/consolidations/versions/{version_id}/files/retry` SHALL выбирать видимые ERROR-файлы
|
||||
консолидации по периметру версии. Другие статусы не входят в пакет и не блокируются.
|
||||
Ответ — 202 с accepted[{file_id, job_id, attempt}] и skipped[{file_id, code}]. В skipped SHALL входить
|
||||
только ERROR-файлы, которые нельзя повторить (FILE_NOT_RETRYABLE, MAX_ATTEMPTS_REACHED).
|
||||
Файлы без контекста консолидации MUST NOT включаться по совпадению ЮЛ.
|
||||
|
||||
#### Scenario: Смешанный пакет
|
||||
- **WHEN** у версии есть файл timeout/TIMEOUT, OCR, успешный файл и файл с исчерпанным лимитом
|
||||
- **THEN** новый job создаётся только для первого; OCR-файл в skipped с `FILE_NOT_RETRYABLE`, файл с исчерпанным
|
||||
лимитом — с `MAX_ATTEMPTS_REACHED`; успешный (`PROCESSED`) файл не попадает ни в accepted, ни в skipped
|
||||
|
||||
#### Scenario: Периметр и контекст
|
||||
- **WHEN** файл загружен в другую консолидацию либо его ЮЛ не входит в выбранную версию
|
||||
- **THEN** файл не участвует ни в accepted, ни в skipped этой версии
|
||||
|
||||
#### Scenario: Общие файлы разных версий
|
||||
- **WHEN** две версии одной консолидации содержат одно ЮЛ и вызывают пакетный повтор
|
||||
- **THEN** они используют общий набор файлов консолидации; уже повторяемый файл второй раз не запускается
|
||||
|
||||
#### Scenario: Пустой пакет
|
||||
- **WHEN** подходящих файлов нет
|
||||
- **THEN** 202 с пустым accepted; в skipped — только файлы в `ERROR`, которые повторить нельзя (без них список пуст);
|
||||
аудита повторов нет
|
||||
|
||||
### Requirement: Ограничения версии при повторе
|
||||
Повтор в контексте версии SHALL разрешаться только для версий «в работе» (INITIATED, DOCUMENTS_PROCESSING,
|
||||
DATA_CORRECTION). Иначе SHALL возвращаться 409 VERSION_NOT_EDITABLE. consolidation_lock MUST NOT требоваться.
|
||||
Одиночный повтор файла с consolidation_id SHALL требовать наличия версии «в работе» с ЮЛ файла в периметре;
|
||||
без consolidation_id SHALL применяться только policy ЮЛ. Повтор MUST NOT изменять статусы версий.
|
||||
|
||||
#### Scenario: Проверенная версия
|
||||
- **WHEN** пакет запрошен для VERIFIED
|
||||
- **THEN** 409 VERSION_NOT_EDITABLE, ни один файл не изменён
|
||||
|
||||
#### Scenario: Консолидация уже закрыта для обработки
|
||||
- **WHEN** одиночный повтор файла консолидации запрошен без версии «в работе» с его ЮЛ
|
||||
- **THEN** 409 VERSION_NOT_EDITABLE; другая версия консолидации не открывает недоступный пакет версии
|
||||
|
||||
#### Scenario: Сохранение статуса
|
||||
- **WHEN** пакет допустимой версии INITIATED принят без пользовательской блокировки
|
||||
- **THEN** попытки созданы, INITIATED сохраняется; дальнейшие переходы происходят по существующим событиям
|
||||
|
||||
@ -82,8 +82,7 @@
|
||||
### Requirement: Таймаут ожидания результата
|
||||
Фоновый вахтёр SHALL с периодом 30 с переводить задания `processing` и `published` с `reply_deadline < now()` в
|
||||
`status = timeout`, `error_code = TIMEOUT`, `finished_at = now()`, а их файлы (по `current_recognition_job_id`) — в
|
||||
`stored_file.status = ERROR`. Публикатор при переводе задания в `dlq` SHALL так же переводить файл в `ERROR`. Replay
|
||||
задания (новая попытка, `attempt + 1`) — вне этого среза.
|
||||
`stored_file.status = ERROR`. Публикатор при переводе задания в `dlq` SHALL так же переводить файл в `ERROR`. Ручной повтор после TIMEOUT/NO_ACK SHALL создавать новое задание по требованию «Новая попытка обработки файла».
|
||||
|
||||
#### Scenario: Сервис взял, но не ответил
|
||||
- **WHEN** задание в `processing` и `reply_deadline` прошёл без результата
|
||||
@ -162,3 +161,38 @@ Inbox SHALL принимать конверты (включая ack `processing`
|
||||
#### Scenario: Результат 2.0 разбирает маппер 2.0
|
||||
- **WHEN** inbox принял файл результата со `schema_version = "2.0"`
|
||||
- **THEN** документы создаёт маппер результата `2.0`; результат `1.x` по-прежнему разбирает маппер `1.x`
|
||||
|
||||
### Requirement: Новая попытка обработки файла
|
||||
Ручной повтор SHALL создать recognition_job с новым id, attempt = прежний attempt + 1, тем же file_id,
|
||||
pipeline и max_attempts, статусом queued. Предыдущая запись SHALL сохраняться без изменений.
|
||||
Файл SHALL ссылаться на новое задание и получить UPLOADED. Ссылка S3 на исходник и consolidation_id сохраняются;
|
||||
новых документов, файлов и копий исходника SHALL NOT создаваться.
|
||||
|
||||
#### Scenario: Повтор после NO_ACK
|
||||
- **WHEN** tabular-задание ОСВ РБ завершилось dlq/NO_ACK с attempt = 1
|
||||
- **THEN** новое задание queued имеет attempt = 2, файл UPLOADED с прежним id и объектом S3,
|
||||
current_recognition_job_id указывает на новый id
|
||||
|
||||
### Requirement: Изоляция новой попытки
|
||||
Новое задание SHALL начинаться без ack, результата, ошибок, метрик и времён старого задания, со свежим
|
||||
requested_at. Публикатор SHALL вычислить новый reply_deadline и Nats-Msg-Id для нового job_id/attempt.
|
||||
Запоздавшие ack/результаты прежней финальной попытки MUST NOT менять файл, создавать документы или
|
||||
мешать обработке нового задания.
|
||||
|
||||
#### Scenario: Старый ответ
|
||||
- **WHEN** после создания попытки 2 пришли ack и done для попытки 1 в timeout
|
||||
- **THEN** текущее задание, статус файла и документы не меняются; ответ логируется и игнорируется
|
||||
|
||||
#### Scenario: Публикация после недоступности NATS
|
||||
- **WHEN** ручной повтор принят, а NATS временно недоступен
|
||||
- **THEN** задание остаётся queued; после восстановления публикуется со свежим сроком и новым Nats-Msg-Id
|
||||
|
||||
### Requirement: Конкурентные запросы повтора
|
||||
Проверка текущего задания и лимита, создание новой попытки и переключение файла SHALL быть атомарны.
|
||||
Два запроса к одной неуспешной попытке MUST NOT создавать два новых задания. При rollback новое задание,
|
||||
переключение файла и аудит SHALL откатываться вместе.
|
||||
|
||||
#### Scenario: Двойное нажатие
|
||||
- **WHEN** два параллельных запроса повторяют attempt = 1
|
||||
- **THEN** создан ровно один job attempt = 2; второй одиночный запрос получает 409 FILE_NOT_RETRYABLE,
|
||||
а пакетный отмечает файл skipped; attempt = 3 не создаётся
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user