arm-tabular-parser/api/tests/fixtures/test_rules_real.py
Andrey Epifancev fcd609c633 feat(rules): словари ОСВ (заголовки, группировки, периоды, типы данных) и загрузчик
Словари api/rules/{osv_headers,osv_groups,periods,data_types}.yaml и src/parser/rules.py
(normalize_text, Pattern, load_rules, pydantic-валидация с RuntimeError по файлу и ключу,
currency_aliases по R-6). AST-линтер координат tests/unit/parser/test_no_coordinates.py
перенесён из Task 12 (R-7, правила R-4/R-5). Тест формулировок реальных ОСВ.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 12:28:46 +04:00

64 lines
3.0 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: формулировки шапки, блоков сумм, группировок и периодов покрыты.
Проверяются сами формулировки файлов (текст ячеек шапки), а не позиции: детекторы Task 4–5 строятся поверх этих же
словарей. Фикстуры — регрессия формулировок, не оракул формата.
"""
from pathlib import Path
from typing import cast
import pytest
from src.contract import FileType
from src.parser.grid import Sheet
from src.parser.readers import read_book
from src.parser.rules import Pattern, Rules, first_match, load_rules
HEAD_ROWS = 30 # шапка и заголовок таблицы — в начале листа
@pytest.fixture(scope="module")
def rules() -> Rules:
return load_rules()
def _head_texts(sheet: Sheet) -> list[str]:
last = min(sheet.n_rows, HEAD_ROWS)
return [cell.text for row in range(1, last + 1) for _, cell in sheet.row_cells(row) if cell.kind == "text"]
def _after_colon(text: str) -> str:
return text.split(":", 1)[1] if ":" in text else text
@pytest.mark.fixtures
def test_real_osv_phrases_are_covered_by_dictionaries(osv_file: Path, rules: Rules) -> None:
sheets, _ = read_book(osv_file.read_bytes(), cast(FileType, osv_file.suffix.lstrip(".").upper()))
texts = [text for sheet in sheets for text in _head_texts(sheet)]
headers = rules.headers
def any_cell(patterns: list[Pattern]) -> bool:
return any(pattern.search(text) for text in texts for pattern in patterns)
assert any_cell(headers.sheet_markers), "нет заголовка ведомости"
assert not any_cell(headers.statement_markers), "ОСВ не должна выглядеть как форма ББ/ОФР"
for group in headers.groups:
assert any_cell(headers.groups[group]), f"нет блока сумм {group}"
assert any_cell(headers.debit)
assert any_cell(headers.credit)
assert any_cell(headers.header_markers["org_params"])
assert any_cell(headers.header_markers["data_type"])
title = next(text for text in texts if any(p.search(text) for p in headers.sheet_markers))
account = next(m for p in headers.header_markers["account"] if (m := p.search(title)))
assert account.string[account.end() :].strip()[:1].isdigit()
period = next(m for p in headers.header_markers["period"] if (m := p.search(title)))
period_text = period.string[period.end() :]
assert rules.periods.match(period_text) is not None, title
assert rules.periods.year.search(period_text) is not None, title
data_type_line = next(t for t in texts if any(p.search(t) for p in headers.header_markers["data_type"]))
assert first_match(rules.data_types.header, _after_colon(data_type_line)) == "BU"
currency_line = next(t for t in texts if any(p.search(t) for p in headers.header_markers["currency"]))
assert rules.data_types.currency_code(_after_colon(currency_line)) == "RUB"