fix(site-finder): per-row SAVEPOINT в noise_loader (#1231)
Loop UPSERT в sync_noise_sources_to_db без begin_nested: один замкнутый 3-точечный natural=water way [A,B,A] даёт POLYGON((A,B,A)), PostGIS отвергает (< 4 точек в ring) → outer tx rollback + raise → весь weekly noise/water/utility sync падает, тот же way отравляет каждый прогон. - Оборачиваем каждый UPSERT в `with db.begin_nested():` + per-row try/except → logger.warning + skipped++ (канон pzz_loader.py:111). - В _way_to_polygon_wkt проверяем итоговое кольцо ≥ 4 точек (fail-safe). - Outer except: добавлен logger.exception для видимости. Closes #1231
This commit is contained in:
parent
285e8f974a
commit
d694e735cd
1 changed files with 49 additions and 26 deletions
|
|
@ -126,6 +126,11 @@ def _way_to_polygon_wkt(nodes: list[dict]) -> str | None:
|
||||||
|
|
||||||
Используется для natural=water (озёра, пруды). Если кольцо не замкнуто
|
Используется для natural=water (озёра, пруды). Если кольцо не замкнуто
|
||||||
автоматически — добавляем первую точку в конец.
|
автоматически — добавляем первую точку в конец.
|
||||||
|
|
||||||
|
PostGIS отвергает linear ring < 4 точек. Замкнутый way из 3 точек
|
||||||
|
[A, B, A] (points[0]==points[-1]) даёт POLYGON((A,B,A)) — "geometry
|
||||||
|
requires more points". Возвращаем None, чтобы caller сделал fallback
|
||||||
|
на LineString или пропустил элемент.
|
||||||
"""
|
"""
|
||||||
points = [f"{n['lon']} {n['lat']}" for n in nodes if "lon" in n and "lat" in n]
|
points = [f"{n['lon']} {n['lat']}" for n in nodes if "lon" in n and "lat" in n]
|
||||||
if len(points) < 3:
|
if len(points) < 3:
|
||||||
|
|
@ -133,6 +138,9 @@ def _way_to_polygon_wkt(nodes: list[dict]) -> str | None:
|
||||||
# Замкнуть кольцо если нужно
|
# Замкнуть кольцо если нужно
|
||||||
if points[0] != points[-1]:
|
if points[0] != points[-1]:
|
||||||
points.append(points[0])
|
points.append(points[0])
|
||||||
|
# Итоговое кольцо должно содержать ≥4 точек (PostGIS требование).
|
||||||
|
if len(points) < 4:
|
||||||
|
return None
|
||||||
return f"POLYGON(({', '.join(points)}))"
|
return f"POLYGON(({', '.join(points)}))"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -203,35 +211,50 @@ def sync_noise_sources_to_db() -> dict[str, int]:
|
||||||
**geom_params,
|
**geom_params,
|
||||||
}
|
}
|
||||||
|
|
||||||
result = db.execute(
|
try:
|
||||||
text(f"""
|
with db.begin_nested(): # SAVEPOINT — откат только этой записи
|
||||||
INSERT INTO osm_noise_sources_ekb
|
result = db.execute(
|
||||||
(osm_type, osm_id, source_type, road_class, name, geom, tags, fetched_at)
|
text(f"""
|
||||||
VALUES (
|
INSERT INTO osm_noise_sources_ekb
|
||||||
:osm_type, :osm_id, :source_type, :road_class, :name,
|
(osm_type, osm_id, source_type, road_class, name, geom, tags,
|
||||||
{geom_sql},
|
fetched_at)
|
||||||
CAST(:tags AS jsonb), NOW()
|
VALUES (
|
||||||
)
|
:osm_type, :osm_id, :source_type, :road_class, :name,
|
||||||
ON CONFLICT (osm_type, osm_id) DO UPDATE
|
{geom_sql},
|
||||||
SET source_type = EXCLUDED.source_type,
|
CAST(:tags AS jsonb), NOW()
|
||||||
road_class = EXCLUDED.road_class,
|
)
|
||||||
name = EXCLUDED.name,
|
ON CONFLICT (osm_type, osm_id) DO UPDATE
|
||||||
geom = EXCLUDED.geom,
|
SET source_type = EXCLUDED.source_type,
|
||||||
tags = EXCLUDED.tags,
|
road_class = EXCLUDED.road_class,
|
||||||
fetched_at = NOW()
|
name = EXCLUDED.name,
|
||||||
RETURNING (xmax = 0) AS is_insert
|
geom = EXCLUDED.geom,
|
||||||
"""),
|
tags = EXCLUDED.tags,
|
||||||
params,
|
fetched_at = NOW()
|
||||||
).scalar()
|
RETURNING (xmax = 0) AS is_insert
|
||||||
|
"""),
|
||||||
if result:
|
params,
|
||||||
inserted += 1
|
).scalar()
|
||||||
else:
|
if result:
|
||||||
updated += 1
|
inserted += 1
|
||||||
|
else:
|
||||||
|
updated += 1
|
||||||
|
except Exception as e:
|
||||||
|
# Дефектный way (например, 3-точечное замкнутое кольцо POLYGON((A,B,A))
|
||||||
|
# отвергается PostGIS) не должен валить весь weekly-sync. SAVEPOINT
|
||||||
|
# откатывает только эту строку, продолжаем с остальными.
|
||||||
|
logger.warning(
|
||||||
|
"noise_sync insert failed for %s/%s (source_type=%s): %s",
|
||||||
|
osm_type,
|
||||||
|
osm_id,
|
||||||
|
source_type,
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
skipped += 1
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception:
|
except Exception as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
|
logger.exception("noise_sync: unexpected error, outer tx rolled back: %s", e)
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue