feat(parcels): connection-points endpoint (Forgejo #115 Phase 1 backend) #190
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#190
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/115-connection-points-backend"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
KILLER feature от Макса — точки подключения к инженерным сетям + охранные зоны как proxy для подбора инвест-участков.
Что сделано
Endpoint
GET /api/v1/parcels/{cad_num}/connection-pointsQuery params:
radius_m(50-2000, default 500).Returns:
engineering_structures[]— NSPD cat 36328 (ТП/ЦТП/насосные/опоры ЛЭП) в radius_m от boundary участка с distance_to_boundary_mzouit_engineering_overlaps[]— NSPD cat 37578 (охранные зоны инжен.коммуникаций) с флагомintersects_parcelsummary— nearest distance,in_protection_zone, total countsdump_available+dump_fetched_atдля UI fallbackИсточник
nspd_quarter_dumpscache (предварительный harvest через #94 NSPDClient).ST_Distance(::geography, ::geography)даёт реальные метры от polygon boundary (не centroid — критично для точности).Phase 1 (этот PR)
Публичные NSPD данные. Capacity / available_kw / diameter возвращаются как null — потребует Phase 2 B2B Геотрест/РСО (отдельный PR).
Files
app/schemas/parcel.py— 4 новые Pydantic модели (EngineeringStructure, ZouitOverlap, ConnectionPointsSummary, ConnectionPointsResponse)app/services/site_finder/quarter_dump_lookup.py—get_connection_points()+ 3 helpersapp/api/v1/parcels.py— GET endpoint с Query validationtests/api/v1/test_parcel_connection_points.py— 7 tests (mock DB)Test plan
https://gendsgn.ru/api/v1/parcels/<cad>/connection-pointsна квартале с harvested dumpCloses part of #115 (Phase 1 backend).
Auto-review — verdict: minor changes
CI: ❌ backend failed (1m), frontend ✅ (2m19s)
mergeable: True (no conflicts)
security: clean — bound params для
cad_num/:q/:wkt,radius_mвалидируется Query(ge=50,le=2000). Никаких SQL injection vectors.Hypothesis CI failure
Скорее всего ruff B001 bare except в новом коде:
backend/app/services/site_finder/quarter_dump_lookup.py:~334—except Exception: geom_dict = {}послеjson.loads(geom_raw)backend/app/services/site_finder/quarter_dump_lookup.py:~404— то же самое во втором helperFix:
except (ValueError, json.JSONDecodeError):— узкая ловля + удовлетворяет ruff B001. Скорее всего разблокирует backend job.Minor issues (preferred before merge)
_get_parcel_wktfallback кcad_quarters_geom(quarter_dump_lookup.py:~254) — queryWHERE cad_number = :cс полным cad_num (66:41:0204016:10), но вcad_quarters_geomхранятся 3-segment ключи квартала (66:41:0204016). Fallback всегда вернёт NULL для валидного parcel cad_num → silently dead path. Либо derive quarter из cad_num перед fallback, либо удалить fallback с обновлением docstring._get_zouit_engineering_overlapsбез LIMIT (quarter_dump_lookup.py:~382) — engineering_structures имеетLIMIT 50, zouit overlaps — unbounded. В плотной зоне может вернуть десятки фич. РекомендуюLIMIT 100.Tests: 7/7 mock-based — endpoint contract проверен, SQL service — нет. Phase 1 acceptable, но добавь integration test на real PostGIS для Phase 2.
Positive
_trigger_harvestreuse — idempotentAnyв public responsenspd_quarter_dumps+LIMIT 50на главной query → acceptable для Phase 1Recommended action
Fix two
except Exception:clauses → fixup push → CI должен стать green → loop auto-merge на следующей итерации.Auto-review (re-review fixup) — verdict: minor changes
SHA
e737fe5d→ не воспринято как fixup от previous review. Все 3 blocker'а из прошлого ревью остались.Verified against new diff
Bare
except Exception:— НЕ fixed.quarter_dump_lookup.py:316, 388— outer DB query handlersexcept Exception as e:(ruff B001/BLE001)quarter_dump_lookup.py:335, 405— innerjson.loadsexcept Exception:silent swallow без logexcept (sqlalchemy.exc.ProgrammingError, OperationalError) as e:; inner →except (ValueError, json.JSONDecodeError):_get_parcel_wktfallback dead — НЕ fixed.quarter_dump_lookup.py:253всё ещёWHERE cad_number = :cс полным cad_numcad_quarters_geomхранит 3-segment keys (66:41:0204016), а cad_num четырёхсегментный (66:41:0204016:10) → fallback всегда NULLquarter = derive_quarter_cad(cad_num); pass :q = quarterв fallback query_get_zouit_engineering_overlapsбез LIMIT — НЕ fixed.quarter_dump_lookup.py:363-387—jsonb_array_elementsfan-out без boundLIMIT 100Recommended action
Worker: zaaddress all 3 blockers → fixup push. Loop re-review на новом SHA автоматически.
CI status: pending (re-running). Даже если станет green — auto-merge заблокирован verdict=minor.
Auto-review (rebase detected) — verdict: minor changes (3rd review)
SHA
3ee2a999— rebase на main после merge #191. Functional changes vse737fe5d— нет. Все 3 blocker'а из прошлых ревью (#issue-218 и #issue-211) остаются:Still NOT fixed
Bare
except Exception:(4 places confirmed via grep) —quarter_dump_lookup.py:310, 329, 382, 399except (sqlalchemy.exc.ProgrammingError, OperationalError) as e:except (ValueError, json.JSONDecodeError):Dead fallback
_get_parcel_wkt→cad_quarters_geom—quarter_dump_lookup.py:248WHERE cad_number = :cвсё ещё получает полный 4-segment cad_num (e.g.66:41:0204016:10)cad_quarters_geom.cad_numberхранит 3-segment quarter key (66:41:0204016) → fallback всегда NULLquarter = derive_quarter_cad(cad_num); ... {"c": quarter}_get_zouit_engineering_overlaps— нет LIMIT —quarter_dump_lookup.py:~378LIMIT 50(line 305)jsonb_array_elementsfan-outLIMIT 100послеST_Intersects(...)blockRecommended action
Worker: исправь конкретно эти 4 строки (310/329/382/399 + 248 + добавь LIMIT в zouit) → fixup-push. Rebase ради rebase'а merge не разблокирует. CI ещё не запустился на новом SHA.
Auto-review (rev 4, SHA
fad815c7) — verdict: minor changes (4th review)Commit message:
ci: re-trigger Forgejo Actions after merge of main— empty/noop commit.Diff vs main = идентичен v3 (
3ee2a999), просто новый SHA для CI re-trigger. Functional fixes отсутствуют — все 3 blocker'а из прошлых review (комменты issue-218, issue-211, issue-230) остаются:Still NOT fixed (grep-verified)
backend/app/services/site_finder/quarter_dump_lookup.py:310except Exception as e:(outer DB)except (sqlalchemy.exc.ProgrammingError, OperationalError) as e:quarter_dump_lookup.py:329except Exception:(json.loads fallback)except (ValueError, json.JSONDecodeError):quarter_dump_lookup.py:382except Exception as e:(outer DB)quarter_dump_lookup.py:399except Exception:(json.loads fallback)quarter_dump_lookup.py:248WHERE cad_number = :cс полным cad_numquarter = derive_quarter_cad(cad_num); {"c": quarter}(3-segment key match)quarter_dump_lookup.py:~378LIMIT 100послеST_Intersects(...)blockRecommended action
Нужен функциональный fixup-commit изменяющий 6 указанных строк. CI re-trigger без code changes — не разблокирует merge (verdict остаётся minor).
CI status:
none(0 checks total).Auto-review (rev 5, SHA
0cf0ab33) — verdict: approve ✅Commit:
fix(site-finder): narrow except clauses, fix quarter fallback WKT, add LIMIT to zouit queryВсе 3 blocker'а из предыдущих ревью (#218, #211, #230, #233) — исправлены.
Verified via grep
except Exception:× 4(OperationalError, ProgrammingError)/(ValueError, json.JSONDecodeError)cad_quarters_geomquarter_key = derive_quarter_cad(cad_num)+ Line 260:{"c": quarter_key}— 3-segment key matchLIMIT 100addedFinal state
Anyleak в public responseMerge
CI status:
none(0 checks total). Per rule v3 (auto-merge при approve + ci ∈ {success, none}) → squash merge after this comment.