fix(analyze): 500 on parcels with NULL geometry (random-parcel crash)
POST /analyze 500'd (TypeError: float(None)) for ~964 cad_parcels_geom rows that have a meta row but geom IS NULL: the resolution query returned the row (no NULL filter), geom_wkt became NULL → ST_Centroid(NULL) → NULL lat/lon → float(centroid_row["lat"]) crashed (the `if centroid_row else` guard checked row existence, not NULL values). A user entering any such "random parcel" hit a 500. Fix: - Add `AND geom IS NOT NULL` to all three geometry-resolution queries (initial UNION, inline-refetch UNION, geom_row WKT subquery) so a NULL-geometry parcel is treated as "no usable geometry" → falls into the #93 graceful fallback (enqueue NSPD on-demand fetch → 202 polling), exactly like a parcel absent from the DB. The fetch then populates real geometry and analyze succeeds. - Defensive: centroid_lat/lon now check the VALUE is not None (not just row existence) before float(), falling back to EKB center. Verified: badformat→400, not-in-DB→202(fetching), valid parcel→200 unaffected. 39 analyze/by-bbox tests green. Refs #944.
This commit is contained in:
parent
e9585bcd3b
commit
cdfa4b3ab4
1 changed files with 25 additions and 11 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue