From 6a81e91a83e800de4f56ab84be83a4a7395353fe Mon Sep 17 00:00:00 2001 From: lekss361 Date: Fri, 29 May 2026 19:04:32 +0300 Subject: [PATCH] =?UTF-8?q?feat(tradein):=20surface=20Avito=20IMV=20anchor?= =?UTF-8?q?=20+=20=D0=94=D0=9A=D0=9F=20corridor=20in=20HeroSummary=20(#651?= =?UTF-8?q?,=20#652)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend surfacing in the DEPLOYED trade-in UI (tradein-mvp/frontend): - types/trade-in.ts: add AvitoImvSummary + DkpCorridor, optional avito_imv / dkp_corridor on AggregatedEstimate (mirror backend snake_case, no any) - HeroSummary.tsx: 'Avito IMV: X млн ₽' caption near price + 'Коридор реальных сделок: X–Y млн · N ДКП (Росреестр)' line. No new fetch, no-op when null. tsc --noEmit clean. Root legacy frontend/ untouched. --- .../src/components/trade-in/HeroSummary.tsx | 35 +++++++++++++++++++ tradein-mvp/frontend/src/types/trade-in.ts | 27 ++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx b/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx index 969aa3a9..d9d2ef33 100644 --- a/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx +++ b/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx @@ -92,6 +92,18 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals const showMockDisclaimer = isMockAddress(estimate); + // ── #651: Avito IMV anchor — реальная рыночная оценка дома. Маркер у цены. ── + const imv = estimate.avito_imv; + const imvPrice = + imv && typeof imv.recommended_price === "number" && imv.recommended_price > 0 + ? imv.recommended_price + : null; + // ── #652: коридор реальных ДКП-сделок (advisory). ₽/м² → млн через площадь. ── + const dkp = estimate.dkp_corridor; + const dkpArea = estimate.area_m2 ?? input.area_m2 ?? 0; + const showDkp = + !!dkp && dkp.count > 0 && dkpArea > 0 && dkp.low_ppm2 > 0 && dkp.high_ppm2 > 0; + // Progressive enrichment state const needsHouseType = estimate.house_type === null; const needsRepairState = estimate.repair_state === null; @@ -264,6 +276,15 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals
{estimate.median_price_per_m2.toLocaleString("ru-RU")} ₽/м²
+ {imvPrice !== null && ( +
+ Avito IMV: {formatMln(imvPrice)} ₽ +
+ )} {hasSold && ( @@ -299,6 +320,20 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals

)} + {showDkp && dkp && ( +

+ Коридор реальных сделок:{" "} + + {formatMln(dkp.low_ppm2 * dkpArea)} – {formatMln(dkp.high_ppm2 * dkpArea)} ₽ + {" "} + · {dkp.median_ppm2.toLocaleString("ru-RU")} ₽/м² ·{" "} + {dkp.count} ДКП (Росреестр) +

+ )} +
diff --git a/tradein-mvp/frontend/src/types/trade-in.ts b/tradein-mvp/frontend/src/types/trade-in.ts index f6aa9ad6..c72f6efd 100644 --- a/tradein-mvp/frontend/src/types/trade-in.ts +++ b/tradein-mvp/frontend/src/types/trade-in.ts @@ -72,6 +72,27 @@ export interface CianValuationSummary { // Базис коэффициента asking→sold (S3): по группе комнат либо городской fallback. export type RatioBasis = "per_rooms" | "global_fallback"; +// ── #651: Avito IMV (Индекс Market Value) якорь для target-дома ── +// Реальная рыночная оценка Avito по дому (house_imv_evaluations) — anchor для +// blend'а медианы. Сурфейсится как референсный маркер. null при отсутствии записи. +export interface AvitoImvSummary { + recommended_price: number | null; // рекомендованная цена Avito, ₽ + lower_price: number | null; // нижняя граница IMV-коридора, ₽ + higher_price: number | null; // верхняя граница IMV-коридора, ₽ + market_count: number | null; // объём рынка, на котором построена оценка +} + +// ── #652: коридор реальных ДКП-сделок Росреестра (advisory, не клампит) ── +// Агрегаты ₽/м² по сопоставимым сделкам за период. null / count=0 если по улице +// нет сделок. count..period_months — required (бэкенд не отдаёт частичный объект). +export interface DkpCorridor { + count: number; // число ДКП-сделок в выборке + low_ppm2: number; // min ₽/м² по сделкам + median_ppm2: number; // медиана ₽/м² + high_ppm2: number; // max ₽/м² + period_months: number; // окно поиска сделок +} + export interface AggregatedEstimate { estimate_id: string; // UUID median_price_rub: number; // ASKING-ориентир: по чему выставлены аналоги @@ -117,6 +138,12 @@ export interface AggregatedEstimate { has_balcony: boolean | null; // ── Cian valuation surface ── cian_valuation?: CianValuationSummary | null; + // ── #651/#652: внешние якоря ── + // avito_imv — реальная Avito IMV-оценка дома, anchor для blend'а медианы. + // dkp_corridor — коридор ₽/м² по ДКП-сделкам Росреестра (advisory). Оба nullable: + // null при отсутствии данных (graceful, common case без регрессий). + avito_imv?: AvitoImvSummary | null; + dkp_corridor?: DkpCorridor | null; } // ── Stage 4a/4b response types ──