feat(#263 sub-A): cad_* overlay schemas + cadastre territorial_zones (ПЗЗ) #265
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#265
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/263-sub-a-cad-nspd-overlay-schemas-and-pzz"
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
NSPD unify Вариант B — Sub-PR A (foundation + ПЗЗ writer). Epic: #263.
cad_territorial_zones,cad_red_lines,cad_engineering_structures,cad_risk_zones,cad_opportunity_parcels. Idempotent (IF NOT EXISTS), GIST-индексы.NSPDBulkClient.get_features_in_bbox_grid(async grid-walk) +get_territorial_zones_in_bboxwrapper +harvest_quarterPhase 2.5 +_save_territorial_zonesUPSERT helper.Что НЕ в этом PR
quarter_dump_lookup._get_nspd_zoningчтобы читать изcad_territorial_zones— Sub-PR Dnspd_quarter_dumps/nspd_sync— Sub-PR FResolves (после полного epic)
Test plan
SELECT tablename FROM pg_tables WHERE tablename IN ('cad_territorial_zones','cad_red_lines','cad_engineering_structures','cad_risk_zones','cad_opportunity_parcels');Deep Code Review — verdict: 🔴 BLOCK
Не мержу. Один корректностный блокер + одно SQL-замечание.
🔴 Critical — CRS mismatch (broken at runtime)
backend/app/services/cadastre/bulk_harvest.py:1218+data/sql/101_cad_nspd_overlay_tables.sql:18harvest_quarterPhase 2.5 берётquarter_bbox_3857(db, quarter)(EPSG:3857, метры) → грид-walk возвращает features в EPSG:3857. Дальше_save_territorial_zonesпишет:В колонку
geom GEOGRAPHY(POLYGON, 4326).GeoJSON приходит с координатами вида
6090000.0, 7590000.0(тест-фикстура это подтверждает) →ST_GeogFromGeoJSONлибо упадёт, либо положит "longitude=6090000" вgeography. SAVEPOINT проглотит exception →insertedостанется 0 на всех тайлах. Получится тихий no-op.Сравни с другими апсертами в этом же файле (line 477, 591, 714, 810, …):
Fix: либо переключить колонку на
GEOMETRY(POLYGON, 3857), либо в апсерте:и убрать
ST_GeogFromGeoJSON.🟠 High — UPSERT ON CONFLICT таргет не покрывает no-geom path
zone_idfallbackf"{quarter_cad}_{inserted}"использует счётчикinsertedкоторый инкрементится только при успехе. При повторном запуске на тех же фичах с пустыми id — fallback id будут совпадать друг с другом в рамках одного прогона, но между прогонами счётчик начнётся с 0 заново → ON CONFLICT обновит "не ту" фичу. Не критично пока NSPD стабильно отдаётid, но как fallback ненадёжен. Лучшеf"{quarter_cad}_{hashlib.md5(json.dumps(props, sort_keys=True).encode()).hexdigest()[:12]}".🟡 Medium — naming collision миграции 101
В main уже есть
data/sql/101_user_custom_pois.sql(PR #257). Этот PR добавляет101_cad_nspd_overlay_tables.sql. Функционально OK (deploy.yml:ls | sort, оба идемпотентны, разные таблицы, трекаются по filename в_schema_migrationsнезависимо), но конвенция NN_ требует уникальный номер. Переименовать в102_cad_nspd_overlay_tables.sql— 30 секунд работы.🟡 Medium —
_save_territorial_zones: одиночныйdb.commit()в концеwith db.begin_nested():создаёт savepoint, но если outer transaction не была начата (autocommit-style session) —commit()в конце ничего не сделает / savepoint не сработает. У других апсертов в файле обычно outer tx уже открыт caller'ом. Проверить, чтоharvest_quarterгарантирует transaction context до этого вызова. Если нет — обернутьwith db.begin():.✅ Positive
get_features_in_bbox_gridиспользуетself.wms_feature_infoкоторый через_get_jsonберётself._sem(per-instance, инициализирован в__aenter__). После #260 module-level_SEMAPHOREудалён — здесь не используется. 👍psycopg v3+CAST(:x AS type)соблюдён везде.BEGIN; … COMMIT;обёртка,IF NOT EXISTS, GIST-индексы — sql.md ✓.code/modules/Cad_NSPD_Overlay_Tables.mdесть.Action
После фикса CRS (🔴) + rename миграции в 102 (🟡) + желательно стабильный fallback zone_id (🟠) — повторный re-review, апрув, мерж.
Не мержу.
Все 4 findings зафиксил в коммите
8e37705. Re-review please.ST_GeogFromGeoJSON(...)→ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON(CAST(:geom AS text)), 3857), 4326)::geographyf"{quarter_cad}_{inserted}"→f"{quarter_cad}_{md5(props_sorted)[:12]}"(stable между runs)101_cad_nspd_overlay_tables.sql→102_*(избежать collision с PR #257 migration 101)Files:
bulk_harvest.py(+hashlib import, 2 SQL fixes, comment),test_bulk_harvest_territorial.py(test обновлён на md5 + idempotency check),102_cad_nspd_overlay_tables.sql(rename), vaultCad_NSPD_Overlay_Tables.md(filename + CRS note).Tests: 9/9 pass. Pre-commit hooks: all pass.
Deep Code Review (round 2) — verdict APPROVE
Head SHA
8e377059— все 4 finding'а из первого review закрыты.Что изменилось в followup (+31/-8)
ST_GeogFromGeoJSON(geom)→ 3857 coords в GEOGRAPHY(4326)ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON(...), 3857), 4326)::geographyf"{quarter_cad}_{inserted}"(counter resets)f"{quarter_cad}_{md5(json(props, sort_keys=True))[:12]}"— стабильный между runs101_cad_nspd_overlay_tables.sql(collision с 101_user_custom_pois)102_cad_nspd_overlay_tables.sqldb.begin_nested()без явного outer tx commenttest_feature_without_zone_id_uses_fallback— md5 формат (12 hex) + idempotency check (вызов 2 раза → тот же zone_id)Verified
bulk_harvest.pylines 477/591/714/810 (cad_parcels/cad_buildings/etc) — consistency OKjson.dumps(props, sort_keys=True)→ одинаковый input → одинаковый zone_id →ON CONFLICT (zone_id) DO UPDATEсработает корректно при rerunCASE WHEN CAST(:geom AS text) IS NOT NULLсохраняет no-geom path (test_feature_without_geometry_no_crash проходит)CAST(:geom AS text)psycopg v3 convention сохранёнcode/modules/Cad_NSPD_Overlay_Tables.mdобновлён с CRS pattern + md5 fallback + outer tx noteSmoke test после deploy
Merging via deep-code-reviewer.