From 15fb561cd8b908ff41264bd7f77ff9712efabab3 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 6 Oct 2026 21:58:24 +0400 Subject: [PATCH] =?UTF-8?q?openspec:=20O-9=20=D1=80=D0=B5=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE,=20D-11=20=D0=BF=D0=BE=20=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D1=83=20(needs=5Fcheck),=20tasks.md=20=D0=BF=D0=BE=20=D1=84?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D1=83,=20=D1=80=D0=B5=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=E2=84=9627=20(R-16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #14 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EAKRDNzXtRrwmWz9z6PZu3 --- .../slice-2b-statement-parser/design.md | 12 ++++--- .../specs/statement-parsing/spec.md | 10 ++++-- .../slice-2b-statement-parser/tasks.md | 36 +++++++++---------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/openspec/changes/slice-2b-statement-parser/design.md b/openspec/changes/slice-2b-statement-parser/design.md index 36a9643..6a6fb35 100644 --- a/openspec/changes/slice-2b-statement-parser/design.md +++ b/openspec/changes/slice-2b-statement-parser/design.md @@ -164,11 +164,12 @@ relation, expected: Money, actual: Money)`; `OsvHeader.period_start: date | None ### D-11. Тесты, golden, синтетика Unit (`tests/unit/parser/statements/`) на `gridbuilder`; property (`tests/fixtures/test_statements_property.py`, маркер -`fixtures`) по манифесту `tests/fixtures/statements_manifest.yaml` (каталог → организация, ИНН, ожидаемые типы и статусы, +`fixtures`) по манифесту `tests/statements_manifest.yaml` (каталог → организация, ИНН, ожидаемые типы и статусы, полная форма или страница); golden `../fixtures/golden/expected_statement_parse///.json` через -обобщённые `golden_helpers` и `make golden-update`; синтетика в `tests/synthetic/generate.py`: `st01_both_forms_one_sheet`, -`st02_csv_like`, `st03_no_codes`, `st04_mixed_book` (ОСВ + ББ), `st05_continuation_only`; линтер «нет координат» -включает `statements/**` и `book.py`; интеграционный тест воркера: книга НПЗ → `done`, `result_schema_version = "1.2"`, +обобщённые `golden_helpers` и `make golden-update`; синтетика в `tests/synthetic/generate_statements.py`: `st01_both_forms_one_sheet`, +`st02_csv_like`, `st03_no_codes`, `st04_mixed_book` (ОСВ + ББ), `st05_continuation_only`, `st06_header_variants`, +`st07_single_code_continuation`; линтер «нет координат» +включает `statements/**` и `book.py`; интеграционный тест воркера: книга НПЗ → `needs_check` (рубли при «тыс. руб.», OQ-138; O-10), `result_schema_version = "1.2"`, два документа. CLI печатает формы так же, как ОСВ. ## Risks / Trade-offs @@ -224,5 +225,6 @@ Unit (`tests/unit/parser/statements/`) на `gridbuilder`; property (`tests/fixt | 22 | 2026-10-06 | Task 12: допуск соотношений не менялся: НПЗ (`npz/2025-Q3`) даёт `needs_check` — 1600 = 1700 расходятся на 1.94 / 9.02 / 2.48 (значения в рублях с копейками при заголовке «тыс. руб.», сам файл не сходится) | спека D-6/D-7: единицы не нормализуются, допуск ±1 в единицах формы; подгонка допуска под файл — решение владельца (OQ-138) | если владелец хочет `done` для НПЗ: допуск/нормировка единиц, одна строка в словаре; манифест в задаче 14 фиксирует `needs_check` с причиной (R-11) | | 23 | 2026-10-06 | R-13: допуск ±1 не меняется; баланс НПЗ ожидаемо `needs_check` (значения в рублях с копейками при шапке «тыс. руб.», 1600 = 1700 расходятся на 1.94/9.02/2.48, OQ-138); решение владельца, вариант А | единицы не нормализуются (D-6), допуск ±1 (D-7) | если владелец передумает — допуск/нормировка в словаре | | 24 | 2026-10-06 | R-14: ОФР НПЗ и ОФР Унагранде (стр. 1) по факту `needs_check` — рубли с копейками при тыс. руб., 2100 расходится на 17.50/9.43/1416; допуск ±1 не меняется | то же, что №23 (OQ-138) | нормировка единиц/допуск в словаре, если владелец хочет `done` | -| 25 | 2026-10-06 | O-9: лист ОФР НПЗ с одной строкой 2400 (`min_codes` = 3) не распознаётся как форма и пропускается — строка 2400 теряется при `needs_check` документа | отложено на финальное ревью | потеря строки 2400 у НПЗ; понизить `min_codes` или принимать лист-продолжение по маркеру | +| 25 | 2026-10-06 | O-9: лист ОФР НПЗ с одной строкой 2400 (`min_codes` = 3) не распознавался как форма и терялся. **Решено в финальной волне (R-16, I-1):** лист после листа формы с ≥ `min_codes_continuation` = 1 кодов (словарь) идёт как продолжение и присоединяется по общим правилам; не присоединён — `needs_check` и предупреждение «лист с кодами форм (N < min_codes) не присоединён: …»; первая страница по-прежнему требует `min_codes` = 3 | потеря строки 2400 при `needs_check` недопустима; порог ослаблен только для продолжения | лишний лист-продолжение с 1 кодом присоединяется к последнему открытому документу (R-16) | | 26 | 2026-10-06 | R-15: наши OQ-137…142 перенумерованы в OQ-138…143 | номер OQ-137 в main занят вопросом о раскладке горячих папок (docs #41/#43) | ссылки в PR-описаниях | +| 27 | 2026-10-06 | R-16: состав финальной волны ревью — I-1 (O-9), M-10 (тип формы маркера «Форма 0710002 с.2» запрещает присоединение к документу иного типа), I-2 (одиночный месяц не задаёт границы при дате на число/квартале, `not_single_month` в словаре), I-3 (название строки из ближайшей текстовой ячейки левее кодов; пустое имя без confidence), M-5 (`units_thousands`, `okud_code` — в словарь), M-8 (строка нумерации и нули не «суммы»), M-11 (предупреждение о дробных — одно на документ), T13.5 (`_upper_header_row`), M-1 (`uv.lock` вне git), M-6/M-7/T15.1/T15.2/M-3/M-4/M-2 — документация и тесты | исправления из финального ревью ветки, без изменения контракта | golden форм перегенерирован, снимки ОСВ не менялись | diff --git a/openspec/changes/slice-2b-statement-parser/specs/statement-parsing/spec.md b/openspec/changes/slice-2b-statement-parser/specs/statement-parsing/spec.md index 918094f..43559bb 100644 --- a/openspec/changes/slice-2b-statement-parser/specs/statement-parsing/spec.md +++ b/openspec/changes/slice-2b-statement-parser/specs/statement-parsing/spec.md @@ -169,12 +169,18 @@ false`; `confidence = 100`. Повтор кода внутри документ #### Scenario: Баланс на пяти листах - **WHEN** разбирается книга НПЗ - **THEN** два документа: `BALANCE` с `locator = "sheet:Бухгалтерский баланс. Лист 1,Бухгалтерский баланс. Лист 2,…,Лист 5"` - и строками от 1105 до 1700 включительно, `INCOME` на двух листах (третий лист отчёта с единственной строкой 2400 не - распознаётся как форма: `min_codes = 3`, ограничение O-9); `checks.relations_failed` баланса содержит только + и строками от 1105 до 1700 включительно, `INCOME` на трёх листах (лист отчёта с единственной строкой 2400 присоединяется + как продолжение: после листа формы хватает одного кода, `min_codes_continuation`), строка 2400 присутствует; `checks.relations_failed` баланса содержит только `1600 = 1700` по трём колонкам (расхождения 1.94 / 9.02 / 2.48 руб.: значения в рублях с копейками при шапке «тыс. руб.», OQ-138; допуск ±1 не меняется); `INCOME` — `needs_check` по `2100`, `2200`, `2300` (OQ-138); статус книги `needs_check` +#### Scenario: Лист-продолжение с одной кодовой строкой +- **WHEN** после листа формы идёт лист с единственной кодовой строкой (`2400`, меньше `min_codes`) и тем же числом колонок сумм +- **THEN** лист присоединяется к последнему открытому документу, строка присутствует; если присоединить нельзя (число + колонок или тип формы маркера не совпали), книга `needs_check` и предупреждение «лист с кодами форм (N < min_codes) не + присоединён: …»; первая страница формы с одной кодовой строкой формой не считается + #### Scenario: Одинокая страница 2 - **WHEN** разбирается `unagrande-group/2024-FY/ОФР. Лист 2.xls` (стр. 1 в пакете нет) - **THEN** один документ `INCOME` со строками 2510–2910, пустой шапкой, статус `needs_check`, предупреждение о продолжении diff --git a/openspec/changes/slice-2b-statement-parser/tasks.md b/openspec/changes/slice-2b-statement-parser/tasks.md index 68d8444..94eff89 100644 --- a/openspec/changes/slice-2b-statement-parser/tasks.md +++ b/openspec/changes/slice-2b-statement-parser/tasks.md @@ -7,44 +7,44 @@ superpowers (`docs/superpowers/plans/`), который пишется посл ## 1. Основание -- [ ] 1.1 Контракт v1.2: копия `../docs/contracts/recognition-result.schema.json` и примеров в `api/contracts/`; +- [x] 1.1 Контракт v1.2: копия `../docs/contracts/recognition-result.schema.json` и примеров в `api/contracts/`; `StatementDocument.document_type` + `DISCLOSURE`, `StatementChecks`/`FailedRelation`, `OsvHeader.period_start/ period_end/is_from_year_start`; `CONTRACT_VERSION = "1.2"`; тесты контракта и воркера (`result_schema_version`). -- [ ] 1.2 Фикстуры форм в тестах: `conftest.py` — параметризация `statement_file` по `../fixtures/statements-xlsx/**` - (кроме `synthetic`), `statement_synthetic_file`; манифест `tests/fixtures/statements_manifest.yaml`; обобщение +- [x] 1.2 Фикстуры форм в тестах: `conftest.py` — параметризация `statement_file` по `../fixtures/statements-xlsx/**` + (кроме `synthetic`), `statement_synthetic_file`; манифест `tests/statements_manifest.yaml`; обобщение `golden_helpers.py` на каталог `expected_statement_parse/`; `make golden-update`/`golden-check` для обоих каталогов. -- [ ] 1.3 Словари форм: `api/rules/statement_headers.yaml`, `statement_periods.yaml`, `statement_checks.yaml`; модели в +- [x] 1.3 Словари форм: `api/rules/statement_headers.yaml`, `statement_periods.yaml`, `statement_checks.yaml`; модели в `rules.py`; тест на битый словарь; `rules/README.md`. ## 2. Оркестратор книги -- [ ] 2.1 `detect.classify_sheet`: `SheetKind.STATEMENT_CONTINUATION`, признаки формы по области до таблицы (D-2); unit +- [x] 2.1 `detect.classify_sheet`: `SheetKind.STATEMENT_CONTINUATION`, признаки формы по области до таблицы (D-2); unit на `gridbuilder`, fixtures-тест классификации всех листов `statements-xlsx/**` и `osv/**` (ОСВ не изменились). -- [ ] 2.2 `book.py` (`BookParser`, D-1): перенос лимита/чтения/дедлайна/цикла из `OsvParser`, `OsvParser` без `parse`; +- [x] 2.2 `book.py` (`BookParser`, D-1): перенос лимита/чтения/дедлайна/цикла из `OsvParser`, `OsvParser` без `parse`; `get_parser` собирает `BookParser`; `test_osv_parser.py` → `test_book_parser.py` + unit на смесь листов; golden ОСВ и property ОСВ без изменений. ## 3. Конвейер форм -- [ ] 3.1 `statements/detect.py`: `find_forms` — заголовок формы, ОКУД, тип, разрез листа на блоки, расшифровки (D-3). -- [ ] 3.2 `statements/columns.py`: заголовок таблицы, склейка двухстрочного заголовка, строка нумерации, колонка кодов, +- [x] 3.1 `statements/detect.py`: `find_forms` — заголовок формы, ОКУД, тип, разрез листа на блоки, расшифровки (D-3). +- [x] 3.2 `statements/columns.py`: заголовок таблицы, склейка двухстрочного заголовка, строка нумерации, колонка кодов, колонка наименований, колонки-периоды и даты (D-4); unit на три раскладки и шаблоны. -- [ ] 3.3 `statements/header.py`: шапка по меткам, ИНН не аудитора, организация над меткой, дата из тройки (D-5). -- [ ] 3.4 `statements/lines.py`: строки с кодом, значения, прочерки, скобки, дубликаты, предупреждение о единицах (D-6). -- [ ] 3.5 `statements/checks.py`: соотношения из словаря, `abs_terms`, допуск, мягкие, подстроки, `StatementChecks` (D-7). -- [ ] 3.6 `statements/merge.py` + `statement.py` (`StatementParser.parse_sheets`): склейка продолжений, одиночная +- [x] 3.3 `statements/header.py`: шапка по меткам, ИНН не аудитора, организация над меткой, дата из тройки (D-5). +- [x] 3.4 `statements/lines.py`: строки с кодом, значения, прочерки, скобки, дубликаты, предупреждение о единицах (D-6). +- [x] 3.5 `statements/checks.py`: соотношения из словаря, `abs_terms`, допуск, мягкие, подстроки, `StatementChecks` (D-7). +- [x] 3.6 `statements/merge.py` + `statement.py` (`StatementParser.parse_sheets`): склейка продолжений, одиночная страница, `locator`, критерий `ok` (D-8); unit на книгу из нескольких листов. ## 4. Реальные файлы, синтетика, регрессия -- [ ] 4.1 Синтетика `tests/synthetic/generate.py`: `st01_both_forms_one_sheet`, `st02_csv_like`, `st03_no_codes`, - `st04_mixed_book`, `st05_continuation_only` → `../fixtures/statements-xlsx/synthetic/`. -- [ ] 4.2 Property-тест `tests/fixtures/test_statements_property.py` по манифесту: типы, ИНН, статусы, сходимость §7 для +- [x] 4.1 Синтетика `tests/synthetic/generate_statements.py`: `st01_both_forms_one_sheet`, `st02_csv_like`, `st03_no_codes`, + `st04_mixed_book`, `st05_continuation_only`, `st06_header_variants`, `st07_single_code_continuation` → `../fixtures/statements-xlsx/synthetic/`. +- [x] 4.2 Property-тест `tests/fixtures/test_statements_property.py` по манифесту: типы, ИНН, статусы, сходимость §7 для полных форм, шаблоны не падают; golden для всех файлов `statements-xlsx/**`. -- [ ] 4.3 Покрытие словарей форм в `test_rules_coverage.py`; линтер координат на `statements/**` и `book.py`. -- [ ] 4.4 Заполнение `period_start/period_end/is_from_year_start` в шапке ОСВ для квартала/года (D-9); для диапазона +- [x] 4.3 Покрытие словарей форм в `test_rules_coverage.py`; линтер координат на `statements/**` и `book.py`. +- [x] 4.4 Заполнение `period_start/period_end/is_from_year_start` в шапке ОСВ для квартала/года (D-9); для диапазона месяцев — по результату ветки #11 (одна задача здесь либо follow-up). -- [ ] 4.5 Интеграционный тест воркера: книга НПЗ → `done`, два документа, `result_schema_version = "1.2"`; CLI печатает формы. +- [x] 4.5 Интеграционный тест воркера: книга НПЗ → `needs_check` (OQ-138), два документа, `result_schema_version = "1.2"`; CLI печатает формы. ## 5. Завершение