feat(site-finder): utilities (power/pipeline) + fix Внешние факторы layout

This commit is contained in:
lekss361 2026-05-11 21:38:20 +03:00
parent 2855a01ca7
commit 6ce634a9b9
3 changed files with 73 additions and 7 deletions

View file

@ -628,6 +628,60 @@ def analyze_parcel(
logger.warning("hydrology query failed for %s: %s", cad_num, e)
hydrology = None
# 9d) Utilities — power lines + pipelines из OSM (магистральные сети)
utilities: dict[str, Any] | None = None
try:
util_rows = (
db.execute(
text("""
SELECT road_class, name,
ST_Distance(
n.geom::geography,
ST_Centroid(ST_GeomFromText(:wkt, 4326))::geography
) AS distance_m
FROM osm_noise_sources_ekb n
WHERE source_type = 'utility'
AND ST_DWithin(
n.geom::geography,
ST_Centroid(ST_GeomFromText(:wkt, 4326))::geography,
2000
)
ORDER BY distance_m ASC
LIMIT 10
"""),
{"wkt": geom_wkt},
)
.mappings()
.all()
)
# Группировка по типу для compactness
by_subtype: dict[str, dict[str, Any]] = {}
for r in util_rows:
sub = r["road_class"] or "other"
if sub not in by_subtype:
by_subtype[sub] = {
"subtype": sub,
"nearest_m": round(float(r["distance_m"])),
"name": r["name"],
"count_within_2km": 0,
}
by_subtype[sub]["count_within_2km"] += 1
utilities = {
"summary": list(by_subtype.values()),
"power_line_охранная_зона_flag": any(
float(r["distance_m"]) < 25 and r["road_class"] == "power_line" for r in util_rows
),
"note": (
"Охранная зона ЛЭП ≥35 кВ — 15-40м по обе стороны (СП 36.13330). "
"В зоне ОЗ нельзя строить капитальные объекты. "
"Точная классификация напряжения / магистральности — ЗОУИТ "
"5 (ОЗ ЛЭП) и 9 (ОЗ газопровода) через ФГИС ТП."
),
}
except Exception as e:
logger.warning("utilities query failed for %s: %s", cad_num, e)
utilities = None
# 10) Market trend — динамика цен ДДУ в радиусе 3 км за 6 vs предыдущие 6 месяцев
market_trend: dict[str, Any] | None = None
try:
@ -748,6 +802,7 @@ def analyze_parcel(
"wind": (weather or {}).get("wind") if weather else None, # backward compat
"geology": geology,
"hydrology": hydrology,
"utilities": utilities,
"geotech_risk": _geotech_risk(66, db, geom_wkt),
"market_trend": market_trend,
}

View file

@ -40,6 +40,11 @@ _NOISE_QUERIES: list[tuple[str, str, str, str | None]] = [
("waterway", "stream", "water", "stream"),
("waterway", "canal", "water", "canal"),
("natural", "water", "water", "lake_or_pond"),
# Инженерные сети — магистральные линии (для proximity-проверки доступности
# подключения / распознавания ЛЭП-охранных зон через ЗОУИТ типа 5/9)
("power", "line", "utility", "power_line"),
("power", "minor_line", "utility", "power_line"),
("man_made", "pipeline", "utility", "pipeline"),
]

View file

@ -460,12 +460,18 @@ function WeatherBlock({
</div>
</div>
{/* Source + note */}
<div style={{ fontSize: 11, color: "#9ca3af" }} title={weather.note}>
{weather.source} ·{" "}
{weather.note.length > 60
? weather.note.slice(0, 60) + "…"
: weather.note}
{/* Source — note shown only on hover */}
<div
style={{
fontSize: 11,
color: "#9ca3af",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
title={weather.note}
>
{weather.source} · 7 дн.
</div>
</div>
);
@ -667,7 +673,7 @@ export function ScoreCard({ data }: Props) {
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
gap: 10,
}}
>