Срез 2a: парсер ОСВ из XLS/XLSX/XLSB — динамическое обнаружение структуры (§1), контракт v1.1, golden на 66 реальных файлах #6

Merged
aep merged 45 commits from feat/5-slice-2a-osv-parser into main 2026-10-05 19:18:07 +03:00
Owner

Closes #5. Спека и реализация подсреза 2a (решение владельца 05.10.2026: только парсер ОСВ). 45 коммитов: 14 задач плана + финальное ревью (opus) и волны правок.

Что в PR

  • Парсер ОСВ api/src/parser/: модель листа Sheet и читатели xlsx (openpyxl), xls (xlrd), xlsb (pyxlsb); детекторы листов/заголовка/колонок по содержимому; шапка и group_schema (варианты 1 и 2); каскад иерархии outline → indent → account_pattern → sums (+ trivial для пустой ОСВ); классификация строк по group_schema, буквенные субсчета (76.АВ), ИНН из колонки ИНН; Decimal из числовых и текстовых ячеек; контрольные суммы §1.4 → done / needs_check; карта статусов и кодов; лимиты PARSE_MAX_BYTES 50 МиБ, PARSE_TIMEOUT_SECONDS 120 (кооперативный дедлайн), предсканирование xlsx (несжатый размер, площадь merged, expat по всем частям архива) и потолок крупных merged в xls.
  • Словари api/rules/*.yaml (заголовки, группировки, периоды, типы данных и валюты) с pydantic-валидацией и тестом покрытия; AST-линтер «нет координат в src/parser/».
  • Контракт v1.1 (rows[].extra), CONTRACT_VERSION = "1.1", копия схемы и примеров.
  • Воркер: get_parser() → OsvParser, CLI python -m src.parser.cli <файл>, интеграционный сценарий «реальная ОСВ через воркер».
  • Тесты: 1818 unit (включая property-тесты инвариантов на всех 66 реальных ОСВ и 16 синтетических), golden 82 снимка (make golden-check/golden-update), fixtures 808, integration 9. Ledger — docs/superpowers/plans/2026-10-05-slice-2a-osv-parser-ledger.md.

Проверки (HEAD 1eb57da)

make check: ruff/format/mypy чисто, 1818 passed; make golden-check: 82/82 без изменений; pytest -m fixtures: 808 passed; make test-integration: 9 passed; openspec validate --strict: valid. Самый долгий реальный файл ≈ 0,6 с (лимит 5 с).

Финальное ревью (opus): Critical — нет; Important I-1 (лимиты чтения xlsx) закрыт в три волны, I-2 закрыт; Minor M-1, M-3 закрыты, M-2, M-4…M-7 отложены (OQ-106…109).

Rulings I made (design.md №11–39, ключевые)

  • R-12: заголовок обязан содержать «обороты» и «сальдо на конец», отсутствовать может только «начало».
  • R-13: пустая ОСВ штатна (hierarchy=trivial); R-14: «Итого» всегда уровень 0; R-20: без «Итого» → needs_check.
  • R-16: буквенные сегменты субсчетов (76.АВ/ВА/ОТ) — счета; R-17: ИНН только из колонки ИНН, ИНН в тексте строки не извлекается (OQ-100).
  • R-19: строки Вал./Кол./код валюты — OTHER с data_type CURRENCY/QUANTITY, без наследования контрагента.
  • R-21 (самое рискованное): сверка «сумма детей = родитель» по сальдо идёт по нетто, если у родителя сальдо свёрнуто (так выгружает 1С; без этого 32 из 66 файлов не сходились). Слепая зона: перестановка сторон при совпавшем нетто ловится только тождеством строки и «Итого = корень» (OQ-105).
  • R-23/R-25/№39: предсканирование xlsx — эшелон защиты, граница безопасности — лимит памяти платформы (OQ-108); defusedxml не добавлен (решение платформы).
  • R-9: xlsb в синтетике собран рукописным писателем BIFF12 (нет Excel/LibreOffice) — заменить на реальный (OQ-102).

Deferred (в follow-up issue при закрытии)

  • Платформа: лимит памяти/RLIMIT воркера, defusedxml, версия expat в образе (OQ-108); размер объекта S3 до скачивания (M-7, OQ-109); реальный xlsb из Excel (OQ-102).
  • Аналитика: OQ-98…OQ-107 (коды валют, несколько ОСВ одного счёта в книге, ИНН в тексте, числа «+5»/«.5», буквенные первые сегменты «МЦ.04», «Валюта» в детализации, нетто-свёртка, несколько корней, hierarchy=sums).
  • Парсер: M-2 (числа после «Итого»), T12.1/O-8, T3.4 NFC, T3.5, T3.7, T5.6/T5.8/T5.9, T7.5, T8.6, T2.2…T2.5, O-4, T6.6, алгоритмическая граница sums.
  • Тесты/CLI: M-6/T3.8 слепые зоны линтера, T10.1…T10.5, T1.2, T1.3, T9.2…T9.4, T11.3…T11.8, T12.6, T13.8.

Порядок merge: RSHB/arm-ka-fixtures#5 и RSHB/arm-ka-docs#10 — до этого PR (golden и тест идентичности контракта). После merge — bounded-PR копии контракта v1.1 в arm-core и правка Purpose в openspec/specs/tabular-recognition-service при архивировании.

🤖 Generated with Claude Code

https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2

Closes #5. Спека и реализация подсреза 2a (решение владельца 05.10.2026: только парсер ОСВ). 45 коммитов: 14 задач плана + финальное ревью (opus) и волны правок. ## Что в PR - **Парсер ОСВ** `api/src/parser/`: модель листа `Sheet` и читатели xlsx (openpyxl), xls (xlrd), xlsb (pyxlsb); детекторы листов/заголовка/колонок по содержимому; шапка и `group_schema` (варианты 1 и 2); каскад иерархии outline → indent → account_pattern → sums (+ `trivial` для пустой ОСВ); классификация строк по `group_schema`, буквенные субсчета (76.АВ), ИНН из колонки ИНН; Decimal из числовых и текстовых ячеек; контрольные суммы §1.4 → `done` / `needs_check`; карта статусов и кодов; лимиты `PARSE_MAX_BYTES` 50 МиБ, `PARSE_TIMEOUT_SECONDS` 120 (кооперативный дедлайн), предсканирование xlsx (несжатый размер, площадь merged, expat по всем частям архива) и потолок крупных merged в xls. - **Словари** `api/rules/*.yaml` (заголовки, группировки, периоды, типы данных и валюты) с pydantic-валидацией и тестом покрытия; **AST-линтер** «нет координат в `src/parser/`». - **Контракт v1.1** (`rows[].extra`), `CONTRACT_VERSION = "1.1"`, копия схемы и примеров. - **Воркер**: `get_parser()` → `OsvParser`, CLI `python -m src.parser.cli <файл>`, интеграционный сценарий «реальная ОСВ через воркер». - **Тесты**: 1818 unit (включая property-тесты инвариантов на всех 66 реальных ОСВ и 16 синтетических), golden 82 снимка (`make golden-check`/`golden-update`), fixtures 808, integration 9. Ledger — `docs/superpowers/plans/2026-10-05-slice-2a-osv-parser-ledger.md`. ## Проверки (HEAD 1eb57da) `make check`: ruff/format/mypy чисто, **1818 passed**; `make golden-check`: 82/82 без изменений; `pytest -m fixtures`: **808 passed**; `make test-integration`: 9 passed; `openspec validate --strict`: valid. Самый долгий реальный файл ≈ 0,6 с (лимит 5 с). Финальное ревью (opus): Critical — нет; Important I-1 (лимиты чтения xlsx) закрыт в три волны, I-2 закрыт; Minor M-1, M-3 закрыты, M-2, M-4…M-7 отложены (OQ-106…109). ## Rulings I made (design.md №11–39, ключевые) - R-12: заголовок обязан содержать «обороты» и «сальдо на конец», отсутствовать может только «начало». - R-13: пустая ОСВ штатна (`hierarchy=trivial`); R-14: «Итого» всегда уровень 0; R-20: без «Итого» → `needs_check`. - R-16: буквенные сегменты субсчетов (76.АВ/ВА/ОТ) — счета; R-17: ИНН только из колонки ИНН, ИНН в тексте строки не извлекается (OQ-100). - R-19: строки Вал./Кол./код валюты — `OTHER` с `data_type` CURRENCY/QUANTITY, без наследования контрагента. - **R-21 (самое рискованное):** сверка «сумма детей = родитель» по сальдо идёт по нетто, если у родителя сальдо свёрнуто (так выгружает 1С; без этого 32 из 66 файлов не сходились). Слепая зона: перестановка сторон при совпавшем нетто ловится только тождеством строки и «Итого = корень» (OQ-105). - R-23/R-25/№39: предсканирование xlsx — эшелон защиты, граница безопасности — лимит памяти платформы (OQ-108); `defusedxml` не добавлен (решение платформы). - R-9: xlsb в синтетике собран рукописным писателем BIFF12 (нет Excel/LibreOffice) — заменить на реальный (OQ-102). ## Deferred (в follow-up issue при закрытии) - Платформа: лимит памяти/RLIMIT воркера, `defusedxml`, версия expat в образе (OQ-108); размер объекта S3 до скачивания (M-7, OQ-109); реальный xlsb из Excel (OQ-102). - Аналитика: OQ-98…OQ-107 (коды валют, несколько ОСВ одного счёта в книге, ИНН в тексте, числа «+5»/«.5», буквенные первые сегменты «МЦ.04», «Валюта» в детализации, нетто-свёртка, несколько корней, `hierarchy=sums`). - Парсер: M-2 (числа после «Итого»), T12.1/O-8, T3.4 NFC, T3.5, T3.7, T5.6/T5.8/T5.9, T7.5, T8.6, T2.2…T2.5, O-4, T6.6, алгоритмическая граница `sums`. - Тесты/CLI: M-6/T3.8 слепые зоны линтера, T10.1…T10.5, T1.2, T1.3, T9.2…T9.4, T11.3…T11.8, T12.6, T13.8. **Порядок merge:** RSHB/arm-ka-fixtures#5 и RSHB/arm-ka-docs#10 — до этого PR (golden и тест идентичности контракта). После merge — bounded-PR копии контракта v1.1 в arm-core и правка Purpose в `openspec/specs/tabular-recognition-service` при архивировании. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
aep added 2 commits 2026-10-05 10:42:57 +03:00
proposal, design (D-1…D-10), спеки osv-parsing (новая) и tabular-recognition-service (MODIFIED «Парсер — заменяемый
интерфейс»), tasks (13 задач). Решение владельца 05.10.2026: подсрез 2a — только парсер ОСВ.

Issue: RSHB/arm-tabular-parser#5

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
aep added 1 commit 2026-10-05 10:42:57 +03:00
aep added 1 commit 2026-10-05 10:50:43 +03:00
aep changed title from Срез 2a: парсер ОСВ (§1) — спека OpenSpec to Срез 2a: парсер ОСВ из XLS/XLSX/XLSB — динамическое обнаружение структуры (§1), контракт v1.1, golden на 66 реальных файлах 2026-10-05 17:16:33 +03:00
aep added 42 commits 2026-10-05 17:16:34 +03:00
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- requirements: openpyxl==3.1.5, pyxlsb==1.0.10, xlrd==2.0.2, et_xmlfile==2.0.0 (транзитивная)
- tests/fixtures_helpers.py, conftest: фикстура fixtures_dir, параметризация osv_file/synthetic_file, skip маркера fixtures
- pyproject: mypy ignore_missing_imports для openpyxl/xlrd/pyxlsb, disallow_untyped_defs для src.parser.*

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
grid.py: Cell, MergedRange, Sheet (1-based, только непустые ячейки, ленивые
индексы строк и merged; диапазоны больше 10 000 ячеек проверяются перебором),
column_letter для диагностики. errors.py: ParserError и коды контракта.
readers: read_book определяет формат по сигнатуре (zip → XLSX/XLSB по
xl/workbook.bin, OLE2 → XLS), предупреждает о несовпадении с file_type;
ошибки библиотек → UnreadableError одной строкой. Числа — Decimal(str(value)),
bool отдельно, даты как datetime; xlsb без outline/merged с предупреждением.
Тесты: синтетическая книга openpyxl, подмена file_type, мусор/обрезанные
файлы, xlsb через подменённый pyxlsb; 66 реальных ОСВ читаются < 5 с с
outline и merged; xls «62 за 3 квартал 2024»: строка 15 — outline 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
make_cell возвращает None для строки, у которой strip() пуст (пробелы, NBSP и
прочие Unicode-пробелы): «пробелы = пусто» на уровне хранения, sheet.cell даёт
EMPTY_CELL. Тесты: пробельные ячейки xlsx/xlsb отсутствуют в cells, строка из
пробелов пуста, merged-диапазон с пробельным origin даёт "".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Словари api/rules/{osv_headers,osv_groups,periods,data_types}.yaml и src/parser/rules.py
(normalize_text, Pattern, load_rules, pydantic-валидация с RuntimeError по файлу и ключу,
currency_aliases по R-6). AST-линтер координат tests/unit/parser/test_no_coordinates.py
перенесён из Task 12 (R-7, правила R-4/R-5). Тест формулировок реальных ОСВ.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
detect.py: classify_sheet (ОСВ / форма ББ-ОФР / прочее по словарю), find_table_header
(строка с ≥ 2 группами сумм, «Дебет/Кредит» под группой в пределах merged-диапазона
или до следующей группы, заголовок в 1–4 строки, в т.ч. «Нач. сальдо Дт» в одной
ячейке; без «начала»; свёрнутое сальдо в *_debit + collapsed; свёрнутые «Обороты» и
половинчатая пара Дт/Кт — InvalidFormatError с названием группы), text_col, inn_col
(заголовок «ИНН» или ≥ 80 % значений из 10/12 цифр), right_edge, extra_cols,
group_label_rows, first_data_row, is_header_repeat (две группы или копия строки блока).

Pattern.search_normalized — ячейка нормализуется один раз (T3.6). osv_groups: известные
группировки OTHER перечислены явно, чтобы строка-метка «Основные средства» в
неслитом заголовке была частью блока. Хелпер тестов gridbuilder (Sheet из «картинки»).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- T4.1 (R-12): строка групп обязана содержать «обороты» и «сальдо на конец»; строка
  только с «началом» и «концом» (преамбула) пропускается, нет подходящей — InvalidFormatError
  с недостающими группами лучшего кандидата.
- T4.2: сторона в заголовке в одну строку — после пунктуации («Сальдо на начало, Дт», «(Кт)»).
- T4.3: inn_from_cell — общий хелпер ИНН (10/12 цифр); число из 9/11 цифр дополняется
  ведущим нулём; переиспользуется классификатором (Task 8).
- T4.4: колонка ИНН по значениям — не меньше INN_MIN_VALUES = 3 значений-ИНН.
- T4.6: у слитого (в том числе только по вертикали) названия группы поиск подзаголовков
  ограничен его диапазоном.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- header.py: parse_doc_header (организация, счёт, период, data_type, валюта, вариант шапки,
  предупреждения на русском) и build_group_schema (вариант 1 — метки в text_col, вариант 2 —
  «субконто: …»); has_inn по ячейке ИНН в строке метки (отдельной или в тексте метки)
- periods.yaml: end — конец текста периода («г.», «год»); PeriodRules.end
- линтер координат: Literal[1, 2] не считается индексацией ячейки
- fixtures-тест на 66 файлах: счёт по имени файла одним правилом без исключений

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- T5.1: организация исключается только по структурным меткам (лист, счёт, тип данных, параметры, субконто),
  строка периода выбирается по опознанному словарём периоду, метка валюты — только с двоеточием
- T5.2: конец периода — самое короткое начало, которое узнаёт словарь; диапазон «01.01.2025 г. - 31.03.2025 г.» цел
- T5.3: граница по цифрам в правиле счёта из имени файла
- T5.4: тест has_inn для варианта 2 (правило оставлено); T5.5: период только датами -> "" + предупреждение
- T5.7: тест обязательного ключа periods.end

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
hierarchy.py: BodyRow, Levels, стратегии outline (сдвиг к 0), indent (ранги
различных отступов — единицы alignment.indent и пробелов не смешиваются),
account_pattern (ранг кода + жирность, ASSUMPTION в docstring), sums
(жадно, без рекурсии); is_consistent, parents (по позициям и уровням,
Review Focus 3), resolve_levels: «Итого» в стратегии не передаётся и
получает уровень 0; меньше двух строк кроме «Итого» — дерево «trivial».
deadline.py: кооперативный Deadline (R-3). Unit-тесты; fixtures-тест — в
Задаче 8 (R-2). Отметки плана и tasks.md 2.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
strategy_sums проверяет Deadline каждые DEADLINE_CHECK_EVERY = 500 шагов
внутреннего перебора поддеревьев (худший случай O(n²) завершается TIMEOUT,
а не висит); линейные стратегии — каждые 500 строк (D-8). Strategy —
Protocol (rows, deadline=None); resolve_levels передаёт дедлайн.
Тесты: истечение внутри sums, проверка перед каждой стратегией, пакинг
линейных стратегий, часы по умолчанию — time.monotonic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
to_money — единственное место квантования сумм до 0.01 (ROUND_HALF_UP): число из ячейки (уже Decimal(str(value)))
без float-хвостов, текст с пробелами/NBSP/узкими пробелами, запятой, (x), U+2212, экспонентой; прочерки и пусто — None
без предупреждения; нечисловой текст, NaN/Infinity, значения вне numeric(18,2) — None с предупреждением; «-0» → 0.00.
inn_digits делегирует detect.inn_from_cell (одно правило ИНН для детектора и классификатора).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Строгая detect.inn_from_cell не меняется (ей детектор отличает колонку ИНН). inn_digits — режим извлечения для ячеек
выбранной колонки: сначала строгое правило, затем для текстовых ячеек цифры всех групп склеиваются и принимаются при
длине 10/12 (без дополнения нулём). Числовые ячейки во второй шаг не попадают.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
ИНН из текста ячейки колонки ИНН принимается, только если в тексте одна запись цифр:
группы, разделённые пробелом/NBSP/узким пробелом («3528 052718», «ИНН 352819549855»).
«№ 12 от 01.02.2025» и «№ 12 / 3528052718 / …» больше не дают ложный ИНН.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
classify.py: collect_body_rows — строки таблицы до «Итого» (пустые, из пробелов,
повторы заголовка и строки правее таблицы пропускаются; подвал после «Итого» не данные),
«Итого» по тексту и корневому оформлению до уровней, без требования сумм (R-13, T3.3),
шесть сумм через to_money с номером строки в предупреждениях, свёрнутое сальдо по знаку
(R-1); classify_rows — ACCOUNT по ACCOUNT_RE, позиция в схеме, OTHER за схемой, TOTAL,
COUNTERPARTY с ИНН (inn_digits), Вал./Кол./код валюты -> data_type, extra без
дубликатов ключей и без колонок подвала (T4.5).

Fixtures-тесты иерархии (R-2) и классификации на всех 66 реальных ОСВ.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
ACCOUNT_RE: сегмент субсчёта — 1–2 цифры или 1–3 прописные буквы кириллицы/латиницы
(без IGNORECASE); ранг — число сегментов («76.АВ» — сосед «76.04»). Остаточный
ложноположительный «12.ООО» описан в docstring.
Тесты: буквенные субсчета и их дети (outline и account_pattern), отказы «76.АВГД»/«76.ав»;
допуск «60,Расчеты» назван намеренным (R-18); fixtures-свойство «нет строк за схемой»,
data_type строк = data_type шапки (T8.3); негатив R-13: «Итого» без закрепления
вкладывается под последний счёт (T8.5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
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
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
CLI `python -m src.parser.cli <файл> [--file-type]` печатает JSON результата (коды 0/1/2 по статусу, 3 — файл/использование);
сборка RecognitionResult выделена из воркера в чистую `build_result` без изменения логики; тесты воркера на реальном
OsvParser, интеграционный сценарий «Реальная ОСВ через воркер», README «Запуск на файле».

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
api/tests/synthetic: детерминированный генератор s01…s13 (openpyxl, метки времени книги и zip зафиксированы),
минимальный писатель BIFF12 для s13.xlsb (ruling R-9, читается pyxlsb), тесты сценариев спеки osv-parsing на файлах
из fixtures/osv/synthetic/ (таблица «файл → ожидания», s13 равен s02 кроме предупреждения xlsb), тест побайтовой
воспроизводимости генерации. synthetic_files() учитывает только файлы Excel (README.md каталога не файл набора).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- golden_helpers, scripts/golden_update.py (--check), test_golden на 66 ОСВ и синтетике; make golden-update/golden-check/test-fixtures
- тест покрытия словарей (R-22): счётчик срабатываний Pattern; из osv_headers.yaml удалены неиспользуемые service_columns kpp/code/quantity/currency
- синтетика s14–s16 в генераторе и таблице ожиданий; skip вместо падения при отсутствии osv/synthetic (T11.1); комментарий R-21 в s07 (T11.2)
- линтер: предел адреса 10 вместо 4 (T3.1), обращение cell(3, 4) (T3.8); реальные файлы не фиксируют BU/RUB (T3.9)
- README: golden-процесс; отмечены Task 12 и пункты 4.2, 4.3

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- README: парсер по модулям, статусы и коды, словари и добавление синонима, лимиты, golden-процесс, настройки PARSE_*
- CLAUDE.md: раздел «Добавлено в Срезе 2a» (модули, правила расширения, линтер, R-21); api/rules/README.md: ключи словарей
- openspec: tasks.md (5.1), design.md (D-1…D-7, O-7/O-8), спека-дельты osv-parsing (R-12…R-22) и tabular-recognition-service («Результат по контракту»)
- tests: check_snapshot в golden_helpers + тесты «нет снимка»/«регрессия»; locator в property-тесте; пример needs_check синхронизирован с docs

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- I-1 (R-23): readers/_limits.py — сумма ZipInfo.file_size ≤ 10 × PARSE_MAX_BYTES (параметр read_book от
  OsvParser), потоковый скан <mergeCell ref> до openpyxl и площадь merged после чтения любого формата
  (MERGED_AREA_LIMIT = 200 000; реальный максимум 10 090) → UNSUPPORTED_FORMAT; дедлайн в циклах detect
  (поиск строки групп, дополнительные колонки); README «Лимиты» исправлен, «Известные ограничения» (defusedxml).
- I-2: OsvParser логирует стек непредвиденной ошибки (loguru ERROR), UNREADABLE — WARNING без стека.
- M-1: ACCOUNT_RE с re.ASCII | re.DOTALL (многострочное название после запятой, только цифры 0–9).
- M-3 (R-24): правило детектора ИНН не дополняет 9/11 цифр нулём; дополнение — только при извлечении
  (numbers.inn_digits) из колонки, выбранной по заголовку или по строгому правилу.
- T9.1: тесты R-21 (свёрнутый родитель на противоположной стороне; (0,0) при детях (X, X)).
- T1.1: хелперы фикстур пропускают скрытые файлы/каталоги и lock-файлы Excel (~$…).
- T12.3: golden-update не удаляет снимки части без исходного каталога (osv/synthetic или реальные файлы).

Golden-снимки не изменились (82/82).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
- T13.1: contracts/examples/result-osv-needs-check.json — предупреждения как пишет парсер
  («sheet=Лист_1 hierarchy=outline», текст нарушения из checks.py), organization_inn = null, версия 1.0.0;
  копия в docs байт в байт.
- T13.2: лист ОСВ — признак в первых 30 строках (SHEET_MARKER_ROWS); T13.3: сломанный code span.
- T13.4: остаточные риски R-21 списком (RR-1…RR-4, слепая зона «+X к обеим сторонам», (0,0) при (X, X), «12.ООО»).
- I-1/I-2/M-1/M-3 в тексте спеки: лимиты чтения (R-23) и сценарии, лог стека, флаги ACCOUNT_RE, R-24.
- T13.6: OQ-81 — «частично закрыт» в design.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
R-25. Регулярочный скан `<mergeCell ref>` в xl/worksheets/*.xml обходился файлом < 5 КБ четырьмя способами
(лист вне xl/worksheets, ссылка на символ в ref, атрибут a:ref перед ref, «>» в кавычках). Теперь до openpyxl —
потоковый разбор xml.parsers.expat (namespace-aware) всех записей zip *.xml и записей, похожих на XML (openpyxl идёт
к листу по цели связи и расширение не проверяет): элемент с локальным именем mergeCell в любом пространстве имён
(openpyxl берёт дочерние mergeCell по локальному имени), только ref без префикса, площадь по строке ref через
CellRange openpyxl, ранний отказ при превышении MERGED_AREA_LIMIT; битый XML части *.xml и неразбираемый ref —
UNREADABLE. Стоимость: 12 мс на самом большом реальном xlsx (openpyxl 0,58 с), 0,8 с на 2 млн ячеек (openpyxl 10 с).

N1: check_merged_area после чтения считает только диапазоны площадью ≤ grid.MAX_INDEXED_AREA (константа стала
публичной) — законный merged на весь столбец в xls больше не отвергается.
Спека и README: правила скана, не покрытые риски (объём ячеек, sharedStrings/стили, entity expansion); N2 —
слепая зона R-21: достаточно свёрнутого прямого родителя.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
Предскан объединённых ячеек xlsx отдаёт expat каждую некаталожную запись zip:
отбор «похоже на XML» по первым 64 байтам обходился листом в части без .xml
с ведущими пробелами и без XML-декларации (1,9 КБ → > 3 с, у ревьюера 27 с /
1,4 ГБ). Части *.xml — строго, остальные — мягко (ошибка разбора лишь
заканчивает скан части). После чтения любого формата число диапазонов листа
площадью > MAX_INDEXED_AREA ограничено LARGE_MERGED_COUNT_LIMIT = 1000:
Sheet.merged_span перебирает их линейно. README, design.md №39, дельта спеки.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
aep merged commit 0db3d9488f into main 2026-10-05 19:18:07 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RSHB/arm-tabular-parser#6
No description provided.