arm-core/api/tests/test_domain_consolidation_periods.py
Andrey Epifancev d5a317a040 feat(domain): сводная ОСВ — выбор источников, приведение периодов, пробелы, признак контрагента
- consolidation_periods: QUARTER_ORDER (единственное определение), quarters_through,
  counts_as_cumulative (R-1), select_sources -> Sources | Gap (D-2)
- consolidated_osv: build (склейка квартальных по ключу, нарастающий как есть, пробелы,
  counterparty_id последнего квартала + warnings, дорезолв без записи, признак GSK/GK/EXTERNAL,
  порядок D-16), apply_filter, summary, classify
- checklist: _osv_coverage — адаптер над select_sources (T8.1), явная таблица статусов
  кандидатов (T8.4), QUARTER_ORDER/account_sort_key из домена (T8.5)
- consolidation_documents: QUARTER_ORDER и counts_as_cumulative из домена
- schemas: CounterpartyType — доменный StrEnum (T2.8)
- design.md: решения №50–56; план и tasks.md 4.1 отмечены

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-06 06:07:55 +04:00

118 lines
4.6 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.

"""Период консолидации (Срез 4): отчётная дата (OQ-40) и выбор источников ОСВ на пару (ЮЛ, счёт) — D-2, §2."""
import datetime
import uuid
from dataclasses import dataclass
import pytest
from src.domain.consolidation_periods import (
QUARTER_ORDER,
Gap,
Sources,
counts_as_cumulative,
quarters_through,
reporting_date,
select_sources,
)
from src.models.enums import PeriodQuarter
Q1, Q2, Q3, Q4 = PeriodQuarter.Q1, PeriodQuarter.Q2, PeriodQuarter.Q3, PeriodQuarter.Q4
@pytest.mark.parametrize(
("year", "quarter", "expected"),
[
(2024, PeriodQuarter.Q1, datetime.date(2024, 4, 1)),
(2024, PeriodQuarter.Q2, datetime.date(2024, 7, 1)),
(2024, PeriodQuarter.Q3, datetime.date(2024, 10, 1)),
(2024, PeriodQuarter.Q4, datetime.date(2025, 1, 1)),
(2100, PeriodQuarter.Q4, datetime.date(2101, 1, 1)),
],
)
def test_reporting_date_is_first_day_after_quarter(year: int, quarter: PeriodQuarter, expected: datetime.date) -> None:
assert reporting_date(year, quarter) == expected
@dataclass(frozen=True)
class _Doc:
"""Документ для `select_sources`: структурно подходит под `DocLike` (как `Document` и `SourceDocument`)."""
period_quarter: PeriodQuarter | None
is_cumulative: bool | None
id: uuid.UUID = uuid.UUID(int=0)
def _doc(quarter: PeriodQuarter | None, cumulative: bool | None = False) -> _Doc:
return _Doc(quarter, cumulative, uuid.uuid4())
def test_quarter_order_and_quarters_through() -> None:
assert QUARTER_ORDER == (Q1, Q2, Q3, Q4)
assert quarters_through(Q1) == (Q1,)
assert quarters_through(Q3) == (Q1, Q2, Q3)
assert quarters_through(Q4) == QUARTER_ORDER
def test_counts_as_cumulative_null_is_cumulative() -> None:
"""R-1, решение №10: NULL (ББ/ОФР) — нарастающий; только `False` — квартальный."""
assert counts_as_cumulative(True) is True
assert counts_as_cumulative(None) is True
assert counts_as_cumulative(False) is False
def test_cumulative_of_version_quarter_is_single_source() -> None:
cumulative = _doc(Q3, True)
assert select_sources(Q3, [cumulative]) == Sources(documents=(cumulative,), cumulative=True)
def test_null_cumulative_flag_of_version_quarter_counts_as_cumulative() -> None:
document = _doc(Q3, None)
assert select_sources(Q3, [document]) == Sources(documents=(document,), cumulative=True)
def test_quarterly_q1_to_q3_are_sources_in_quarter_order() -> None:
q1, q2, q3 = _doc(Q1), _doc(Q2), _doc(Q3)
assert select_sources(Q3, [q3, q1, q2]) == Sources(documents=(q1, q2, q3), cumulative=False)
def test_missing_middle_quarter_is_gap() -> None:
assert select_sources(Q3, [_doc(Q1), _doc(Q3)]) == Gap(missing_quarters=(Q2,))
def test_q1_version_quarterly_q1_is_enough() -> None:
"""Для версии Q1 квартальный Q1 равнозначен нарастающему: источник есть, пробела нет."""
q1 = _doc(Q1)
assert select_sources(Q1, [q1]) == Sources(documents=(q1,), cumulative=False)
def test_cumulative_beats_quarterly_of_same_pair() -> None:
"""Review Focus 2: квартальные Q1–Q3 и нарастающий Q3 — источник один, нарастающий."""
cumulative = _doc(Q3, True)
assert select_sources(Q3, [_doc(Q1), _doc(Q2), _doc(Q3), cumulative]) == Sources((cumulative,), cumulative=True)
def test_cumulative_beats_quarterly_with_gap() -> None:
cumulative = _doc(Q3, True)
assert select_sources(Q3, [_doc(Q1), cumulative]) == Sources((cumulative,), cumulative=True)
def test_cumulative_of_earlier_quarter_is_ignored() -> None:
"""Review Focus 2 (защитно): нарастающий Q2 при версии Q3 не привязывается, но и функция его не берёт."""
assert select_sources(Q3, [_doc(Q2, True)]) == Gap(missing_quarters=(Q1, Q2, Q3))
def test_cumulative_of_earlier_quarter_does_not_fill_gap_of_quarterly() -> None:
assert select_sources(Q3, [_doc(Q1), _doc(Q2, True), _doc(Q3)]) == Gap(missing_quarters=(Q2,))
def test_quarterly_after_version_and_without_quarter_are_ignored() -> None:
q1, q2, q3 = _doc(Q1), _doc(Q2), _doc(Q3)
assert select_sources(Q3, [q1, q2, q3, _doc(Q4), _doc(None), _doc(None, True)]) == Sources((q1, q2, q3), False)
assert select_sources(Q2, [_doc(Q3)]) == Gap(missing_quarters=(Q1, Q2))
def test_no_documents_is_gap_of_all_quarters() -> None:
assert select_sources(Q3, []) == Gap(missing_quarters=(Q1, Q2, Q3))
assert select_sources(Q1, []) == Gap(missing_quarters=(Q1,))