"""Разрез кандидата по смене формы (AURORA-1734, решение владельца 06.11.2026).""" from datetime import date from arm_statements import WarningCode, make_warning, normalize_candidate, split_by_form from arm_statements.loader import Rules from arm_statements.model import DocumentType, Evidence from tests.builders import excel, hdr, ocr BAL_HEAD = ["Наименование показателя", "Коды", "На 30 июня 2026 года", "На 31 декабря 2025 года"] INC_HEAD = ["Наименование показателя", "Коды", "За I полугодие 2026 года", "За I полугодие 2025 года"] HEADER = hdr(organization_name="КФХ «АК-ЮЛ»", inn="5634000539", report_date="30.06.2026", units="тыс. руб.") LOCATOR = "file:x.json;table:0" def balance_rows() -> list[list[str | None]]: """ББ на две страницы: актив, повторная шапка, пассив.""" return [ BAL_HEAD, ["НМА", "1110", "10", "10"], ["Основные средства", "1150", "90", "90"], ["Итого по разделу I", "1100", "100", "100"], ["Запасы", "1210", "50", "50"], ["Итого по разделу II", "1200", "50", "50"], ["БАЛАНС", "1600", "150", "150"], BAL_HEAD, ["Уставный капитал", "1310", "150", "150"], ["Итого по разделу III", "1300", "150", "150"], ["БАЛАНС", "1700", "150", "150"], ] def income_rows() -> list[list[str | None]]: return [ INC_HEAD, ["Выручка", "2110", "4451", "2162"], ["Себестоимость продаж", "2120", "-2700", "-1332"], ["Валовая прибыль (убыток)", "2100", "1751", "830"], ["Чистая прибыль (убыток)", "2400", "1751", "830"], ] def test_balance_and_income_in_one_table_split(rules: Rules) -> None: rows = [*balance_rows(), *income_rows()] cand = ocr(rows, header=HEADER, type_hint=DocumentType.BALANCE, title="Бухгалтерский баланс", locator=LOCATOR) parts = split_by_form(cand, rules) assert len(parts) == 2 # noqa: PLR2004 - две формы bal, inc = parts assert bal.locator == LOCATOR and bal.type_hint is DocumentType.BALANCE and bal.evidence == cand.evidence assert len(bal.rows) == len(balance_rows()) assert inc.locator == f"{LOCATOR};form:1" assert inc.type_hint is None and inc.evidence == Evidence(okud=None, title=None) and inc.warnings == () assert inc.rows[0] == cand.rows[len(balance_rows())] assert inc.header == cand.header and inc.source is cand.source bal_doc = normalize_candidate(bal, rules).document inc_doc = normalize_candidate(inc, rules).document assert bal_doc is not None and inc_doc is not None assert bal_doc.document_type is DocumentType.BALANCE assert not [line.line_code for line in bal_doc.lines if (line.line_code or "").startswith("2")] assert inc_doc.document_type is DocumentType.INCOME assert [c.period_date for c in inc_doc.columns] == [date(2026, 6, 30), date(2025, 6, 30)] assert "TYPE_CONFLICT" not in [w.code for w in inc_doc.warnings] def test_same_form_repeat_header_not_split(rules: Rules) -> None: cand = ocr(balance_rows(), header=HEADER, type_hint=DocumentType.BALANCE, locator=LOCATOR) result = split_by_form(cand, rules) assert result == (cand,) assert result[0] is cand def test_income_tail_25xx_not_split(rules: Rules) -> None: tail = [ INC_HEAD, ["Результат от переоценки", "2510", "0", "0"], ["Результат от прочих операций", "2520", "0", "0"], ["Совокупный финансовый результат", "2500", "1751", "830"], ] cand = ocr([*income_rows(), *tail], locator=LOCATOR) assert split_by_form(cand, rules) == (cand,) def test_two_foreign_codes_not_split(rules: Rules) -> None: foreign = [INC_HEAD, ["Выручка", "2110", "4451", "2162"], ["Чистая прибыль (убыток)", "2400", "1751", "830"]] cand = ocr([*balance_rows(), *foreign], locator=LOCATOR) assert split_by_form(cand, rules) == (cand,) def test_foreign_segment_without_header_not_split(rules: Rules) -> None: cand = ocr([*balance_rows(), *income_rows()[1:]], locator=LOCATOR) assert split_by_form(cand, rules) == (cand,) def test_excel_breaks_shifted(rules: Rules) -> None: continuation = [["Прочие доходы", "2340", "1", "1"], ["Прочие расходы", "2350", "-1", "-1"]] cand = excel([("Б", balance_rows()), ("О", income_rows()), ("О2", continuation)], header=HEADER) cut = len(balance_rows()) assert cand.segment_breaks == (cut, cut + len(income_rows())) bal, inc = split_by_form(cand, rules) assert bal.segment_breaks == () assert inc.segment_breaks == (0, len(income_rows())) assert inc.locator == f"{cand.locator};form:1" assert len(inc.rows) == len(income_rows()) + len(continuation) def test_xlsb_source_warning_kept_in_every_part(rules: Rules) -> None: """`XLSB_NO_FORMATS` — свойство источника, нужно каждой части для проверки масштаба (ревью I-2).""" no_formats = make_warning(WarningCode.XLSB_NO_FORMATS, "xlsb без числовых форматов", rules) cand = excel([("Б", balance_rows()), ("О", income_rows())], header=HEADER, warnings=[no_formats]) bal, inc = split_by_form(cand, rules) assert bal.warnings == (no_formats,) assert inc.warnings == (no_formats,)