Andrey Epifancev 4d30718689 docs: дельты OpenSpec, design.md, README и CLAUDE.md под контракт 2.0 (#54)
I-3: дельты osv-parsing (листы ОСВ, иерархия, golden, XLS_NO_CODEPAGE) и
перечень словарей api/rules; пункт об архивации (Purpose спек) в tasks.md.
I-4: README, CLAUDE.md и docstring hierarchy.py совпадают с кодом, пример
вывода CLI переснят. M-6: tasks.md, proposal.md, design.md с решениями P4/R.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
2026-10-18 22:08:30 +04:00

75 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## MODIFIED Requirements
### Requirement: Результат по контракту
После подтверждения сервис SHALL прочитать объект `s3.key` из бакета `s3.bucket`, вызвать парсер, записать файл результата
(по `recognition-result.schema.json` v2.0, `schema_version = "2.0"`) в `results/{file_id}/{job_id}/result.json` и
опубликовать конверт результата (по `recognition-result-message.schema.json` v2.0, `result_schema_version = "2.0"`) с
заголовком `Nats-Msg-Id = result:<job_id>:<attempt>`. Ack `processing` SHALL нести `schema_version = "2.0"`; запрос
`recognition-request` остаётся v1.0. `service.statements_version` файла результата SHALL быть коммитом копии пакета
`arm_statements` (файл `VERSION` копии; сервис без него не стартует) и записываться при наличии `statementDocument`.
`metrics.warnings` конверта SHALL содержать предупреждения уровня файла структурами `{code, severity, message, locator?}`,
не строками. Статус конверта — по общему правилу: `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня
`error`. При неудаче публикации сервис SHALL повторить её до трёх раз с паузой, затем залогировать ошибку.
#### Scenario: Запрос с заглушкой парсера (Срез 0)
- **WHEN** воркеру явно передана заглушка `NullParser` (только тесты воркера; реализация по умолчанию — реальный парсер),
запрос подтверждён и объект существует в `uploads/`
- **THEN** в `files.tabular_parsed` появляется конверт `status = error`, `error.code = NOT_IMPLEMENTED`, `job_id`/`file_id`
из запроса, `metrics.duration_ms ≥ 0`; файл результата может отсутствовать
#### Scenario: Запрос с реальным парсером
- **WHEN** запрос подтверждён и объект в `uploads/` существует, но не является книгой Excel (сигнатура не XLS/XLSX/XLSB)
- **THEN** в `files.tabular_parsed` появляется конверт `status = error`, `error.code = UNSUPPORTED_FORMAT` (повреждённая
книга — `UNREADABLE`), `job_id`/`file_id` из запроса, `metrics.duration_ms ≥ 0`; файл результата может отсутствовать;
корректная ОСВ в том же потоке даёт `done` (см. «Парсер — заменяемый интерфейс»)
#### Scenario: Результат и ack версии 2.0
- **WHEN** обработан запрос `schema_version = "1.0"` с книгой, содержащей баланс
- **THEN** ack `processing` и конверт результата несут `schema_version = "2.0"`, `result_schema_version = "2.0"`, файл
результата проходит схему v2.0, `service.statements_version` равен коммиту копии `arm_statements`
#### Scenario: Предупреждение файла структурой
- **WHEN** в книге лист «Примечания» без признаков ОСВ и формы
- **THEN** `metrics.warnings` конверта содержит `{code: "SHEET_OTHER", severity: "info", message, locator: "sheet:Примечания"}`
#### Scenario: Повтор публикации
- **WHEN** первая публикация конверта результата завершается ошибкой соединения, вторая — успешно
- **THEN** конверт опубликован один раз с тем же `Nats-Msg-Id`, обработка не повторяется
### Requirement: Парсер — заменяемый интерфейс
Разбор файла SHALL выполняться через единый интерфейс «байты + тип файла → документы/метрики/ошибка», не знающий о
NATS и S3; воркер MUST NOT зависеть от конкретной реализации. Реализация по умолчанию (`get_parser()`) SHALL быть
оркестратором книги `BookParser`: он читает книгу один раз, классифицирует листы по содержимому и отдаёт листы ОСВ
конвейеру спеки `osv-parsing`, листы форм и их продолжения — конвейеру спеки `statement-parsing` (адаптер Excel и копия
пакета `arm_statements`); прочие листы дают предупреждение файла `SHEET_OTHER`. Файл результата SHALL писаться с
`schema_version = "2.0"`; документы книги — ОСВ в порядке листов, затем формы в порядке листов. Заглушка `NullParser`
(`error NOT_IMPLEMENTED`) SHALL использоваться только в тестах воркера и MUST NOT быть реализацией по умолчанию.
Реализация по умолчанию SHALL создаваться один раз при старте приложения (словари ОСВ читаются и проверяются там же,
словари форм — `default_rules()` пакета). Каталог `api/rules/` SHALL содержать словари ОСВ (`osv_headers`, `osv_groups`,
`periods`, `data_types`, `osv_checks`) и README; словарей форм ББ/ОФР в сервисе нет — они часть копии `arm_statements`.
#### Scenario: Подмена реализации в тесте
- **WHEN** в тесте воркеру передан парсер, возвращающий один `osvDocument`
- **THEN** файл результата с этим документом записан в S3, конверт имеет `status = done` и `result_s3` на этот файл
#### Scenario: Реальная ОСВ через воркер
- **WHEN** в интеграционном тесте в S3 лежит `rb/2024-FY/ОСВ_66 за 2024.xls` и опубликован запрос `file_type = XLS`
- **THEN** после ack публикуется конверт `done` с `result_schema_version = "2.0"`, файл результата проходит схему v2.0 и
содержит один `osvDocument` со `status = done` и `account_code_raw = "66"`
#### Scenario: Реальные формы через воркер
- **WHEN** в интеграционном тесте в S3 лежит книга `statements-xlsx/npz/2025-Q3/…xlsx` и опубликован запрос `file_type = XLSX`
- **THEN** после ack публикуется конверт `done` с `result_schema_version = "2.0"`, файл результата проходит схему v2.0 и
содержит два `statementDocument` (`BALANCE`, `INCOME`) со строками `line_status`, `ref`, `checks` и пустыми
`checks.relations_failed`; расхождения итогов в пределах округления (`1600 = 1700` на 1.94 / 9.02 / 2.48, `2100`/`2200`/
`2300`) — в `soft_relations_failed` (OQ-138); `service.statements_version` равен коммиту копии пакета
#### Scenario: Книга без ОСВ и форм
- **WHEN** в S3 лежит книга Excel, ни один лист которой не имеет признаков ОСВ или формы
- **THEN** публикуется конверт `error`, `error.code = INVALID_DOCUMENT`, сообщение называет число просмотренных листов
#### Scenario: Сервис без версии общего кода не стартует
- **WHEN** в копии `arm_statements` нет файла `VERSION`
- **THEN** `require_statements_version()` бросает `RuntimeError` с текстом «копия arm_statements без VERSION», сервис не
стартует