"""Шапка и схема группировок на реальных ОСВ из ../fixtures: общие свойства на всех 72 файлах. Фикстуры — регрессия, не оракул: ожидаемые значения выводятся из имени файла и каталога одним общим правилом (без таблицы исключений), а парсер читает только содержимое. Правило имени файла — `expected_account`. """ import re from pathlib import Path from typing import cast import pytest from fixtures_helpers import osv_fixture_files from src.contract import FileType from src.parser.detect import SheetKind, classify_sheet, find_table_header from src.parser.grid import Sheet from src.parser.header import build_group_schema, parse_doc_header from src.parser.readers import read_book from src.parser.rules import Rules, load_rules # Первое число имени файла — счёт: «ОСВ_58 за 2024», «сч 01», «по счету 62 за 3 квартал»; «583» — три цифры — # счёт и субсчёт без точки («58», субсчёт «3» -> «58.03») ACCOUNT_IN_NAME = re.compile(r"(?\d{4})-(?PFY|Q[1-4])$") EXPECTED_FILES = 72 # 66 (Срез 2a) + 6 ОСВ Счастливочной за 1 кв. 2025 (пакет ДКБ 06.10.2026) @pytest.fixture(scope="module") def rules() -> Rules: return load_rules() def expected_account(path: Path) -> str: """Код счёта из имени файла: первое число; три цифры без точки — счёт и субсчёт («583» -> «58.03»).""" found = ACCOUNT_IN_NAME.search(path.stem) assert found is not None, path.name code = found.group(0) if "." not in code and len(code) == SUBACCOUNT_WITHOUT_DOT: return f"{code[:ACCOUNT_DIGITS]}.{code[ACCOUNT_DIGITS:].zfill(SUBACCOUNT_DIGITS)}" return code def _osv_sheet(path: Path, rules: Rules) -> Sheet: sheets, _ = read_book(path.read_bytes(), cast(FileType, path.suffix.lstrip(".").upper())) osv = [sheet for sheet in sheets if classify_sheet(sheet, rules) is SheetKind.OSV] assert len(osv) == 1 return osv[0] @pytest.mark.fixtures def test_every_real_osv_header_and_group_schema(osv_file: Path, rules: Rules) -> None: sheet = _osv_sheet(osv_file, rules) table = find_table_header(sheet, rules) doc = parse_doc_header(sheet, table, rules) groups, variant = build_group_schema(sheet, table, rules) assert doc.organization_name assert doc.account_code_raw == expected_account(osv_file) assert doc.period_text assert doc.period_quarter is not None assert doc.period_year is not None assert doc.is_cumulative is not None assert doc.data_type is not None # T3.9: значение («BU» и др.) не фиксируем — файлы не спецификация assert doc.currency is not None assert doc.header_variant == variant == 1 assert doc.warnings == () assert groups[0].type == "ACCOUNT" assert len(groups) == len(table.group_label_rows) @pytest.mark.fixtures def test_real_osv_period_matches_its_directory(osv_file: Path, rules: Rules) -> None: sheet = _osv_sheet(osv_file, rules) doc = parse_doc_header(sheet, find_table_header(sheet, rules), rules) directory = PERIOD_DIR.fullmatch(osv_file.parent.name) assert directory is not None, osv_file.parent.name year, kind = directory.group("year"), directory.group("kind") assert doc.period_year == int(year) if kind == "FY": assert (doc.period_quarter, doc.is_cumulative) == ("Q4", True) else: assert doc.period_quarter == kind if kind == "Q3": assert doc.is_cumulative is False # ASSUMPTION(OQ-02): квартальная ведомость, не нарастающая @pytest.mark.fixtures def test_group_schemas_of_known_files(fixtures_dir: Path, rules: Rules) -> None: schastl_62 = fixtures_dir / "osv/schastl/2024-Q3/Оборотносальдовая ведомость по счету 62 за 3 квартал 2024 г..xls" sheet = _osv_sheet(schastl_62, rules) groups, variant = build_group_schema(sheet, find_table_header(sheet, rules), rules) assert variant == 1 assert [(g.type, g.has_inn) for g in groups] == [("ACCOUNT", None), ("DEPARTMENT", None), ("COUNTERPARTY", True)] doc = parse_doc_header(sheet, find_table_header(sheet, rules), rules) assert doc.period_text == "3 квартал 2024 г." @pytest.mark.fixtures def test_all_real_files_are_covered(fixtures_dir: Path) -> None: assert len(osv_fixture_files(fixtures_dir)) == EXPECTED_FILES def test_expected_account_rule() -> None: assert expected_account(Path("сч 583_3 кв 2024.xlsx")) == "58.03" assert expected_account(Path("ОСВ_01_ОС по субсчетам.xlsx")) == "01" assert expected_account(Path("ОСВ_58 за 2024.xls")) == "58" assert expected_account(Path("сч 66_3 кв 2024.xlsx")) == "66" assert expected_account(Path("отчет 2024 сч 60.xlsx")) == "60" # четыре цифры — не счёт: граница по цифрам