arm-tabular-parser/api/tests/fixtures/test_rules_real.py
Andrey Epifancev bc143ef140 test: golden-снимки, линтер координат, покрытие словарей; make golden-update
- golden_helpers, scripts/golden_update.py (--check), test_golden на 66 ОСВ и синтетике; make golden-update/golden-check/test-fixtures
- тест покрытия словарей (R-22): счётчик срабатываний Pattern; из osv_headers.yaml удалены неиспользуемые service_columns kpp/code/quantity/currency
- синтетика s14–s16 в генераторе и таблице ожиданий; skip вместо падения при отсутствии osv/synthetic (T11.1); комментарий R-21 в s07 (T11.2)
- линтер: предел адреса 10 вместо 4 (T3.1), обращение cell(3, 4) (T3.8); реальные файлы не фиксируют BU/RUB (T3.9)
- README: golden-процесс; отмечены Task 12 и пункты 4.2, 4.3

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

65 lines
3.2 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"]))
# T3.9: реальные файлы не спецификация — не фиксируем «BU» и «RUB», проверяем, что словарь значение опознал
assert first_match(rules.data_types.header, _after_colon(data_type_line)) is not None
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)) is not None