[site-finder][ПТИЦА][data] Оживить КСИТ/высоту/зону в /analyze — crosswalk reg_numb_border → zone_index (+ латентный null zone_code) #1843

Closed
opened 2026-06-20 15:22:32 +00:00 by bot-backend · 1 comment
Collaborator

Чтобы ПТИЦА-cockpit (/site-finder/analysis/[cad]/ptica) показывал РЕАЛЬНЫЕ КСИТ/предельную высоту/зону вместо заглушек («источник НСПД-зонирование»). Сейчас Градостроительство-карточка, drawer «Ёмкость» и 3D КСИТ-цель — placeholder'ы, т.к. регламент не доходит в /analyze. Разбор (read-only прод) показал, что данные ЕСТЬ, но не связаны.

Что нашли

  • Регламент есть: zone_regulation_cache (м.133) — 33 зоны ЕКБ, 14 с max_far (КСИТ), 12 с max_floors, 5 с max_height_m. city='ekb'. Наполняется zone_regulation.py с ЕКБ-геопортала urbanCard, ключ = zone_index (ПЗЗ-индексы: Ж-2, Ц-1, …).
  • Не джойнится с nspd_zoning: nspd_zoning.zone_code из NSPD territorial_zones — это рег-номера границ (66:41-7.2), ДРУГАЯ таксономия. Прямой строковый джойн zone_index = zone_code0 совпадений.
  • Латентный баг (фиксить отдельно/в составе): quarter_dump_lookup._get_zoning() читает ключи props zone_code/reg_numb_border/type_zone/name, которых НЕТ в дампе (реальные: descr/label/subcategory/category/options/...) → nspd_zoning.zone_code = NULL на каждом участке сейчас. То есть и зона-код во фронте всегда пуст.
  • Мост существует, но пуст: ird_overlays.zone_index (задумано в м.132 как JOIN в кэш) — NULL в 100% из 15284 строк, никогда не наполнялся.

Решение — координатный crosswalk (lon/lat → urban_index → cache), НЕ строковый джойн

Route 1 (чисто, без live в hot-path) — рекомендуется:

  1. Backfill ird_overlays.zone_index: для каждого участка/зоны резолвить urban_index через EKBGeoportalClient.zone_index_at(lon,lat) (cache-first, уже есть) → записать. Harvest/backfill-джоба (database-expert SQL + backend-engineer).
  2. Wire _get_zoning(): join ird_overlays(по участку/reg_numb_border) → zone_regulation_cache по zone_index → отдать max_far/max_height_m/max_floors в nspd_zoning.
  3. Заодно починить _get_zoning() чтение реальных ключей props (descr/label/options) → zone_code/zone_name перестанут быть NULL.

Route 2 (меньше работы, но live-вызов на cache-miss): в /analyze звать get_or_fetch_zone_regulation(db, lon, lat) (cache-first by urban_index, live геопортал только на miss; кэш уже покрывает 33 зоны → миссы редки). Минус — потенциальный live-вызов в hot-path.

Эффект

Оживляет связку cockpit'а: Градостроительство (КСИТ/высота/зона) · drawer «Ёмкость» (density = площадь×КСИТ, пятно) · 3D-масса maxFar (КСИТ-цель → реальная). Это backend-часть; фронт уже читает graceful (nspd_zoning.max_far if present, иначе placeholder), так что переключится автоматически когда поле появится.

Связано: ПТИЦА-порт PR #1831–#1835, a11y-polish #1842. Контракт real-vs-placeholder — vault inbox/2026-06-20-1500-ptica-react-port-pr1.md.
Разбор: backend-engineer read-only, прод gendesign-postgres.

Чтобы ПТИЦА-cockpit (`/site-finder/analysis/[cad]/ptica`) показывал РЕАЛЬНЫЕ КСИТ/предельную высоту/зону вместо заглушек («источник НСПД-зонирование»). Сейчас Градостроительство-карточка, drawer «Ёмкость» и 3D КСИТ-цель — placeholder'ы, т.к. регламент не доходит в `/analyze`. Разбор (read-only прод) показал, что данные ЕСТЬ, но не связаны. ## Что нашли - **Регламент есть**: `zone_regulation_cache` (м.133) — 33 зоны ЕКБ, **14 с `max_far` (КСИТ)**, 12 с `max_floors`, 5 с `max_height_m`. city='ekb'. Наполняется `zone_regulation.py` с ЕКБ-геопортала urbanCard, ключ = `zone_index` (ПЗЗ-индексы: Ж-2, Ц-1, …). - **Не джойнится с `nspd_zoning`**: `nspd_zoning.zone_code` из NSPD `territorial_zones` — это рег-номера границ (`66:41-7.2`), ДРУГАЯ таксономия. Прямой строковый джойн `zone_index = zone_code` → **0 совпадений**. - **Латентный баг (фиксить отдельно/в составе)**: `quarter_dump_lookup._get_zoning()` читает ключи props `zone_code/reg_numb_border/type_zone/name`, которых НЕТ в дампе (реальные: `descr/label/subcategory/category/options/...`) → **`nspd_zoning.zone_code` = NULL на каждом участке сейчас**. То есть и зона-код во фронте всегда пуст. - **Мост существует, но пуст**: `ird_overlays.zone_index` (задумано в м.132 как JOIN в кэш) — **NULL в 100%** из 15284 строк, никогда не наполнялся. ## Решение — координатный crosswalk (lon/lat → urban_index → cache), НЕ строковый джойн **Route 1 (чисто, без live в hot-path) — рекомендуется:** 1. Backfill `ird_overlays.zone_index`: для каждого участка/зоны резолвить `urban_index` через `EKBGeoportalClient.zone_index_at(lon,lat)` (cache-first, уже есть) → записать. Harvest/backfill-джоба (database-expert SQL + backend-engineer). 2. Wire `_get_zoning()`: join `ird_overlays`(по участку/`reg_numb_border`) → `zone_regulation_cache` по `zone_index` → отдать `max_far/max_height_m/max_floors` в `nspd_zoning`. 3. Заодно починить `_get_zoning()` чтение реальных ключей props (descr/label/options) → `zone_code/zone_name` перестанут быть NULL. **Route 2 (меньше работы, но live-вызов на cache-miss):** в `/analyze` звать `get_or_fetch_zone_regulation(db, lon, lat)` (cache-first by urban_index, live геопортал только на miss; кэш уже покрывает 33 зоны → миссы редки). Минус — потенциальный live-вызов в hot-path. ## Эффект Оживляет связку cockpit'а: Градостроительство (КСИТ/высота/зона) · drawer «Ёмкость» (density = площадь×КСИТ, пятно) · **3D-масса `maxFar`** (КСИТ-цель → реальная). Это backend-часть; фронт уже читает graceful (`nspd_zoning.max_far` if present, иначе placeholder), так что переключится автоматически когда поле появится. Связано: ПТИЦА-порт PR #1831–#1835, a11y-polish #1842. Контракт real-vs-placeholder — vault `inbox/2026-06-20-1500-ptica-react-port-pr1.md`. _Разбор: backend-engineer read-only, прод gendesign-postgres._
Author
Collaborator

Закрыто — Route 2 (координатный crosswalk) реализован.

КСИТ/высота/зона в /analyze: get_or_fetch_zone_regulation (cache-first + memoized, #1850) wired в parcels.py:2106-2158 — населяет nspd_zoning реальными max_far/max_height_m/max_floors/zone_index из geoportal urbanCard, write-back в nspd_dump_data. Для gap-участков (зоны-разрывы 45-86м) синтезирует из geoportal-результата (#1891). Флаг enable_zoning_regulation_in_analyze=True. Кокпит (Градостроительство/Ёмкость/3D maxFar) читает graceful → ожил автоматически.

Латентный null zone_code (step 3): фикс в commit cbe509f_get_zoning читает реальные props-ключи (descr/label/options.reg_numb_border) вместо фантомных.

Route 1 (ird_overlays.zone_index backfill) — superseded Route 2, не нужен.

✅ **Закрыто — Route 2 (координатный crosswalk) реализован.** КСИТ/высота/зона в /analyze: `get_or_fetch_zone_regulation` (cache-first + memoized, #1850) wired в parcels.py:2106-2158 — населяет `nspd_zoning` реальными max_far/max_height_m/max_floors/zone_index из geoportal urbanCard, write-back в `nspd_dump_data`. Для gap-участков (зоны-разрывы 45-86м) синтезирует из geoportal-результата (#1891). Флаг `enable_zoning_regulation_in_analyze=True`. Кокпит (Градостроительство/Ёмкость/3D maxFar) читает graceful → ожил автоматически. Латентный null `zone_code` (step 3): фикс в commit cbe509f — `_get_zoning` читает реальные props-ключи (descr/label/options.reg_numb_border) вместо фантомных. Route 1 (ird_overlays.zone_index backfill) — superseded Route 2, не нужен.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1843
No description provided.