31 KiB
ОСВ других учётных систем — план реализации
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
- Расшифровка ББ, которую пакет не узнал как форму («Остаток на начало | Поступило | Выбыло | Остаток на конец»),
не становится ОСВ: в ней нет оборотов →
has_osv_tableложь (тест в Task 3). - Преамбула шапки «Остаток на начало: … | Остаток на конец: …» над таблицей не принимается за строку групп: в ней нет «оборотов» (тест в Task 2 на синтетике s49 — преамбула выше таблицы).
- Сторона без границы слова («ДТВВ») не ловится в ячейках, где нет группы: «Дт» внутри текста контрагента («ООО Дтвв-Сервис») не даёт стороны (тест в Task 2).
- Лист «прочее» после листа формы, который сейчас идёт как слабое продолжение (
is_weak_continuation), остаётся продолжением — запасной признак проверяется только в ветке «прочее» (тест в Task 3). - Книга с формами и без единого листа ОСВ: на листах форм
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 ветки (код = maina8c9605). Ожидаемо по индексу: ~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: 7osvDocumentпо листам,SHEET_OTHERнет; у листов «ВСЕГО» итог —TOTAL, предупреждения «строка «Итого» не найдена» нет; суммыbase_tree()s49_balance_and_turnover_by_side.xlsx«Остаток на начало периода» / «Остаток на конец периода» с «Дебет/Кредит» под ними, split_groups={"turnover": ("Оборот по дебету", "Оборот по кредиту")}; над таблицей преамбула «Остаток на начало: 0 | Остаток на конец: 0» (Review Focus 2)шесть колонок, суммы дерева, статус dones50_sap_in_out_glued_side.xlsxлист SAP «Сальдо вх Дт | Сальдо вх Кт | Оборот Дт | Оборот Кт | Сальдо исх Дт | Сальдо исх Кт»; лист НМЖК «Сальдо НачальноеДТВВ | … | Сальдо КонечноеКТВВ»; контрагент «ООО Дтвв-Сервис» (Review Focus 3) оба листа — шесть колонок с верными сторонами, суммы дерева, «ООО Дтвв-Сервис» — строка контрагента, dones51_untitled_navision.xlsxuntitled=True, триsplit_groups«НачалоДт/НачалоКт», «Оборот…», «КонецДт/КонецКт» (поheaders.md), первая колонка «ФинСчет»один osvDocument,SHEET_OTHERнет, суммы дерева; статус — фактический (без шапки ожидаемоneeds_checkпо периоду/организации), зафиксировать вEXPECTEDс комментариемs52_mixed_book.xlsxstatement_sheet()(форма ББ) → лист как в s51 →reference_sheet()документ формы тот же, что у книги без Navision-листа; Navision — osvDocument; «Справка» —SHEET_OTHERs53_balance_by_side.xls,s54_untitled_navision.xlsbs49 через to_xls, s51 черезto_xlsbте же проверки, что у s49 / s51 (читатели xls и xlsb) Негативы (проходят и на базе, и после — иначе успех опровергнут):
Файл Что в нём Тест и проверки s55_not_osv_titles.xlsxобычная ОСВ 1С (чтобы книга не была ошибкой) + листы с таблицей без групп ОСВ: «Справка: оборотная ведомость прилагается», «Оборотный капитал и его структура», отчёт 1С «Обороты счета 60» с колонками корр. счетов внутри «Оборот Дт/Кт» (R-1) 1 osvDocument; три листа —SHEET_OTHERs56_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), docstringgenerate.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)».