Andrey Epifancev e705d75520 feat(parser): контрольные суммы §1.4, статусы, OsvParser как парсер по умолчанию, лимиты
checks.py: тождество сальдо по каждой строке, суммы родителя = Σ детей того же data_type
(CURRENCY/QUANTITY исключены; обороты — по колонкам, сальдо — по колонкам или по нетто при
свёрнутом сальдо родителя), «Итого» = корневой счёт (R-13: нет корня — «Итого» пусто/0;
R-20: нет «Итого» — false и предупреждение).
osv.py: OsvParser — лимит размера, чтение по сигнатуре, листы по содержимому (формы ББ/ОФР и
прочие — пропуск с предупреждением), карта исходов D-6, кооперативный дедлайн, metrics
(rows без TOTAL, warnings с «sheet=<имя> hierarchy=<стратегия>» первой записью по листу).
get_parser(settings, rules_dir=…) → OsvParser (словари грузятся при создании: битый словарь —
падение старта); PARSE_MAX_BYTES / PARSE_TIMEOUT_SECONDS в Settings и .env.*; app.py передаёт
настройки. test_null_parser.py → test_parser_factory.py.
Тесты: юнит-карта статусов, «Испорченная сумма», «Пустая ОСВ», два листа ОСВ, новый синоним
без релиза кода (R-8); property-тест на 66 реальных файлах (все done, < 5 с, схема v1.1).
test_app и интеграционный round-trip ждут ошибку реального парсера вместо NOT_IMPLEMENTED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 14:37:21 +04:00

74 lines
8.9 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.

# Tasks: Срез 2a — парсер ОСВ (tabular-parser)
Каждая задача ≤ половины дня, начинается с тестов, заканчивается зелёным `make check`. Пути — от корня репозитория.
Фикстуры — `../fixtures` (worktree `.wt/slice-2/fixtures`, ветка `feat/4-slice-2a-osv-parser`); контракт v1.1 —
`../docs/contracts` (ветка `feat/9-slice-2a-osv-parser`).
## 1. Основание
- [x] 1.1 Зависимости и фикстуры: `openpyxl==3.1.5`, `xlrd==2.0.2`, `pyxlsb==1.0.10` в `api/requirements.txt`; `make venv`;
`tests/fixtures_helpers.py` (`ARM_KA_FIXTURES`, как в arm-core) и `conftest.py` с фикстурой `osv_files` (все файлы
`../fixtures/osv/**` кроме `synthetic`, параметризация по относительному пути); маркер `fixtures` в деле; `test_layout`
не ломается. Проверка: `pytest -m fixtures --collect-only` показывает 66 параметров.
- [x] 1.2 Контракт v1.1: скопировать `../docs/contracts/recognition-result.schema.json` v1.1 в `api/contracts/`, `OsvRow.extra:
dict[str, str | None] | None`, `CONTRACT_VERSION = "1.1"`; примеры docs проходят; тест идентичности зелёный; воркер пишет
`result_schema_version = "1.1"` (`test_worker` обновлён). Проверка: `make check`.
- [x] 1.3 `grid.py` и читатели: `Cell`, `Sheet`, `MergedRange`, `column_letter`; `readers/xlsx.py` (openpyxl, полное чтение,
`outline_level`, `alignment.indent`, bold), `readers/xls.py` (xlrd `formatting_info`, `rowinfo_map`, XF bold/indent),
`readers/xlsb.py` (pyxlsb, без outline), `readers/__init__.py:read_book(data, file_type) -> tuple[Sheet, ...]` с
определением по сигнатуре и предупреждением при несоответствии; ошибки → `UnreadableError`/`UnsupportedFormatError`.
Тесты: три читателя на одном синтетическом листе дают одинаковые ячейки; реальные xls/xlsx читаются с outline;
`fixtures`-тест: все 66 файлов читаются < 5 с каждый.
- [x] 1.4 Словари: `api/rules/{osv_headers,osv_groups,periods,data_types}.yaml` (содержимое по §1.1/§1.5 и синонимам из §1.0),
`src/parser/rules.py:load_rules(path) -> Rules` (pydantic, компиляция регулярок с нормализацией ё/е и пробелов),
тест на падение при битом словаре. Покрытие словарей — задача 4.3.
## 2. Обнаружение структуры
- [x] 2.1 `detect.py`: `find_osv_sheets`, `find_table_header` (группы по merged-тексту в 1–4 строках, Дебет/Кредит под
группой, свёрнутое сальдо, отсутствие «начала», `right_edge`, `text_col`, `inn_col` по заголовку и по доле 10/12 цифр,
`extra`-колонки). Unit-тесты на сетках: заголовок 1/2/3/4 строки, merged, без «начала», свёрнутое сальдо, КПП между
ИНН и суммами, мусор правее. `fixtures`-тест: `columns` для РБ `E/H/J/L/O/Q`, для Счастливочной xls `G/J/K/N/Q/S` + `inn=E`.
- [x] 2.2 `header.py`: шапка документа (организация, счёт, период, data_type, валюта), `group_schema` вариант 1 и 2, `has_inn`,
период → `period_year`, `period_quarter`, `is_cumulative` по `periods.yaml` (`ASSUMPTION(OQ-02)`). Unit-тесты: обе шапки,
отсутствие полей → `null` + warning, субсчёт 58.03, все строки таблицы §1.5. `fixtures`-тест: у всех 66 файлов
`account_code_raw` совпадает с кодом в имени каталога/файла по регулярке и период распознан.
- [x] 2.3 `hierarchy.py`: стратегии `outline`, `indent`, `account_pattern`, `sums`, каскад с проверкой непротиворечивости,
`parent_row_number`. Unit-тесты на сетках для каждой стратегии и для отказа стратегии (одинаковые уровни); `fixtures`-тест:
`hierarchy=outline` у всех 66.
- [x] 2.4 `classify.py` + `numbers.py`: классификация (ACCOUNT по регулярке, позиция в `group_schema`, OTHER при выходе,
TOTAL, COUNTERPARTY с ИНН, Вал./Кол./код валюты → `data_type`), числа (Decimal, текстовые форматы, скобки, прочерки,
экспонента), `extra`. Unit-тесты по §1.3 и §1.2; `fixtures`-тест: в `schastl …62 за 3 кв 2024` «БП Аренда» → DEPARTMENT,
«МАЛЕЧКИНСКАЯ ПТИЦЕФАБРИКА ООО» с ИНН, физлицо без ИНН.
## 3. Сборка и статусы
- [x] 3.1 `checks.py` + `osv.py`: инварианты §1.4 (±0.01, `null` = 0, CURRENCY/QUANTITY исключены), `checks`, статусы D-6,
`Deadline` и `PARSE_MAX_BYTES`/`PARSE_TIMEOUT_SECONDS` в `Settings` (+ `.env.example`), `OsvParser.parse(data, file_type)
-> ParseOutcome`, `get_parser()` возвращает его, `NullParser` остаётся только для тестов воркера. Unit-тесты: карта
статусов (нет листов, нет заголовка, лимит, таймаут через монкипатч часов, исключение → RECOGNITION_FAILED), испорченная
сумма → `needs_check` с номерами строк. `fixtures`-тест (property): все 66 — `done`, инварианты, корень = счёт шапки.
- [ ] 3.2 Интеграция с воркером: `test_worker` на реальном `OsvParser` с байтами `ОСВ_66 за 2024.xls` (unit, без NATS) и
интеграционный roundtrip (NATS + MinIO): конверт `done`, `result_schema_version = "1.1"`, файл результата проходит схему.
README: раздел «Запуск на файле» (`python -m src.parser.cli <файл>` — печать результата JSON; добавить CLI).
## 4. Синтетика, golden, линтер
- [ ] 4.1 Генератор синтетики `tests/synthetic/generate.py` (детерминированный openpyxl) → `../fixtures/osv/synthetic/`: вариант 2
шапки; без outline (отступы); без outline и отступов; Вал./Кол.; текстовые числа и скобки; два листа ОСВ + посторонний;
без «начала» + свёрнутое сальдо; КПП-колонка; название субсчёта в строке; испорченная сумма; плюс один xlsb, сконвертированный
вручную из синтетического xlsx и закоммиченный с пометкой в README fixtures. Тесты сценариев спеки на этих файлах.
- [ ] 4.2 Golden: `tests/golden_helpers.py` (нормализация результата без volatile-полей), тест сравнения для всех
`fixtures/osv/**` включая синтетику, `make golden-update` (перезапись + diff), первичная запись снимков в
`../fixtures/golden/expected_osv_parse/` после ручной сверки «Итого» трёх файлов (РБ 60 1 кв 2025, Счастливочная 62 3 кв
2024 xls, 58.03) — сверка описывается в отчёте задачи.
- [ ] 4.3 Линтер координат `tests/unit/test_no_coordinates.py` (AST по `src/parser/**` кроме `grid.py`, `readers/`) и тест
покрытия словарей (каждая запись сработала хотя бы раз на фикстурах+синтетике; список несработавших — в сообщении).
## 5. Завершение
- [ ] 5.1 Документы: README (архитектура парсера, статусы и коды, словари, golden-процесс, лимиты), CLAUDE.md (модули, правила
расширения), `rules/README.md`; `make check-full` (lint, unit, fixtures, integration, docker) зелёный, вывод — в PR.
Проверка: сценарии всех требований `osv-parsing` имеют тест (таблица в отчёте задачи).
- [ ] 5.2 После merge: `openspec archive slice-2a-osv-parser`.