25 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
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 НМЖК — «Оборот» со стороной.
Коммита нет (каталог admin/ не репозиторий).
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/generate.py(OsvSpec,_header_v1,_header_rows,build_osv, новые_s48…_s50,FILE_NAMES, docstring модуля) - Modify:
api/tests/synthetic/test_synthetic_files.py(EXPECTED, список файлов, новые тесты) - Modify:
api/tests/unit/parser/test_detect.py - Create (генерация):
W/fixtures/osv/synthetic/s48_*.xlsx,s49_*.xlsx,s50_*.xlsx; Modify:W/fixtures/osv/synthetic/README.md
Interfaces:
-
Consumes:
headers.mdиз Task 1. -
Produces: поля
OsvSpec(используются в Task 3):title: str | None = None— строка названия вместо «Оборотно-сальдовая ведомость по счету {account} за {period}» (None— как сейчас);untitled: bool = False— без строк организации, названия, параметров: заголовок таблицы с первой строки;split_groups: dict[str, tuple[str, str]] = {}— группы, у которых вместо слитого названия + «Дебет/Кредит» две ячейки в строке групп со своими текстами (сторона в ячейке группы); если разделены все три группы, строки подзаголовков нет;total_text: str = TOTAL_TEXT.
-
Step 1: падающие тесты синтетики. Тексты брать из
headers.md; ниже — ориентир:s48_other_system_titles.xlsx— пять листовbuild_osvсtitle: «Оборотная ведомость по счету 60 за 1 квартал 2025 г.», «ОСВ по сч. 60 за 1 квартал 2025 г.», «Оборотный баланс …», «Фин. Обороты Листок», «Отчет по Главной книге …»; у последнего листаtotal_text="ВСЕГО". Тестtest_s48_other_system_titles_are_osv: 5 документовosvDocumentс локаторами листов,SHEET_OTHERнет; у листа «ВСЕГО» строка итогаTOTAL, предупреждения «строка «Итого» не найдена» нет.s49_balance_and_turnover_by_side.xlsx—group_titles«Остаток на начало периода» / «Остаток на конец периода»,split_groups={"turnover": ("Оборот по дебету", "Оборот по кредиту")}; над таблицей преамбула «Остаток на начало: 0 | Остаток на конец: 0» (Review Focus 2). Тест: шесть колонок сумм, суммыbase_tree()верны, статусdone.s50_sap_in_out_glued_side.xlsx— два листа: SAP «Сальдо вх Дт | Сальдо вх Кт | Оборот Дт | Оборот Кт | Сальдо исх Дт | Сальдо исх Кт» (все триsplit_groups), SAP НМЖК «Сальдо НачальноеДТВВ | Сальдо НачальноеКТВВ | … | Сальдо КонечноеКТВВ»; у контрагента имя «ООО Дтвв-Сервис» (Review Focus 3). Тест: оба листа — шесть колонок с верными сторонами, суммы как у дерева, «ООО Дтвв-Сервис» — строка контрагента.test_detect.py::test_side_without_word_boundary_only_in_group_cell— сеткаsheet_from_rows: заголовок «Сальдо НачальноеДТВВ | …», ниже строка с текстом «Дтвв» в колонке сумм →find_table_headerберёт колонки только из строки групп. Добавить имена вEXPECTED(s48 —documents=5), в список файлов иFILE_NAMES.
-
Step 2: генерация и прогон — тесты падают.
cd api && .venv/bin/python -m tests.synthetic.generate W/fixtures/osv/synthetic→.venv/bin/pytest tests/synthetic tests/unit/parser/test_detect.py -k "s48 or s49 or s50 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.tests/fixtures/test_golden.pyтребует снимок на каждый синтетический файл:make golden-update, в diff — только новые снимки s48–s50 (изменился старый — стоп, разобрать). Затемmake check→ PASS (покрытие словарей видит новые записи на s48–s50). -
Step 5: коммиты. tabular-parser:
rules/osv_headers.yaml,tests/synthetic/*,tests/unit/parser/test_detect.py— «ОСВ: названия ведомостей, группы и стороны других систем, «ВСЕГО» (#81)». fixtures (git -C W/fixtures):osv/synthetic/s48…s50, README — «Синтетика s48–s50 под tp#81»; снимкиgolden/expected_osv_parse/s48…s50— отдельным коммитом «Golden синтетики s48–s50 под 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/generate.py(_s51),api/tests/synthetic/test_synthetic_files.py - 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: поля
OsvSpecиз Task 2 (untitled,split_groups). -
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).
- синтетика
s51_untitled_navision.xlsx(build_osvсuntitled=True,split_groupsдля трёх групп поheaders.md) иtest_s51_untitled_sheet_is_osv_by_table: одинosvDocument,SHEET_OTHERнет, суммы дерева верны;EXPECTED— статус по факту разбора шапки без периода (ожидаемоneeds_checkиз-за ненайденного периода/организации; если иначе — зафиксировать фактический и объяснить в комментарии). test_book_parser.py::test_statement_book_sheets_unchanged_by_fallback— книга s14 (формы + ОСВ): виды листов те же, что без запасного признака.
-
Step 2: тесты падают.
make venvуже есть;cd api && .venv/bin/python -m tests.synthetic.generate W/fixtures/osv/synthetic && .venv/bin/pytest tests/unit/parser tests/synthetic -k "osv_table or plan_sheets or s51 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;make golden-update— в diff только новый снимок s51;make check→ PASS. -
Step 5: коммиты. tabular-parser: «ОСВ: запасной признак листа по заголовку таблицы (#81, AURORA-1717)». fixtures:
osv/synthetic/s51…, README — «Синтетика s51 под tp#81»; снимок s51 — отдельным коммитом.
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–s51, ни одного изменённого (изменённый — стоп, разобрать с объяснением в design.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–s51 в абзаце golden), docstringgenerate.py(s48–s51) -
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–s51 (#81)».