Merge pull request 'fix(tradein): восстановленная оценка показывает этаж/площадь, не 0/0' (#424) from fix/tradein-restore-estimate-params into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 4s
Deploy Trade-In / build-backend (push) Successful in 42s
Deploy Trade-In / build-frontend (push) Successful in 1m27s
Deploy Trade-In / deploy (push) Successful in 18s

This commit is contained in:
lekss361 2026-05-22 10:51:04 +00:00
commit 7a9b183296
5 changed files with 62 additions and 13 deletions

View file

@ -299,7 +299,9 @@ def get_estimate(
SELECT id, median_price, range_low, range_high, median_price_per_m2,
confidence, confidence_explanation, n_analogs,
analogs, actual_deals, sources_used, data_freshness_minutes,
expires_at, address, lat, lon
expires_at, address, lat, lon,
area_m2, rooms, floor, total_floors,
year_built, house_type, repair_state, has_balcony
FROM trade_in_estimates
WHERE id = CAST(:id AS uuid)
AND expires_at > NOW()
@ -335,6 +337,14 @@ def get_estimate(
target_lon=row.lon,
sources_used=row.sources_used or [],
data_freshness_minutes=row.data_freshness_minutes,
area_m2=row.area_m2,
rooms=row.rooms,
floor=row.floor,
total_floors=row.total_floors,
year_built=row.year_built,
house_type=row.house_type,
repair_state=row.repair_state,
has_balcony=row.has_balcony,
)

View file

@ -66,6 +66,16 @@ class AggregatedEstimate(BaseModel):
sources_used: list[str] = Field(default_factory=list) # ['avito', 'cian', 'rosreestr']
data_freshness_minutes: int | None = None # сколько минут назад был самый свежий парсинг
est_days_on_market: int | None = None # прогноз срока продажи (медиана по аналогам)
# ── Параметры оценённой квартиры — нужны, чтобы восстановить карточку
# при открытии оценки по ссылке (?id=), когда формы-инпута уже нет ──
area_m2: float | None = None
rooms: int | None = None
floor: int | None = None
total_floors: int | None = None
year_built: int | None = None
house_type: str | None = None
repair_state: str | None = None
has_balcony: bool | None = None
class PhotoMeta(BaseModel):

View file

@ -288,6 +288,14 @@ async def estimate_quality(
sources_used=sources_used,
data_freshness_minutes=freshness_min,
est_days_on_market=_estimate_days_on_market(listings_clean, deals),
area_m2=payload.area_m2,
rooms=payload.rooms,
floor=payload.floor,
total_floors=payload.total_floors,
year_built=target_year,
house_type=target_house_type,
repair_state=payload.repair_state,
has_balcony=payload.has_balcony,
)

View file

@ -58,6 +58,17 @@ export function HeroSummary({ estimate, input }: Props) {
const medianPctRaw = span > 0 ? ((m - lo) / span) * 100 : 50;
const medianPct = Math.max(5, Math.min(95, medianPctRaw));
// Параметры квартиры берём из estimate (надёжно при восстановлении по ?id=),
// с откатом на форму input — у свежей оценки оба источника совпадают.
const floor = estimate.floor ?? input.floor;
const totalFloors = estimate.total_floors ?? input.total_floors;
const areaM2 = estimate.area_m2 ?? input.area_m2;
const rooms = estimate.rooms ?? input.rooms;
const yearBuilt = estimate.year_built ?? input.year_built;
const houseType = estimate.house_type ?? input.house_type;
const repairState = estimate.repair_state ?? input.repair_state;
const hasBalcony = estimate.has_balcony ?? input.has_balcony;
return (
<article className="card hero-card">
<div className="card-head">
@ -103,48 +114,48 @@ export function HeroSummary({ estimate, input }: Props) {
{estimate.target_address ?? input.address}
</div>
<div className="hero-cad">
ЭТАЖ {input.floor}/{input.total_floors}
{input.year_built ? ` · ${input.year_built}` : ""}
ЭТАЖ {floor}/{totalFloors}
{yearBuilt ? ` · ${yearBuilt}` : ""}
</div>
<div className="meta-grid">
{input.year_built && (
{yearBuilt && (
<div className="meta-row">
<span className="k">Год постройки</span>
<span className="v mono">{input.year_built}</span>
<span className="v mono">{yearBuilt}</span>
</div>
)}
{input.house_type && (
{houseType && (
<div className="meta-row">
<span className="k">Тип дома</span>
<span className="v">{HOUSE_TYPE_LABELS[input.house_type] ?? input.house_type}</span>
<span className="v">{HOUSE_TYPE_LABELS[houseType] ?? houseType}</span>
</div>
)}
<div className="meta-row">
<span className="k">Этаж</span>
<span className="v mono">
{input.floor} / {input.total_floors}
{floor} / {totalFloors}
</span>
</div>
<div className="meta-row">
<span className="k">Площадь</span>
<span className="v mono">{input.area_m2} м²</span>
<span className="v mono">{areaM2} м²</span>
</div>
<div className="meta-row">
<span className="k">Планировка</span>
<span className="v">
{input.rooms === 0 ? "Студия" : `${input.rooms}`}, классическая
{rooms === 0 ? "Студия" : `${rooms}`}, классическая
</span>
</div>
{input.repair_state && (
{repairState && (
<div className="meta-row">
<span className="k">Состояние</span>
<span className="v">{REPAIR_LABELS[input.repair_state] ?? input.repair_state}</span>
<span className="v">{REPAIR_LABELS[repairState] ?? repairState}</span>
</div>
)}
<div className="meta-row">
<span className="k">Балкон</span>
<span className="v">{input.has_balcony ? "есть" : "нет"}</span>
<span className="v">{hasBalcony ? "есть" : "нет"}</span>
</div>
<div className="meta-row">
<span className="k">Аналогов</span>

View file

@ -65,4 +65,14 @@ export interface AggregatedEstimate {
target_lon: number | null;
sources_used: string[]; // ['avito', 'cian', 'rosreestr']
data_freshness_minutes: number | null; // «обновлено N минут назад»
est_days_on_market: number | null; // прогноз срока продажи
// ── Параметры оценённой квартиры (для восстановления карточки по ?id=) ──
area_m2: number | null;
rooms: number | null;
floor: number | null;
total_floors: number | null;
year_built: number | null;
house_type: HouseType | null;
repair_state: RepairState | null;
has_balcony: boolean | null;
}