feat(tradein): insufficient_data flag on AggregatedEstimate (#697) (#734)
All checks were successful
Deploy Trade-In / changes (push) Successful in 4s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 39s
Deploy Trade-In / test (push) Successful in 25s
Deploy Trade-In / deploy (push) Successful in 33s

Co-authored-by: bot-backend <bot-backend@gendsgn.local>
Co-committed-by: bot-backend <bot-backend@gendsgn.local>
This commit is contained in:
bot-backend 2026-05-30 14:40:04 +00:00 committed by bot-reviewer
parent 066fab11d3
commit ef7b5f8c54
2 changed files with 62 additions and 1 deletions

View file

@ -9,7 +9,7 @@ from datetime import date, datetime
from typing import Any, Literal
from uuid import UUID
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, computed_field
class TradeInEstimateInput(BaseModel):
@ -132,6 +132,18 @@ class AggregatedEstimate(BaseModel):
confidence: Literal["low", "medium", "high"]
confidence_explanation: str | None = None # «Найдено 15 аналогов, разброс ±7%»
n_analogs: int
@computed_field # type: ignore[prop-decorator]
@property
def insufficient_data(self) -> bool:
"""#697: нет пригодной оценки — комплов/якоря не нашлось → headline median=0.
Фронт по этому флагу рендерит явное «недостаточно данных», а не буквальный
«0 ». Производное от median_price_rub (==0 нет оценки), поэтому корректно
во всех конструкторах AggregatedEstimate автоматически (вкл. rehydrate на GET).
"""
return self.median_price_rub <= 0
period_months: int # 24
analogs: list[AnalogLot] # top 5-10 listings
actual_deals: list[AnalogLot] # реальные продажи last 12 mo

View file

@ -0,0 +1,49 @@
"""#697 — AggregatedEstimate.insufficient_data computed flag.
median=0 (нет комплов/якоря) insufficient_data=True, чтобы фронт показал
«недостаточно данных», а не «0 ». Производное от median_price_rub.
"""
import os
from datetime import UTC, datetime
from uuid import uuid4
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
from app.schemas.trade_in import AggregatedEstimate
def _estimate(median: int) -> AggregatedEstimate:
return AggregatedEstimate(
estimate_id=uuid4(),
median_price_rub=median,
range_low_rub=0 if median == 0 else median - 100,
range_high_rub=0 if median == 0 else median + 100,
median_price_per_m2=0 if median == 0 else 150_000,
confidence="low" if median == 0 else "medium",
n_analogs=0 if median == 0 else 12,
period_months=12,
analogs=[],
actual_deals=[],
expires_at=datetime(2026, 6, 1, tzinfo=UTC),
)
def test_insufficient_when_median_zero() -> None:
assert _estimate(0).insufficient_data is True
def test_sufficient_when_median_positive() -> None:
assert _estimate(6_900_000).insufficient_data is False
def test_flag_serialized_in_model_dump() -> None:
dump = _estimate(0).model_dump()
assert dump["insufficient_data"] is True
dump2 = _estimate(5_000_000).model_dump()
assert dump2["insufficient_data"] is False
def test_negative_median_is_insufficient() -> None:
# защита от случайного отрицательного headline.
assert _estimate(-1).insufficient_data is True