## Context Плоская форма ОСВ 1С по одному счёту (образцы — выгрузка контура `awp-ca/result_dir.zip` и файл письма `awp-ca/ОСВ сч. 60 за 2 кв. 2026.xlsx`; в git не кладутся, только локальная сверка): ``` r2 Оборотно-сальдовая ведомость по счету: 76.Н.1 r3 Контрагенты ← вид субконто (у письма — «Организации», у O-3 — «Материалы») r4 за 2025 г. ← у письма: «за 2 Квартал 2026 г. План счетов ООО Хреновская Нива» r5 ООО "Товарное хозяйство" ← организация (у письма — хвост «План счетов …» строки периода) r7 Единица измерения: рубль (код по ОКЕИ 383) r9 Субконто | Сальдо на начало периода | Обороты за период | Сальдо на конец периода r10 | Дебет | Кредит | Дебет | Кредит | Дебет | Кредит r11 Август - Агро ООО | … суммы … ← сразу контрагенты: outline 0, отступ 0, строки счёта нет … Итого развернутое | … ← у файлов ТХО и письма — ПЕРЕД «Итого» Итого | … ``` Вариант со строками субсчетов (ТХО 76, `270b41da`): строки «76.2», «76.5», …, «76.Н.1» на уровне 0 без строки «76», «Итого развернутое» перед «Итого». Вариант «Субконто» с отступами (ОСВОКТ-24, `8c7e95d9`, лист «60 сч.»): «60.01», под ним контрагенты с отступом 1, «Итого», затем «Итого развернутое» — контрагенты сейчас `OTHER` без имени. Текущее поведение (main `aef2d35`): письмо и `26e4c90d` — `INVALID_FORMAT` на шаге «иерархия строк»; `270b41da` — `account_code_raw = "76.2"` из первой строки, «Итого развернутое» (r27) — `OTHER` ребёнок «76.Н.1», нарушения `parent_sum [13, 26]`, `balance_identity [27]` (13 — валюта, трек A). ## Goals / Non-Goals **Goals:** разбор плоской формы без `INVALID_FORMAT`; счёт и организация из шапки; «Итого развернутое» — итог в любом порядке; контрагенты расчётных счетов видны ядру как `COUNTERPARTY`. **Non-Goals:** см. proposal (валюта и `data_type` — трек A; «Учредители»; «Организации» по имени). ## Decisions Решения владельца — текст ожидаемого результата AURORA-1661 (заведён по команде владельца 16.10.2026) и запуск трека 19.10.2026 («трек B можешь запускать»): 1. Счёт — из шапки «по счету[:] X»; строки таблицы — его аналитика. Фолбэк OQ-134 (код первой строки-счёта) — только если в шапке счёта нет. 2. «Итого развернутое» — итоговая строка в любом порядке относительно «Итого». 3. Если вид субконто не назван: на расчётных счетах (58, 60, 62, 66, 67, 71, 73, 75, 76, 80) нижний уровень — контрагенты (`COUNTERPARTY`) с предупреждением. Реализация: - **Код счёта с двоеточием** — `header._account_code`: хвост после метки `account` очищается от ведущих «:» и пробелов перед `_ACCOUNT_CODE`. Отдельный синоним не нужен: «по счету:» уже матчит `\bпо\s+счету\b`. - **Строка вида субконто** — ячейки шапки строго между строкой названия (ячейка с меткой `account`) и строкой периода (`_find_period`), если название выше периода. Они не кандидаты в организацию. Их текст (через запятую) — виды субконто: элемент, который `rules.groups` узнаёт **явной** записью `mapping` (не `default`), добавляется в схему после ACCOUNT, если схема варианта 1 состоит из одной ACCOUNT («Субконто»). Неузнанный элемент («Организации», «Материалы») в схему не добавляется. - **Организация** — порядок: хвост метки «План счетов» (`header_markers.chart_of_accounts`, новый ключ) → первая не-метка шапки без строки вида субконто → хвост названия ведомости (как сейчас). - **Итоговый блок** — `collect_body_rows` возвращает номером «Итого» **первую** строку итогового блока: вторые итоговые строки, стоящие непосредственно перед основной «Итого» (`total_row_secondary`), входят в блок. Всё, что использует `row >= total_row` (каскад, классификация, `inn_rows`), работает без изменений. Сверка «Итого = корень» (`checks._total`) берёт первую строку TOTAL, текст которой не матчит `total_row_secondary`; если такой нет — первую TOTAL (как сейчас). - **Стратегия `header_account`** — `hierarchy.strategy_header_account`: все строки уровня 0. `resolve_levels` получает `header_account: bool` (в шапке есть код счёта) и пробует стратегию после `account_pattern` и перед `sums`, только если `header_account` и ни одна строка тела не `is_account`. Код предупреждения `OSV_HIERARCHY_BY_HEADER_ACCOUNT`, текст «иерархия строк — плоский список под счётом шапки (header_account)». - **Счёт шапки у корня ветки** — `classify_rows` получает `header_account: str | None`; корневая ветка `_Branch(header_account, 0)` вместо `_Branch(None, 0)`. - **«Итого» без корня ACCOUNT** — `checks.total_matches_root`: нет корневой ACCOUNT, но есть строки уровня 0 (кроме показателей) — сверка «Итого» с их суммой (допуски R-21, как у суммы уровня 0); строк уровня 0 нет — прежнее правило R-13 (пустая/нулевая «Итого»). - **Контрагенты по плану счетов** — `osv_groups.yaml: counterparty_accounts` (коды верхнего уровня; запись `76` покрывает `76`, `76.2`, `76.Н.1`, но не `760` — как `accounts_without_analytic_balance`). В `_classify_row`: позиция `1` (первая не-счётная) за пределами схемы и счёт ветки из списка → `COUNTERPARTY` без имени группировки; ИНН — по общим правилам (колонка, строка ИНН, хвост названия). Глубже — `OTHER`, как сейчас. Предупреждение один раз на документ (`OSV_ROWS`, `osv.py`): «вид субконто в шапке не назван: строки аналитики счёта <код> приняты за контрагентов по плану счетов — проверьте». ## Decisions made without discussion | № | Маркер | Дата | Решение | Обоснование | Цена ошибки | |---|---|---|---|---|---| | 1 | R-1 | 19.10.2026 | Строка между названием и периодом — вид субконто, не организация | 6 из 98 xlsx контура (все — «Контрагенты»), в `fixtures` таких строк нет — регрессии golden нет | Организация, напечатанная между названием и периодом, не найдётся: предупреждение «наименование организации не найдено», ядро сверяет по ИНН | | 2 | R-2 | 19.10.2026 | Известный вид субконто из этой строки — группировка схемы; неизвестный — не добавляется | «Контрагенты» даёт COUNTERPARTY с именем без угадывания по счёту; «Организации» у письма (60) — неизвестно, что это за вид | Неизвестный вид на нерасчётном счёте остаётся OTHER без имени (как сейчас) | | 3 | R-3 | 19.10.2026 | `header_account` — перед `sums`, только без строк-счетов и при счёте в шапке | Плоский список с равными суммами соседей `sums` вложил бы ложно; условие — определение плоской формы 1С | Файл без оформления и счетов, но с реальной вложенностью по суммам станет плоским → «Итого» не сойдётся → `needs_check` (видно) | | 4 | R-4 | 19.10.2026 | Корень ветки наследует счёт шапки | Ядро группирует свод по `account_code`; без этого у строк плоского файла счёт `null` | Строки до первой строки-счёта в обычных файлах получат счёт шапки вместо `null` (golden покажет) | | 5 | R-5 | 19.10.2026 | «Итого» без корня ACCOUNT сверяется с суммой уровня 0 | Плоский список — несколько корней без счёта; прежнее R-13 требовало нулевой «Итого» | — (пустая ОСВ — прежнее правило) | | 6 | R-6 | 19.10.2026 | Правило расчётных счетов — только позиция 1, только без имени в схеме | 1С кладёт контрагентов первым видом на этих счетах; именованные группировки («Работники организаций», «Учредители») не трогаем | Физлица и подотчётные на 71/73/76 без названия вида → COUNTERPARTY: попадут в «Осталось N» сопоставления (OQ-158) | | 7 | R-7 | 19.10.2026 | Синтетика `s40`–`s44` | Номера `s33`–`s39` оставлены параллельному треку A, чтобы реестр генератора не конфликтовал | Перенумерация при слиянии | ## Risks / Trade-offs - [OQ-158] Контрагенты по плану счетов на 71/73/76 — в том числе физлица и подотчётные: «Осталось N» в сопоставлении вырастет. Смягчение: предупреждение `OSV_ROWS` на документе; вопрос владельцу — сузить список (без 71/73). - [Трек A] До слияния трека A строки без «Выводимые данные» — `data_type = null`; ядро их в свод не берёт. - [Golden] Изменения затронут снимки, где схема — одна ACCOUNT («Субконто») на расчётном счёте и есть не-счётные строки (пакет ДКБ, `osv-dkb/**`): OTHER → COUNTERPARTY. Каждое изменение снимка — с просмотром diff. ## Open Questions - «Учредители» на 80 (именованная OTHER, O-29, ОСВ 80 «Тестовой ГК 1» `02c5df51`): считать COUNTERPARTY? Нужен для гудвилла (§4 п. 2). - Сузить список расчётных счетов (71, 73 — подотчётные и персонал)? - «Организации» в строке вида субконто у письма (60): что это за вид в 1С и стоит ли считать его контрагентами по имени.