fix(schemas): expose per-bucket elasticity + velocity_source в RecommendBucket (#1204)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 1m32s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m56s
Deploy / deploy (push) Successful in 1m9s

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
This commit is contained in:
Light1YT 2026-06-13 10:05:58 +05:00 committed by bot-backend
parent 9a2cf32787
commit 4648e1aee7

View file

@ -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