/** * FutureSupply — 6.6 «Будущее предложение и конкуренты». Renders * §9.7 forward-looking competitors (report.future_market.future_competitors): * one row per forecast competitor — ЖК / проект · класс · квартир · дистанция · * темп поглощения. Distinct from the market_now competitors block: these are the * top-N projects ranked by `relevance_weight` at the horizon that will compete * for the same demand. * * The prototype's «Срок сдачи» column has NO backing field in the §22 report, so * it is dropped in favour of the real `velocity_per_month` (темп поглощения) — * every value traces to a real forecast field. Rows are sorted by distance asc * (closest competitors first). Nullable fields render «нет данных», never a * fabricated 0. Empty/absent list → honest empty-state, NOT a hidden section. */ import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css"; import type { FutureCompetitor } from "@/types/forecast"; import { fmtInt, fmtNum } from "./scenario-helpers"; interface Props { competitors: FutureCompetitor[] | undefined; } /** Distance in metres → «445 м» / «1.20 км» (mirrors PticaMapInner.formatMeters). */ function formatMeters(m: number): string { if (m >= 1000) return `${(m / 1000).toFixed(2)} км`; return `${Math.round(m)} м`; } export function FutureSupply({ competitors }: Props) { const rows = (competitors ?? []) .slice() .sort((a, b) => a.distance_m - b.distance_m); return ( <>
6.6 · будущее предложение и конкуренты
{rows.length === 0 ? (
Нет данных по конкурирующему предложению
) : ( <> {rows.map((c) => ( ))}
ЖК / проект Класс Квартир Дистанция Поглощение
{c.comm_name ?? "нет данных"} {c.obj_class ?? "нет данных"} {c.flats_total != null ? `${fmtInt(c.flats_total)} кв` : "нет данных"} {formatMeters(c.distance_m)} {`${fmtNum(c.velocity_per_month, 1)} кв/мес`}

Конкурирующие проекты в горизонте прогноза, отранжированные по близости. Поглощение — темп продаж проекта (квартир в месяц).

)}
); }