arm-tabular-parser/api/tests/fixtures/test_header_real.py
Andrey Epifancev fafe11349b test(fixtures): 72 реальных ОСВ — Счастливочная 1 кв 2025 (02, 58, 62, 66, 67, 76), 88 golden-снимков
EXPECTED_FILES 66 → 72, порог покрытия словарей, имя теста без числа, CLAUDE.md. Снимки — arm-ka-fixtures#7.
make test-fixtures: 873 passed (ошибка только у roundtrip без NATS).

Refs #10

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 12:24:18 +04:00

115 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Шапка и схема группировок на реальных ОСВ из ../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)\d{2,3}(?:\.\d{1,2})?(?!\d)")
SUBACCOUNT_WITHOUT_DOT = 3 # цифр в коде «счёт + субсчёт» без точки
ACCOUNT_DIGITS = 2 # цифр в корне счёта
SUBACCOUNT_DIGITS = 2 # цифр в записи субсчёта: «58.03»
PERIOD_DIR = re.compile(r"^(?P<year>\d{4})-(?P<kind>FY|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" # четыре цифры — не счёт: граница по цифрам