gendesign/frontend/src/app/analytics/prinzip/page.tsx
Light1YT 86e9ea2937 fix(week-review): автофиксы код-ревью — 169 issue (label «week ревью 1»)
Многоагентный аудит + имплементация: один воркер на файл, точечные правки.
Верификация: py_compile (47/47 .py) + tsc --noEmit (0 ошибок). Unit-тесты
не прогонялись (окружение не поднято: rollup native dep / нет pytest-venv).

Полностью исправлено (169): #1336, #1337, #1339, #1340, #1341, #1342, #1343, #1345, #1346, #1348, #1349, #1350, #1351, #1354, #1356, #1358, #1359, #1360, #1362, #1364, #1365, #1366, #1367, #1368, #1369, #1370, #1371, #1372, #1373, #1374, #1375, #1376, #1377, #1378, #1379, #1380, #1381, #1382, #1384, #1385, #1386, #1387, #1388, #1389, #1390, #1391, #1392, #1394, #1395, #1396, #1397, #1399, #1400, #1401, #1402, #1403, #1404, #1408, #1409, #1410, #1411, #1412, #1413, #1414, #1415, #1416, #1417, #1418, #1420, #1423, #1425, #1426, #1427, #1428, #1429, #1430, #1431, #1432, #1433, #1434, #1435, #1437, #1438, #1439, #1440, #1441, #1442, #1443, #1444, #1445, #1446, #1447, #1448, #1449, #1450, #1451, #1452, #1453, #1454, #1455, #1456, #1457, #1458, #1459, #1460, #1461, #1462, #1463, #1464, #1465, #1466, #1467, #1468, #1469, #1471, #1472, #1473, #1474, #1476, #1478, #1479, #1481, #1482, #1483, #1484, #1485, #1487, #1488, #1489, #1490, #1491, #1492, #1493, #1494, #1495, #1496, #1497, #1499, #1500, #1501, #1502, #1504, #1505, #1506, #1507, #1510, #1514, #1515, #1516, #1517, #1518, #1519, #1521, #1522, #1523, #1524, #1525, #1526, #1527, #1528, #1529, #1531, #1532, #1533, #1534, #1535, #1536, #1537, #1538

Частично (9, in-file часть, остаток cross-file): #1361, #1419, #1422, #1424, #1470, #1475, #1477, #1480, #1498
Требуют cross-file (3, не тронуты): #1338, #1363, #1421
Пропущено (1): #1539

Не входило в партию: 22 needs-Leha issue (нужны решения владельца).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:21:11 +05:00

161 lines
5.7 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 { 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;
const areaGap =
d?.avg_area_sqm != null ? Math.round(49 - d.avg_area_sqm) : null;
const areaDeltaValue =
areaGap == null
? "vs рынок 49 м²"
: `vs рынок 49 м² · разрыв ${
areaGap > 0 ? `-${areaGap}` : areaGap < 0 ? `+${-areaGap}` : "0"
} м²`;
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: areaDeltaValue,
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>
<Section
title="ЖК PRINZIP — таблица с velocity"
subtitle="28 объектов по объёму квартир. Sparkline — динамика реализованных квартир по месяцам (apartments). Клик по «Drill-in» открывает страницу ЖК с галереей и POI."
>
<PrinzipObjectsTable />
</Section>
<Section
title="Инсайты и рекомендации"
subtitle="На основе анализа портфеля + реальных ДДУ-сделок Свердл (PRINZIP_Strategy_Apr27)."
>
<InsightCards />
</Section>
</>
);
}