Backend: GET /api/v1/trade-in/estimate/{id}/house-analytics — resolves target
house_ids через нормализованный адрес или geo-fallback 100m, expand до 300m
если в самом доме <8 hist rows. Возвращает:
- price_history: median ₽/м² по году (из house_placement_history)
- recent_sold: последние 12 мес снятые с фактической ценой + торгом
- kpi: median exposure_days, median bargain %, sold_rate %
Frontend: HouseAnalyticsSection (под PlacementHistoryCard) c 3 блоками:
- PriceHistoryChart (Recharts line, median ₽/м²)
- HouseAnalyticsKpiRow (3 KPI cards)
- RecentSoldList (таблица снятых лотов)
Использует уже собранные данные house_placement_history (15k+ rows от
backfill scripts: avito_imv + yandex_valuation). Новых таблиц нет.
66 lines
1.5 KiB
TypeScript
66 lines
1.5 KiB
TypeScript
"use client";
|
||
import type { HouseAnalyticsKpi } from "@/types/trade-in";
|
||
|
||
type Props = { kpi: HouseAnalyticsKpi };
|
||
|
||
function KpiCard({
|
||
label,
|
||
value,
|
||
sub,
|
||
}: {
|
||
label: string;
|
||
value: string;
|
||
sub?: string;
|
||
}) {
|
||
return (
|
||
<div className="card" style={{ padding: 12, flex: 1, minWidth: 0 }}>
|
||
<div
|
||
style={{ fontSize: 11, color: "var(--muted, #6b7280)", marginBottom: 4 }}
|
||
>
|
||
{label}
|
||
</div>
|
||
<div style={{ fontSize: 20, fontWeight: 700 }}>{value}</div>
|
||
{sub && (
|
||
<div
|
||
style={{
|
||
fontSize: 11,
|
||
color: "var(--muted, #6b7280)",
|
||
marginTop: 2,
|
||
}}
|
||
>
|
||
{sub}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function HouseAnalyticsKpiRow({ kpi }: Props) {
|
||
return (
|
||
<div style={{ display: "flex", gap: 12, marginTop: 8 }}>
|
||
<KpiCard
|
||
label="Средняя экспозиция"
|
||
value={
|
||
kpi.median_exposure_days != null
|
||
? `${kpi.median_exposure_days} дн.`
|
||
: "—"
|
||
}
|
||
sub="по архивным объявлениям"
|
||
/>
|
||
<KpiCard
|
||
label="Средний торг"
|
||
value={
|
||
kpi.median_bargain_pct != null
|
||
? `−${kpi.median_bargain_pct.toFixed(1)}%`
|
||
: "—"
|
||
}
|
||
sub="от start_price до last_price"
|
||
/>
|
||
<KpiCard
|
||
label="Доля снятых"
|
||
value={`${kpi.sold_rate_pct.toFixed(0)}%`}
|
||
sub={`${kpi.sold_count} из ${kpi.total_lots}`}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|