diff --git a/api/tests/fixtures/test_statements_property.py b/api/tests/fixtures/test_statements_property.py index b02ce99..f82553f 100644 --- a/api/tests/fixtures/test_statements_property.py +++ b/api/tests/fixtures/test_statements_property.py @@ -17,6 +17,32 @@ EXPECTED_FILES = 18 MAX_PARSE_SECONDS = 5 +def document_ok(document: StatementDocument) -> bool: + """Документ пригоден (done): критерий `StatementParser._is_ok`, выраженный полями результата.""" + if document.document_type == "DISCLOSURE": + return True + checks = document.checks + return ( + checks is not None + and bool(document.columns) + and bool(document.header.organization_name or document.header.inn) + and not checks.relations_failed + and not checks.duplicate_line_codes + ) + + +def explained_needs_check(document: StatementDocument) -> bool: + """Причина needs_check видна в результате: нарушенное соотношение, дубликаты кодов, нет колонок или шапки.""" + checks = document.checks + return bool( + checks is None + or checks.relations_failed + or checks.duplicate_line_codes + or not document.columns + or not (document.header.organization_name or document.header.inn) + ) + + def test_manifest_covers_every_file(fixtures_dir: Path) -> None: files = statement_files(fixtures_dir) assert len(files) == EXPECTED_FILES @@ -40,13 +66,24 @@ def test_statement_file_matches_manifest(fixtures_dir: Path, statement_file: Pat assert all(value.column_index < len(document.columns) for line in document.lines for value in line.values) if item.sheets: assert document.locator.count(",") == item.sheets - 1 + failed = {relation.relation for relation in document.checks.relations_failed} if document.checks else set() + if item.failed_relations is not None: + assert failed == set(item.failed_relations) if item.complete: assert document.checks is not None assert document.checks.relations_failed == [], document.checks assert document.columns assert all(column.period_date is not None for column in document.columns) - if expectation.organization and document.header.organization_name is not None: + # статус документа — по критерию парсера (statement.py::_is_ok), независимо от манифеста + assert document_ok(document) == (item.status == "done"), (item, document.header) + if item.status == "needs_check": + assert not document_ok(document) + assert explained_needs_check(document), "needs_check должен объясняться полями результата" + if expectation.organization: assert document.header.organization_name == expectation.organization - if expectation.inn and document.document_type not in expectation.inn_optional_for and document.header.inn: - assert document.header.inn == expectation.inn + if expectation.inn: + if document.document_type in expectation.inn_optional_for: + assert document.header.inn in {None, expectation.inn} + else: + assert document.header.inn == expectation.inn assert_valid_result(outcome) diff --git a/api/tests/statement_helpers.py b/api/tests/statement_helpers.py index 1df9d91..b125199 100644 --- a/api/tests/statement_helpers.py +++ b/api/tests/statement_helpers.py @@ -19,6 +19,7 @@ class DocumentExpectation(BaseModel): complete: bool reason: str | None = None sheets: int | None = None + failed_relations: list[str] | None = None # None — не проверяется; список — точное множество relations_failed class Expectation(BaseModel): diff --git a/api/tests/statements_manifest.yaml b/api/tests/statements_manifest.yaml index 52a6283..2dda5f7 100644 --- a/api/tests/statements_manifest.yaml +++ b/api/tests/statements_manifest.yaml @@ -6,59 +6,59 @@ files: "unagrande-co/2024-FY/Б.Б Лист 1.xls": organization: 'Акционерное общество "Унагранде Компани"' inn: "3249004697" - documents: [{type: BALANCE, status: needs_check, complete: false, reason: "только актив: 1600 = 1700 не сходится"}] + documents: [{type: BALANCE, status: needs_check, complete: false, reason: "только актив: 1600 = 1700 не сходится", failed_relations: ["1600 = 1700"]}] "unagrande-co/2024-FY/Б.Б. Лист 2.xls": - documents: [{type: BALANCE, status: needs_check, complete: false, reason: "страница 2 без шапки"}] + documents: [{type: BALANCE, status: needs_check, complete: false, reason: "страница 2 без шапки", failed_relations: []}] "unagrande-co/2024-FY/ОФР. Лист 1.xls": organization: 'Акционерное общество "Унагранде Компани"' inn: "3249004697" documents: - - {type: INCOME, status: needs_check, complete: false, reason: "по факту: значения в рублях с копейками при шапке тыс. руб., итоги 2100/2200/2300 округлены до тысяч и расходятся с суммой частей (OQ-137)"} + - {type: INCOME, status: needs_check, complete: false, reason: "по факту: значения в рублях с копейками при шапке тыс. руб., итоги 2100/2200/2300 округлены до тысяч и расходятся с суммой частей (OQ-137)", failed_relations: ["2100 = 2110 - 2120", "2200 = 2100 - 2210 - 2220", "2300 = 2200 + 2310 + 2320 - 2330 + 2340 - 2350"]} "unagrande-co/2024-FY/ОФР. Лист 2 .xls": - documents: [{type: INCOME, status: needs_check, complete: false, reason: "страница 2 без шапки"}] + documents: [{type: INCOME, status: needs_check, complete: false, reason: "страница 2 без шапки", failed_relations: []}] "unagrande-group/2024-FY/ББ. Лист 1 .xls": organization: 'Акционерное общество "Унагранде Групп"' inn: "7728757141" - documents: [{type: BALANCE, status: needs_check, complete: false, reason: "только актив"}] + documents: [{type: BALANCE, status: needs_check, complete: false, reason: "только актив", failed_relations: ["1600 = 1700"]}] "unagrande-group/2024-FY/ББ. Лист 2 (2).xls": - documents: [{type: BALANCE, status: needs_check, complete: false, reason: "страница 2 без шапки"}] + documents: [{type: BALANCE, status: needs_check, complete: false, reason: "страница 2 без шапки", failed_relations: []}] "unagrande-group/2024-FY/ОФР. Лист 2.xls": - documents: [{type: INCOME, status: needs_check, complete: false, reason: "страница 2 без шапки, стр. 1 в пакете нет"}] + documents: [{type: INCOME, status: needs_check, complete: false, reason: "страница 2 без шапки, стр. 1 в пакете нет", failed_relations: []}] "unagrande-group/2025-Q3/ББ. Лист 1 3кв.25.xls": organization: 'Акционерное общество "Унагранде Групп"' inn: "7728757141" - documents: [{type: BALANCE, status: needs_check, complete: false, reason: "только актив"}] + documents: [{type: BALANCE, status: needs_check, complete: false, reason: "только актив", failed_relations: ["1600 = 1700"]}] "unagrande-group/2025-Q3/ББ. Лист 2 3кв.25.xls": - documents: [{type: BALANCE, status: needs_check, complete: false, reason: "страница 2 без шапки"}] + documents: [{type: BALANCE, status: needs_check, complete: false, reason: "страница 2 без шапки", failed_relations: []}] "unagrande-group/2025-Q3/ОФР. Лист 1 3кв.25.xls": organization: 'Акционерное общество "Унагранде Групп"' inn: "7728757141" documents: - - {type: INCOME, status: needs_check, complete: false, reason: "по факту: значения в рублях с копейками при шапке тыс. руб., 2100 расходится на 17.50/9.43 руб., 2200/2300 — округление итогов до тысяч (OQ-137)"} + - {type: INCOME, status: needs_check, complete: false, reason: "по факту: значения в рублях с копейками при шапке тыс. руб., 2100 расходится на 17.50/9.43 руб., 2200/2300 — округление итогов до тысяч (OQ-137)", failed_relations: ["2100 = 2110 - 2120", "2200 = 2100 - 2210 - 2220", "2300 = 2200 + 2310 + 2320 - 2330 + 2340 - 2350"]} "unagrande-group/2025-Q3/ОФР. Лист 2 3кв.25.xls": - documents: [{type: INCOME, status: needs_check, complete: false, reason: "страница 2 без шапки"}] + documents: [{type: INCOME, status: needs_check, complete: false, reason: "страница 2 без шапки", failed_relations: []}] "npz/2025-Q3/Бухгалтерская отчетность за Январь - Сентябрь 2025 г. (НОВОРОССИЙСКИЙ ПРОКАТНЫЙ ЗАВОД).xlsx": organization: 'ООО "НОВОРОССИЙСКИЙ ПРОКАТНЫЙ ЗАВОД"' inn: "2315995868" inn_optional_for: [INCOME] documents: - - {type: BALANCE, status: needs_check, complete: false, sheets: 5, reason: "1600 = 1700 расходится на 1.94/9.02/2.48 руб.: значения в рублях при шапке тыс. руб. (OQ-137)"} - - {type: INCOME, status: needs_check, complete: false, sheets: 2, reason: "по факту: рубли с копейками при шапке тыс. руб., 2100/2200/2300 расходятся с округлёнными итогами (OQ-137); одностроечный лист продолжения с 2400 пропущен: меньше min_codes"} + - {type: BALANCE, status: needs_check, complete: false, sheets: 5, reason: "1600 = 1700 расходится на 1.94/9.02/2.48 руб.: значения в рублях при шапке тыс. руб. (OQ-137)", failed_relations: ["1600 = 1700"]} + - {type: INCOME, status: needs_check, complete: false, sheets: 2, reason: "по факту: рубли с копейками при шапке тыс. руб., 2100/2200/2300 расходятся с округлёнными итогами (OQ-137); одностроечный лист продолжения с 2400 пропущен: меньше min_codes", failed_relations: ["2100 = 2110 - 2120", "2200 = 2100 - 2210 - 2220", "2300 = 2200 + 2310 + 2320 - 2330 + 2340 - 2350"]} "mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx": organization: 'ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "АГРОФИРМА "МЕЖЕНИНОВСКАЯ"' inn: "7016006635" - documents: [{type: BALANCE, status: done, complete: true}] + documents: [{type: BALANCE, status: done, complete: true, failed_relations: []}] "mezheninovskaya/2025-Q3/1_Форма2_бух_09.xlsx": organization: 'ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "АГРОФИРМА "МЕЖЕНИНОВСКАЯ"' inn: "7016006635" - documents: [{type: INCOME, status: done, complete: true}] + documents: [{type: INCOME, status: done, complete: true, failed_relations: []}] "templates/ББ до 01.01.2025.xlsx": documents: - - {type: BALANCE, status: needs_check, complete: false, reason: "шаблон без сумм и шапки"} + - {type: BALANCE, status: needs_check, complete: false, reason: "шаблон без сумм и шапки", failed_relations: []} - {type: DISCLOSURE, status: done, complete: false, reason: "по факту: «Примечания» шаблона образуют пустой документ DISCLOSURE"} "templates/ББ после 01.01.2025.xlsx": - documents: [{type: BALANCE, status: needs_check, complete: false, reason: "шаблон"}] + documents: [{type: BALANCE, status: needs_check, complete: false, reason: "шаблон", failed_relations: []}] "templates/ОФР_до 01.01.2025.xlsx": - documents: [{type: INCOME, status: needs_check, complete: false, reason: "шаблон"}] + documents: [{type: INCOME, status: needs_check, complete: false, reason: "шаблон", failed_relations: []}] "templates/ОФР_после 01.01.2025.xlsx": - documents: [{type: INCOME, status: needs_check, complete: false, reason: "шаблон"}] + documents: [{type: INCOME, status: needs_check, complete: false, reason: "шаблон", failed_relations: []}]