From afa7d80ecf204751b9cb99ca6abc7f1b6c4af3fe Mon Sep 17 00:00:00 2001 From: bot-backend Date: Wed, 17 Jun 2026 20:38:00 +0300 Subject: [PATCH] =?UTF-8?q?fix(forecasting):=20deal=5Fcount=20confidence?= =?UTF-8?q?=20note=20carries=20=C2=AB=D0=B7=D0=B0=20N=20=D0=BC=D0=B5=D1=81?= =?UTF-8?q?=C2=BB=20window=20(#1637)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `deal_count_months: int | None = None` to `compute_report_confidence`. When provided, threads it as suffix into `_factor_from_count` so the deal_count ConfidenceFactor note reads «7 сделок за 6 мес — мало» instead of the windowless «7 сделок — мало». Existing callers unaffected (default None). Tests: two new cases in TestComputeReportConfidence — with/without window. --- .../services/forecasting/confidence_engine.py | 5 +++++ .../forecasting/test_confidence_engine.py | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/backend/app/services/forecasting/confidence_engine.py b/backend/app/services/forecasting/confidence_engine.py index 16fd9399..64192458 100644 --- a/backend/app/services/forecasting/confidence_engine.py +++ b/backend/app/services/forecasting/confidence_engine.py @@ -375,6 +375,7 @@ def compute_report_confidence( *, component_confidences: list[Confidence] | None = None, deal_count: int | None = None, + deal_count_months: int | None = None, analog_count: int | None = None, domrf_coverage: float | None = None, history_months: int | None = None, @@ -403,6 +404,8 @@ def compute_report_confidence( component_confidences: per-service confidence (#950/#952/#985/#986…), None/[]→ нет вкладывающих компонентов. deal_count: число сделок за окно (None → нет данных, тянет в low). + deal_count_months: окно наблюдения для deal_count (мес) — добавляет «за N мес» + в ноту фактора («7 сделок за 6 мес — мало»). None → нота без периода. analog_count: число ЖК-аналогов в выборке (= market_metrics.obj_count). domrf_coverage: доля domrf↔objective ∈ [0,1] (главный sparse-риск проекта). history_months: глубина ряда (мес). @@ -417,6 +420,7 @@ def compute_report_confidence( # ── 1. Сырые счётчики качества данных → факторы (только заданные) ────────── if deal_count is not None: + _deal_suffix = f"за {deal_count_months} мес" if deal_count_months is not None else "" factors.append( _factor_from_count( _F_DEAL_COUNT, @@ -424,6 +428,7 @@ def compute_report_confidence( high_at=_DEAL_COUNT_HIGH, low_below=_DEAL_COUNT_LOW, unit="сделок", + suffix=_deal_suffix, ) ) if analog_count is not None: diff --git a/backend/tests/services/forecasting/test_confidence_engine.py b/backend/tests/services/forecasting/test_confidence_engine.py index f14c5991..df6718b5 100644 --- a/backend/tests/services/forecasting/test_confidence_engine.py +++ b/backend/tests/services/forecasting/test_confidence_engine.py @@ -342,6 +342,26 @@ class TestComputeReportConfidence: # Полностью JSON-сериализуем (контракт для экспортёров/чата). assert json.loads(json.dumps(d, ensure_ascii=False)) == d + def test_deal_count_note_carries_window_months(self) -> None: + # #1637: deal_count_months → нота «за N мес» в факторе (и в rationale). + res = compute_report_confidence( + deal_count=7, + deal_count_months=6, + advisory=False, + ) + dc_factor = next(f for f in res.factors if f.name == "deal_count") + assert "за 6 мес" in dc_factor.note + assert "7 сделок" in dc_factor.note + # Структурная причина тоже содержит период (через ноту фактора-виновника). + assert "за 6 мес" in res.rationale + + def test_deal_count_note_without_window_has_no_period(self) -> None: + # deal_count_months=None (по умолчанию) → нота без «за N мес» (backward compat). + res = compute_report_confidence(deal_count=7, advisory=False) + dc_factor = next(f for f in res.factors if f.name == "deal_count") + assert "7 сделок" in dc_factor.note + assert "за" not in dc_factor.note + def test_ignores_garbage_component_confidence(self) -> None: # Мусорный component-уровень не учитывается (whitelist), не роняет искусственно. res = compute_report_confidence(