ОСВ: запасной признак проверяет ту строку групп, что возьмёт разбор (#81, ревью I-1)

Преамбула «Обороты: …» над шапкой давала ОСВ, разбор листа падал и ронял книгу. Негатив s56 «Преамбула».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-28 21:23:27 +04:00
parent f7cb32e3d4
commit 57c22547be
5 changed files with 45 additions and 8 deletions

View File

@ -184,17 +184,21 @@ def has_osv_table(sheet: Sheet, rules: Rules) -> bool:
"""Запасной признак листа ОСВ без названия ведомости (#81, design D1): в первых `SHEET_MARKER_ROWS` строках есть
строка со всеми тремя группами сумм, и у каждой группы — колонки «Дебет» и «Кредит» (свёрнутое сальдо не годится,
R-3), между которыми в строках заголовка нет других подписей (R-11: у отчёта 1С «Обороты счета» между «Оборот Дт» и
«Оборот Кт» — колонки корр. счетов, это не ОСВ). Колонки ищутся тем же разбором, что у обычного заголовка (R-6)."""
«Оборот Кт» — колонки корр. счетов, это не ОСВ). Проверяется та строка групп, которую возьмёт `find_table_header`
(первая с «оборотами» и «концом»), — иначе преамбула «Обороты: …» над шапкой уронила бы разбор листа; колонки
ищутся тем же разбором, что у обычного заголовка (R-6)."""
for row in range(1, min(sheet.n_rows, SHEET_MARKER_ROWS) + 1):
cells = _group_cells(sheet, row, rules)
if {cell.group for cell in cells} != set(GROUP_ORDER):
groups = {cell.group for cell in cells}
if not REQUIRED_GROUPS <= groups:
continue
if groups != set(GROUP_ORDER):
return False
try:
groups = [_columns_of_group(sheet, row, cells, group, rules) for group in GROUP_ORDER]
columns = [_columns_of_group(sheet, row, cells, group, rules) for group in GROUP_ORDER]
except InvalidFormatError:
continue
if all(_sides_side_by_side(sheet, row, columns) for columns in groups):
return True
return False
return all(_sides_side_by_side(sheet, row, group_columns) for group_columns in columns)
return False

View File

@ -1359,9 +1359,19 @@ def _s56() -> list[SheetSpec]:
],
),
_navision_sheet("Ниже 30", top_padding=30),
_preamble_navision_sheet(),
]
def _preamble_navision_sheet() -> SheetSpec:
"""Полная шапка Navision, над ней преамбула «Обороты за период: … | Остаток на конец: …»: разбор берёт строку
преамбулы, значит, и запасной признак смотрит её — лист прочий, книга не падает (ревью #81, Important-1)."""
sheet = _navision_sheet("Преамбула", top_padding=1)
sheet.put(1, 1, "Обороты за период: 50")
sheet.put(1, 2, "Остаток на конец: 150")
return sheet
def _s57() -> list[SheetSpec]:
"""Негатив: форма ББ с примечанием «см. оборотную ведомость …» и текстом «Остаток на начало периода …», за ней
лист слабого продолжения (одна строка с кодом) — форма и продолжение остаются формой."""

View File

@ -955,7 +955,13 @@ def test_neg_s56_almost_osv_tables_are_other(fixtures_dir: Path) -> None:
outcome = parse_named(fixtures_dir, S56)
assert [doc.locator for doc in documents(outcome)] == ["sheet:ОСВ 60"]
assert sheet_other(outcome) == ["sheet:Расшифровка", "sheet:Без начала", "sheet:Свёрнутое начало", "sheet:Ниже 30"]
assert sheet_other(outcome) == [
"sheet:Расшифровка",
"sheet:Без начала",
"sheet:Свёрнутое начало",
"sheet:Ниже 30",
"sheet:Преамбула",
]
def test_neg_s57_form_with_osv_words_stays_form(fixtures_dir: Path) -> None:

View File

@ -170,6 +170,23 @@ def test_has_osv_table_only_first_rows(rules: Rules, blank_rows: int, expected:
assert has_osv_table(sheet, rules) is expected
@pytest.mark.parametrize(
"preamble",
[
["Обороты за период: 50", "Остаток на конец: 150"],
["Остаток на начало: 100", "Обороты: 50", "Остаток на конец: 150"],
],
)
def test_has_osv_table_checks_the_row_find_table_header_takes(rules: Rules, preamble: list[object]) -> None:
"""Финальное ревью, Important-1: преамбула с «оборотами» над полной шапкой — строка, которую возьмёт разбор
(`find_table_header`); она не годится, значит, лист не ОСВ (иначе разбор листа упадёт и уронит книгу)."""
sheet = sheet_from_rows([preamble, NAVISION_HEADER, NAVISION_ROW])
with pytest.raises(InvalidFormatError):
find_table_header(sheet, rules)
assert has_osv_table(sheet, rules) is False
def test_classify_sheet_falls_back_to_table(rules: Rules) -> None:
assert classify_sheet(sheet_from_rows([NAVISION_HEADER, NAVISION_ROW]), rules) is SheetKind.OSV

View File

@ -135,7 +135,7 @@ xlsb) на main `a8c9605` и на ветке. Сравнение по каждо
| 2026-10-28 | R-2. Запасной признак ищет строку групп только в первых `SHEET_MARKER_ROWS` (30) строках | у безымянных листов таблица начинается сразу; ограничение снижает время и ложные срабатывания на длинных листах «прочее» | безымянный лист с преамбулой длиннее 30 строк не распознаётся; поднимется регрессией D3 |
| 2026-10-28 | R-3. Запасной признак не принимает свёрнутое сальдо (одна колонка в группе) | строгость против ложных ОСВ (D1) | ОСВ без названия со свёрнутым сальдо не распознаётся |
| 2026-10-28 | R-5. Новые `sheet_markers` якорятся к началу ячейки (`^…`); якорь снимается для конкретного названия, только если по спискам 1717 файлы с ним иначе не находятся | «Оборотная ведомость», «Оборотный баланс» могут встретиться в примечаниях к ББ; признак по названию проверяется раньше форм | часть ведомостей с текстом перед названием не распознаётся; видно в регрессии D3 |
| 2026-10-28 | R-6. Запасной признак переиспользует поиск строки групп и колонок обычного заголовка (`_find_group_row`, `_columns_of_group`) в строгом режиме, а не отдельную функцию | запасной признак и конвейер не должны расходиться в том, что считать заголовком | — |
| 2026-10-28 | R-6. Запасной признак проверяет ту же строку групп, что возьмёт `find_table_header` (первую с «оборотами» и «концом» в первых 30 строках), и колонки ищет тем же `_columns_of_group`; строгие условия — только к этой строке (финальное ревью, I-1: иначе преамбула «Обороты: …» над шапкой давала ОСВ, разбор падал и ронял книгу; негатив s56 «Преамбула») | запасной признак и конвейер не должны расходиться в том, что считать заголовком | безымянная ОСВ с преамбулой «Обороты: …» над шапкой не распознаётся |
| 2026-10-28 | R-7. Порядок PR: `fixtures` (golden синтетики, отдельный коммит `make golden-update`) → tabular-parser → docs | golden-тесты tabular-parser читают `../fixtures` | — |
| 2026-10-28 | R-8. Приёмка — синтетика + `make check-full` + нет ухудшений D3; число исправленных файлов банка — отчёт, не порог | эксперимент автора задач — ориентир, а не обязательство; остаток держат 1720–1723 | — |
| 2026-10-28 | R-9. Написание «Остаток по дебету/кредиту на начало/конец» (6 файлов КТЗ/ЧАЗ) включено в словарь: группа «остаток» с опережающей проверкой, сторона — из остатка ячейки | та же семья «Остаток на начало/конец» (1719), решается записью словаря | лишняя запись, удаляется |