ekburg.ru/isogd direct scraper: РНС/ППТ/ГПЗУ для ЕКБ-city #107

Closed
opened 2026-05-12 12:11:44 +00:00 by lekss361 · 3 comments
lekss361 commented 2026-05-12 12:11:44 +00:00 (Migrated from github.com)

Эшелон: Foundation / single source of truth для ЕКБ

Контекст

После полного user-driven теста theme=99 (см. #101 wontfix + #106 + NSPD_API_Map_May12 part 7):

  • ЕКБ как город-миллионник не выгружает данные в federal-каталоги
  • РИАСУРТ Свердл покрывает только окраины (Берёзовский, В.Пышма, Среднеуральск, Арамиль)
  • Минстрой Стройкомплекс.РФ пустой для ЕКБ
  • Из 4531 слоёв theme=99 для ЕКБ работает 1 слой (846615 Функ.зоны)

ЕКБ имеет собственную муниципальную ИСОГД через ekburg.ru/isogd (Минстрой Свердл сайт minstroy.midural.ru) — это single point of truth для ЕКБ-специфичных градостроительных данных.

См. OpenData_Sources_Sverdl_May12 — ekburg.rf permits jackpot (2022-2026 xlsx).

Что есть на ekburg.ru/isogd

  • РНС (разрешения на строительство) — XLSX exports 2022-2026, с координатами / адресами
  • РВЭ (разрешения на ввод в эксплуатацию) — XLSX
  • ППТ (проекты планировки территории) — PDF + shape
  • ГПЗУ (готовые градпланы ЗУ) — PDF + dependency на cad_num
  • Изменения в ПЗЗ — XLSX

Что сделать

1. Scraper module backend/app/services/scrapers/ekburg_isogd.py

class EkburgIsogdClient:
    BASE = "http://minstroy.midural.ru/isogd"

    async def fetch_rns_excel(self, year: int) -> list[Permit]:
        """Скачать XLSX permits за год и распарсить."""

    async def fetch_rve_excel(self, year: int) -> list[Permit]:
        """РВЭ XLSX."""

    async def fetch_ppt_index(self) -> list[PptDoc]:
        """Список PPT documents с PDF-ссылками."""

    async def fetch_gpzu_for_cad(self, cad: str) -> Optional[GpzuDoc]:
        """Найти ГПЗУ по cad — если есть PDF."""

2. Schema migration data/sql/87_ekburg_isogd.sql

CREATE TABLE IF NOT EXISTS ekburg_rns_permits (
  id BIGSERIAL PRIMARY KEY,
  permit_number TEXT NOT NULL,
  issued_date DATE,
  expiry_date DATE,
  developer_name TEXT,
  developer_inn TEXT,
  object_purpose TEXT,
  address TEXT,
  cad_num TEXT,
  floors INT,
  build_area NUMERIC,
  status TEXT,                        -- 'active' | 'expired' | 'used'
  pdf_url TEXT,
  raw_props JSONB,
  geom GEOMETRY(POLYGON, 4326),
  geom_msk66 GEOMETRY(POLYGON, 100000) GENERATED ALWAYS AS (ST_Transform(geom, 100000)) STORED,
  fetched_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(permit_number, issued_date)
);

CREATE TABLE IF NOT EXISTS ekburg_gpzu (
  id BIGSERIAL PRIMARY KEY,
  cad_num TEXT NOT NULL,
  gpzu_number TEXT,
  approval_date DATE,
  max_floors INT,
  max_height NUMERIC,
  max_far NUMERIC,             -- коэффициент использования территории
  min_parking_per_flat NUMERIC,
  permitted_use_codes TEXT[],
  pdf_url TEXT,
  raw_props JSONB,
  fetched_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS ekburg_ppt (
  id BIGSERIAL PRIMARY KEY,
  ppt_number TEXT,
  approval_date DATE,
  area_name TEXT,
  pdf_url TEXT,
  shape_url TEXT,
  raw_props JSONB,
  geom GEOMETRY(POLYGON, 4326),
  fetched_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS gist_rns_geom_msk66 ON ekburg_rns_permits USING GIST(geom_msk66);
CREATE INDEX IF NOT EXISTS rns_cad_idx ON ekburg_rns_permits(cad_num);
CREATE INDEX IF NOT EXISTS gpzu_cad_idx ON ekburg_gpzu(cad_num);

3. Celery tasks

@celery.task
def sync_ekburg_rns_yearly(year: int): ...

@celery.task
def sync_ekburg_gpzu_index(): ...

@celery.task
def sync_ekburg_ppt_index(): ...

Beat schedule:

  • РНС: раз в месяц (новые permits)
  • ГПЗУ: раз в неделю
  • ППТ: раз в месяц

4. analyze_parcel extension

gpzu = await get_gpzu_for_cad(cad)
if gpzu:
    response["gate"]["gpzu"] = {
        "approval_date": gpzu.approval_date,
        "max_floors": gpzu.max_floors,        # G4 #31 закрывается
        "max_height": gpzu.max_height,
        "max_far": gpzu.max_far,
        "min_parking_per_flat": gpzu.min_parking_per_flat,
    }

nearby_rns = await get_recent_rns_in_radius(cad_geom, radius_m=5000, years_back=3)
response["demand"]["nearby_rns"] = nearby_rns      # D4 #36 Project pipeline

Closes / addresses

  • #28 G1 ПЗЗ (через ППТ + изменения в ПЗЗ)
  • #31 G4 max_floors / FAR (через ГПЗУ)
  • #36 D4 Project pipeline 5км (через РНС)
  • 🆕 Foundation для G5 Gate Verdict UI

Acceptance

  • ekburg_rns_permits >300 записей (РНС 2022-2026)
  • ekburg_gpzu >100 ГПЗУ с распознанными max_floors/FAR
  • Test cad 66:41:0204016:10: ГПЗУ найден или маркирован not_found
  • /parcels/{cad}/analyze возвращает ГПЗУ-параметры если есть

Effort

~3-4 дня (scraper + parser + integration)

Owner

backend-engineer (scraper + Celery + integration) + database-expert (schema)

Risks

  • HTML / XLSX структура ekburg.ru может меняться — фиксировать в tests
  • PDF ГПЗУ может потребовать OCR (если scan а не текстовый PDF)
  • Геопривязка РНС по адресу — нужен геокодинг через Nominatim / OSM
**Эшелон:** Foundation / single source of truth для ЕКБ ## Контекст После полного user-driven теста theme=99 (см. #101 wontfix + #106 + [[NSPD_API_Map_May12]] part 7): - **ЕКБ как город-миллионник** не выгружает данные в federal-каталоги - РИАСУРТ Свердл покрывает только окраины (Берёзовский, В.Пышма, Среднеуральск, Арамиль) - Минстрой Стройкомплекс.РФ пустой для ЕКБ - Из 4531 слоёв theme=99 для ЕКБ работает **1** слой (846615 Функ.зоны) **ЕКБ имеет собственную муниципальную ИСОГД** через **ekburg.ru/isogd** (Минстрой Свердл сайт minstroy.midural.ru) — это **single point of truth** для ЕКБ-специфичных градостроительных данных. См. [[OpenData_Sources_Sverdl_May12]] — ekburg.rf permits jackpot (2022-2026 xlsx). ## Что есть на ekburg.ru/isogd - РНС (разрешения на строительство) — XLSX exports 2022-2026, с координатами / адресами - РВЭ (разрешения на ввод в эксплуатацию) — XLSX - ППТ (проекты планировки территории) — PDF + shape - ГПЗУ (готовые градпланы ЗУ) — PDF + dependency на cad_num - Изменения в ПЗЗ — XLSX ## Что сделать ### 1. Scraper module `backend/app/services/scrapers/ekburg_isogd.py` ```python class EkburgIsogdClient: BASE = "http://minstroy.midural.ru/isogd" async def fetch_rns_excel(self, year: int) -> list[Permit]: """Скачать XLSX permits за год и распарсить.""" async def fetch_rve_excel(self, year: int) -> list[Permit]: """РВЭ XLSX.""" async def fetch_ppt_index(self) -> list[PptDoc]: """Список PPT documents с PDF-ссылками.""" async def fetch_gpzu_for_cad(self, cad: str) -> Optional[GpzuDoc]: """Найти ГПЗУ по cad — если есть PDF.""" ``` ### 2. Schema migration `data/sql/87_ekburg_isogd.sql` ```sql CREATE TABLE IF NOT EXISTS ekburg_rns_permits ( id BIGSERIAL PRIMARY KEY, permit_number TEXT NOT NULL, issued_date DATE, expiry_date DATE, developer_name TEXT, developer_inn TEXT, object_purpose TEXT, address TEXT, cad_num TEXT, floors INT, build_area NUMERIC, status TEXT, -- 'active' | 'expired' | 'used' pdf_url TEXT, raw_props JSONB, geom GEOMETRY(POLYGON, 4326), geom_msk66 GEOMETRY(POLYGON, 100000) GENERATED ALWAYS AS (ST_Transform(geom, 100000)) STORED, fetched_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(permit_number, issued_date) ); CREATE TABLE IF NOT EXISTS ekburg_gpzu ( id BIGSERIAL PRIMARY KEY, cad_num TEXT NOT NULL, gpzu_number TEXT, approval_date DATE, max_floors INT, max_height NUMERIC, max_far NUMERIC, -- коэффициент использования территории min_parking_per_flat NUMERIC, permitted_use_codes TEXT[], pdf_url TEXT, raw_props JSONB, fetched_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS ekburg_ppt ( id BIGSERIAL PRIMARY KEY, ppt_number TEXT, approval_date DATE, area_name TEXT, pdf_url TEXT, shape_url TEXT, raw_props JSONB, geom GEOMETRY(POLYGON, 4326), fetched_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS gist_rns_geom_msk66 ON ekburg_rns_permits USING GIST(geom_msk66); CREATE INDEX IF NOT EXISTS rns_cad_idx ON ekburg_rns_permits(cad_num); CREATE INDEX IF NOT EXISTS gpzu_cad_idx ON ekburg_gpzu(cad_num); ``` ### 3. Celery tasks ```python @celery.task def sync_ekburg_rns_yearly(year: int): ... @celery.task def sync_ekburg_gpzu_index(): ... @celery.task def sync_ekburg_ppt_index(): ... ``` Beat schedule: - РНС: раз в месяц (новые permits) - ГПЗУ: раз в неделю - ППТ: раз в месяц ### 4. analyze_parcel extension ```python gpzu = await get_gpzu_for_cad(cad) if gpzu: response["gate"]["gpzu"] = { "approval_date": gpzu.approval_date, "max_floors": gpzu.max_floors, # G4 #31 закрывается "max_height": gpzu.max_height, "max_far": gpzu.max_far, "min_parking_per_flat": gpzu.min_parking_per_flat, } nearby_rns = await get_recent_rns_in_radius(cad_geom, radius_m=5000, years_back=3) response["demand"]["nearby_rns"] = nearby_rns # D4 #36 Project pipeline ``` ## Closes / addresses - ✅ #28 G1 ПЗЗ (через ППТ + изменения в ПЗЗ) - ✅ #31 G4 max_floors / FAR (через ГПЗУ) - ✅ #36 D4 Project pipeline 5км (через РНС) - 🆕 Foundation для G5 Gate Verdict UI ## Acceptance - [ ] `ekburg_rns_permits` >300 записей (РНС 2022-2026) - [ ] `ekburg_gpzu` >100 ГПЗУ с распознанными max_floors/FAR - [ ] Test cad `66:41:0204016:10`: ГПЗУ найден или маркирован not_found - [ ] `/parcels/{cad}/analyze` возвращает ГПЗУ-параметры если есть ## Effort ~3-4 дня (scraper + parser + integration) ## Owner `backend-engineer` (scraper + Celery + integration) + `database-expert` (schema) ## Risks - HTML / XLSX структура ekburg.ru может меняться — фиксировать в tests - PDF ГПЗУ может потребовать OCR (если scan а не текстовый PDF) - Геопривязка РНС по адресу — нужен геокодинг через Nominatim / OSM ## Links - See: [[OpenData_Sources_Sverdl_May12]] Update part 4 (ekburg.rf permits) - See: [[NSPD_API_Map_May12]] Update part 7 (почему ekburg.ru — единственный канал) - Replaces: parts of #101 (closed) - Foundation for: #28, #31, #36
lekss361 commented 2026-05-12 13:10:24 +00:00 (Migrated from github.com)

Update 2026-05-12 — Реальная структура ekburg.rf XLSX (проверено)

Скачал реальный файл rns_2025.xlsx (487 KB) с https://xn--80acgfbsl1azdqr.xn--p1ai/file/6a0a18c9ee327e6e4f76c32a5385a6bd и распарсил через openpyxl. Точная структура:

Sheets

Лист Строк Колонок
«реестр разрешений на строительс» 2650 47
«реестр разрешений на ввод» 1883 41
«Справочник» (27 типов ОКС) 27 1
«Лист1» (пусто)

Колонки РНС (Sheet 1)

# Поле Тип Пример
1 Наименование застройщика TEXT ООО Брусника СЗ
2 ИНН TEXT (10 цифр) 6671382990
3 Адрес застройщика TEXT г.Екатеринбург, ул.Гоголя, стр. 18
4 Тип ОКС TEXT (enum-like) многоквартирные жилые дома
5 Наименование ОКС TEXT 7-секционный МКД с встроенными нежилыми
6 Cad ЗУ TEXT 66:41:0000000:211779
7-8 X, Y МСК-66 NUMERIC 1533089.4397, 387768.6801
9 Адрес ОКС TEXT г.Екатеринбург, по ул.Шаумяна-Фурманова-Московская
10 Номер РНС TEXT 66-41-121-2024 (с изменением от 13.01.2025)
11 Дата выдачи DATE 2024-04-17
12 Дата окончания DATE 2026-04-17
13 Общая площадь NUMERIC (м²) 35986.2
14 Жилая площадь NUMERIC (м²) 19252.66

Колонки РВЭ (Sheet 2 — extends)

Всё то же + дополнительно:
| 15 | Жилая площадь ФАКТ | NUMERIC | 10429.8 (vs 10327.6 проект) |
| 16 | Номер РВЭ | TEXT | 66-41-248-2024 |
| 17 | Дата РВЭ | DATE | 2024-12-26 |
| 18 | Введённые мощности | TEXT | списком |

Volume (2025 год)

  • РНС: ~600-800 уникальных permits (2650 строк с подобъектами/этапами)
  • РВЭ: ~500-600 уникальных
  • × 5 годов (2022-2026) = ~3000-4000 РНС + ~2500-3000 РВЭ total

Особенности парсера

  1. Header на rows 4-6 (R4 = main, R5 = sub-headers для X/Y и номер/дата, R6 = column numbers 1-18)
  2. Multi-row объекты: один РНС часто содержит несколько подобъектов в отдельных строках с пустыми реквизитами застройщика — нужен forward-fill
  3. Номера РНС с изменениями: regex r"(\d+-\d+-\d+-\d+)\s*\(с изменением от ([\d.]+)\)"
  4. Координаты в МСК-66 (SRID 100000) — наш app/core/crs.py:msk66_to_wgs84() готов
  5. Многосемейные cad: иногда cad1 cad2 (через пробелы) — split + iterate

Refined schema

CREATE TABLE ekburg_rns_permits (
  id BIGSERIAL PRIMARY KEY,
  permit_number TEXT NOT NULL,
  base_permit_number TEXT,                  -- "66-41-121-2024" без скобок
  amendment_date DATE,                       -- "13.01.2025" из скобок
  issued_date DATE NOT NULL,
  expiry_date DATE,
  developer_name TEXT NOT NULL,
  developer_inn TEXT,                        -- 10 цифр
  developer_address TEXT,
  object_type TEXT,                          -- enum: 'multi_apartment' | 'industrial' | 'warehouse' | etc
  object_type_raw TEXT,                      -- raw rus текст из справочника
  object_name TEXT,
  object_address TEXT,
  cad_nums TEXT[],                           -- может быть несколько
  total_area_m2 NUMERIC,
  residential_area_m2 NUMERIC,
  is_residential BOOLEAN GENERATED ALWAYS AS (residential_area_m2 > 0) STORED,
  source_year INT,                           -- 2022..2026
  raw_row JSONB,
  geom_msk66 GEOMETRY(POINT, 100000),        -- из X/Y в файле
  geom_wgs84 GEOMETRY(POINT, 4326) GENERATED ALWAYS AS (ST_Transform(geom_msk66, 4326)) STORED,
  fetched_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(permit_number, issued_date)
);

CREATE TABLE ekburg_rve (
  id BIGSERIAL PRIMARY KEY,
  rve_number TEXT NOT NULL,
  rve_date DATE NOT NULL,
  parent_rns_number TEXT,                    -- ref to ekburg_rns_permits.permit_number
  developer_inn TEXT,
  cad_nums TEXT[],
  fact_residential_area_m2 NUMERIC,
  introduced_facilities TEXT,
  raw_row JSONB,
  source_year INT,
  fetched_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(rve_number, rve_date)
);

CREATE TABLE ekburg_object_types_dict (
  type_raw TEXT PRIMARY KEY,                 -- ровно как в Справочнике
  category TEXT,                              -- normalized: 'multi_apartment' | 'ijs' | 'apartments' | etc
  is_residential BOOLEAN
);

CREATE INDEX gist_rns_geom_msk66 ON ekburg_rns_permits USING GIST(geom_msk66);
CREATE INDEX gist_rns_geom_wgs84 ON ekburg_rns_permits USING GIST(geom_wgs84);
CREATE INDEX rns_inn_idx ON ekburg_rns_permits(developer_inn);
CREATE INDEX rns_residential_idx ON ekburg_rns_permits(is_residential) WHERE is_residential;
CREATE INDEX rns_cad_gin ON ekburg_rns_permits USING GIN(cad_nums);

Real sample records (из 2025)

Брусника МКД — большой жилой комплекс:

  • застройщик: ООО Брусника СЗ (ИНН 6671382990)
  • cad: 66:41:0000000:211779
  • площадь: 35986 м² / жилья 19252 м²
  • РНС 17.04.2024 → действует до 17.04.2026
  • адрес: Шаумяна-Фурманова-Московская

Стандарт МКД с РВЭ — введён 2024:

  • застройщик: ООО СЗ Стандарт (ИНН 6670387636)
  • cad: 66:41:0502114:9
  • проект 10327 м² → факт 10429 м² (на 1% больше)
  • РНС 10.10.2022, РВЭ 26.12.2024
  • адрес: ул.Щербакова, 145в

Парковый Квартал паркинг — подземная автостоянка:

  • застройщик: ООО СЗ Парковый Квартал (ИНН 6671127662)
  • cad: 66:41:0402030:786
  • площадь: 3226 м²
  • адрес: ул.Цвиллинга

Updated effort estimate

Так как структура простая (Excel + понятные колонки) — effort снижается до 1.5-2 дней:

  • Day 1: scraper + parser (openpyxl) + schema migration
  • Day 1.5: forward-fill multi-row + geocoding fallback (для пустых X/Y) + tests
  • Day 2: Celery task + Site Finder integration

Linkage с другими таблицами

  • cad_nums → JOIN с cad_parcels_geom для точной геометрии (когда есть)
  • geom_msk66 → JOIN с pzz_zones (когда #28 закроется) — какой зоной разрешено
  • developer_inn → JOIN с domrf_kn_developers для cross-check
  • cad_nums → JOIN с PKK search_by_quarter results (#94) для соседей

Закрывает разом

Issue Как
#36 D4 Project pipeline 5км SELECT * FROM ekburg_rns_permits WHERE ST_DWithin(geom_wgs84, $1, 5000) AND issued_date > NOW() - INTERVAL '3 years'
#46 P2 cad_buildings соседи JOIN ekburg_rve.cad_nums (что введено в эксплуатацию рядом)
#38 D6 obj_class object_type из справочника (27 категорий) → class normalization
#33 D1 mv_quarter_price RNS-density per cad_quarter — signal активности
Generative B1-1 После РВЭ — реальные жилые площади per ОКС → ground truth для unit-mix

См. вверху issue для полного плана + OpenData_Sources_Sverdl_May12 Update part 2.

## Update 2026-05-12 — Реальная структура ekburg.rf XLSX (проверено) Скачал реальный файл `rns_2025.xlsx` (487 KB) с https://xn--80acgfbsl1azdqr.xn--p1ai/file/6a0a18c9ee327e6e4f76c32a5385a6bd и распарсил через openpyxl. Точная структура: ### Sheets | Лист | Строк | Колонок | |---|---:|---:| | «реестр разрешений на строительс» | **2650** | 47 | | «реестр разрешений на ввод» | **1883** | 41 | | «Справочник» (27 типов ОКС) | 27 | 1 | | «Лист1» (пусто) | — | — | ### Колонки РНС (Sheet 1) | # | Поле | Тип | Пример | |---:|---|---|---| | 1 | Наименование застройщика | TEXT | ООО Брусника СЗ | | 2 | **ИНН** | TEXT (10 цифр) | 6671382990 | | 3 | Адрес застройщика | TEXT | г.Екатеринбург, ул.Гоголя, стр. 18 | | 4 | **Тип ОКС** | TEXT (enum-like) | многоквартирные жилые дома | | 5 | Наименование ОКС | TEXT | 7-секционный МКД с встроенными нежилыми | | 6 | **Cad ЗУ** | TEXT | 66:41:0000000:211779 | | 7-8 | **X, Y МСК-66** | NUMERIC | 1533089.4397, 387768.6801 | | 9 | Адрес ОКС | TEXT | г.Екатеринбург, по ул.Шаумяна-Фурманова-Московская | | 10 | Номер РНС | TEXT | 66-41-121-2024 (с изменением от 13.01.2025) | | 11 | Дата выдачи | DATE | 2024-04-17 | | 12 | Дата окончания | DATE | 2026-04-17 | | 13 | **Общая площадь** | NUMERIC (м²) | 35986.2 | | 14 | **Жилая площадь** | NUMERIC (м²) | 19252.66 | ### Колонки РВЭ (Sheet 2 — extends) Всё то же + дополнительно: | 15 | Жилая площадь ФАКТ | NUMERIC | 10429.8 (vs 10327.6 проект) | | 16 | Номер РВЭ | TEXT | 66-41-248-2024 | | 17 | Дата РВЭ | DATE | 2024-12-26 | | 18 | Введённые мощности | TEXT | списком | ### Volume (2025 год) - РНС: ~600-800 уникальных permits (2650 строк с подобъектами/этапами) - РВЭ: ~500-600 уникальных - × 5 годов (2022-2026) = **~3000-4000 РНС + ~2500-3000 РВЭ** total ### Особенности парсера 1. **Header на rows 4-6** (R4 = main, R5 = sub-headers для X/Y и номер/дата, R6 = column numbers 1-18) 2. **Multi-row объекты**: один РНС часто содержит несколько подобъектов в отдельных строках с пустыми реквизитами застройщика — нужен **forward-fill** 3. **Номера РНС с изменениями**: regex `r"(\d+-\d+-\d+-\d+)\s*\(с изменением от ([\d.]+)\)"` 4. **Координаты в МСК-66** (SRID 100000) — наш `app/core/crs.py:msk66_to_wgs84()` готов 5. **Многосемейные cad**: иногда `cad1 cad2` (через пробелы) — split + iterate ### Refined schema ```sql CREATE TABLE ekburg_rns_permits ( id BIGSERIAL PRIMARY KEY, permit_number TEXT NOT NULL, base_permit_number TEXT, -- "66-41-121-2024" без скобок amendment_date DATE, -- "13.01.2025" из скобок issued_date DATE NOT NULL, expiry_date DATE, developer_name TEXT NOT NULL, developer_inn TEXT, -- 10 цифр developer_address TEXT, object_type TEXT, -- enum: 'multi_apartment' | 'industrial' | 'warehouse' | etc object_type_raw TEXT, -- raw rus текст из справочника object_name TEXT, object_address TEXT, cad_nums TEXT[], -- может быть несколько total_area_m2 NUMERIC, residential_area_m2 NUMERIC, is_residential BOOLEAN GENERATED ALWAYS AS (residential_area_m2 > 0) STORED, source_year INT, -- 2022..2026 raw_row JSONB, geom_msk66 GEOMETRY(POINT, 100000), -- из X/Y в файле geom_wgs84 GEOMETRY(POINT, 4326) GENERATED ALWAYS AS (ST_Transform(geom_msk66, 4326)) STORED, fetched_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(permit_number, issued_date) ); CREATE TABLE ekburg_rve ( id BIGSERIAL PRIMARY KEY, rve_number TEXT NOT NULL, rve_date DATE NOT NULL, parent_rns_number TEXT, -- ref to ekburg_rns_permits.permit_number developer_inn TEXT, cad_nums TEXT[], fact_residential_area_m2 NUMERIC, introduced_facilities TEXT, raw_row JSONB, source_year INT, fetched_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(rve_number, rve_date) ); CREATE TABLE ekburg_object_types_dict ( type_raw TEXT PRIMARY KEY, -- ровно как в Справочнике category TEXT, -- normalized: 'multi_apartment' | 'ijs' | 'apartments' | etc is_residential BOOLEAN ); CREATE INDEX gist_rns_geom_msk66 ON ekburg_rns_permits USING GIST(geom_msk66); CREATE INDEX gist_rns_geom_wgs84 ON ekburg_rns_permits USING GIST(geom_wgs84); CREATE INDEX rns_inn_idx ON ekburg_rns_permits(developer_inn); CREATE INDEX rns_residential_idx ON ekburg_rns_permits(is_residential) WHERE is_residential; CREATE INDEX rns_cad_gin ON ekburg_rns_permits USING GIN(cad_nums); ``` ### Real sample records (из 2025) **Брусника МКД** — большой жилой комплекс: - застройщик: ООО Брусника СЗ (ИНН 6671382990) - cad: 66:41:0000000:211779 - площадь: 35986 м² / жилья 19252 м² - РНС 17.04.2024 → действует до 17.04.2026 - адрес: Шаумяна-Фурманова-Московская **Стандарт МКД с РВЭ** — введён 2024: - застройщик: ООО СЗ Стандарт (ИНН 6670387636) - cad: 66:41:0502114:9 - проект 10327 м² → факт 10429 м² (на 1% больше) - РНС 10.10.2022, РВЭ 26.12.2024 - адрес: ул.Щербакова, 145в **Парковый Квартал паркинг** — подземная автостоянка: - застройщик: ООО СЗ Парковый Квартал (ИНН 6671127662) - cad: 66:41:0402030:786 - площадь: 3226 м² - адрес: ул.Цвиллинга ### Updated effort estimate Так как структура простая (Excel + понятные колонки) — **effort снижается до 1.5-2 дней**: - Day 1: scraper + parser (openpyxl) + schema migration - Day 1.5: forward-fill multi-row + geocoding fallback (для пустых X/Y) + tests - Day 2: Celery task + Site Finder integration ### Linkage с другими таблицами - `cad_nums` → JOIN с `cad_parcels_geom` для точной геометрии (когда есть) - `geom_msk66` → JOIN с `pzz_zones` (когда #28 закроется) — какой зоной разрешено - `developer_inn` → JOIN с `domrf_kn_developers` для cross-check - `cad_nums` → JOIN с PKK search_by_quarter results (#94) для соседей ### Закрывает разом | Issue | Как | |---|---| | **#36 D4** Project pipeline 5км | `SELECT * FROM ekburg_rns_permits WHERE ST_DWithin(geom_wgs84, $1, 5000) AND issued_date > NOW() - INTERVAL '3 years'` | | **#46 P2** cad_buildings соседи | JOIN ekburg_rve.cad_nums (что введено в эксплуатацию рядом) | | **#38 D6** obj_class | object_type из справочника (27 категорий) → class normalization | | **#33 D1** mv_quarter_price | RNS-density per cad_quarter — signal активности | | **Generative B1-1** | После РВЭ — реальные жилые площади per ОКС → ground truth для unit-mix | См. вверху issue для полного плана + [[OpenData_Sources_Sverdl_May12]] Update part 2.
Owner

Status update — частично реализовано через PR #211 (feat(permits): ЕКБ РНС/РВЭ xlsx parser + schema + ETL, #105 PR A) и PR #213 (Celery beat monthly + analyze recent_permits, Phase 4+5).

Что сделано:

  • ekburg_rns_permits schema + XLSX parser
  • Celery beat task для monthly refresh
  • analyze integration → recent_permits блок в response

Что НЕ сделано (остаётся в этом issue):

  • ППТ scraper (PDF + shape)
  • ГПЗУ scraper (per-cad) — для закрытия #31 G4 max_floors/FAR
  • Изменения ПЗЗ XLSX

Reformulated scope: ППТ + ГПЗУ остаются. Issue можно держать open, но title уточнить — РНС/РВЭ уже live.

Status update — частично реализовано через **PR #211** (`feat(permits): ЕКБ РНС/РВЭ xlsx parser + schema + ETL`, #105 PR A) и **PR #213** (`Celery beat monthly + analyze recent_permits`, Phase 4+5). **Что сделано:** - `ekburg_rns_permits` schema + XLSX parser - Celery beat task для monthly refresh - analyze integration → `recent_permits` блок в response **Что НЕ сделано (остаётся в этом issue):** - ППТ scraper (PDF + shape) - ГПЗУ scraper (per-cad) — для закрытия #31 G4 max_floors/FAR - Изменения ПЗЗ XLSX Reformulated scope: ППТ + ГПЗУ остаются. Issue можно держать open, но title уточнить — РНС/РВЭ уже live.
Collaborator

Drift/mostly-done (ground-truth 2026-06-14, verified с prod). РВЭ + ППТ done, ГПЗУ blocked-by-source.

#107 создавался при #105/#106; репо ушёл вперёд (#1060/#1067/#1085/#1136), 2 из 3 задач закрыты:

  • РВЭ (разрешения на ввод): ekburg_permits.py парсит лист «реестр разрешений на ввод» → ekburg_construction_permits permit_type='RVE' (living_area_fact_sqm/rve_number/rve_date/geom МСК-66). Beat ekburg-permits-monthly. 1208 строк на prod, wire в analyze (rve_count).
  • ППТ/ПМТ: planning_harvest.py (геопортал WFS ppt/pmt) → planning_projects (1170 строк) + ТЭП (ekb_ppt_tep) → ИРД-блок analyze (granddoc_lookup/ppt_tep_lookup/planning_lookup). Beat planning-harvest-monthly.
  • ГПЗУ: blocked-by-source — verified с prod: екатеринбург.рф ГПЗУ-страница (HTTP 200) = только форма заявления, .xlsx/.pdf/.csv=0; геопортал GetCapabilities 1150 feature-types, НИ ОДНОГО gpzu-слоя; data.екатеринбург.рф 404. Публичного bulk-реестра выданных ГПЗУ в ЕКБ не существует (ГПЗУ выдаётся заявителю по запросу). ГПЗУ-параметры покрыты эквивалентно через zone_regulation_cache (max_far/max_floors/max_height_m из ПЗЗ-регламента зоны urbanCard — ГПЗУ по сути транслирует ПЗЗ-регламент на участок).

Acceptance: РНС>300 → 2851 ✓, ППТ ✓, РВЭ ✓ (1208). ГПЗУ ✗ (нет источника, покрыто zone_regulation). Закрываю: РВЭ/ППТ done, ГПЗУ blocked-by-source (если критичен — ФГИС ЕГИСОГД с авторизацией / ручной OCR, новый scope).

**Drift/mostly-done (ground-truth 2026-06-14, verified с prod). РВЭ + ППТ done, ГПЗУ blocked-by-source.** #107 создавался при #105/#106; репо ушёл вперёд (#1060/#1067/#1085/#1136), 2 из 3 задач закрыты: - **РВЭ** (разрешения на ввод): `ekburg_permits.py` парсит лист «реестр разрешений на ввод» → `ekburg_construction_permits` permit_type='RVE' (living_area_fact_sqm/rve_number/rve_date/geom МСК-66). Beat ekburg-permits-monthly. **1208 строк на prod**, wire в analyze (rve_count). - **ППТ/ПМТ**: `planning_harvest.py` (геопортал WFS ppt/pmt) → `planning_projects` (**1170 строк**) + ТЭП (ekb_ppt_tep) → ИРД-блок analyze (granddoc_lookup/ppt_tep_lookup/planning_lookup). Beat planning-harvest-monthly. - **ГПЗУ**: blocked-by-source — verified с prod: екатеринбург.рф ГПЗУ-страница (HTTP 200) = только форма заявления, .xlsx/.pdf/.csv=0; геопортал GetCapabilities 1150 feature-types, НИ ОДНОГО gpzu-слоя; data.екатеринбург.рф 404. Публичного bulk-реестра выданных ГПЗУ в ЕКБ не существует (ГПЗУ выдаётся заявителю по запросу). **ГПЗУ-параметры покрыты эквивалентно через zone_regulation_cache** (max_far/max_floors/max_height_m из ПЗЗ-регламента зоны urbanCard — ГПЗУ по сути транслирует ПЗЗ-регламент на участок). Acceptance: РНС>300 → 2851 ✓, ППТ ✓, РВЭ ✓ (1208). ГПЗУ ✗ (нет источника, покрыто zone_regulation). Закрываю: РВЭ/ППТ done, ГПЗУ blocked-by-source (если критичен — ФГИС ЕГИСОГД с авторизацией / ручной OCR, новый scope).
Sign in to join this conversation.
No project
No assignees
3 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#107
No description provided.