gendesign/frontend/src/components/site-finder/LandTab.tsx
lekss361 f4e74916be feat(site-finder): P1 geometry suitability score (#45)
Backend (parcels.py):
- _polygon_suitability() — Shapely metrics on parcel WGS84 polygon:
  area_ha/m2 (projected via cos(lat) к UTM-like meters), perimeter_m,
  aspect_ratio (long/short side of MABR), convex_hull_ratio (area / hull area),
  min_inscribed_rect_dim_m.
- Composite suitability score 0..1:
  base = area_subscore (0 при <0.2 ha, linear → 1 при ≥0.5 ha)
  −0.3 если aspect_ratio > 5 (вытянутый)
  −0.3 если convex_hull_ratio < 0.65 (изрезанный)
  −0.5 если min_inscribed_rect_dim_m < 30 (узкий)
- Label: микро / подходящий / сложная форма / слабо подходит
- Recommendation: "для МКД 16+ нужно >0.3 ha + минимум 40м"
- В analyze response → geometry_suitability.

Frontend:
- Новый GeometrySuitabilityBlock.tsx с color-coded badge + метрики grid +
  penalties + recommendation
- Добавлен в LandTab (выше "Зонирование (ПЗЗ)")
- TS типы расширены: GeometrySuitability

Closes #45.
2026-05-12 00:51:08 +03:00

131 lines
3.6 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 type { ParcelAnalysis } from "@/types/site-finder";
import { GeologyBlock } from "./GeologyBlock";
import { GeometrySuitabilityBlock } from "./GeometrySuitabilityBlock";
import { GeotechRiskBlock } from "./GeotechRiskBlock";
interface Props {
data: ParcelAnalysis;
}
export function LandTab({ data }: Props) {
const hasAny =
data.geotech_risk !== undefined ||
data.geology !== undefined ||
data.geometry_suitability !== undefined;
return (
<div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
{/* P1 (#45) — Geometry suitability */}
{data.geometry_suitability !== undefined && (
<GeometrySuitabilityBlock data={data.geometry_suitability} />
)}
{/* Zoning note */}
<div
style={{
background: "#f9fafb",
border: "1px solid #e5e7eb",
borderRadius: 10,
padding: "14px 18px",
}}
>
<div
style={{
fontSize: 12,
fontWeight: 600,
color: "#6b7280",
textTransform: "uppercase",
letterSpacing: "0.05em",
marginBottom: 8,
}}
>
Зонирование (ПЗЗ)
</div>
<div style={{ fontSize: 13, color: "#6b7280" }}>
Данные ПЗЗ доступны через{" "}
<a
href="https://pkk.rosreestr.ru/"
target="_blank"
rel="noopener noreferrer"
style={{ color: "#1d4ed8", textDecoration: "none" }}
>
Публичную кадастровую карту
</a>
. API Росреестра закрыт в 2024 используйте deep-link для кад. номера{" "}
<strong style={{ color: "#374151" }}>{data.cad_num}</strong>.
</div>
<a
href={`https://pkk.rosreestr.ru/#/search/${encodeURIComponent(data.cad_num)}`}
target="_blank"
rel="noopener noreferrer"
style={{
display: "inline-block",
marginTop: 10,
padding: "6px 14px",
background: "#1d4ed8",
color: "#fff",
borderRadius: 6,
fontSize: 13,
fontWeight: 500,
textDecoration: "none",
}}
>
Открыть в ПКК
</a>
</div>
{/* Geotech risk */}
{data.geotech_risk !== undefined && (
<div>
<div
style={{
fontSize: 12,
fontWeight: 600,
color: "#6b7280",
textTransform: "uppercase",
letterSpacing: "0.05em",
marginBottom: 12,
}}
>
Геотехнический риск
</div>
<GeotechRiskBlock risk={data.geotech_risk} />
</div>
)}
{/* Geology */}
{data.geology !== undefined && (
<div>
<div
style={{
fontSize: 12,
fontWeight: 600,
color: "#6b7280",
textTransform: "uppercase",
letterSpacing: "0.05em",
marginBottom: 12,
}}
>
Геология
</div>
<GeologyBlock geology={data.geology} />
</div>
)}
{!hasAny && (
<div
style={{
fontSize: 13,
color: "#9ca3af",
padding: "24px 0",
textAlign: "center",
}}
>
Данные о земле и геологии недоступны
</div>
)}
</div>
);
}