From b08597ba61df415fbcc54b865ca534b3697edcde Mon Sep 17 00:00:00 2001 From: lekss361 Date: Fri, 15 May 2026 08:26:40 +0300 Subject: [PATCH] =?UTF-8?q?fix(parcels):=20SAVEPOINT=20around=20velocity?= =?UTF-8?q?=20compute=20=E2=86=92=20=D0=BD=D0=B5=20abort=20outer=20tx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Production POST /parcels/{cad}/analyze падает с 500: sqlalchemy.exc.InternalError: (psycopg.errors.InFailedSqlTransaction) current transaction is aborted, commands ignored until end of transaction block ## Root cause `compute_velocity()` SQL fails (probably на cad's без conkurrentов / sparse sale_graph data) → exception caught в try/except → НО db.rollback() отсутствует → transaction остаётся в aborted state. Следующая query (_geotech_risk на line 828) пытается выполниться → крашится с InFailedSqlTransaction. ## Fix Wrap velocity в `with db.begin_nested()` — SAVEPOINT pattern (consistent with PR #124 pzz_loader fix). Failure внутри savepoint: - Rollbacks ТОЛЬКО savepoint - Outer transaction остаётся clean - Subsequent queries (_geotech_risk и пр.) работают Pattern matches feedback_subagent_delegation audit recommendation для in-loop / per-section exception handling. ## Impact POST /parcels/{cad}/analyze больше не 500 при velocity failure. Возвращает `velocity: null` + остальные fields normal. Refs: user report 2026-05-15 InFailedSqlTransaction --- backend/app/api/v1/parcels.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/app/api/v1/parcels.py b/backend/app/api/v1/parcels.py index cfb5d396..8b65121f 100644 --- a/backend/app/api/v1/parcels.py +++ b/backend/app/api/v1/parcels.py @@ -1808,11 +1808,15 @@ def analyze_parcel( pipeline_24mo = _aggregate_pipeline(pipeline_rows) # D2 (#34): velocity-score — темп продаж конкурентов вокруг участка. + # SAVEPOINT защищает outer transaction если velocity SQL падает — + # иначе следующие queries (_geotech_risk и пр.) крашатся + # с InFailedSqlTransaction. velocity_data: dict[str, Any] | None = None try: - v_result = compute_velocity(db, parcel_geom_wkt=geom_wkt, radius_km=3.0) - if v_result is not None: - velocity_data = v_result.as_dict() + with db.begin_nested(): + v_result = compute_velocity(db, parcel_geom_wkt=geom_wkt, radius_km=3.0) + if v_result is not None: + velocity_data = v_result.as_dict() except Exception as _ve: logger.warning("velocity compute failed for %s: %s", cad_num, _ve) -- 2.45.3