arm-tabular-parser/docs/superpowers/plans/2026-10-28-osv-other-systems.md
Andrey Epifancev 29071bdc7b plan: снимки контрольного набора по шагам (#81)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:14:59 +04:00

293 lines
31 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.

# ОСВ других учётных систем — план реализации
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** листы ОСВ SAP, Navision, выгрузок КТЗ и отчётов 1С с другим названием распознаются как ОСВ: признаки листа по
названию, запасной признак по полному заголовку таблицы, написания групп и сторон, итог «ВСЕГО».
**Architecture:** новые написания — только записи `api/rules/osv_headers.yaml` + синтетика на каждую (R-22). Код меняется
в одном месте: запасной признак `has_osv_table` в `detect.py`, который `plan_sheets` вызывает для листа, попавшего бы в
«прочее». Регрессия — прогон банка первички до и после, сравнение по документам.
**Tech Stack:** Python 3.11, pytest, ruff, mypy; openpyxl (генератор синтетики); OpenSpec.
**Spec:** `tabular-parser/openspec/changes/osv-other-systems/` (proposal.md, design.md, specs/osv-parsing/spec.md).
Правило — `docs/context/05-domain-rules.md` §1.1–§1.2 (меняется в Task 5). Issue RSHB/arm-tabular-parser#81. Jira
AURORA-1717, AURORA-1719, AURORA-1732. Ветка `feat/81-osv-other-systems` в трёх worktree: `.wt/osv-dict/tabular-parser`,
`.wt/osv-dict/fixtures`, `.wt/osv-dict/docs` (корень `W = /Users/andrey.epifancev/projects/awp-ca/.wt/osv-dict`).
## Global Constraints
- Никаких `if` под файл, организацию, систему-источник или имя листа: новое написание = запись YAML + синтетика (CLAUDE.md
tabular-parser, «Правила расширения»).
- Каждая новая запись словаря срабатывает хотя бы на одной синтетике (`tests/fixtures/test_rules_coverage.py`, R-22).
- Паттерны пишутся по нормализованному тексту: нижний регистр, «ё» → «е», дефисы/тире/переносы/nbsp → пробел, пробелы
схлопнуты (шапка `osv_headers.yaml`).
- Новые `sheet_markers` — с якорем `^` (R-5); якорь снимается для названия, только если файлы списка 1717 без этого не
находятся (записать в design.md, раздел решений).
- Запасной признак: только лист, который `plan_sheets` иначе отнёс бы к «прочее»; строка групп в первых
`SHEET_MARKER_ROWS` (30) строках; все три группы; у каждой — и Дебет, и Кредит; свёрнутое сальдо не годится (D1, R-2,
R-3, R-6).
- Пакет `api/arm_statements/` не трогать. Контракт не меняется.
- Код — только Edit/Write, не sed/скрипты через Bash (правило владельца 18.10.2026). cwd оболочки держать в
`W/tabular-parser`; fixtures и docs править по абсолютным путям, git — `git -C <абсолютный путь>`.
- Файлы банка первички (`/Users/andrey.epifancev/projects/awp-ca/doc-bank/`) — только чтение, в git не кладутся.
- Golden правится только `make golden-update` с просмотром diff, отдельным коммитом в fixtures.
- Комментарии, сообщения коммитов — по-русски; коммиты заканчиваются строкой
`Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`.
## Review Focus
1. Расшифровка ББ, которую пакет не узнал как форму («Остаток на начало | Поступило | Выбыло | Остаток на конец»),
не становится ОСВ: в ней нет оборотов → `has_osv_table` ложь (тест в Task 3).
2. Преамбула шапки «Остаток на начало: … | Остаток на конец: …» над таблицей не принимается за строку групп: в ней нет
«оборотов» (тест в Task 2 на синтетике s49 — преамбула выше таблицы).
3. Сторона без границы слова («ДТВВ») не ловится в ячейках, где нет группы: «Дт» внутри текста контрагента
(«ООО Дтвв-Сервис») не даёт стороны (тест в Task 2).
4. Лист «прочее» после листа формы, который сейчас идёт как слабое продолжение (`is_weak_continuation`), остаётся
продолжением — запасной признак проверяется только в ветке «прочее» (тест в Task 3).
5. Книга с формами и без единого листа ОСВ: на листах форм `has_osv_table` не вызывается (тест в Task 3 — счётчик
вызовов на книге s14).
---
### Task 1: Базовая линия: банк первички, образцы, контрольный набор синтетики
**Files:**
- Create: `/Users/andrey.epifancev/projects/awp-ca/admin/osv-other-systems-regress/regress.py`
- Create: `/Users/andrey.epifancev/projects/awp-ca/admin/osv-other-systems-regress/headers.md`
- Create (вывод): `.../osv-other-systems-regress/base.csv`, `control-base.md`
- Modify: `api/tests/synthetic/generate.py` (`OsvSpec`, `_header_v1`, `_header_rows`, `build_osv`, `table_sheet`,
`_s48…_s57`, `FILE_NAMES`, docstring модуля), `api/tests/synthetic/test_synthetic_files.py`
- Create (генерация): `W/fixtures/osv/synthetic/s48…s57`; Modify: `W/fixtures/osv/synthetic/README.md`
**Interfaces:**
- Produces: `regress.py run <out.csv>` и `regress.py diff <base.csv> <new.csv>`; `headers.md` — точные тексты шапок и
заголовков реальных образцов для синтетики Task 2–3.
- [ ] **Step 1: venv.** `cd W/tabular-parser && make venv` → `api/.venv/bin/python` есть; `make check` зелёный на
исходной ветке (golden и покрытие читают `../fixtures` — это worktree fixtures на той же базе `f1f7f83`).
- [ ] **Step 2: `regress.py run`.** Обходит `doc-bank/index/index.csv`, строки с `route == "парсер"`; разбирает файл
в процессе тем же путём, что `src.parser.cli` (`get_parser(Settings())`, тип файла — по расширению), запуск из
`W/tabular-parser/api` с `ENV_FILE=.env.test` и `PYTHONPATH=.`. Пишет CSV, по строке на документ (на файл без
документов — одна строка): `sha256, name, locator, kind (osv|BALANCE|INCOME|…|none), status, error_code,
sheet_other_count`. Исключение парсера — строка с `error_code = EXCEPTION:<класс>`, прогон не прерывается.
- [ ] **Step 3: `regress.py diff`.** Сопоставляет по `(sha256, locator)`; печатает таблицу переходов `(kind, status) →
(kind, status)` с числом файлов и список ухудшений по D3: `done → needs_check|error`, `needs_check → error`, любой
`kind` формы → `osv`, `none/other → osv`. Переходы в `osv` помечает «проверить вручную» с именем файла.
- [ ] **Step 4: базовый прогон.** `regress.py run base.csv` на текущем HEAD ветки (код = main `a8c9605`). Ожидаемо по
индексу: ~1681 файл; среди ошибок — `INVALID_DOCUMENT` у файлов из списка 1717.
- [ ] **Step 5: образцы заголовков.** Из `doc-bank` найти по `index.csv` (`parse_bug` содержит `AURORA-1717`/`1719`)
по одному файлу на каждое написание из таблиц AURORA-1717 и 1719 (+ «ВСЕГО» из 1732) и выписать в `headers.md`
дословно: строку названия, строку(и) групп и подзаголовков (ячейка за ячейкой), подпись итога. Особо: как у Navision
подписан оборот («ОборотДт»? «Оборот Дт»?), как у SAP НМЖК — «Оборот» со стороной.
- [ ] **Step 6: генератор.** В `api/tests/synthetic/generate.py` поля `OsvSpec` (см. Interfaces ниже) и их поддержка в
`_header_v1`/`_header_rows`/`build_osv`; существующие s01–s47 побайтно не меняются (`test_generate.py` и повторная
генерация в `W/fixtures/osv/synthetic` — `git -C W/fixtures status` чистый по старым файлам).
- [ ] **Step 7: контрольный набор синтетики** — файлы и тесты по таблице ниже, тексты подписей — из `headers.md`, где
есть реальный образец. Набор пишется до правок словаря и кода и должен уметь опровергнуть успех: позитивы проверяют
не только «лист — ОСВ», но и точные суммы по сторонам (перепутанные Дебет/Кредит ломают тождество строки и суммы);
негативы проверяют, что вид листа и результат не изменились.
Позитивы (на базе падают, после Task 2–3 проходят):
| Файл | Что в нём | Тест и проверки |
|---|---|---|
| `s48_other_system_titles.xlsx` | листы `build_osv` с `title`: «Оборотная ведомость по счету 60 за 1 квартал 2025 г.», «ОСВ по сч. 60 …», «Оборотный баланс …», «Фин. Обороты Листок» (+ строка «Период: …»), «Отчет по Главной книге …»; ещё два листа — название во второй колонке и «ОБОРОТНАЯ\nВЕДОМОСТЬ по счету 60» (верхний регистр, перенос); у одного листа `total_text="ВСЕГО"`, у другого `"Всего "` | `test_s48_other_system_titles_are_osv`: 7 `osvDocument` по листам, `SHEET_OTHER` нет; у листов «ВСЕГО» итог — `TOTAL`, предупреждения «строка «Итого» не найдена» нет; суммы `base_tree()` |
| `s49_balance_and_turnover_by_side.xlsx` | «Остаток на начало периода» / «Остаток на конец периода» с «Дебет/Кредит» под ними, `split_groups={"turnover": ("Оборот по дебету", "Оборот по кредиту")}`; над таблицей преамбула «Остаток на начало: 0 \| Остаток на конец: 0» (Review Focus 2) | шесть колонок, суммы дерева, статус `done` |
| `s50_sap_in_out_glued_side.xlsx` | лист SAP «Сальдо вх Дт \| Сальдо вх Кт \| Оборот Дт \| Оборот Кт \| Сальдо исх Дт \| Сальдо исх Кт»; лист НМЖК «Сальдо НачальноеДТВВ \| … \| Сальдо КонечноеКТВВ»; контрагент «ООО Дтвв-Сервис» (Review Focus 3) | оба листа — шесть колонок с верными сторонами, суммы дерева, «ООО Дтвв-Сервис» — строка контрагента, `done` |
| `s51_untitled_navision.xlsx` | `untitled=True`, три `split_groups` «НачалоДт/НачалоКт», «Оборот…», «КонецДт/КонецКт» (по `headers.md`), первая колонка «ФинСчет» | один `osvDocument`, `SHEET_OTHER` нет, суммы дерева; статус — фактический (без шапки ожидаемо `needs_check` по периоду/организации), зафиксировать в `EXPECTED` с комментарием |
| `s52_mixed_book.xlsx` | `statement_sheet()` (форма ББ) → лист как в s51 → `reference_sheet()` | документ формы тот же, что у книги без Navision-листа; Navision — `osvDocument`; «Справка» — `SHEET_OTHER` |
| `s53_balance_by_side.xls`, `s54_untitled_navision.xlsb` | s49 через `to_xls`, s51 через `to_xlsb` | те же проверки, что у s49 / s51 (читатели xls и xlsb) |
Негативы (проходят и на базе, и после — иначе успех опровергнут):
| Файл | Что в нём | Тест и проверки |
|---|---|---|
| `s55_not_osv_titles.xlsx` | обычная ОСВ 1С (чтобы книга не была ошибкой) + листы с таблицей без групп ОСВ: «Справка: оборотная ведомость прилагается», «Оборотный капитал и его структура», отчёт 1С «Обороты счета 60» с колонками корр. счетов внутри «Оборот Дт/Кт» (R-1) | 1 `osvDocument`; три листа — `SHEET_OTHER` |
| `s56_almost_osv_tables.xlsx` | обычная ОСВ 1С + безымянные листы: (а) «Наименование \| Остаток на начало \| Поступило \| Выбыло \| Остаток на конец»; (б) «Оборот Дт \| Оборот Кт \| Сальдо на конец Дт \| Кт» без начала; (в) свёрнутое начало «Сальдо на начало» одной колонкой + обороты и конец с Дт/Кт; (г) полный заголовок Navision с 31-й строки | 1 `osvDocument`; (а)–(г) — `SHEET_OTHER` (D1, R-2, R-3, Review Focus 1) |
| `s57_form_mentions_osv.xlsx` | форма ББ (`statement_sheet()`) с ячейкой примечания «см. оборотную ведомость по сч. 60» и строкой «Остаток на начало периода» в тексте пояснения; следом лист слабого продолжения формы (1–2 кода, как в существующих тестах `is_weak_continuation`) | документы формы те же, что на базе (вид, `locator`, число строк); `osvDocument` нет; продолжение присоединено (Review Focus 4) |
`EXPECTED`, список файлов, `FILE_NAMES` — для всех. Негативам — отдельный тест-маркер в имени (`test_neg_…`), чтобы
их прогон читался отдельно.
- [ ] **Step 8: прогон набора на базе.** Сгенерировать в `W/fixtures/osv/synthetic`, `make golden-update` (снимки
всех s48–s57; у негативов это регрессия базового поведения, снимки позитивов фиксируют ошибку базы и обновятся в
Task 2–3), затем
`.venv/bin/pytest tests/synthetic -k "s48 or s49 or s50 or s51 or s52 or s53 or s54 or s55 or s56 or s57" -rA`.
Ожидаемо: все `test_neg_…` — PASS, все позитивы — FAIL. Иначе стоп: позитив, проходящий на базе, ничего не
доказывает (переписать), негатив, падающий на базе, неверно описывает текущее поведение (разобрать). Итог — таблица
«тест → результат на базе» в `admin/osv-other-systems-regress/control-base.md`.
- [ ] **Step 9: коммиты.** tabular-parser: генератор и тесты контрольного набора — «Контрольный набор синтетики ОСВ
других систем (#81)» (позитивы падают — допустимо в ветке до Task 3: пометить их `pytest.mark.xfail(strict=True,
reason="tp#81: до Task 2–3")`, снимать `xfail` по мере реализации). fixtures: файлы s48–s57 и README — «Синтетика
s48–s57 под tp#81»; снимки негативов — отдельным коммитом.
**Interfaces (генератор, Step 6):**
- Поля `OsvSpec`:
- `title: str | None = None` — строка названия вместо «Оборотно-сальдовая ведомость по счету {account} за {period}»
(`None` — как сейчас);
- `title_col: int = 1` — колонка строки названия;
- `untitled: bool = False` — без строк организации, названия, параметров: заголовок таблицы с первой строки;
- `top_padding: int = 0` — пустых строк над заголовком таблицы при `untitled` (для s56 (г): 30);
- `split_groups: dict[str, tuple[str, str]] = {}` — группы, у которых вместо слитого названия + «Дебет/Кредит» две
ячейки в строке групп со своими текстами (сторона в ячейке группы); если разделены все три группы, строки
подзаголовков нет;
- `total_text: str = TOTAL_TEXT`.
- Листы-негативы без дерева ОСВ — функцией по образцу `reference_sheet`/`_flat_sheet`: `table_sheet(name: str,
rows: Sequence[Sequence[str | D | None]], *, start_row: int = 1) -> SheetSpec`.
---
### Task 2: Словари (1717 названия, 1719 группы и стороны, 1732 «ВСЕГО»)
**Files:**
- Modify: `api/rules/osv_headers.yaml` (`sheet_markers`, `groups.opening`, `groups.closing`, `groups.turnover` — если
нужно для «ОборотДт», `debit`, `credit`, `total_row_exact`)
- Modify: `api/tests/synthetic/test_synthetic_files.py` (снять `xfail` у s48–s50, s53)
- Modify: `api/tests/unit/parser/test_detect.py`
**Interfaces:**
- Consumes: контрольный набор и `headers.md` из Task 1.
- [ ] **Step 1: падающий unit-тест.** `test_detect.py::test_side_without_word_boundary_only_in_group_cell` — сетка
`sheet_from_rows`: заголовок «Сальдо НачальноеДТВВ | …», ниже строка с текстом «Дтвв» в колонке сумм →
`find_table_header` берёт колонки только из строки групп. Снять `xfail` у тестов s48, s49, s50, s53.
- [ ] **Step 2: тесты падают.** `.venv/bin/pytest tests/synthetic tests/unit/parser/test_detect.py -k "s48 or s49 or
s50 or s53 or word_boundary"` — FAIL (`INVALID_DOCUMENT`/`INVALID_FORMAT`).
- [ ] **Step 3: записи словаря.** Ориентир (уточнить по `headers.md`; ловушки: `\w*` жадно съест приклеенную сторону —
«сальдо\s+начальн\w*» оставит пустой остаток вместо «дтвв»; `\bоборот(ы)?\b` не матчит «оборотдт»):
- `sheet_markers`: `^оборотн\w*\s+ведомост`, `^осв\b`, `^оборотн\w*\s+баланс`, `^фин\.?\s*обороты`,
`^отчет\s+по\s+главной\s+книге`;
- `groups.opening`: `остат\w*\s+на\s+начало`, `сальдо\s+начальное`, `сальдо\s+вх\b`, `^начало(?=\s*(дт|кт))`;
- `groups.closing`: `остат\w*\s+на\s+конец`, `сальдо\s+конечное`, `сальдо\s+исх\b`, `^конец(?=\s*(дт|кт))`;
- `groups.turnover` (если `headers.md` подтверждает «ОборотДт» без пробела): `^оборот(?=\s*(дт|кт))`;
- `debit`: `^по\s+дебету\b`, `^дт\s*вв\b`; `credit`: `^по\s+кредиту\b`, `^кт\s*вв\b`;
- `total_row_exact`: `^всего$` (комментарий: ГК КТЗ, сч. 009, AURORA-1732).
Каждой записи — комментарий с источником (AURORA-…, система). Если сторону без границы слова нельзя выразить
записью (остаток ячейки не начинается со стороны), правка `_side`/`_group_cells` общая, без упоминания системы
(design D2), и тест на неё.
- [ ] **Step 4: тесты зелёные.** Тот же `pytest` → PASS; все `test_neg_…` контрольного набора → PASS (негатив упал —
запись словаря слишком широкая: сузить, не ослаблять тест). `make golden-update`: меняются снимки s48–s50, s53; снимки
негативов s55–s57 и всех старых файлов не изменились (изменились — стоп, разобрать). `make check` → PASS (покрытие
словарей видит новые записи).
- [ ] **Step 5: коммиты.** tabular-parser: `rules/osv_headers.yaml`, `tests/synthetic/test_synthetic_files.py`,
`tests/unit/parser/test_detect.py` — «ОСВ: названия ведомостей, группы и стороны других систем, «ВСЕГО» (#81)».
fixtures (`git -C W/fixtures`): снимки s48–s50, s53 — «Golden синтетики s48–s50, s53 под tp#81».
---
### Task 3: Запасной признак листа ОСВ по заголовку таблицы
**Files:**
- Modify: `api/src/parser/detect.py` (новая `has_osv_table`, `classify_sheet`, `__all__`)
- Modify: `api/src/parser/statements/adapter.py:56-83` (`plan_sheets`)
- Modify: `api/src/parser/book.py:146`
- Modify: `api/tests/synthetic/test_synthetic_files.py` (снять `xfail` у s51, s52, s54)
- Test: `api/tests/unit/parser/test_detect.py`, `api/tests/unit/parser/statements/test_adapter.py` (тесты `plan_sheets`), `api/tests/unit/parser/test_book_parser.py`
**Interfaces:**
- Consumes: контрольный набор из Task 1 (s51, s52, s54 — позитивы; s55–s57 — негативы).
- Produces:
- `detect.has_osv_table(sheet: Sheet, rules: Rules) -> bool` — в первых `SHEET_MARKER_ROWS` строках есть строка, где
`_group_cells` даёт все три группы, и `_columns_of_group` для каждой возвращает `debit` и `credit` (не `collapsed`);
`InvalidFormatError` из `_columns_of_group` → `False`. Без дедлайна (≤ 30 строк).
- `plan_sheets(sheets, is_osv, rules, deadline, osv_by_table: Callable[[Sheet], bool] | None = None) -> SheetPlan` —
в ветке `else` (лист пошёл бы в `other`): если `osv_by_table` задан и истинен — лист в `osv`, `last_type = None`;
иначе `other`, как сейчас.
- `classify_sheet`: если пакет дал «прочее» и `has_osv_table` — `SheetKind.OSV` (согласованность с `plan_sheets`).
- `book.py`: `plan_sheets(..., osv_by_table=lambda sheet: has_osv_table(sheet, self._rules))`.
- [ ] **Step 1: падающие тесты.**
- `test_detect.py`:
- `test_has_osv_table_navision_split_sides` — `sheet_from_rows`: первая строка «ФинСчет | НачалоДт | НачалоКт |
ОборотДт | ОборотКт | КонецДт | КонецКт» (тексты — из `headers.md`), строки данных → `True`;
- `test_has_osv_table_requires_opening` — то же без «НачалоДт/Кт» → `False`;
- `test_has_osv_table_rejects_collapsed` — «Сальдо на начало | Обороты (Дебет/Кредит) | Сальдо на конец» с одной
колонкой у начала → `False`;
- `test_has_osv_table_rejects_disclosure` — «Наименование | Остаток на начало | Поступило | Выбыло | Остаток на
конец» → `False` (Review Focus 1);
- `test_has_osv_table_only_first_rows` — полный заголовок в строке 31 → `False`;
- `test_classify_sheet_falls_back_to_table` — Navision-сетка → `SheetKind.OSV`.
- тест `plan_sheets`:
- `test_plan_sheets_table_fallback_only_for_other` — лист формы, затем Navision-лист, затем «Справка» → `osv`
содержит только Navision-лист, «Справка» в `other`; `osv_by_table` вызван только для двух последних листов
(счётчик вызовов, Review Focus 5);
- `test_plan_sheets_weak_continuation_not_taken` — лист формы, затем лист из 1–2 кодов (слабое продолжение,
образец — существующие тесты `is_weak_continuation`) → остаётся `STATEMENT_CONTINUATION`, `osv_by_table` не
вызван (Review Focus 4).
- снять `xfail` у тестов s51, s52, s54 контрольного набора.
- `test_book_parser.py::test_statement_book_sheets_unchanged_by_fallback` — книга s14 (формы + ОСВ): виды листов те же,
что без запасного признака.
- [ ] **Step 2: тесты падают.** `cd api && .venv/bin/pytest tests/unit/parser tests/synthetic -k "osv_table or
plan_sheets or s51 or s52 or s54 or fallback"` → FAIL (`ImportError: has_osv_table`, `INVALID_DOCUMENT`).
- [ ] **Step 3: реализация** по Interfaces. `has_osv_table` переиспользует `_group_cells` и `_columns_of_group` (R-6),
отдельного разбора сторон нет.
- [ ] **Step 4: тесты зелёные.** Тот же `pytest` → PASS; все `test_neg_…` → PASS; `make golden-update` — в diff
меняются только снимки s51, s52, s54, снимки негативов s55–s57 и старых файлов не изменились; `make check` → PASS.
- [ ] **Step 5: коммиты.** tabular-parser: «ОСВ: запасной признак листа по заголовку таблицы (#81, AURORA-1717)».
fixtures: снимки s51, s52, s54 — отдельным коммитом.
---
### Task 4: Golden, регрессия банка, check-full
**Files:**
- Modify (генерация): `W/fixtures/golden/expected_osv_parse/*.json`
- Create (вывод): `/Users/andrey.epifancev/projects/awp-ca/admin/osv-other-systems-regress/new.csv`, `report.md`
- [ ] **Step 1: golden.** `make golden-check` → код 0; `git -C W/fixtures diff --stat f1f7f83 -- golden/` — только
добавленные снимки s48–s57, ни одного изменённого (изменённый — стоп, разобрать с объяснением в design.md).
Контрольный набор целиком: `.venv/bin/pytest tests/synthetic -k "s48 or s49 or s50 or s51 or s52 or s53 or s54 or
s55 or s56 or s57" -rA` → все PASS, `xfail` не осталось; таблица «тест → база → ветка» дописывается в
`control-base.md`.
- [ ] **Step 2: прогон ветки.** `regress.py run new.csv`; `regress.py diff base.csv new.csv > report.md`.
- [ ] **Step 3: разбор.** Каждое ухудшение и каждый переход в `osv` из формы/«прочее» — открыть файл, решить: ведомость
(ожидаемо: «Оборотный баланс» 1718 и файлы списков 1717/1719) или ложное срабатывание. Ложное — правка записи словаря
или якоря, повтор Task 2 Step 4 и этого шага. Не устраняется — стоп, вопрос владельцу с файлом и тем, что видно.
В `report.md` — таблица переходов, список ручных проверок с вердиктом, файлы списков 1717/1719, оставшиеся с
ошибкой, с причиной (1720–1723, «Обороты счета», безымянный лист, ушедший в форму).
- [ ] **Step 4: `make check-full`** (поднимает compose для интеграционных тестов) → PASS. Также
`.venv/bin/pytest tests/fixtures/test_osv_dkb.py` → PASS.
---
### Task 5: Правило 05-domain-rules, документация сервиса
**Files:**
- Modify: `W/docs/context/05-domain-rules.md` §1.1 (поиск листа), §1.2 (написания групп и сторон); строка «Итого» —
где перечислены итоговые подписи (§1.3 или §1.4)
- Modify: `W/tabular-parser/README.md` (раздел «Парсер ОСВ»: признаки листа, запасной признак), `W/tabular-parser/CLAUDE.md`
(перечень синтетики s48–s57 в абзаце golden), docstring `generate.py` (s48–s57)
- Modify: `W/tabular-parser/openspec/changes/osv-other-systems/design.md` — решения, принятые по ходу (R-9…), и
сводка регрессии из `report.md`
- [ ] **Step 1: §1.1** по design D4: признак — название ведомости по `sheet_markers` (перечислить написания) или,
если лист не опознан как форма или продолжение, полный заголовок таблицы ОСВ в первых 30 строках: три группы, у каждой
Дебет и Кредит, без свёрнутого сальдо. §1.2 — написания групп и сторон из Task 2 (сторона в ячейке группы, «по
дебету», приклеенная «ДТВВ»). «ВСЕГО» — рядом с «ИТОГ».
- [ ] **Step 2: проверки.** `openspec validate osv-other-systems --strict` → valid; `make check` → PASS (docstring и
README не ломают линтеры).
- [ ] **Step 3: коммиты.** docs (`git -C W/docs`): «05 §1.1–§1.2: ОСВ других учётных систем (tp#81)»; tabular-parser:
«Документация: признаки листа ОСВ, синтетика s48–s57 (#81)».