"""Период консолидации (Срез 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, effective_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,)) # ------------------------------------------------------------------------- R-14: документ за Q1 равнозначен обоим видам @pytest.mark.parametrize( ("flag", "quarter", "expected"), [ (True, Q1, False), # нарастающий с начала года за Q1 — это и есть квартал Q1 (None, Q1, False), (False, Q1, False), (True, Q2, True), (None, Q3, True), # ББ/ОФР вне Q1 — нарастающие (решение №10) (False, Q3, False), (True, None, True), # квартал не определён — по флагу (такой документ не привязывается) ], ) def test_effective_cumulative(flag: bool | None, quarter: PeriodQuarter | None, expected: bool) -> None: assert effective_cumulative(flag, quarter) is expected def test_cumulative_q1_is_quarterly_q1_source_for_later_version() -> None: """Фикстуры РБ: парсер помечает ОСВ за Q1 нарастающей, Q2/Q3 — квартальными; для версии Q3 это полный комплект.""" q1, q2, q3 = _doc(Q1, True), _doc(Q2), _doc(Q3) assert select_sources(Q3, [q3, q2, q1]) == Sources(documents=(q1, q2, q3), cumulative=False) assert select_sources(Q2, [q2, q1]) == Sources(documents=(q1, q2), cumulative=False) def test_null_flag_q1_is_quarterly_q1_source_for_later_version() -> None: q1, q2 = _doc(Q1, None), _doc(Q2) assert select_sources(Q2, [q1, q2]) == Sources(documents=(q1, q2), cumulative=False) def test_cumulative_q1_does_not_fill_other_quarters() -> None: assert select_sources(Q2, [_doc(Q1, True)]) == Gap(missing_quarters=(Q2,)) # нарастающий Q2 при версии Q3 — не квартальный Q2: не хватает квартальных Q2 и Q3 assert select_sources(Q3, [_doc(Q1, True), _doc(Q2, True)]) == Gap(missing_quarters=(Q2, Q3)) def test_cumulative_of_version_quarter_still_beats_cumulative_q1() -> None: cumulative = _doc(Q3, True) assert select_sources(Q3, [_doc(Q1, True), _doc(Q2), _doc(Q3), cumulative]) == Sources((cumulative,), True) def test_q1_version_cumulative_q1_is_taken_as_is() -> None: """Версия Q1: нарастающий Q1 — источник «как есть» (решение №50), квартальный — склейка из одного квартала.""" cumulative, quarterly = _doc(Q1, True), _doc(Q1, False) assert select_sources(Q1, [cumulative]) == Sources((cumulative,), cumulative=True) assert select_sources(Q1, [quarterly, cumulative]) == Sources((cumulative,), cumulative=True) assert select_sources(Q1, [quarterly]) == Sources((quarterly,), cumulative=False)