35 KiB
ОСВ 1С в плоской форме (AURORA-1661, структура) Implementation Plan
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: ОСВ 1С по одному счёту в плоской форме («по счету: X», колонка «Субконто», без строки счёта, «Итого развернутое» перед «Итого») разбирается без INVALID_FORMAT, со счётом и организацией из шапки, а контрагенты расчётных счетов без названия вида получают COUNTERPARTY.
Architecture: Изменения в конвейере листа ОСВ tabular-parser: шапка (header.py) — код счёта с двоеточием, строка вида субконто, «План счетов»; итоговый блок (classify.collect_body_rows, checks._total); новая стратегия каскада header_account (hierarchy.py) и счёт шапки у корня ветки (classify_rows); правило расчётных счетов — данные osv_groups.yaml. Контракт результата не меняется.
Tech Stack: Python 3.11, pydantic v2, openpyxl, pytest; OpenSpec 1.13.
Spec: openspec/changes/osv-flat-form/ (proposal.md, design.md, specs/osv-parsing/spec.md)
Ветка: feat/59-osv-flat-form во всех трёх worktree: /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/{tabular-parser,fixtures,docs}. Issue — RSHB/arm-tabular-parser#59. Jira — AURORA-1661 (структурная часть).
Global Constraints
- Каждая shell-команда начинается с
cd /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/<repo> &&; git — толькоgit -C /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/<repo>; в основные checkout'ы (/Users/andrey.epifancev/projects/awp-ca/tabular-parserи т. п., ветка main) не заходить и не писать. Хук отказал — стоп, вернуться к контроллеру с текстом отказа. - Код Python правится только Edit/Write, не
sed -i/heredoc через Bash. - Нет координат в
src/parser/**(AST-линтерtests/unit/parser/test_no_coordinates.py): ни букв колонок, ни номеров строк, ни имён листов. Новые синонимы и списки — данныеapi/rules/*.yaml, неifв коде. - Деньги —
Decimal,floatнет.needs_check— только по контрольным суммам; структуру под сходимость не подгонять. - Контракт (
api/contracts/,src/contract/) не меняется;CONTRACT_VERSIONпрежний. - Синтетика этого плана —
s40–s44(номераs33–s39— у параллельного трека A, не занимать). - Golden (
../fixtures/golden/expected_osv_parse/) правится толькоmake golden-updateс просмотром diff, коммит — в репозиторий fixtures отдельно от кода, сообщение с(tabular-parser#59). Изменение снимка реального файла, не объяснённое задачей, — стоп и вопрос контроллеру. - Реальные файлы контура (
/Users/andrey.epifancev/projects/awp-ca/result_dir.zip,/Users/andrey.epifancev/projects/awp-ca/ОСВ сч. 60 за 2 кв. 2026.xlsx) в git не кладутся: только распаковка во временный каталог вне репозиториев для сверки. - Трек A (
.wt/osv-indicators/, AURORA-1613 + валюта 1661) владеет путёмdata_type(rules/data_types.yaml,rules.RowDataType,classify.row_data_type,checks._INDICATOR_DATA_TYPES, колонка «Показатели», умолчание BU) — эти места не трогать. - Коммиты — по-русски, с
(#59)в теме и строкойCo-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>. - Проверка задачи:
cd /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/tabular-parser && make checkзелёный (≈95 с; включает fixtures-тесты).
Review Focus
- «по счету:» без кода после двоеточия (код в соседней ячейке или нет вовсе) —
account_code_rawпуст, работает фолбэк OQ-134 из строки таблицы с прежним предупреждением; тест в задаче 2. - Название и период в одной строке («…по счету 60 за 1 квартал 2025 г.») — строки вида субконто нет, организация по прежнему правилу (первая не-метка); тест в задаче 2.
- На листе только «Итого развернутое», без обычной «Итого» — она единственная итоговая и сверяется как «Итого»; тест в задаче 3.
- Счёт в шапке, строк-счетов нет, но есть outline (контрагент → договоры) — побеждает
outline,header_accountне используется, строки всё равно наследуют счёт шапки; тест в задаче 4. - Счёт ветки «760» или «600» при записи
76/60вcounterparty_accounts— не расчётный, строки OTHER; тест в задаче 5.
Task 1: Синтетика s40–s44 и снимки текущего поведения
Files:
- Modify:
api/tests/synthetic/generate.py(функции_s40…_s44, записи реестраFILES, docstringmain— «s01…s44») - Create:
../fixtures/osv/synthetic/s40_flat_header_account.xlsx,s41_flat_chart_of_accounts.xlsx,s42_subaccounts_totals_reversed.xlsx,s43_subkonto_unnamed_60.xlsx,s44_subkonto_unnamed_10.xlsx - Create: снимки
../fixtures/golden/expected_osv_parse/synthetic/s40…s44.json(make golden-update)
Interfaces:
- Produces: пять файлов синтетики; ожидания по ним задачи 2–5 проверяют разбором через
OsvParser/get_parser().
Содержимое листов (все — один лист, колонка A — текст, B–G — суммы «Сальдо на начало / Обороты за период / Сальдо на конец периода» × «Дебет | Кредит»; заголовок групп — объединения B:C, D:E, F:G; числа — Decimal; пусто — нет ячейки):
-
s40: r2 «Оборотно-сальдовая ведомость по счету: 76.Н.1», r3 «Контрагенты», r4 «за 2025 г.», r5 «ООО "Товарное хозяйство"», r7 «Единица измерения: рубль (код по ОКЕИ 383)», r9 «Субконто» + группы, r10 подзаголовки; данные без outline и отступов: «Август - Агро ООО» 1000.00 | — | 500.00 | 300.00 | 1200.00 | —; «АВИС ООО» — | 200.00 | 100.00 | 400.00 | — | 500.00; «Агроаспект ООО» — | — | 50.00 | 50.00 | — | —; «Итого развернутое» 1000.00 | 200.00 | — | — | 1200.00 | 500.00; «Итого» 800.00 | — | 650.00 | 750.00 | 700.00 | —. -
s41: r2 «Оборотно-сальдовая ведомость по счету: 60», r3 «Организации», r4 «за 2 Квартал 2026 г. План счетов ООО Хреновская Нива», r5 «Субконто» + группы, r6 подзаголовки; «Автодор-Платные Дороги ООО» 820.21 | — | 5000.00 | 4991.00 | 829.21 | —; «АГРОПРОФИ ООО» — | 2763.79 | — | — | — | 2763.79; «Итого развернутое» 820.21 | 2763.79 | — | — | 829.21 | 2763.79; «Итого» — | 1943.58 | 5000.00 | 4991.00 | — | 1934.58. -
s42: r2 «Оборотно-сальдовая ведомость по счету: 76», r3 «за 2025 г.», r4 «ООО "Товарное хозяйство"», r6 «Единица измерения: рубль (код по ОКЕИ 383)», r8 «Субконто» + группы, r9 подзаголовки; «76.2» 1000.00 | — | — | 1000.00 | — | —; «76.5» — | 300.00 | 700.00 | 600.00 | — | 200.00; «76.Н» — | — | 5000.00 | 5000.00 | — | —; «76.Н.1» — | — | 5000.00 | 5000.00 | — | —; «Итого развернутое» 1000.00 | 300.00 | — | — | — | 200.00; «Итого» 700.00 | — | 5700.00 | 6600.00 | — | 200.00. -
s43: r1 «ООО "Октябрьское"», r2 «Оборотно-сальдовая ведомость по счету 60», r3 «Период: 9 месяцев 2024 г.», r5 «Субконто» + группы, r6 подзаголовки; «60.01» 34618000.00 | — | 4236500.00 | 38854500.00 | — | —; отступ 1: «ИП Иванов А.А.» 34618000.00 | — | — | 34618000.00 | — | —; отступ 1: «ООО "ЭСТЕТ ОМСК"» — | — | 4236500.00 | 4236500.00 | — | —; «Итого» и затем «Итого развернутое» — обе 34618000.00 | — | 4236500.00 | 38854500.00 | — | —. -
s44: какs43, но счёт 10: r2 «…по счету 10», «10.01» и строки «Мука пшеничная», «Сахар-песок» с теми же суммами. -
Step 1: Добавить
_s40…_s44и записи реестра, по образцу_s31/_s32(листы черезSheetSpec/put, отступ —CellSpec(..., indent=1)). -
Step 2: Сгенерировать файлы
Run: cd /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/tabular-parser/api && .venv/bin/python -m tests.synthetic.generate ../../fixtures/osv/synthetic (если .venv нет — cd .. && make venv).
Expected: в выводе пути s40…s44; git -C /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/fixtures status — пять новых файлов, прежние файлы синтетики без изменений (генератор детерминирован).
- Step 3: Снимки текущего поведения
Run: cd /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/tabular-parser && make golden-update
Expected: новые снимки только synthetic/s40…s44.json; у s40, s41 — status: "error", INVALID_FORMAT «иерархия строк не определена»; s42 — needs_check, «Итого развернутое» — OTHER; s43, s44 — контрагенты/материалы OTHER. Других изменений снимков нет.
-
Step 4:
make checkзелёный -
Step 5: Commit (два репозитория)
git -C /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/fixtures add osv/synthetic golden/expected_osv_parse/synthetic
git -C /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/fixtures commit -m "Синтетика s40–s44: плоская форма ОСВ 1С, снимки текущего поведения (tabular-parser#59)"
git -C /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/tabular-parser add api/tests/synthetic/generate.py
git -C /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/tabular-parser commit -m "test: синтетика s40–s44 — плоская форма ОСВ 1С (#59)"
Task 2: Шапка — код счёта с двоеточием, строка вида субконто, «План счетов»
Files:
- Modify:
api/rules/osv_headers.yaml(header_markers.chart_of_accounts: ['\bплан\s+счетов\b']) - Modify:
api/src/parser/rules.py(HeaderMarker+"chart_of_accounts";OsvGroupRules.known) - Modify:
api/src/parser/header.py(_account_code,_organization, новый_kind_row_cells,_variant1_groups/build_group_levels) - Test:
api/tests/unit/parser/test_header.py,api/tests/unit/parser/test_rules.py
Interfaces:
-
Produces:
OsvGroupRules.known(text: str) -> GroupType | None— тип по явной записиmapping,Noneдляdefault;header._kind_row_cells(cells: list[_HeadCell], rules: Rules) -> list[_HeadCell]— ячейки строго между строкой ячейки с меткойaccountи строкой периода (_find_period), если первая выше; иначе[]. -
Step 1: Тесты (падают) — в
test_header.pyчерезsheet_from_rows:test_account_code_after_colon: «Оборотно-сальдовая ведомость по счету: 76.Н.1» →account_code_raw == "76.Н.1",ACCOUNT_NOT_FOUND_WARNINGнет вwarnings.test_colon_without_code_falls_back_to_table_row(Review Focus 1): «…по счету:» без кода →account_code_raw == ""иACCOUNT_NOT_FOUND_WARNINGвwarnings(фолбэк делаетosv._with_table_account).test_kind_row_between_title_and_period_is_not_organization: строки «…по счету: 76», «Контрагенты», «за 2025 г.», «ООО "Товарное хозяйство"» →organization_name == 'ООО "Товарное хозяйство"'.test_organization_from_chart_of_accounts_tail: «…по счету: 60», «Организации», «за 2 Квартал 2026 г. План счетов ООО Хреновская Нива» →organization_name == "ООО Хреновская Нива",period_quarter == "Q2",period_year == 2026.test_title_and_period_in_one_row_keep_old_organization_rule(Review Focus 2): существующая раскладка_variant1(orgвыше названия с периодом) →organization_name == 'АО "Ромашка"'.test_known_kind_row_extends_single_account_schema: шапка как уs40, заголовок «Субконто» →build_group_schema(...)[0] == [ACCOUNT("Субконто"), COUNTERPARTY("Контрагенты")].test_unknown_kind_row_is_not_added_to_schema: строка «Материалы» → схема[ACCOUNT("Субконто")].test_organizations_kind_row_is_counterparty(R-10): строка «Организации» → схема[ACCOUNT("Субконто"), COUNTERPARTY("Организации")]; вosv_groups.yamlдобавить['^организаци', COUNTERPARTY]после^контрагент.- в
test_rules.py:test_known_group_is_none_for_default—rules.groups.known("Контрагенты") == "COUNTERPARTY",known("Материалы") is None,known("Организации") == "COUNTERPARTY",known("Номенклатура") == "OTHER".
-
Step 2: Запуск —
cd …/tabular-parser/api && .venv/bin/pytest tests/unit/parser/test_header.py tests/unit/parser/test_rules.py -q→ новые тесты FAIL. -
Step 3: Реализация.
_account_code: хвост_tail(...)очистить от ведущих:и пробелов перед_ACCOUNT_CODE.match._organization: сначала хвост меткиchart_of_accountsв любой ячейке шапки (текст после совпадения,strip(" ,;:"), непустой); затем первая не-метка, исключая ячейки_kind_row_cells; затем_title_tail_organization. Ячейка периода, содержащая «План счетов», остаётся периодом (_cut_periodрежет на «г.»)._is_structural_marker: меткаchart_of_accounts— структурная.- Схема: если
_variant1_groupsдал ровно одну ACCOUNT, добавить по элементу текста_kind_row_cells(через запятую,strip), для которогоrules.groups.known(item)неNone:_group(known, item); вариант 2 не меняется.
-
Step 4: Тесты PASS, затем
make golden-update: ожидаемо меняются толькоsynthetic/s40иs41(если они ещёerror— без изменений) иs42(account_code_raw«76» вместо «76.2», без предупреждения «взят из первой строки-счёта»). Изменение реального снимка — стоп (Global Constraints). -
Step 5:
make checkзелёный; commit — tabular-parser:feat(osv): код счёта «по счету: X», строка вида субконто, «План счетов» в шапке (#59); fixtures (если снимки изменились):Снимки s40–s42: шапка плоской формы (tabular-parser#59).
Task 3: «Итого развернутое» перед «Итого»
Files:
- Modify:
api/src/parser/classify.py(collect_body_rows, новый_leading_totals) - Modify:
api/src/parser/checks.py(_total,total_matches_root,run_checks) - Modify:
api/src/parser/osv.py(передатьrules.headers.total_row_secondaryвrun_checks) - Test:
api/tests/unit/parser/test_classify.py,api/tests/unit/parser/test_checks.py
Interfaces:
-
Produces:
collect_body_rows(...)— тот же кортеж(body, total_row, warnings), ноtotal_row— первая строка итогового блока (вторые итоговые строки непосредственно перед основной «Итого» входят в блок);run_checks(rows, exempt=None, *, secondary_totals: Sequence[Pattern] = ()) -> tuple[OsvChecks, list[OsvNote]];total_matches_root(rows, secondary_totals: Sequence[Pattern] = ()) -> bool. -
Step 1: Тесты (падают)
test_secondary_total_before_main_total_is_in_total_block(test_classify.py): лист какs42→total_row= номер строки «Итого развернутое»; послеclassify_rowsобе строки —TOTAL,level == 0,parent is None.test_only_secondary_total_is_the_total(Review Focus 3): на листе только «Итого развернутое» → онаTOTAL,total_row— её номер.test_total_check_uses_main_total_when_secondary_comes_first(test_checks.py): строки TOTAL в порядке «Итого развернутое», «Итого»; суммы «Итого» = корень, «Итого развернутое» — нет →total_matches_root(rows, secondary) is True; безsecondary(прежний вызов) —False.- Разбор
s42черезget_parser():status == "done",checks.parent_sum_violations == [],checks.balance_identity_violations == [].
-
Step 2: Запуск — FAIL.
-
Step 3: Реализация.
_leading_totals(sheet, header, rows, total, rules) -> int— идти от индекса основной «Итого» назад, пока текст строки (колонка текста) матчитtotal_row_secondary; вернуть номер самой верхней (илиtotal). Вcollect_body_rowsвернуть его какtotal_row; хвост — по-прежнему от последней из вторых строк после основной.checks._total(rows, secondary)— первая TOTAL, чейbody.textне матчит ни один паттернsecondary, иначе первая TOTAL. -
Step 4: PASS;
make golden-update— ожидаемоsynthetic/s42.json(обе итоговые строки TOTAL,status: done); реальные снимки (пакет ДКБ с «Итого развернутое» после «Итого») не меняются. -
Step 5:
make check; commit —fix(osv): «Итого развернутое» перед «Итого» — итоговая строка, сверка по основной «Итого» (#59); fixtures —Снимок s42: «Итого развернутое» перед «Итого» (tabular-parser#59).
Task 4: Плоский список под счётом шапки
Files:
- Modify:
api/src/parser/hierarchy.py(strategy_header_account,resolve_levels) - Modify:
api/src/parser/osv_warnings.py(HIERARCHY_TEXTS["header_account"] = "плоский список под счётом шапки (header_account)") - Modify:
api/src/parser/classify.py(classify_rows(..., header_account: str | None = None)) - Modify:
api/src/parser/checks.py(total_matches_rootбез корня ACCOUNT) - Modify:
api/src/parser/osv.py(передатьheader_accountвresolve_levelsиclassify_rows) - Test:
api/tests/unit/parser/test_hierarchy.py,api/tests/unit/parser/test_checks.py, Createapi/tests/unit/parser/test_osv_flat_form.py
Interfaces:
-
Consumes:
collect_body_rowsиrun_checks(..., secondary_totals=...)задачи 3. -
Produces:
strategy_header_account(rows: Sequence[BodyRow], deadline: Deadline | None = None) -> dict[int, int] | None—{row: 0}для всех строк,None, если хоть однаis_account;resolve_levels(rows, total_row, deadline, *, header_account: bool = False) -> Levels—header_accountпробуется послеaccount_patternи передsums, только приheader_account=True;classify_rows(..., *, inns=None, unpaired=frozenset(), header_account: str | None = None)— корневая ветка_Branch(header_account, 0). -
Step 1: Тесты (падают)
test_header_account_strategy_flat_rows(test_hierarchy.py): три строки без outline/отступа/счёта,header_account=True→strategy == "header_account", все уровни 0, родителиNone.test_header_account_not_used_without_header_code: те же строки,header_account=False→InvalidFormatError(прежнее поведение).test_header_account_not_used_with_account_rows: строки «60.01», «ООО А» без оформления,header_account=True→account_pattern.test_total_without_account_root_matches_level_zero_sum(test_checks.py): три корня OTHER/COUNTERPARTY без ACCOUNT, «Итого» = их сумма (сальдо «Итого» свёрнуто нетто) →True; одна сумма испорчена на 1 →False; только «Итого» с нулями →True(R-13 прежний).test_osv_flat_form.py::test_flat_list_s40(разборs40черезget_parser()илиOsvParser.parse_sheetна листе изsheet_from_rows):status == "done", предупреждениеOSV_HIERARCHY_BY_HEADER_ACCOUNT, у каждой строки данныхlevel == 0,parent_row_number is None,account_code_raw == "76.Н.1",organization_name == 'ООО "Товарное хозяйство"'.test_flat_list_s41:status == "done",account_code_rawстрок"60".test_outline_wins_over_header_account_and_rows_inherit_header_code(Review Focus 4): счёт в шапке «60», строк-счетов нет, outline: «ООО А» (0) → «Договор 1» (1) →strategy == "outline", у обеих строкaccount_code_raw == "60".
-
Step 2: Запуск — FAIL.
-
Step 3: Реализация по Interfaces; в
osv.py:resolve_levels(body, total_row, deadline, header_account=bool(doc.account_code_raw)),classify_rows(..., header_account=doc.account_code_raw or None).total_matches_root: нет корня ACCOUNT — если_level_zero(rows)не пуст,_sums_match(_values(total), _sum_of(roots)), иначе прежняя проверка нулей. -
Step 4: PASS;
make golden-update— ожидаемоs40,s41(done,header_account); реальные снимки — только если у файла есть строки до первой строки-счёта (получатaccount_code_rawшапки, R-4) — каждое такое изменение перечислить в отчёте; иные изменения — стоп. -
Step 5:
make check; commit —feat(osv): стратегия header_account — плоский список под счётом шапки (#59); fixtures —Снимки: плоский список под счётом шапки (tabular-parser#59).
Task 5: Контрагенты расчётных счетов без названия вида
Перед задачей: если трек A (
feat/*в.wt/osv-indicators/tabular-parser, AURORA-1613) уже слит в main —git -C …/tabular-parser fetch origin && git -C …/tabular-parser rebase origin/main,make golden-update, разрешить конфликты вclassify._classify_row(трек A меняет определениеdata_typeв той же функции; сохранить обе правки). Не слит — работать как есть.
Files:
- Modify:
api/rules/osv_groups.yaml(counterparty_accounts: ['58', '60', '62', '66', '67', '75', '76', '80']с комментарием про AURORA-1661, OQ-158 и R-9 (71/73 исключены)) - Modify:
api/src/parser/rules.py(OsvGroupRules.counterparty_accounts: list[str] = [], проверка кодов как уOsvCheckRules,is_counterparty_account; общий хелпер сопоставления «код или его субсчёт» для обоих классов) - Modify:
api/src/parser/classify.py(_classify_row,_Kind,ClassifiedRow.counterparty_by_account: bool = False) - Modify:
api/src/parser/osv.py(заметкаOSV_ROWS) - Test:
api/tests/unit/parser/test_classify.py,api/tests/unit/parser/test_rules.py,api/tests/unit/parser/test_osv_flat_form.py
Interfaces:
-
Consumes:
classify_rows(..., header_account=...)задачи 4. -
Produces:
OsvGroupRules.is_counterparty_account(account_code: str | None) -> bool("76"→76,76.2,76.Н.1; не760);ClassifiedRow.counterparty_by_account— строка получила COUNTERPARTY по плану счетов. -
Step 1: Тесты (падают)
test_rules.py::test_counterparty_accounts_match_code_and_subaccounts:is_counterparty_account("76.Н.1"),("60.01")—True;("760"),("600"),("10.01"),(None)—False(Review Focus 5).test_classify.py::test_unnamed_first_level_on_settlement_account_is_counterparty: схема[ACCOUNT("Субконто")], «60.01» → «ИП Иванов А.А.» (уровень 1) → COUNTERPARTY,group_name is None,counterparty_name == "ИП Иванов А.А.",counterparty_by_account is True.test_unnamed_second_level_stays_other: под таким контрагентом строка уровня 2 → OTHER.test_named_other_group_is_not_reclassified: схема[ACCOUNT, OTHER("Работники организаций")]на 71 → OTHER.test_non_settlement_account_stays_other: счёт «10.01» → OTHER,counterparty_by_account is False.test_osv_flat_form.py::test_s43_counterparties_with_warning: разборs43→ строки «ИП Иванов А.А.», «ООО "ЭСТЕТ ОМСК"» — COUNTERPARTY; вwarningsровно одноOSV_ROWSс текстом «вид субконто в шапке не назван: строки аналитики счёта 60.01 приняты за контрагентов по плану счетов — проверьте»;status == "done".test_s44_materials_stay_other: разборs44→ строки OTHER, предупреждения «вид субконто…» нет.test_s41_flat_counterparties: разборs41→ строки данных COUNTERPARTY (по схеме с видом «Организации», R-10),account_code_raw == "60", предупреждения «вид субконто…» нет.
-
Step 2: Запуск — FAIL.
-
Step 3: Реализация. В
_classify_rowпосле_schema_group: еслиposition == 1,position >= len(schema)иrules.groups.is_counterparty_account(code)— COUNTERPARTY без имени, ИНН — по общей ветке COUNTERPARTY (колонка, строка ИНН, хвост названия); флагcounterparty_by_account. Вosv.py— по одной заметкеOsvNote(OSV_ROWS, …)на каждый различныйaccount_code_rawстрок с флагом, в порядке листа. -
Step 4: PASS;
make golden-update— ожидаемоs41,s43; реальные снимки, где схема — одна ACCOUNT на расчётном счёте с не-счётными строками (пакет ДКБosv-dkb/**), — OTHER → COUNTERPARTY и новоеOSV_ROWS: перечислить файлы в отчёте; статусы снимков не должны меняться (если меняются — стоп). -
Step 5:
make check; commit —feat(osv): контрагенты расчётных счетов без названия вида — по плану счетов (#59); fixtures —Снимки: контрагенты расчётных счетов по плану счетов (tabular-parser#59).
Task 6: Правила, открытые вопросы, README, приёмка на реальных файлах
Files:
-
Modify:
/Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/docs/context/05-domain-rules.md(§1.1, §1.3, §1.4) -
Modify:
/Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/docs/context/07-open-questions.md -
Modify:
README.md(корень tabular-parser, раздел «Парсер ОСВ»: каскад стратегий +header_account),CLAUDE.mdtabular-parser (строка каскада в «Модули» —outline → indent → account_pattern → header_account → sums) -
Modify:
openspec/changes/osv-flat-form/tasks.md(отметить пункты),design.md(раздел «Decisions made without discussion» — дописать рулинги исполнения, если были) -
Step 1: §1 правил (docs). §1.1: код счёта после «по счету» — и через двоеточие; счёт шапки важнее строки таблицы; строка вида субконто между названием и периодом — не организация, известные виды — группировки после ACCOUNT; организация из «План счетов <орг>»; строка таблицы «Нормализация значений шапки» — без изменений. §1.3: стратегия
header_account(условия, место в каскаде); счёт шапки у строк до первой строки-счёта; правило расчётных счетов (список — данныеosv_groups.yaml, позиция 1, без имени, предупреждение; риск OQ-158). §1.4: «Итого развернутое» в любом порядке; «Итого» без корневой ACCOUNT = сумма уровня 0. Ссылки — AURORA-1661, tabular-parser#59, решения владельца 16.10/19.10.2026. -
Step 2: Открытые вопросы (docs) — следующие свободные номера OQ на момент правки (трек A может занять номера раньше — сверить с
origin/maindocs): «Учредители» на 80 (именованная OTHER, гудвилл §4 п. 2); сузить список расчётных счетов (71, 73); «Организации» в строке вида субконто — что это за вид в 1С. Каждый — «открыт», с допущением принятого поведения (R-8 остаётся OTHER, R-9 71/73 исключены, R-10 синоним COUNTERPARTY). -
Step 3: Приёмка на реальных файлах контура (без коммита данных).
Run:
T=$(mktemp -d) && cd "$T" && unzip -q /Users/andrey.epifancev/projects/awp-ca/result_dir.zip 'files/26e4c90d*' 'files/638ecb3d*' 'files/f2e0e04e*' 'files/7e49f8b5*' 'files/45e06f7d*' 'files/cb7e6eb0*' 'files/270b41da*' 'files/738bfcf5*' 'files/8c7e95d9*' && cp "/Users/andrey.epifancev/projects/awp-ca/ОСВ сч. 60 за 2 кв. 2026.xlsx" files/ && echo "$T"
cd /Users/andrey.epifancev/projects/awp-ca/.wt/osv-flat/tabular-parser/api && for f in "$T"/files/*; do ENV_FILE=.env.test .venv/bin/python -m src.parser.cli "$f"; done
Expected (записать таблицей в отчёт задачи): ни одного INVALID_FORMAT; у плоских файлов (26e4c90d, 638ecb3d, f2e0e04e, 7e49f8b5, 45e06f7d, cb7e6eb0, письмо) — OSV_HIERARCHY_BY_HEADER_ACCOUNT, account_code_raw из шапки («76.Н.1», «76.АВ», «76.2», «60»), организация «ООО "Товарное хозяйство"» / «ООО Хреновская Нива», строки — COUNTERPARTY; 270b41da/738bfcf5 — account_code_raw = "76", «Итого развернутое» — TOTAL, нарушение остаётся только у валютного «76.55» (трек A); 8c7e95d9 лист «60 сч.» — контрагенты COUNTERPARTY с предупреждением. Письмо — COUNTERPARTY по схеме «Организации», без предупреждения «вид субконто…». Отклонение — описать, не чинить молча. После — rm -rf "$T".
- Step 4:
make check-fullв tabular-parser (нужен стек compose на портах.env.test; если порты заняты демо-стендом — стоп и вопрос контроллеру);make checkв docs. - Step 5:
openspec validate osv-flat-form --strict(tabular-parser) — без ошибок. - Step 6: Commit — docs:
§1 правил ОСВ: плоская форма 1С — счёт из шапки, «Итого развернутое», header_account, контрагенты по плану счетов (tabular-parser#59, AURORA-1661); tabular-parser:docs: README, CLAUDE.md и change osv-flat-form (#59).