classify.py: collect_body_rows — строки таблицы до «Итого» (пустые, из пробелов, повторы заголовка и строки правее таблицы пропускаются; подвал после «Итого» не данные), «Итого» по тексту и корневому оформлению до уровней, без требования сумм (R-13, T3.3), шесть сумм через to_money с номером строки в предупреждениях, свёрнутое сальдо по знаку (R-1); classify_rows — ACCOUNT по ACCOUNT_RE, позиция в схеме, OTHER за схемой, TOTAL, COUNTERPARTY с ИНН (inn_digits), Вал./Кол./код валюты -> data_type, extra без дубликатов ключей и без колонок подвала (T4.5). Fixtures-тесты иерархии (R-2) и классификации на всех 66 реальных ОСВ. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
45 lines
2.2 KiB
Python
45 lines
2.2 KiB
Python
"""Иерархия строк на реальных ОСВ из ../fixtures (ruling R-2, R-13; spec «Иерархия строк — каскад стратегий»).
|
||
|
||
Свойства без таблицы файлов: дерево непротиворечиво, «Итого» — уровень 0; у файла со строками данных — `outline`
|
||
(у всех фикстур есть группировки Excel), у пустой ОСВ (только «Итого») — `trivial`. Разбор файла укладывается в 5 с.
|
||
"""
|
||
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from real_osv import parse_real
|
||
|
||
from src.parser.hierarchy import TRIVIAL_STRATEGY, is_consistent
|
||
|
||
MAX_SECONDS_PER_FILE = 5
|
||
RB_66_FY = Path("osv/rb/2024-FY/ОСВ_66 за 2024.xls")
|
||
YUG_FINANS = "Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ"
|
||
COUNTERPARTY_LEVEL = 2 # счёт (0) → субсчёт (1) → контрагент (2)
|
||
|
||
|
||
@pytest.mark.fixtures
|
||
def test_real_osv_hierarchy(osv_file: Path) -> None:
|
||
parsed = parse_real(osv_file)
|
||
|
||
assert is_consistent(parsed.body, parsed.levels.by_row, parsed.total_row)
|
||
assert parsed.total_row is not None
|
||
assert parsed.levels.by_row[parsed.total_row] == 0
|
||
assert parsed.levels.parent[parsed.total_row] is None
|
||
expected = "outline" if parsed.data_rows else TRIVIAL_STRATEGY
|
||
assert parsed.levels.strategy == expected
|
||
assert parsed.seconds < MAX_SECONDS_PER_FILE
|
||
|
||
|
||
@pytest.mark.fixtures
|
||
def test_rb_66_fy_outline_tree(fixtures_dir: Path) -> None:
|
||
"""Spec «Outline level (реальные файлы)»: 66 (0) → 66.01 (1) → контрагент (2); «Юг-Финанс» — под 66.03."""
|
||
parsed = parse_real(fixtures_dir / RB_66_FY)
|
||
|
||
assert parsed.levels.strategy == "outline"
|
||
account, subaccount, counterparty = parsed.rows[:3]
|
||
assert [(account.body.text, account.level), (subaccount.body.text, subaccount.level)] == [("66", 0), ("66.01", 1)]
|
||
assert (counterparty.level, counterparty.parent) == (COUNTERPARTY_LEVEL, subaccount.body.row)
|
||
first = parsed.by_text(YUG_FINANS)[0]
|
||
assert first.parent is not None
|
||
assert parsed.row(first.parent).body.text == "66.03"
|