feat(sf): zone_regulation_cache — ПЗЗ-регламент по zone_index + числовой экстракт (#1067 C8b) #1099

Merged
bot-reviewer merged 1 commit from feat/1067-c8b-zone-regulation-cache into main 2026-06-06 19:31:28 +00:00
Owner

#1067 C8b — кэш ПЗЗ-регламента по индексу зоны + числовой экстракт предельных параметров

Регламент ПЗЗ идентичен для всех участков одной терзоны, а зон на город ~100 → резолвить ВРИ+предельные параметры через ЕКБ-геопортал urbanCard на каждый участок расточительно (reverse-eng API без SLA). Кэшируем один раз на (city, zone_index).

Что добавлено

  • data/sql/133_zone_regulation_cache.sql — таблица zone_regulation_cache: 3 списка ВРИ (540н) + сырой текст предельных параметров (limit_params_raw) + числовой экстракт (params_numeric JSONB) + headline-скаляры для capacity (max_far/max_building_pct/underground_pct/min_parcel_area_m2/max_floors/max_height_m). UNIQUE (city, zone_index), GIST не нужен (нет geom). Паттерн м.125/132 (BEGIN/COMMIT, IF NOT EXISTS, guarded DO для UNIQUE).
  • backend/app/services/site_finder/zone_regulation.py:
    • parse_limit_params — чистый regex-экстрактор предельных параметров из свободного текста otherDescription (без IO; основной тест-таргет).
    • upsert_zone_regulation / get_cached_zone_regulation — запись/чтение кэша (graceful на pre-migration: возвращают None/skip).
    • get_or_fetch_zone_regulation — get-or-fetch: индекс зоны по точке (WFS, дёшево) → если в кэше отдаём без urbanCard, иначе полный резолв + upsert.
  • backend/tests/services/test_zone_regulation_extract.py — 10 тестов на РЕАЛЬНЫХ строках с живого геопортала (probe 2026-06-06: Ц-1, ЦС-1, ЦС-6).

Экстрактор — что ловит (на реальном тексте)

  • минимальная площадь земельного участка - 1500 кв. м → per-ВРИ список [1500, 3000]
  • максимальный процент застройки … – 100% · процент застройки подземного пространства – 100%
  • коэффициент строительного использования … – 4 / – 2,4 (десятичная запятая)
  • предельная этажность … – 5 этажей · тысячи через пробел (5 000 кв.м)
  • игнорит не подлежат установлению (числа нет → ключ отсутствует)
  • headline-скаляры: most-permissive (max FAR/застройка/этажи; min из минимальных площадей)

Валидация

  • pytest tests/services/test_zone_regulation_extract.py10 passed
  • ruff check → clean (line-100)
  • SQL применён на prod-PostGIS в rollback-tx (прод не изменён): BEGIN→CREATE TABLE→COMMENT×4→DO→CREATE INDEX×2→ROLLBACK

Scope

Self-contained: НЕ трогает parcels.py (HOT-файл Антона). Wiring в analyze — отдельный flag-gated PR (#1067 D9b). Связь: ird_overlays.zone_index (м.132) JOIN-ится сюда за полным регламентом.

## #1067 C8b — кэш ПЗЗ-регламента по индексу зоны + числовой экстракт предельных параметров Регламент ПЗЗ идентичен для всех участков одной терзоны, а зон на город ~100 → резолвить ВРИ+предельные параметры через ЕКБ-геопортал urbanCard на каждый участок расточительно (reverse-eng API без SLA). Кэшируем **один раз на (city, zone_index)**. ### Что добавлено - **`data/sql/133_zone_regulation_cache.sql`** — таблица `zone_regulation_cache`: 3 списка ВРИ (540н) + сырой текст предельных параметров (`limit_params_raw`) + числовой экстракт (`params_numeric` JSONB) + headline-скаляры для capacity (`max_far`/`max_building_pct`/`underground_pct`/`min_parcel_area_m2`/`max_floors`/`max_height_m`). UNIQUE `(city, zone_index)`, GIST не нужен (нет geom). Паттерн м.125/132 (BEGIN/COMMIT, IF NOT EXISTS, guarded DO для UNIQUE). - **`backend/app/services/site_finder/zone_regulation.py`**: - `parse_limit_params` — чистый regex-экстрактор предельных параметров из свободного текста `otherDescription` (без IO; основной тест-таргет). - `upsert_zone_regulation` / `get_cached_zone_regulation` — запись/чтение кэша (graceful на pre-migration: возвращают None/skip). - `get_or_fetch_zone_regulation` — get-or-fetch: индекс зоны по точке (WFS, дёшево) → если в кэше отдаём без urbanCard, иначе полный резолв + upsert. - **`backend/tests/services/test_zone_regulation_extract.py`** — 10 тестов на **РЕАЛЬНЫХ** строках с живого геопортала (probe 2026-06-06: Ц-1, ЦС-1, ЦС-6). ### Экстрактор — что ловит (на реальном тексте) - `минимальная площадь земельного участка - 1500 кв. м` → per-ВРИ список `[1500, 3000]` - `максимальный процент застройки … – 100%` · `процент застройки подземного пространства – 100%` - `коэффициент строительного использования … – 4` / `– 2,4` (десятичная **запятая**) - `предельная этажность … – 5 этажей` · тысячи через пробел (`5 000 кв.м`) - **игнорит** `не подлежат установлению` (числа нет → ключ отсутствует) - headline-скаляры: most-permissive (max FAR/застройка/этажи; min из минимальных площадей) ### Валидация - `pytest tests/services/test_zone_regulation_extract.py` → **10 passed** - `ruff check` → clean (line-100) - SQL применён на **prod-PostGIS в rollback-tx** (прод не изменён): BEGIN→CREATE TABLE→COMMENT×4→DO→CREATE INDEX×2→ROLLBACK ✅ ### Scope Self-contained: НЕ трогает `parcels.py` (HOT-файл Антона). Wiring в analyze — отдельный flag-gated PR (#1067 D9b). Связь: `ird_overlays.zone_index` (м.132) JOIN-ится сюда за полным регламентом.
lekss361 added 1 commit 2026-06-06 19:18:45 +00:00
feat(sf): zone_regulation_cache — ПЗЗ-регламент по zone_index + числовой экстракт (#1067 C8b)
All checks were successful
CI / changes (push) Successful in 6s
CI / changes (pull_request) Successful in 6s
CI / backend-tests (push) Successful in 5m51s
CI / backend-tests (pull_request) Successful in 5m51s
4542ecf6c9
lekss361 added the
status/review
label 2026-06-06 19:18:50 +00:00
bot-reviewer reviewed 2026-06-06 19:31:20 +00:00
bot-reviewer left a comment
Collaborator

APPROVE

C8b кэш ПЗЗ-регламента, чисто и self-contained (parcels.py не тронут, прод-вызовов нет — wiring D9b):

  • Номер 133 свободен (main → 132 ird_overlays; нет другого 133). DDL идемпотентна (BEGIN/COMMIT, IF NOT EXISTS ×3, guarded DO для UNIQUE) — паттерн м.132.
  • UPSERT/SELECT-колонки = DDL (16 INSERT-колонок все существуют; ON CONFLICT (city,zone_index) = uq_zone_regulation_cache). _PARAM_SPECS 6 ключей синхронны DDL↔UPSERT↔extractor (DRY, нет key-drift).
  • psycopg v3: JSONB через CAST(:x AS jsonb), скаляры plain :x (NUMERIC/INTEGER, нет ::-trap). max_floors→int, прочие float; min для площади, max для прочих (most-permissive capacity).
  • SAVEPOINT begin_nested + graceful except (Operational/Programming); get_or_fetch commit'ит.
  • regex non-vacuous: false-match guards проверены (общая площадь ≠ площадь ЗУ; «не подлежат» → ключ отсутствует; en/em-dash; nbsp-тысячи; десятичная запятая). 10 тестов на реальных geoportal-строках.

Минор (advisory, cosmetic): get_or_fetch делает db.commit() даже когда upsert вернул None (table unavailable) — harmless (пустой txn-commit), но if upsert(...) is not None: commit() явнее. Не блок.

Merge → deploy-verify. NB: DDL-PR — прод-health недостаточен (миграция до compose-up); подтверждение «таблица создана» за postgres/qa-окном.

<!-- gendesign-review-bot: sha=4542ecf verdict=approve --> ✅ **APPROVE** C8b кэш ПЗЗ-регламента, чисто и self-contained (parcels.py не тронут, прод-вызовов нет — wiring D9b): - **Номер 133 свободен** (main → 132 ird_overlays; нет другого 133). DDL идемпотентна (BEGIN/COMMIT, IF NOT EXISTS ×3, guarded DO для UNIQUE) — паттерн м.132. - **UPSERT/SELECT-колонки = DDL** (16 INSERT-колонок все существуют; ON CONFLICT (city,zone_index) = uq_zone_regulation_cache). `_PARAM_SPECS` 6 ключей синхронны DDL↔UPSERT↔extractor (DRY, нет key-drift). - **psycopg v3**: JSONB через `CAST(:x AS jsonb)`, скаляры plain `:x` (NUMERIC/INTEGER, нет `::`-trap). `max_floors`→int, прочие float; min для площади, max для прочих (most-permissive capacity). - **SAVEPOINT** `begin_nested` + graceful except (Operational/Programming); `get_or_fetch` commit'ит. - **regex non-vacuous**: false-match guards проверены (общая площадь ≠ площадь ЗУ; «не подлежат» → ключ отсутствует; en/em-dash; nbsp-тысячи; десятичная запятая). 10 тестов на реальных geoportal-строках. **Минор (advisory, cosmetic):** `get_or_fetch` делает `db.commit()` даже когда upsert вернул None (table unavailable) — harmless (пустой txn-commit), но `if upsert(...) is not None: commit()` явнее. Не блок. Merge → deploy-verify. NB: DDL-PR — прод-health недостаточен (миграция до compose-up); подтверждение «таблица создана» за postgres/qa-окном.
bot-reviewer merged commit e5b12d579a into main 2026-06-06 19:31:28 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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#1099
No description provided.