From 4648e1aee7a795c150a8f5cc058d9b58ef1b93a5 Mon Sep 17 00:00:00 2001 From: Light1YT Date: Sat, 13 Jun 2026 10:05:58 +0500 Subject: [PATCH] =?UTF-8?q?fix(schemas):=20expose=20per-bucket=20elasticit?= =?UTF-8?q?y=20+=20velocity=5Fsource=20=D0=B2=20RecommendBucket=20(#1204)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit recommend_mix кладёт в bucket поля elasticity / elasticity_r2 / elasticity_n / elasticity_source / velocity_source (analytics_queries.py:2592-2606), но RecommendBucket их не объявляет и не имеет extra='allow' → Pydantic v2 молча выкидывает их из response. Фронт (RecommendVelocityPanel.tsx:68 `r.elasticity ?? elasticity`) при движении price-слайдера всегда падает на ГЛОБАЛЬНУЮ эластичность — live what-if расходится и с серверным months_to_sellout (тот считается per-bucket), и с UI-таблицей «Эластичность по сегментам». Patch: добавлены 5 опциональных полей в схему. Совместимость: фронт уже читает их с `?? globalFallback`, поэтому non-breaking. Producer/consumer- контракт восстановлен. 113/113 recommend-тестов зелёные. Closes #1204 --- backend/app/schemas/recommend.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/app/schemas/recommend.py b/backend/app/schemas/recommend.py index 62e6ba82..962ea09c 100644 --- a/backend/app/schemas/recommend.py +++ b/backend/app/schemas/recommend.py @@ -45,6 +45,18 @@ class RecommendBucket(BaseModel): # what-if recompute. velocity_per_month: float | None = None months_to_sellout: float | None = None + # Per-bucket эластичность спроса по цене (#1204) — recommend_mix считает её + # отдельно на каждую квартирографию (см. analytics_queries.py:2592-2606), фронт + # скейлит velocity_per_month как `velocity * price_factor^elasticity` при движении + # слайдера цены (RecommendVelocityPanel.tsx:68). Без этих полей в схеме + # response_model молча их отбрасывал → фронт всегда падал на глобальную + # эластичность, расходясь с серверным months_to_sellout и UI-таблицей сегментов. + elasticity: float | None = None + elasticity_r2: float | None = None + elasticity_n: int | None = None + elasticity_source: str | None = None + # Источник базового темпа продаж (analytics_queries → 'hedonic'|'deals'|'fallback'). + velocity_source: str | None = None # Success-driven mix flag (issue #25): bucket has top success_score in district is_top_success: bool = False