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