From e8d086399630d1eea56b7a28792f4dc5e1d94f73 Mon Sep 17 00:00:00 2001
From: Light1YT
Date: Sun, 21 Jun 2026 11:34:11 +0500
Subject: [PATCH] =?UTF-8?q?feat(ptica):=20close=20tab=20prototype-parity?=
=?UTF-8?q?=20gaps=20(=D0=A1=D1=86=D0=B5=D0=BD=D0=B0=D1=80=D0=B8=D0=B8=206?=
=?UTF-8?q?.6=20+=20=D0=9E=D1=82=D1=87=D1=91=D1=82=D1=8B=20=D0=9F=D0=B0?=
=?UTF-8?q?=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D1=8B)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Сценарии: add section 6.6 «будущее предложение и конкуренты» (FutureSupply)
rendering report.future_market.future_competitors after ScoringTransparency
(order 6.1→6.6). Columns ЖК/Класс/Квартир/Дистанция/Поглощение, real data,
honest empty-state. Reuses .dtable.
- Отчёты: add «Параметры» panel (карта/финмодель/прогноз §22); §22 row tied to
the real forecast-ready flag. Reuses StatusRow.
Verified on the all-tabs harness; no console errors. tokens-only, no fabrication.
---
.../ptica/reports/PticaReports.tsx | 14 ++++
.../ptica/scenarios/FutureSupply.tsx | 84 +++++++++++++++++++
.../ptica/scenarios/PticaScenarios.tsx | 3 +
3 files changed, 101 insertions(+)
create mode 100644 frontend/src/components/site-finder/ptica/scenarios/FutureSupply.tsx
diff --git a/frontend/src/components/site-finder/ptica/reports/PticaReports.tsx b/frontend/src/components/site-finder/ptica/reports/PticaReports.tsx
index b0c5e76d..0dbce1a8 100644
--- a/frontend/src/components/site-finder/ptica/reports/PticaReports.tsx
+++ b/frontend/src/components/site-finder/ptica/reports/PticaReports.tsx
@@ -31,6 +31,7 @@ import { API_BASE_URL } from "@/lib/api";
import { triggerDownload } from "@/lib/download";
import { useParcelForecastQuery } from "@/lib/site-finder-api";
import type { ParcelAnalysis } from "@/types/site-finder";
+import { StatusRow } from "@/components/site-finder/ptica/drawers/DrawerPrimitives";
import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css";
interface Props {
@@ -345,6 +346,19 @@ export function PticaReports({ cad, analysis }: Props) {
{error}
)}
+
+ {/* ── Параметры — что войдёт в сгенерированный отчёт ─────────────── */}
+
+
+ Параметры OPTIONS
+
+
+
+
{/* ── Report-section checklist ──────────────────────────────────── */}
diff --git a/frontend/src/components/site-finder/ptica/scenarios/FutureSupply.tsx b/frontend/src/components/site-finder/ptica/scenarios/FutureSupply.tsx
new file mode 100644
index 00000000..26cea25c
--- /dev/null
+++ b/frontend/src/components/site-finder/ptica/scenarios/FutureSupply.tsx
@@ -0,0 +1,84 @@
+/**
+ * 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)} кв/мес`}
+ |
+
+ ))}
+
+
+
+ Конкурирующие проекты в горизонте прогноза, отранжированные по
+ близости. Поглощение — темп продаж проекта (квартир в месяц).
+
+ >
+ )}
+
+ >
+ );
+}
diff --git a/frontend/src/components/site-finder/ptica/scenarios/PticaScenarios.tsx b/frontend/src/components/site-finder/ptica/scenarios/PticaScenarios.tsx
index 4a3d1c9e..4bd66a62 100644
--- a/frontend/src/components/site-finder/ptica/scenarios/PticaScenarios.tsx
+++ b/frontend/src/components/site-finder/ptica/scenarios/PticaScenarios.tsx
@@ -24,6 +24,7 @@ import { ScenarioCompareTable } from "./ScenarioCompareTable";
import { ConfidencePanel } from "./ConfidencePanel";
import { RecommendedProduct } from "./RecommendedProduct";
import { ScoringTransparency } from "./ScoringTransparency";
+import { FutureSupply } from "./FutureSupply";
interface Props {
cad: string;
@@ -148,6 +149,8 @@ export function PticaScenarios({ cad, horizon, onHorizonChange }: Props) {
{report.scoring && }
+
+
);
}
--
2.45.3