test(statements): failed_relations в манифесте, статус документа по критерию парсера, строгая шапка (T14.I2)
Refs #14 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b
This commit is contained in:
parent
d82940a8ac
commit
6cd2ea8a7a
41
api/tests/fixtures/test_statements_property.py
vendored
41
api/tests/fixtures/test_statements_property.py
vendored
@ -17,6 +17,32 @@ EXPECTED_FILES = 18
|
|||||||
MAX_PARSE_SECONDS = 5
|
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:
|
def test_manifest_covers_every_file(fixtures_dir: Path) -> None:
|
||||||
files = statement_files(fixtures_dir)
|
files = statement_files(fixtures_dir)
|
||||||
assert len(files) == EXPECTED_FILES
|
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)
|
assert all(value.column_index < len(document.columns) for line in document.lines for value in line.values)
|
||||||
if item.sheets:
|
if item.sheets:
|
||||||
assert document.locator.count(",") == item.sheets - 1
|
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:
|
if item.complete:
|
||||||
assert document.checks is not None
|
assert document.checks is not None
|
||||||
assert document.checks.relations_failed == [], document.checks
|
assert document.checks.relations_failed == [], document.checks
|
||||||
assert document.columns
|
assert document.columns
|
||||||
assert all(column.period_date is not None for column in 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
|
assert document.header.organization_name == expectation.organization
|
||||||
if expectation.inn and document.document_type not in expectation.inn_optional_for and document.header.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 document.header.inn == expectation.inn
|
||||||
assert_valid_result(outcome)
|
assert_valid_result(outcome)
|
||||||
|
|||||||
@ -19,6 +19,7 @@ class DocumentExpectation(BaseModel):
|
|||||||
complete: bool
|
complete: bool
|
||||||
reason: str | None = None
|
reason: str | None = None
|
||||||
sheets: int | None = None
|
sheets: int | None = None
|
||||||
|
failed_relations: list[str] | None = None # None — не проверяется; список — точное множество relations_failed
|
||||||
|
|
||||||
|
|
||||||
class Expectation(BaseModel):
|
class Expectation(BaseModel):
|
||||||
|
|||||||
@ -6,59 +6,59 @@ files:
|
|||||||
"unagrande-co/2024-FY/Б.Б Лист 1.xls":
|
"unagrande-co/2024-FY/Б.Б Лист 1.xls":
|
||||||
organization: 'Акционерное общество "Унагранде Компани"'
|
organization: 'Акционерное общество "Унагранде Компани"'
|
||||||
inn: "3249004697"
|
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":
|
"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":
|
"unagrande-co/2024-FY/ОФР. Лист 1.xls":
|
||||||
organization: 'Акционерное общество "Унагранде Компани"'
|
organization: 'Акционерное общество "Унагранде Компани"'
|
||||||
inn: "3249004697"
|
inn: "3249004697"
|
||||||
documents:
|
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":
|
"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":
|
"unagrande-group/2024-FY/ББ. Лист 1 .xls":
|
||||||
organization: 'Акционерное общество "Унагранде Групп"'
|
organization: 'Акционерное общество "Унагранде Групп"'
|
||||||
inn: "7728757141"
|
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":
|
"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":
|
"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":
|
"unagrande-group/2025-Q3/ББ. Лист 1 3кв.25.xls":
|
||||||
organization: 'Акционерное общество "Унагранде Групп"'
|
organization: 'Акционерное общество "Унагранде Групп"'
|
||||||
inn: "7728757141"
|
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":
|
"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":
|
"unagrande-group/2025-Q3/ОФР. Лист 1 3кв.25.xls":
|
||||||
organization: 'Акционерное общество "Унагранде Групп"'
|
organization: 'Акционерное общество "Унагранде Групп"'
|
||||||
inn: "7728757141"
|
inn: "7728757141"
|
||||||
documents:
|
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":
|
"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":
|
"npz/2025-Q3/Бухгалтерская отчетность за Январь - Сентябрь 2025 г. (НОВОРОССИЙСКИЙ ПРОКАТНЫЙ ЗАВОД).xlsx":
|
||||||
organization: 'ООО "НОВОРОССИЙСКИЙ ПРОКАТНЫЙ ЗАВОД"'
|
organization: 'ООО "НОВОРОССИЙСКИЙ ПРОКАТНЫЙ ЗАВОД"'
|
||||||
inn: "2315995868"
|
inn: "2315995868"
|
||||||
inn_optional_for: [INCOME]
|
inn_optional_for: [INCOME]
|
||||||
documents:
|
documents:
|
||||||
- {type: BALANCE, status: needs_check, complete: false, sheets: 5, reason: "1600 = 1700 расходится на 1.94/9.02/2.48 руб.: значения в рублях при шапке тыс. руб. (OQ-137)"}
|
- {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"}
|
- {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":
|
"mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx":
|
||||||
organization: 'ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "АГРОФИРМА "МЕЖЕНИНОВСКАЯ"'
|
organization: 'ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "АГРОФИРМА "МЕЖЕНИНОВСКАЯ"'
|
||||||
inn: "7016006635"
|
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":
|
"mezheninovskaya/2025-Q3/1_Форма2_бух_09.xlsx":
|
||||||
organization: 'ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "АГРОФИРМА "МЕЖЕНИНОВСКАЯ"'
|
organization: 'ОБЩЕСТВО С ОГРАНИЧЕННОЙ ОТВЕТСТВЕННОСТЬЮ "АГРОФИРМА "МЕЖЕНИНОВСКАЯ"'
|
||||||
inn: "7016006635"
|
inn: "7016006635"
|
||||||
documents: [{type: INCOME, status: done, complete: true}]
|
documents: [{type: INCOME, status: done, complete: true, failed_relations: []}]
|
||||||
"templates/ББ до 01.01.2025.xlsx":
|
"templates/ББ до 01.01.2025.xlsx":
|
||||||
documents:
|
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"}
|
- {type: DISCLOSURE, status: done, complete: false, reason: "по факту: «Примечания» шаблона образуют пустой документ DISCLOSURE"}
|
||||||
"templates/ББ после 01.01.2025.xlsx":
|
"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":
|
"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":
|
"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: []}]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user