arm-tabular-parser/api/tests/fixtures/test_statements_property.py
Andrey Epifancev 6cd2ea8a7a 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
2026-10-06 21:04:50 +04:00

90 lines
4.4 KiB
Python

"""Инварианты форм на всех `../fixtures/statements-xlsx/**` по манифесту (spec «Словари форм, координаты, golden»)."""
import time
from pathlib import Path
from typing import cast
import pytest
from fixtures_helpers import statement_files
from result_contract import assert_valid_result
from statement_helpers import load_manifest, relative_key
from src.contract import FileType, ResultStatus, StatementDocument
from src.parser import get_parser
pytestmark = pytest.mark.fixtures
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
assert {relative_key(fixtures_dir, path) for path in files} == set(load_manifest())
def test_statement_file_matches_manifest(fixtures_dir: Path, statement_file: Path) -> None:
expectation = load_manifest()[relative_key(fixtures_dir, statement_file)]
started = time.monotonic()
file_type = cast(FileType, statement_file.suffix.lstrip(".").upper())
outcome = get_parser().parse(statement_file.read_bytes(), file_type)
assert time.monotonic() - started < MAX_PARSE_SECONDS
assert outcome.error is None, outcome.error
documents = [document for document in outcome.documents if isinstance(document, StatementDocument)]
assert [document.document_type for document in documents] == [item.type for item in expectation.documents]
all_done = all(item.status == "done" for item in expectation.documents)
assert outcome.status is (ResultStatus.DONE if all_done else ResultStatus.NEEDS_CHECK)
for document, item in zip(documents, expectation.documents, strict=True):
codes = [line.line_code for line in document.lines]
assert len(codes) == len(set(codes)), "коды уникальны"
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)
# статус документа — по критерию парсера (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:
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)