+
+
+ Геометрия участка
+
+
+ {data.label} · {scorePct}%
+
+
+
+ {/* Метрики */}
+
+
+
Площадь
+
+ {fmtArea(data.area_ha, data.area_m2)}
+
+
+
+
Периметр
+
+ {data.perimeter_m ?? "—"} м
+
+
+
+
+ Соотношение сторон
+
+
+ {data.aspect_ratio?.toFixed(2) ?? "—"}
+
+
+
+
+ Выпуклость
+
+
+ {data.convex_hull_ratio !== undefined
+ ? `${(data.convex_hull_ratio * 100).toFixed(0)}%`
+ : "—"}
+
+
+
+
+ Мин. ширина
+
+
+ {data.min_inscribed_rect_dim_m ?? "—"} м
+
+
+
+
+ {/* Penalties */}
+ {data.penalties && data.penalties.length > 0 && (
+
+ Проблемы формы: {data.penalties.join(", ")}
+
+ )}
+
+ {/* Recommendation */}
+ {data.recommendation && (
+
+ {data.recommendation}
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/site-finder/LandTab.tsx b/frontend/src/components/site-finder/LandTab.tsx
index 7cd76b64..8176c7f4 100644
--- a/frontend/src/components/site-finder/LandTab.tsx
+++ b/frontend/src/components/site-finder/LandTab.tsx
@@ -2,6 +2,7 @@
import type { ParcelAnalysis } from "@/types/site-finder";
import { GeologyBlock } from "./GeologyBlock";
+import { GeometrySuitabilityBlock } from "./GeometrySuitabilityBlock";
import { GeotechRiskBlock } from "./GeotechRiskBlock";
import { NeighborsBlock } from "./NeighborsBlock";
@@ -13,15 +14,21 @@ export function LandTab({ data }: Props) {
const hasAny =
data.geotech_risk !== undefined ||
data.geology !== undefined ||
+ data.geometry_suitability !== undefined ||
data.neighbors_summary !== undefined;
return (
- {/* P2 (#46) — Соседи + overlap warning */}
+ {/* P2 (#46) — Соседи + overlap warning (hard warn если overlap) */}
{data.neighbors_summary && (
)}
+ {/* P1 (#45) — Geometry suitability */}
+ {data.geometry_suitability !== undefined && (
+
+ )}
+
{/* Zoning note */}
"
+export type GeometrySuitabilityBaseLabel =
+ | "микро"
+ | "подходящий"
+ | "сложная форма"
+ | "слабо подходит";
+
+export interface GeometrySuitability {
+ data_available: boolean;
+ area_ha?: number;
+ area_m2?: number;
+ perimeter_m?: number;
+ aspect_ratio?: number;
+ convex_hull_ratio?: number;
+ min_inscribed_rect_dim_m?: number;
+ suitability_score?: number;
+ // string — допускаем combo-label "микро, узкий"; см. GeometrySuitabilityBaseLabel
+ label?: string;
+ penalties?: string[];
+ recommendation?: string;
+ note: string;
+}
+
export interface SuccessRankingBucket {
bucket: string;
success_score: number;
@@ -218,6 +242,8 @@ export interface ParcelAnalysis {
score_without_center?: number;
location?: ParcelLocation;
success_recommendation?: ParcelSuccessRecommendation | null;
+ // P1 (#45) — physical suitability участка
+ geometry_suitability?: GeometrySuitability;
// P2 (#46) — cad_buildings соседи + overlap check
neighbors_summary?: NeighborsSummary;
// X2 (#48) — confidence indicator