feat(site-finder): utilities (power/pipeline) + fix Внешние факторы layout
This commit is contained in:
parent
2855a01ca7
commit
6ce634a9b9
3 changed files with 73 additions and 7 deletions
|
|
@ -628,6 +628,60 @@ def analyze_parcel(
|
||||||
logger.warning("hydrology query failed for %s: %s", cad_num, e)
|
logger.warning("hydrology query failed for %s: %s", cad_num, e)
|
||||||
hydrology = None
|
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 месяцев
|
# 10) Market trend — динамика цен ДДУ в радиусе 3 км за 6 vs предыдущие 6 месяцев
|
||||||
market_trend: dict[str, Any] | None = None
|
market_trend: dict[str, Any] | None = None
|
||||||
try:
|
try:
|
||||||
|
|
@ -748,6 +802,7 @@ def analyze_parcel(
|
||||||
"wind": (weather or {}).get("wind") if weather else None, # backward compat
|
"wind": (weather or {}).get("wind") if weather else None, # backward compat
|
||||||
"geology": geology,
|
"geology": geology,
|
||||||
"hydrology": hydrology,
|
"hydrology": hydrology,
|
||||||
|
"utilities": utilities,
|
||||||
"geotech_risk": _geotech_risk(66, db, geom_wkt),
|
"geotech_risk": _geotech_risk(66, db, geom_wkt),
|
||||||
"market_trend": market_trend,
|
"market_trend": market_trend,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,11 @@ _NOISE_QUERIES: list[tuple[str, str, str, str | None]] = [
|
||||||
("waterway", "stream", "water", "stream"),
|
("waterway", "stream", "water", "stream"),
|
||||||
("waterway", "canal", "water", "canal"),
|
("waterway", "canal", "water", "canal"),
|
||||||
("natural", "water", "water", "lake_or_pond"),
|
("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"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -460,12 +460,18 @@ function WeatherBlock({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Source + note */}
|
{/* Source — note shown only on hover */}
|
||||||
<div style={{ fontSize: 11, color: "#9ca3af" }} title={weather.note}>
|
<div
|
||||||
{weather.source} ·{" "}
|
style={{
|
||||||
{weather.note.length > 60
|
fontSize: 11,
|
||||||
? weather.note.slice(0, 60) + "…"
|
color: "#9ca3af",
|
||||||
: weather.note}
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
title={weather.note}
|
||||||
|
>
|
||||||
|
{weather.source} · 7 дн.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -667,7 +673,7 @@ export function ScoreCard({ data }: Props) {
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
gridTemplateColumns: "repeat(3, 1fr)",
|
gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))",
|
||||||
gap: 10,
|
gap: 10,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue