arm-core/api/tests/test_domain_statement_mapping.py

156 lines
6.5 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.

"""Чистые функции маппинга формы (spec osv-result-mapping «Документ формы ББ/ОФР»; design D-2, D-3, D-6, D-14)."""
import datetime
from decimal import Decimal
import pytest
from src.contract.result import StatementDocument
from src.domain.statement_mapping import (
build_statement,
edition_for,
quarter_of,
statement_period,
statement_status,
values_ok,
)
from src.models.enums import DocumentCheckResult, DocumentStatus, DocumentType, PeriodQuarter, StatementType
d = datetime.date
@pytest.mark.parametrize(
("day", "quarter"),
[
(d(2025, 3, 31), PeriodQuarter.Q1),
(d(2025, 6, 30), PeriodQuarter.Q2),
(d(2025, 9, 30), PeriodQuarter.Q3),
(d(2024, 12, 31), PeriodQuarter.Q4),
(d(2025, 10, 31), None),
(d(2025, 6, 29), None),
],
)
def test_quarter_of(day: datetime.date, quarter: PeriodQuarter | None) -> None:
assert quarter_of(day) is quarter
def test_period_and_edition() -> None:
assert statement_period(d(2025, 9, 30), [d(2024, 12, 31)]) == d(2025, 9, 30)
assert statement_period(None, [d(2024, 12, 31), d(2025, 6, 30), None]) == d(2025, 6, 30)
assert statement_period(None, [None]) is None
assert edition_for(d(2024, 12, 31)) == "66n"
assert edition_for(d(2025, 3, 31)) == "fsbu4"
assert edition_for(None) is None
def _doc(**overrides: object) -> StatementDocument:
data: dict[str, object] = {
"document_type": "BALANCE",
"locator": "sheet:ББ",
"header": {
"form_name": "Бухгалтерский баланс",
"inn": "7700000000",
"inn_confidence": 100,
"report_date": "2025-09-30",
"report_date_text": "на 30 сентября 2025 г.",
"units": "тыс. руб.",
"organization_name": "ООО Пример",
"okud": "0710001",
},
"columns": [
{"column_index": 0, "label": "На 30.09.2025", "period_date": "2025-09-30"},
{"column_index": 1, "label": "На 31.12.2024", "period_date": "2024-12-31"},
],
"lines": [
{
"line_code": "1150",
"line_code_confidence": 100,
"line_name": "ОС",
"line_name_confidence": 100,
"values": [{"column_index": 0, "amount": "10", "confidence": 100, "is_recognized": True}],
},
{"line_code": "1150", "line_name": "дубль", "values": []},
{
"line_code": "1600",
"line_name": "БАЛАНС",
"values": [{"column_index": 5, "amount": "1", "confidence": 100, "is_recognized": True}],
},
],
}
data.update(overrides)
return StatementDocument.model_validate(data)
def test_build_statement_cells_and_header() -> None:
draft = build_statement(_doc())
expected_cells = 4 # 2 строки × 2 колонки
assert draft.document_type is DocumentType.BALANCE and draft.statement_type is StatementType.balance
assert (draft.period_year, draft.period_quarter, draft.period_date) == (2025, PeriodQuarter.Q3, d(2025, 9, 30))
assert draft.header["inn"] == "7700000000" and draft.header["inn_original_value"] == "7700000000"
assert draft.header["inn_verified"] is True
assert draft.header["report_date_original_value"] == "на 30 сентября 2025 г."
assert draft.header["units"] == draft.header["units_original_value"] == "тыс. руб."
assert draft.columns == [(0, "На 30.09.2025", d(2025, 9, 30), True), (1, "На 31.12.2024", d(2024, 12, 31), False)]
assert [line.line_code for line in draft.lines] == ["1150", "1600"] # дубль 1150 отброшен
cells = {(v.line_code, v.column_index): v for v in draft.values}
assert len(cells) == expected_cells
assert cells[("1150", 0)].amount == Decimal("10") and cells[("1150", 1)].amount is None
assert cells[("1150", 1)].is_recognized is None and cells[("1600", 0)].confidence is None
assert any("1150" in w and "повтор" in w for w in draft.warnings)
assert any("колонк" in w and "5" in w for w in draft.warnings)
def test_value_outside_columns_skipped() -> None:
"""Review Focus 3: значение с column_index вне колонок — пропущено с предупреждением, остальное смаплено."""
draft = build_statement(_doc())
assert all(v.column_index in (0, 1) for v in draft.values)
def test_period_from_columns_and_non_quarter() -> None:
no_date = _doc(header={"form_name": "ББ", "report_date": None})
draft = build_statement(no_date)
assert (draft.period_year, draft.period_quarter) == (2025, PeriodQuarter.Q3)
assert draft.header["report_date"] is None
october = _doc(header={"form_name": "ББ", "report_date": "2025-10-31"})
draft = build_statement(october)
assert (draft.period_year, draft.period_quarter) == (2025, None)
assert any("конец квартала" in w for w in draft.warnings)
def test_no_dates_at_all() -> None:
draft = build_statement(_doc(header={"form_name": "ББ"}, columns=[{"column_index": 0, "label": ""}]))
assert (draft.period_year, draft.period_quarter, draft.period_date) == (None, None, None)
assert draft.columns == [(0, "", None, None)]
def test_status_rules() -> None:
ok = dict(
job_done=True,
checks_passed=True,
counterparty=DocumentCheckResult.MATCH,
period_known=True,
values_ok=True,
has_lines=True,
warnings=[],
)
assert statement_status(**ok) is DocumentStatus.RECOGNIZED # type: ignore[arg-type]
for key, bad in [
("job_done", False),
("checks_passed", False),
("counterparty", DocumentCheckResult.MISMATCH),
("period_known", False),
("values_ok", False),
("has_lines", False),
("warnings", ["x"]),
]:
assert statement_status(**{**ok, key: bad}) is DocumentStatus.NEEDS_CHECK # type: ignore[arg-type]
assert statement_status(**{**ok, "counterparty": DocumentCheckResult.UNKNOWN}) is DocumentStatus.RECOGNIZED # type: ignore[arg-type]
def test_values_ok() -> None:
hundred = Decimal("100")
assert values_ok([(False, True, hundred), (False, None, None)])
assert not values_ok([(False, False, hundred)])
assert not values_ok([(False, True, Decimal("99"))])
assert values_ok([(True, False, Decimal("10"))]) # правленое аналитиком значение не тянет статус вниз