- 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
118 lines
4.6 KiB
Python
118 lines
4.6 KiB
Python
"""Период консолидации (Срез 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,))
|