gendesign/frontend/src/app/analytics/prinzip/page.tsx
lekss361 522ecca5c4 ship #13-16: multi-target Dockerfile, per-object drill-in, CRM pipeline,
442-sweep instructions
#16 Dockerfile: runner (lean, 1.6 GB) + runner-with-chromium (2.9 GB).
   Compose + deploy.yml pushes 2 GHCR images. Backend saves 1.3 GB.
#15 5 backend object endpoints + /analytics/objects/[id] page (gallery,
   POI table, sale chart) + sparkline-driven PrinzipObjectsTable.
#14 prinzip_leads/deals + 2 MVs. Import script with PII-hashing &
   --inspect-only mode. 3 funnel endpoints + Sankey/Monthly charts.
#13 README section with UI/CLI/SQL for 442-object sweep.
2026-04-27 19:32:37 +03:00

176 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState } from "react";
import { InsightCards } from "@/components/analytics/InsightCards";
import { KpiCard } from "@/components/analytics/KpiCard";
import { PrinzipDistrictsBar } from "@/components/analytics/PrinzipDistrictsBar";
import { PrinzipFunnelMonthly } from "@/components/analytics/PrinzipFunnelMonthly";
import { PrinzipFunnelSankey } from "@/components/analytics/PrinzipFunnelSankey";
import { PrinzipGapBar } from "@/components/analytics/PrinzipGapBar";
import { PrinzipObjectsTable } from "@/components/analytics/PrinzipObjectsTable";
import { PrinzipQuartirographyPie } from "@/components/analytics/PrinzipQuartirographyPie";
import { PrinzipVelocityChart } from "@/components/analytics/PrinzipVelocityChart";
import { Section } from "@/components/analytics/Section";
import { useDeveloperDetail } from "@/lib/analytics-api";
const PRINZIP = "6208_0";
const BENCH_OPTIONS: { id: string; name: string }[] = [
{ id: "5791_0", name: "Брусника" },
{ id: "5832_0", name: "Холдинг Форум-групп" },
{ id: "5654_0", name: "КОРТРОС" },
{ id: "8809_0", name: "Атлас Девелопмент" },
{ id: "5904_0", name: "Эталон" },
{ id: "5868_0", name: "Унистрой" },
{ id: "5772_0", name: "Атомстройкомплекс" },
];
export default function PrinzipPage() {
const detail = useDeveloperDetail(PRINZIP);
const [benchmarks, setBenchmarks] = useState<string[]>(["5791_0", "5832_0"]);
const toggle = (id: string) =>
setBenchmarks((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id],
);
const ids = [PRINZIP, ...benchmarks];
const names: Record<string, string> = {
[PRINZIP]: "PRINZIP",
...Object.fromEntries(BENCH_OPTIONS.map((o) => [o.id, o.name])),
};
const d = detail.data;
return (
<>
<div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
<KpiCard
label="Квартир в стройке"
value={d?.agg_flats_total?.toLocaleString("ru") ?? "—"}
hint={d ? `${d.jk_count} ЖК · ${d.jk_ekb} в ЕКБ` : undefined}
/>
<KpiCard
label="Объём Свердл"
value={d?.sverdl_sqm_th?.toFixed(0) ?? "—"}
unit="тыс м²"
hint="DOM.РФ realization, посл. снимок"
/>
<KpiCard
label="Sold %"
value={d?.sverdl_sold_pct?.toFixed(0) ?? "—"}
unit="%"
delta={{
value: "+33 пп за 14 мес — #1 velocity Свердл",
positive: true,
}}
/>
<KpiCard
label="Средний метраж"
value={d?.avg_area_sqm?.toFixed(1) ?? "—"}
unit="м²"
delta={{
value: `vs рынок 49 м² · разрыв -${d?.avg_area_sqm ? Math.round(49 - d.avg_area_sqm) : 0} м²`,
positive: false,
}}
/>
</div>
<Section
title="Velocity: PRINZIP vs benchmark-девелоперы"
subtitle="Ежемесячный sold% по DOM.РФ realization (endpoint=developer, регион 66). PRINZIP толще — для контраста."
right={
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
{BENCH_OPTIONS.map((o) => {
const on = benchmarks.includes(o.id);
return (
<button
key={o.id}
onClick={() => toggle(o.id)}
style={{
padding: "6px 10px",
fontSize: 12,
border: "1px solid #d1d5db",
borderRadius: 16,
background: on ? "#1d4ed8" : "#fff",
color: on ? "#fff" : "#374151",
cursor: "pointer",
}}
>
{o.name}
</button>
);
})}
</div>
}
>
<PrinzipVelocityChart developerIds={ids} developerNames={names} />
</Section>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 16,
marginTop: 16,
}}
>
<Section
title="Квартирография PRINZIP"
subtitle="Распределение по типам квартир в стройке (75% — однушки)."
>
<PrinzipQuartirographyPie />
</Section>
<Section
title="Разрыв с рынком и benchmarks"
subtitle="Ключевые метрики: PRINZIP vs Свердл / Брусника / Форум."
>
<PrinzipGapBar />
</Section>
</div>
<Section
title="География PRINZIP по районам ЕКБ"
subtitle="Серый — все ЖК в районе, синий — PRINZIP. Виден главный пробел: Академический (330 ЖК / 0 PRINZIP)."
>
<PrinzipDistrictsBar />
</Section>
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: 16,
marginTop: 16,
}}
>
<Section
title="Воронка CRM (по source)"
subtitle="Sankey leads → engaged → converted из prinzip_leads. Импорт SQLite — см. data/sql/52_import_prinzip_crm.py."
>
<PrinzipFunnelSankey months={12} />
</Section>
<Section
title="Конверсия по месяцам"
subtitle="Заявки + Engaged + Сделки (бары) + Conversion % (линия)."
>
<PrinzipFunnelMonthly months={24} />
</Section>
</div>
<Section
title="ЖК PRINZIP — таблица с velocity"
subtitle="28 объектов по объёму квартир. Sparkline — динамика реализованных квартир по месяцам (apartments). Клик по «Drill-in» открывает страницу ЖК с галереей и POI."
>
<PrinzipObjectsTable />
</Section>
<Section
title="Инсайты и рекомендации"
subtitle="На основе анализа портфеля + реальных ДДУ-сделок Свердл (PRINZIP_Strategy_Apr27)."
>
<InsightCards />
</Section>
</>
);
}