"use client"; /** * DealsCard — Секция 3 «Сделки» из mockup. Реальные сделки из Росреестра / ДомКлик. */ import type { AggregatedEstimate, AnalogLot } from "@/types/trade-in"; import { openRosreestrWithAddress } from "@/lib/rosreestr"; interface Props { estimate: AggregatedEstimate; } const SOURCE_DOTS: Record = { rosreestr: "ros", domklik: "dom", etagi: "etagi", }; const SOURCE_LABELS: Record = { rosreestr: "Росреестр", domklik: "ДомКлик", etagi: "Этажи", }; function fmtRub(v: number): string { return v.toLocaleString("ru-RU"); } function fmtDate(iso: string | null): string { if (!iso) return "—"; try { const d = new Date(iso); return d.toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit", year: "2-digit" }); } catch { return "—"; } } export function DealsCard({ estimate }: Props) { const deals = estimate.actual_deals; if (deals.length === 0) { return null; } // Сортируем по дате DESC, берем медиану и range const prices = deals.map((d) => d.price_rub).sort((a, b) => a - b); const m = prices[Math.floor(prices.length / 2)]; const lo = prices[0]; const hi = prices[prices.length - 1]; const sourceCounts: Record = {}; for (const d of deals) { if (d.source) sourceCounts[d.source] = (sourceCounts[d.source] ?? 0) + 1; } return (
Секция 3 · Сделки

Фактические сделки по аналогичным квартирам

Период: за {estimate.period_months} мес.
ground truth · подтверждено Росреестром
Сделок по аналогам
{deals.length} шт
подтверждено Росреестром
Медиана сделок
{(m / 1_000_000).toFixed(2)} млн ₽
{estimate.median_price_rub > 0 ? `на ${Math.abs(((estimate.median_price_rub - m) / estimate.median_price_rub) * 100).toFixed(0)}% ${ estimate.median_price_rub - m >= 0 ? "ниже" : "выше" } рынка` : "—"}
Диапазон
{(lo / 1_000_000).toFixed(1)} – {(hi / 1_000_000).toFixed(1)} млн
7 месяцев · разница к рынку
Источники
{Object.entries(sourceCounts).map(([src, c]) => ( {" "} {SOURCE_LABELS[src] ?? src} ·{" "} {c} сделок ))}
{deals.map((d, i) => ( ))}
Адрес Источник ₽ / м² Цена сделки Дата
Показано {deals.length} фактических сделок
); } function DealRow({ deal }: { deal: AnalogLot }) { const src = deal.source ?? ""; const dot = SOURCE_DOTS[src] ?? "dom"; const label = SOURCE_LABELS[src] ?? src; // Tier badge для rosreestr deals (PR M / #564 Phase 3). // T0_per_house — точный кадастровый match (high confidence). // T1_per_street — улица-уровень (open dataset default). const tierBadge = deal.tier === "T0_per_house" ? { text: "по дому", className: "tier-badge tier-t0" } : deal.tier === "T1_per_street" ? { text: "по улице", className: "tier-badge tier-t1" } : null; return (
{deal.address} {deal.area_m2.toFixed(1)} м² · {deal.rooms === 0 ? "студия" : `${deal.rooms}-к`} {deal.floor !== null && deal.total_floors !== null ? ` · этаж ${deal.floor}/${deal.total_floors}` : ""}
{label} {tierBadge ? ( {tierBadge.text} ) : null} {fmtRub(deal.price_per_m2)} {fmtRub(deal.price_rub)} {fmtDate(deal.listing_date)} ); }