"""Шапка документа из `data_ocr` (§11.1 п. 8) на реальных `aep_3.json` (мусор) и `aep_6.json` (чисто).""" import json import time from datetime import date from pathlib import Path import pytest from src.normalizer.ocr.header import FileHeader, parse_file_header API_DIR = Path(__file__).resolve().parents[3] REAL_FIXTURES = API_DIR.parent.parent / "fixtures" / "ocr" LOOKALIKES = {"О": "0", "о": "0", "З": "3", "l": "1", "I": "1", "|": "1", "б": "6", "S": "5", "Ч": "4"} def _real(name: str) -> dict: if not REAL_FIXTURES.is_dir(): pytest.skip("fixtures недоступен: положите репозиторий fixtures рядом с workspace") return dict(json.loads((REAL_FIXTURES / name).read_text(encoding="utf-8"))["data_ocr"]) def _field(text: str, confidence: float) -> dict: return {"form_name": text, "confidence": confidence} def test_clean_header() -> None: header = parse_file_header( { "organization": _field("ООО Ромашка", 97.5), "inn": _field("7700000000", 100), "units": _field("тыс. руб.", 100), "date": _field("на 31 декабря 2024 г.", 100), }, LOOKALIKES, ) assert header == FileHeader( "ООО Ромашка", 97.5, "7700000000", 100.0, "тыс. руб.", "на 31 декабря 2024 г.", date(2024, 12, 31), True, ) def test_noisy_inn_caps_confidence() -> None: header = parse_file_header({"inn": _field("3|1 21 2| о! О' 0 0; 3: 51", 100)}, LOOKALIKES) assert header.inn == "3121200351" assert header.inn_confidence == 99.0 assert header.organization_name is None and header.organization_confidence is None @pytest.mark.parametrize(("raw", "expected"), [(float("nan"), 0.0), (150, 100.0), (-5, 0.0), (float("inf"), 0.0)]) def test_header_confidence_is_clamped(raw: float, expected: float) -> None: """Решение №30: уверенность полей шапки — в [0, 100], NaN → 0 (контракт требует 0..100).""" header = parse_file_header( {"organization": _field("ООО Ромашка", raw), "inn": _field("7700000000", raw), "date": _field("2024", raw)}, LOOKALIKES, ) assert header.organization_confidence == expected and header.inn_confidence == expected assert header.report_date_recognized is (expected == 100.0) @pytest.mark.parametrize( "raw", ["1" + " " * 4000 + "b", "1" + "\n" * 4000, "1" + "О" * 5000], ids=["spaces", "nl", "look"] ) def test_pathological_report_date_is_fast(raw: str) -> None: """Решение №31: длинный текст даты не разбирается и не проходит `fix_lookalikes` (иначе — секунды и минуты).""" start = time.perf_counter() header = parse_file_header({"date": _field(raw, 100)}, LOOKALIKES) assert time.perf_counter() - start < 1.0 assert header.report_date is None and header.report_date_recognized is False assert header.report_date_text == raw.strip() # исходный текст не теряется def test_missing_fields() -> None: header = parse_file_header({}, LOOKALIKES) assert header == FileHeader(None, None, None, None, None, None, None, False) @pytest.mark.fixtures def test_aep6_header() -> None: header = parse_file_header(_real("aep_6.json"), LOOKALIKES) assert header.inn is None # текст «+» assert header.units == "тыс. руб." assert header.report_date is None # в поле date — название организации assert header.report_date_text is not None and header.report_date_recognized is False assert header.organization_name is not None and "Дорстроймонтаж" in header.organization_name @pytest.mark.fixtures def test_aep3_header() -> None: header = parse_file_header(_real("aep_3.json"), LOOKALIKES) assert header.inn == "3121200351" and header.inn_confidence is not None and header.inn_confidence < 100 assert header.report_date == date(2024, 12, 31) assert header.report_date_recognized is False # confidence 75