from collections.abc import Sequence from datetime import date from decimal import Decimal as D from typing import Any from arm_statements.columns import amount_columns, document_dates, finalize_columns, segment_columns from arm_statements.header import parse_header from arm_statements.loader import Rules from arm_statements.model import HeaderCells, PeriodKind, Severity, Source, StatementColumn, StatementWarning from arm_statements.tests.builders import excel, hdr, ocr OCR, EXCEL = Source.OCR, Source.EXCEL def run( # noqa: PLR0913, PLR0917 - вспомогательная функция тестов, сигнатура задана планом (Task 9) rows: Sequence[Sequence[Any]], source: Source, rules: Rules, header: HeaderCells | None = None, header_rows: tuple[int, ...] = (0,), data_rows: tuple[int, ...] = (1, 2), ) -> tuple[tuple[StatementColumn, ...], list[StatementWarning]]: cand = ocr(rows) if source is OCR else excel([("Лист1", rows)]) drafts, warnings = segment_columns(cand.rows, header_rows, data_rows, 1 if source is OCR else 2, source, rules) head = parse_header(header or hdr(), rules) columns, more = finalize_columns(drafts, document_dates(drafts, head), rules) return columns, [*warnings, *more] def test_blank_66n_relative_labels(rules: Rules) -> None: rows: list[list[Any]] = [ ["Наименование", "Код", "На отчетную дату", "На 31 декабря предыдущего года", "На 31 декабря года, предшествующего предыдущему"], ["Запасы", "1210", "5", "4", "3"], ["Итого", "1200", "5", "4", "3"], ] # fmt: skip columns, warnings = run(rows, OCR, rules, header=hdr(report_date="30.09.2025")) assert [c.period_date for c in columns] == [date(2025, 9, 30), date(2024, 12, 31), date(2023, 12, 31)] assert [c.period_kind for c in columns] == [ PeriodKind.CURRENT, PeriodKind.PREV_YEAR_END, PeriodKind.PREV_PREV_YEAR_END, ] assert warnings == [] def test_excel_skipped_change_column(rules: Rules) -> None: rows: list[list[Any]] = [ ["Наименование", "", "Код", "На 30.09.2025", "На 31.12.2024", "Изменение, %"], ["Запасы", "", "1210", D("5"), D("4"), D("25")], ["Итого", "", "1200", D("5"), D("4"), D("25")], ] columns, warnings = run(rows, EXCEL, rules) assert [c.column_index for c in columns] == [3, 4] assert [(w.code, w.severity, w.column_index) for w in warnings] == [("COLUMN_SKIPPED", Severity.INFO, 5)] assert {c.label_confidence for c in columns} == {100.0} def test_junk_label_over_numbers_is_kept(rules: Rules) -> None: rows: list[list[Any]] = [ ["Наименование", "Код", "На 30.09.2025", ("| | г", 17.0)], ["Запасы", "1210", "5", "4"], ["Итого", "1200", "5", "4"], ] columns, warnings = run(rows, OCR, rules, header=hdr(report_date="30.09.2025")) junk = columns[1] assert (junk.label, junk.period_date, junk.period_kind) == ("| | г", None, PeriodKind.NONE) assert [(w.code, w.severity, w.column_index) for w in warnings] == [("COLUMN_WITHOUT_DATE", Severity.ERROR, 3)] def test_empty_label_over_numbers_is_kept(rules: Rules) -> None: rows: list[list[Any]] = [ ["Наименование", "Код", "На 30.09.2025", ""], ["Запасы", "1210", "5", "4"], ["Итого", "1200", "5", "4"], ] columns, warnings = run(rows, OCR, rules) assert [(c.column_index, c.label, c.label_confidence, c.period_kind) for c in columns] == [ (2, "На 30.09.2025", 100.0, PeriodKind.ABSOLUTE), (3, "", None, PeriodKind.NONE), ] assert [(w.code, w.column_index) for w in warnings] == [("COLUMN_WITHOUT_DATE", 3)] def test_two_row_header_is_joined_with_min_confidence(rules: Rules) -> None: rows: list[list[Any]] = [ ["", "", ("На 31 декабря", 90.0)], ["Наименование", "Код", ("2024 г.", 80.0)], ["Запасы", "1210", "5"], ] cand = ocr(rows) drafts, _ = segment_columns(cand.rows, (0, 1), (2,), 1, OCR, rules) assert [(d.grid_col, d.label, d.label_confidence, d.date) for d in drafts] == [ (2, "На 31 декабря 2024 г.", 80.0, date(2024, 12, 31)) ] def test_sparse_ocr_columns(rules: Rules) -> None: rows: list[list[Any]] = [ ["Наименование", "Код", "На 30.09.2025", None, "На 31.12.2024", "На 31.12.2023"], ["Запасы", "1210", "5", None, "4", "3"], ["Итого", "1200", "5", None, "4", "3"], ] columns, _ = run(rows, OCR, rules) assert [c.column_index for c in columns] == [2, 4, 5] def test_uncertain_label_does_not_confirm_itself(rules: Rules) -> None: rows: list[list[Any]] = [ ["Наименование", "Код", ("На 30.09.2О25", 60.0), ""], ["Запасы", "1210", "5", "4"], ["Итого", "1200", "5", "4"], ] cand = ocr(rows) drafts, _ = segment_columns(cand.rows, (0,), (1, 2), 1, OCR, rules) dates = document_dates(drafts, parse_header(hdr(), rules)) assert (dates.effective, dates.confident_effective) == (date(2025, 9, 30), None) _, warnings = finalize_columns(drafts, dates, rules) assert [(w.code, w.column_index) for w in warnings] == [("COLUMN_DATE_UNCERTAIN", 2)] def test_uncertain_label_confirmed_by_report_date(rules: Rules) -> None: rows: list[list[Any]] = [ ["Наименование", "Код", "На 30.09.2025", ("На 31.12.2024", 60.0), ("На 31.03.2025", 60.0)], ["Запасы", "1210", "5", "4", "3"], ["Итого", "1200", "5", "4", "3"], ] _, warnings = run(rows, OCR, rules, header=hdr(report_date="30.09.2025")) assert [(w.code, w.column_index) for w in warnings] == [("COLUMN_DATE_UNCERTAIN", 4)] def test_segment_without_header(rules: Rules) -> None: cand = ocr([["Запасы", "1210", "5", "", "4"], ["Итого", "1200", "5", "", "4"]]) drafts, warnings = segment_columns(cand.rows, (), (0, 1), 1, OCR, rules) assert [(d.grid_col, d.label, d.kind) for d in drafts] == [(2, "", PeriodKind.NONE), (4, "", PeriodKind.NONE)] assert warnings == [] def test_public_amount_columns(rules: Rules) -> None: rows: list[list[Any]] = [ ["Наименование", "", "Код", "На 30.09.2025", "На 31.12.2024", "Изменение"], ["Запасы", "", "1210", D("5"), D("4"), "рост"], ["Итого", "", "1200", D("5"), "", "рост"], ] assert amount_columns(excel([("Лист1", rows)]).rows, 2, rules) == (3, 4) assert amount_columns(ocr([["Запасы", "1210", "5", "", "4"]]).rows, 1, rules, source=OCR) == (2, 4)