diff --git a/backend/app/services/site_finder/pzz_loader.py b/backend/app/services/site_finder/pzz_loader.py index 2274a53e..7d3c3a12 100644 --- a/backend/app/services/site_finder/pzz_loader.py +++ b/backend/app/services/site_finder/pzz_loader.py @@ -80,6 +80,10 @@ def sync_pzz_zones_to_db() -> dict[str, int]: """Загрузить зоны из PKK6 и UPSERT в pzz_zones_ekb. Возвращает словарь {fetched, inserted, updated, skipped}. + + Каждый UPSERT обёрнут в SAVEPOINT (db.begin_nested), чтобы ошибка + одной записи не откатывала всю транзакцию и счётчики оставались + согласованными с тем, что реально записано в DB. """ features = asyncio.run(fetch_pkk6_zones()) inserted = 0 @@ -105,41 +109,45 @@ def sync_pzz_zones_to_db() -> dict[str, int]: ) description = props.get("description") try: - result = db.execute( - text(""" - INSERT INTO pzz_zones_ekb - (rosreestr_id, zone_code, zone_name, description, raw_props, geom) - VALUES ( - :rid, :code, :nm, :desc, CAST(:props AS jsonb), - ST_Multi(ST_SetSRID(ST_GeomFromGeoJSON(:g), 4326)) - ) - ON CONFLICT (rosreestr_id) DO UPDATE - SET zone_code = EXCLUDED.zone_code, - zone_name = EXCLUDED.zone_name, - description = EXCLUDED.description, - raw_props = EXCLUDED.raw_props, - geom = EXCLUDED.geom, - fetched_at = NOW() - RETURNING (xmax = 0) AS is_insert - """), - { - "rid": rosreestr_id, - "code": zone_code, - "nm": zone_name, - "desc": description, - "props": json.dumps(props, ensure_ascii=False), - "g": json.dumps(geom), - }, - ).scalar() + with db.begin_nested(): # SAVEPOINT — откат только этой записи + result = db.execute( + text(""" + INSERT INTO pzz_zones_ekb + (rosreestr_id, zone_code, zone_name, description, raw_props, geom) + VALUES ( + :rid, :code, :nm, :desc, CAST(:props AS jsonb), + ST_Multi(ST_SetSRID(ST_GeomFromGeoJSON(:g), 4326)) + ) + ON CONFLICT (rosreestr_id) DO UPDATE + SET zone_code = EXCLUDED.zone_code, + zone_name = EXCLUDED.zone_name, + description = EXCLUDED.description, + raw_props = EXCLUDED.raw_props, + geom = EXCLUDED.geom, + fetched_at = NOW() + RETURNING (xmax = 0) AS is_insert + """), + { + "rid": rosreestr_id, + "code": zone_code, + "nm": zone_name, + "desc": description, + "props": json.dumps(props, ensure_ascii=False), + "g": json.dumps(geom), + }, + ).scalar() if result: inserted += 1 else: updated += 1 except Exception as e: logger.warning("PZZ insert failed for rosreestr_id=%s: %s", rosreestr_id, e) - db.rollback() skipped += 1 db.commit() + except Exception as e: + db.rollback() + logger.exception("pzz_loader: unexpected error, outer tx rolled back: %s", e) + raise finally: db.close() return {