- 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
65 lines
3.2 KiB
Python
65 lines
3.2 KiB
Python
"""Словари на реальных ОСВ из ../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
|