import math import pytest from arm_statements import Cell, DocumentType, Source, default_rules from src.normalizer.ocr.adapter import TableStructureError, confidence_of, header_cells, table_candidate RULES = default_rules() DATA_OCR = { "label": {"form_name": "Бухгалтерская отчетность", "confidence": 100.0}, "organization": {"form_name": "ООО «Пример»", "confidence": 100.0}, "inn": {"form_name": "3|1 21 2|", "confidence": 61.538464}, "units": {"form_name": "тыс. руб.", "confidence": 100.0}, "date": {"form_name": "", "confidence": 0.0}, } def row(index: object, *cells: tuple[object, object, object]) -> dict[str, object]: return {"row_index": index, "cells": [{"column_index": c, "value": v, "confidence": p} for c, v, p in cells]} def table(label: str, *rows: dict[str, object]) -> dict[str, object]: return {"label": {"form_name": label, "confidence": 100.0}, "rows": list(rows)} def test_header_is_copied_from_json() -> None: h = header_cells(DATA_OCR) assert h.organization_name == Cell("ООО «Пример»", None, 100.0, "data_ocr.organization") assert h.inn == Cell("3|1 21 2|", None, 61.538464, "data_ocr.inn") assert h.units == Cell("тыс. руб.", None, 100.0, "data_ocr.units") assert (h.report_date, h.okud, h.okpo, h.okopf, h.okfs, h.okei, h.address) == (None,) * 7 def test_grid_by_row_and_column_index() -> None: t = table( "Бухгалтерский баланс", row(9, (0, "Итого", 100.0)), row(8, (1, "[190 :", 25.0), (0, "Прочие", 100.0), (4, "", 0.0), (5, None, 100.0)), ) cand = table_candidate("j.json", 0, t, header_cells(DATA_OCR), RULES) assert (cand.source, cand.locator, cand.type_hint) == (Source.OCR, "file:j.json;table:0", DocumentType.BALANCE) assert cand.segment_breaks == () and cand.warnings == () assert cand.evidence.okud is None assert cand.evidence.title == Cell("Бухгалтерский баланс", None, 100.0, "table:0.label") assert cand.rows == ( ( Cell("Прочие", None, 100.0, "row:8"), Cell("[190 :", None, 25.0, "row:8"), None, None, Cell("", None, 0.0, "row:8"), Cell("", None, 100.0, "row:8"), ), (Cell("Итого", None, 100.0, "row:9"), None, None, None, None, None), ) assert cand.header == header_cells(DATA_OCR) def test_multiline_text_is_kept() -> None: t = table("Бухгалтерский баланс", row(93, (1, "1500\n1700", 78.0), (2, "917 775\n2 371 333", 100.0))) cand = table_candidate("j.json", 0, t, header_cells(DATA_OCR), RULES) assert cand.rows[0][1] == Cell("1500\n1700", None, 78.0, "row:93") def test_income_label_and_unknown_label() -> None: income = table_candidate("j.json", 1, table("Отчет о финансовых результатах"), header_cells(DATA_OCR), RULES) assert income.type_hint is DocumentType.INCOME and income.rows == () blank = table_candidate( "j.json", 2, {"label": {"form_name": " ", "confidence": 0.0}, "rows": []}, header_cells(DATA_OCR), RULES ) assert (blank.evidence.title, blank.type_hint) == (None, None) @pytest.mark.parametrize( ("raw", "expected"), [(25, 25.0), (150.0, 100.0), (-3, 0.0), (math.nan, 0.0), (math.inf, 0.0), ("100", 0.0), (True, 0.0), (None, 0.0)], ) def test_confidence_of(raw: object, expected: float) -> None: assert confidence_of(raw) == expected @pytest.mark.parametrize( "broken", [ "не объект", {"label": {}, "rows": "x"}, table("ББ", {"cells": []}), table("ББ", row(True, (0, "a", 100.0))), table("ББ", row(1, (0, "a", 100.0)), row(1, (0, "b", 100.0))), table("ББ", {"row_index": 0, "cells": "x"}), table("ББ", row(0, ("1", "a", 100.0))), table("ББ", row(0, (-1, "a", 100.0))), table("ББ", row(0, (1, "a", 100.0), (1, "b", 100.0))), table("ББ", row(0, (True, "a", 100.0))), table("ББ", {"row_index": 0, "cells": ["не объект"]}), ], ) def test_broken_structure_raises(broken: object) -> None: with pytest.raises(TableStructureError): table_candidate("j.json", 0, broken, header_cells(DATA_OCR), RULES)