diff --git a/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx b/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx
index 617524de..4cf8d274 100644
--- a/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx
+++ b/tradein-mvp/frontend/src/components/trade-in/HeroSummary.tsx
@@ -95,6 +95,9 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals
const m = estimate.median_price_rub;
const lo = estimate.range_low_rub;
const hi = estimate.range_high_rub;
+ // Пустая оценка (backend #697): нет аналогов/сделок → median<=0. Вместо
+ // «0,00 млн ₽» в headline показываем явный empty-state.
+ const insufficientData = estimate.insufficient_data || m <= 0;
// ── Ожидаемая цена продажи (S3). Рисуем второй блок только при валидном числе:
// null/undefined/0 (старые оценки или пустая ratio-таблица) → одиночный layout. ──
@@ -294,33 +297,47 @@ export function HeroSummary({ estimate, input, onResubmit, isResubmitting = fals
запроса, с −X%). Без него (старые оценки / пустая ratio) — headline =
asking-медиана, чтобы блок не оставался без главного числа. */}
-
- {hasSold ? "Ожидаемая цена сделки" : "Рекомендованная цена"}
- {showDiscount && (
-
- −{discountPct}% к объявлению
-
- )}
-
-
- {formatMln(hasSold ? (sold as number) : m)} ₽
-
- {hasSold && typeof soldLo === "number" && typeof soldHi === "number" && (
-
- ожидаемый диапазон {formatMln(soldLo)} – {formatMln(soldHi)} ₽
- {typeof soldPerM2 === "number"
- ? ` · ${soldPerM2.toLocaleString("ru-RU")} ₽/м²`
- : ""}
-
- )}
- {hasSold && (
-
- Реальные сделки проходят на 5–12% ниже цен в объявлениях — это
- ожидаемая цена сделки по данным ДКП Росреестра, а не цена запроса.
-
+ {insufficientData ? (
+ <>
+
Оценка недоступна
+
Недостаточно данных для оценки
+
+ Рядом не нашлось достаточно сопоставимых объявлений и сделок, чтобы
+ рассчитать цену. Уточните адрес или параметры объекта и попробуйте
+ снова.
+
+ >
+ ) : (
+ <>
+
+ {hasSold ? "Ожидаемая цена сделки" : "Рекомендованная цена"}
+ {showDiscount && (
+
+ −{discountPct}% к объявлению
+
+ )}
+
+
+ {formatMln(hasSold ? (sold as number) : m)} ₽
+
+ {hasSold && typeof soldLo === "number" && typeof soldHi === "number" && (
+
+ ожидаемый диапазон {formatMln(soldLo)} – {formatMln(soldHi)} ₽
+ {typeof soldPerM2 === "number"
+ ? ` · ${soldPerM2.toLocaleString("ru-RU")} ₽/м²`
+ : ""}
+
+ )}
+ {hasSold && (
+
+ Реальные сделки проходят на 5–12% ниже цен в объявлениях — это
+ ожидаемая цена сделки по данным ДКП Росреестра, а не цена запроса.
+
+ )}
+ >
)}
diff --git a/tradein-mvp/frontend/src/components/trade-in/trade-in.css b/tradein-mvp/frontend/src/components/trade-in/trade-in.css
index ff0da9a1..c963192f 100644
--- a/tradein-mvp/frontend/src/components/trade-in/trade-in.css
+++ b/tradein-mvp/frontend/src/components/trade-in/trade-in.css
@@ -705,6 +705,12 @@
letter-spacing: -0.025em;
color: var(--fg);
}
+ .headline-value-empty {
+ font-size: 22px;
+ font-weight: 600;
+ line-height: 1.27;
+ color: var(--fg);
+ }
.headline-range {
font-size: 12px;
color: var(--muted);
diff --git a/tradein-mvp/frontend/src/types/trade-in.ts b/tradein-mvp/frontend/src/types/trade-in.ts
index 5d0e66cb..9b75662e 100644
--- a/tradein-mvp/frontend/src/types/trade-in.ts
+++ b/tradein-mvp/frontend/src/types/trade-in.ts
@@ -123,6 +123,7 @@ export interface AggregatedEstimate {
confidence: ConfidenceLevel;
confidence_explanation: string | null;
n_analogs: number;
+ insufficient_data: boolean; // backend #697: true когда median_price_rub <= 0 (нет данных)
period_months: number; // 24
analogs: AnalogLot[]; // top 5-10
actual_deals: AnalogLot[]; // last 12 mo