fix(analyze): 500 on NULL-geometry parcels (random-parcel crash) #1033

Merged
bot-backend merged 1 commit from fix/analyze-null-geom-500 into main 2026-06-03 14:50:13 +00:00

View file

@ -1214,19 +1214,19 @@ def analyze_parcel(
g.geom AS geom_wkb, g.geom AS geom_wkb,
'cad_quarter' AS source 'cad_quarter' AS source
FROM cad_quarters_geom g FROM cad_quarters_geom g
WHERE g.cad_number = :c WHERE g.cad_number = :c AND g.geom IS NOT NULL
UNION ALL UNION ALL
SELECT ST_AsGeoJSON(b.geom) AS geom_geojson, SELECT ST_AsGeoJSON(b.geom) AS geom_geojson,
b.geom AS geom_wkb, b.geom AS geom_wkb,
'cad_building' AS source 'cad_building' AS source
FROM cad_buildings b FROM cad_buildings b
WHERE b.cad_num = :c WHERE b.cad_num = :c AND b.geom IS NOT NULL
UNION ALL UNION ALL
SELECT ST_AsGeoJSON(p.geom) AS geom_geojson, SELECT ST_AsGeoJSON(p.geom) AS geom_geojson,
p.geom AS geom_wkb, p.geom AS geom_wkb,
'cad_parcel' AS source 'cad_parcel' AS source
FROM cad_parcels_geom p FROM cad_parcels_geom p
WHERE p.cad_num = :c WHERE p.cad_num = :c AND p.geom IS NOT NULL
LIMIT 1 LIMIT 1
"""), """),
{"c": cad_num}, {"c": cad_num},
@ -1235,6 +1235,10 @@ def analyze_parcel(
.first() .first()
) )
# NB: geom IS NOT NULL во всех ветках — участок с meta-строкой, но NULL-geometry
# (964 таких в cad_parcels_geom) НЕ должен «найтись» с пустой геометрией (иначе
# ST_Centroid→NULL→float(None)→500). Без geom он попадает в #93 graceful fallback
# ниже (enqueue NSPD fetch → 202), как и участок, которого нет в БД вовсе.
if not row: if not row:
# #93 — graceful fallback: enqueue NSPD fetch, await inline до 15s # #93 — graceful fallback: enqueue NSPD fetch, await inline до 15s
# (см. _INLINE_FETCH_WAIT_S — снижено с 25 для threadpool safety). # (см. _INLINE_FETCH_WAIT_S — снижено с 25 для threadpool safety).
@ -1260,15 +1264,15 @@ def analyze_parcel(
g.geom AS geom_wkb, g.geom AS geom_wkb,
'cad_quarter' AS source 'cad_quarter' AS source
FROM cad_quarters_geom g FROM cad_quarters_geom g
WHERE g.cad_number = :c WHERE g.cad_number = :c AND g.geom IS NOT NULL
UNION ALL UNION ALL
SELECT ST_AsGeoJSON(b.geom), b.geom, 'cad_building' SELECT ST_AsGeoJSON(b.geom), b.geom, 'cad_building'
FROM cad_buildings b FROM cad_buildings b
WHERE b.cad_num = :c WHERE b.cad_num = :c AND b.geom IS NOT NULL
UNION ALL UNION ALL
SELECT ST_AsGeoJSON(p.geom), p.geom, 'cad_parcel' SELECT ST_AsGeoJSON(p.geom), p.geom, 'cad_parcel'
FROM cad_parcels_geom p FROM cad_parcels_geom p
WHERE p.cad_num = :c WHERE p.cad_num = :c AND p.geom IS NOT NULL
LIMIT 1 LIMIT 1
"""), """),
{"c": cad_num}, {"c": cad_num},
@ -1299,11 +1303,14 @@ def analyze_parcel(
text(""" text("""
SELECT ST_AsText(g.geom) AS wkt SELECT ST_AsText(g.geom) AS wkt
FROM ( FROM (
SELECT g.geom FROM cad_quarters_geom g WHERE g.cad_number = :c SELECT g.geom FROM cad_quarters_geom g
WHERE g.cad_number = :c AND g.geom IS NOT NULL
UNION ALL UNION ALL
SELECT b.geom FROM cad_buildings b WHERE b.cad_num = :c SELECT b.geom FROM cad_buildings b
WHERE b.cad_num = :c AND b.geom IS NOT NULL
UNION ALL UNION ALL
SELECT p.geom FROM cad_parcels_geom p WHERE p.cad_num = :c SELECT p.geom FROM cad_parcels_geom p
WHERE p.cad_num = :c AND p.geom IS NOT NULL
) g ) g
LIMIT 1 LIMIT 1
"""), """),
@ -1586,8 +1593,15 @@ def analyze_parcel(
.mappings() .mappings()
.first() .first()
) )
centroid_lat: float = float(centroid_row["lat"]) if centroid_row else 56.838 # Defensive: centroid_row может существовать, но с NULL lat/lon (вырожденная/
centroid_lon: float = float(centroid_row["lon"]) if centroid_row else 60.605 # невалидная геометрия → ST_Centroid NULL). Проверяем именно значения, а не только
# наличие строки — иначе float(None) → 500. Fallback = центр ЕКБ.
centroid_lat: float = (
float(centroid_row["lat"]) if centroid_row and centroid_row["lat"] is not None else 56.838
)
centroid_lon: float = (
float(centroid_row["lon"]) if centroid_row and centroid_row["lon"] is not None else 60.605
)
# 6b) Distance to EKB center + center bonus # 6b) Distance to EKB center + center bonus
dist_to_center_km = _haversine_km(centroid_lat, centroid_lon, EKB_CENTER_LAT, EKB_CENTER_LON) dist_to_center_km = _haversine_km(centroid_lat, centroid_lon, EKB_CENTER_LAT, EKB_CENTER_LON)