160 lines
7.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import date
from typing import Any
import pytest
import src.normalizer.ocr.answer_set as answer_set_module
from arm_statements import DocStatus, DocumentType, EnvelopeStatus, PeriodKind, Severity, default_rules
from src.contract import ErrorCode
from src.normalizer.ocr.answer_set import NO_TABLES_MESSAGE, normalize_answer_set
from tests.unit.normalizer.ocr_builders import BAD, BALANCE, HEAD, INCOME, OK, merged, ocr_table, response
RULES = default_rules()
def test_one_json_two_forms_copy_header() -> None:
res = normalize_answer_set(
merged(
(
"j.json",
response(
ocr_table("Бухгалтерский баланс", BALANCE), ocr_table("Отчет о финансовых результатах", INCOME)
),
)
),
RULES,
)
assert [(d.locator, d.document_type, d.header.form_name) for d in res.documents] == [
("file:j.json;table:0", DocumentType.BALANCE, "Бухгалтерский баланс"),
("file:j.json;table:1", DocumentType.INCOME, "Отчет о финансовых результатах"),
]
assert {d.header.inn.value for d in res.documents} == {"7700000000"}
assert res.documents[0].status is DocStatus.DONE and res.error is None
def test_lost_ofr_json_is_file_error() -> None:
res = normalize_answer_set(
merged(("j.json", response(ocr_table("Бухгалтерский баланс", BALANCE))), ("j_ofr.json", response(error=BAD))),
RULES,
)
assert res.status is EnvelopeStatus.NEEDS_CHECK and len(res.documents) == 1
assert [(w.code, w.severity, w.locator, w.message) for w in res.file_warnings] == [
("JSON_FAILED", Severity.ERROR, "file:j_ofr.json", "INVALID_DOCUMENT: Не удалось определить тип документа.")
]
def test_table_exception_is_file_warning(monkeypatch: pytest.MonkeyPatch) -> None:
real = answer_set_module.normalize_candidate
def flaky(candidate: Any, rules: Any = None) -> Any:
if candidate.locator.endswith("table:1"):
raise RuntimeError("сломалось")
return real(candidate, rules)
monkeypatch.setattr(answer_set_module, "normalize_candidate", flaky)
res = normalize_answer_set(
merged(
(
"j.json",
response(
ocr_table("Бухгалтерский баланс", BALANCE), ocr_table("Отчет о финансовых результатах", INCOME)
),
)
),
RULES,
)
assert [d.document_type for d in res.documents] == [DocumentType.BALANCE]
[w] = res.file_warnings
assert (w.code, w.severity, w.locator) == ("TABLE_PARSE_ERROR", Severity.ERROR, "file:j.json;table:1")
assert w.message == "BACKEND_PARSING_ERROR: RuntimeError: сломалось"
assert res.status is EnvelopeStatus.NEEDS_CHECK
def test_broken_table_structure_is_table_parse_error() -> None:
res = normalize_answer_set(
merged(("j.json", response(ocr_table("Бухгалтерский баланс", BALANCE), {"label": {}, "rows": "x"}))), RULES
)
assert [w.code for w in res.file_warnings] == ["TABLE_PARSE_ERROR"] and len(res.documents) == 1
def test_text_only_table_skipped_and_no_documents() -> None:
res = normalize_answer_set(merged(("j.json", response(ocr_table("", [("Руководитель", "Иванов")])))), RULES)
assert [(w.code, w.severity, w.locator) for w in res.file_warnings] == [
("TABLE_SKIPPED", Severity.INFO, "file:j.json;table:0")
]
assert res.status is EnvelopeStatus.ERROR
assert (res.error.code, res.error.message) == (ErrorCode.RECOGNITION_FAILED, NO_TABLES_MESSAGE)
def test_lost_table_with_numbers() -> None:
res = normalize_answer_set(
merged(
("j.json", response(ocr_table("Бухгалтерский баланс", BALANCE), ocr_table("", [("Итого", "", "15 000")])))
),
RULES,
)
assert [(w.code, w.severity, w.locator) for w in res.file_warnings] == [
("TABLE_LOST", Severity.ERROR, "file:j.json;table:1")
]
assert res.status is EnvelopeStatus.NEEDS_CHECK
def test_all_json_failed_first_code_and_all_listed() -> None:
res = normalize_answer_set(merged(("j.json", response(error=BAD)), ("j_ofr.json", "не объект")), RULES)
assert res.status is EnvelopeStatus.ERROR and res.documents == ()
assert (res.error.code, res.error.message) == (ErrorCode.INVALID_DOCUMENT, BAD["message"])
assert [(w.code, w.locator) for w in res.file_warnings] == [
("JSON_FAILED", "file:j.json"),
("JSON_FAILED", "file:j_ofr.json"),
]
@pytest.mark.parametrize(
("data_ocr", "message"),
[
(None, "ответ ИС без data_ocr"),
({**HEAD, "table_structure": None}, "ответ ИС без table_structure"),
({**HEAD, "table_structure": []}, "в ответе ИС нет таблиц"),
],
)
def test_successful_json_without_tables(data_ocr: object, message: str) -> None:
res = normalize_answer_set(merged(("j.json", {"error": OK, "data_ocr": data_ocr})), RULES)
assert [(w.code, w.severity, w.message) for w in res.file_warnings] == [("JSON_FAILED", Severity.ERROR, message)]
assert res.error is not None and res.error.code is ErrorCode.RECOGNITION_FAILED
@pytest.mark.parametrize("raw", [None, {"files": "x"}, {"job_id": "j"}])
def test_not_merged(raw: object) -> None:
res = normalize_answer_set(raw, RULES)
assert res.status is EnvelopeStatus.ERROR and res.error is not None
assert res.error.code is ErrorCode.INVALID_FORMAT
# Сквозной тест дат шлюза: строки OCR из test_parses_real_forms (правило — в пакете, план 1, задача 5)
@pytest.mark.parametrize(
("label", "expected"),
[
("На 30 июня\n2025 г", date(2025, 6, 30)),
("За январь - июнь\n2025 г.", date(2025, 6, 30)),
("30 сентября 2025 r.", date(2025, 9, 30)),
("3l.12.2О24", date(2024, 12, 31)),
("9 мес. 2025", date(2025, 9, 30)),
("30.09.2025 0:00:00", date(2025, 9, 30)),
],
)
def test_ocr_column_dates_end_to_end(label: str, expected: date) -> None:
rows = [("Наименование показателя", "Код", label), *BALANCE[1:]]
head = {**HEAD, "date": {"form_name": "2|0|2|4|", "confidence": 75.0}}
res = normalize_answer_set(merged(("j.json", response(ocr_table("Бухгалтерский баланс", rows), head=head))), RULES)
doc = res.documents[0]
assert [(c.period_kind, c.period_date) for c in doc.columns] == [(PeriodKind.ABSOLUTE, expected)]
assert doc.header.report_date.value == date(2024, 12, 31)
@pytest.mark.parametrize("second", [response(), response(ocr_table("", [("Руководитель", "Иванов")]))])
def test_one_failed_one_successful_without_documents_is_recognition_failed(second: dict[str, Any]) -> None:
res = normalize_answer_set(merged(("j.json", response(error=BAD)), ("j_ofr.json", second)), RULES)
assert res.status is EnvelopeStatus.ERROR and res.documents == ()
assert (res.error.code, res.error.message) == (ErrorCode.RECOGNITION_FAILED, NO_TABLES_MESSAGE)
assert ("JSON_FAILED", "file:j.json") in [(w.code, w.locator) for w in res.file_warnings]