diff --git a/.gitignore b/.gitignore index 651896c9..f8d0bfc2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ node_modules/ .env .env.local .env.*.local +.mcp.json # IDE .vscode/ diff --git a/README.md b/README.md index d4e9f767..4539bb8d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Acceptance criteria разбиты на 3 блока (функционально --- -## Текущий статус (26 апреля 2026) +## Текущий статус (27 апреля 2026) **Инфраструктура: live на HTTPS.** Прод доступен по [https://gendsgn.ru/](https://gendsgn.ru/), Let's Encrypt cert получен. Полный цикл автодеплоя из GitHub Actions работает. 26.04 прод полностью пересоздан (`down -v` + redeploy) — БД свежая, backend в multi-stage образе под non-root пользователем, healthcheck-и работают, ловит ошибки в Sentry. @@ -46,6 +46,251 @@ Acceptance criteria разбиты на 3 блока (функционально 2. **ДОМ.РФ фронт-API** (`DataSources_Analysis_Apr26` + 12 вкладок) — открытые JSON без auth (либо `Basic MTpqd2U=` хардкоден), per-flat прайс-лист, реализация/готовность как velocity. 3. **НСПД MarketAnalytics** (`NSPD_MarketAnalytics_FullArchitecture_Apr26`) — индикаторы цен по кадастровым кварталам, 8 лет longitudinal, GeoJSON-полигоны для heatmap. Архитектура полностью раскрыта в debug-браузере (saveUserFilters → graphEgrn*/tableEgrn/geomDataForHeatMap, серверная сессия фильтров через userFiltersId). ⚠ `graphEgrnCandleMedian`/`LineMedian` (абсолютные ₽/м²) дают 403 для physic-аккаунта — но эти данные мы уже получаем из CSV-датасета Росреестра. +**27.04 — analytical layer полностью построен.** В прод-БД 43 `domrf_*/cbr_*/yandex_*/ekb_*/sv_*/rosreestr_*` таблицы (см. блок «Аналитический пласт данных» ниже). За день: +- Загружено 5 lazy-load страниц ДОМ.РФ (`DomRF_Lazy_Loaded_Apr27_2026`) — `domrf_realization` 9.7K + stat_series XLSX 809K observations + 333 raw JSONB endpoints. +- Догружена история 14 месяцев (Янв 2025 - Февр 2026) для realization + sold_ready через `repYear/repMonth` параметры (`DomRF_History_Loaded_Apr27_2026`) — `domrf_realization` итого **143 963** рядов, sold_ready_breakdown расширен до 7 chart-types с типизированными колонками. +- Импортирован SQLite Антона (23 МБ) — 13 новых таблиц: `domrf_kn_flats` (27 563 поквартирно), `domrf_kn_objects` (1 516 ЖК), `cbr_mortgage_series` (3 308 ЦБ), `sber_ddf_metrics`, `yandex_realty_zk`, `ekb_districts`, `rosreestr_rent_deals`. Архив девелоперов смерджен (2 734 → 8 629). См. `Anton_Imported_Apr27_2026`. +- Подключён MCP `crystaldba/postgres-mcp` (`.mcp.json`) — все DDL/SELECT теперь через `mcp__postgres__execute_sql` без `docker run psql` обёртки. +- FK-аудит и cleanup: добавлены 4 missing FK, дропнуты 2 дублирующих индекса и 2 устаревшие таблицы (`sold_out_by_year`, `sold_out_by_progress` ⊂ `domrf_realization`), 8 `COMMENT ON TABLE` для документации источников. +- Territory aliases для stat_series ↔ regions name mapping (`v_stat_series_resolved` view): 100% покрытие 809K observations через 101 alias. +- Готовы 2 стратегических отчёта: `Sverdl_Quartirography_Report_Apr27` (общий по Свердл) + `PRINZIP_Strategy_Apr27` (что строить и куда смотреть). + +--- + +## Аналитический пласт данных (data warehouse) — 27.04.2026 + +В прод-БД `gendesign` живёт **43 предметных таблицы** + 5 helper views + Rosreestr partitions. Размер БД ~2.1 ГБ. + +### 🔌 Подключение + +| Метод | Команда | +|---|---| +| **MCP** (рекомендуется) | конфиг в `.mcp.json` (project root) — `mcp__postgres__execute_sql`, `analyze_db_health`, `list_objects`, `explain_query` и т.д. | +| **psql via SSH-туннель** | `ssh -fN -L 15432:127.0.0.1:5432 gendesign` затем `PGPASSWORD=… psql -h 127.0.0.1 -p 15432 -U gendesign gendesign` | +| **DBeaver / TablePlus** | localhost:15432 после открытия SSH-туннеля | + +⚠️ Туннель уже открыт на машинах команды (gendesign@46.173.16.127). `.mcp.json` в `.gitignore` (содержит пароль). + +### 📊 Карта таблиц (43 предметных) + +**ROSREESTR per-deal (1.1 ГБ)** +| Таблица | Рядов | Описание | +|---|---:|---| +| `rosreestr_deals` (+ 7 партиций по кварталам 2024Q3-2026Q1) | 4 975 398 | Сырые сделки ДКП+ДДУ из ФГИС ЕГРН, 89 регионов | +| `rosreestr_deals_staging` | UNLOGGED | буфер для `\copy` из CSV | +| `rosreestr_rent_deals` | 1 071 | Договоры аренды Свердл Q1 2026 (Антон) | + +**ROSREESTR materialized views (305 МБ)** +| MV | Рядов | Описание | +|---|---:|---| +| `rr_agg_cad_quarter` | 952 799 | (region, cad_quarter, period, doc_type, area_bucket) → p10/median/p90 ₽/м² | +| `rr_agg_settlement` | 422 852 | (region, settlement, period, doc_type, realestate_type) | +| `rr_agg_region` | 3 549 | percentile-агрегаты по регионам | + +**DOM.РФ справочники / dimensions** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_regions` | 90 | PK region_id (0=РФ + 89 субъектов). FK target для 4 таблиц | +| `domrf_developers` | **8 629** | реестр + архив (после merge с Антоном). PK developer_id ('6208_0'=PRINZIP) | +| `domrf_developers_full` | 2 563 | XLSX-выгрузка с метриками area/permits/flats/share | +| `domrf_snapshots` | 18 | snapshot_date dimension (4 базовых + 14 history Янв 2025-Февр 2026) | +| `domrf_federal_districts` | 8 | 8 ФО как first-class lookup | +| `domrf_territory_aliases` | 101 | text→(level, region_id\|fo_id) для нормализации имён | +| `domrf_stat_series_indicators` | 123 | XLSX sheet codes → indicator_name | + +**DOM.РФ realization & sold_ready (history)** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_realization` | **143 963** | 4 endpoint_types (total/housing/ready_year/developer) × 89 регионов × 14 monthly snapshots | +| `domrf_sold_ready_index` | 15 | РФ-итог helio метрики per snapshot | +| `domrf_sold_ready_breakdown` | 40 044 | 7 chart_types × dim_value × 14 snapshots (regionChart/devChart/foChart/cityPopulationChart/classChart/devValueChart/readyYearChart) с типизированными колонками | +| `domrf_sold_ready_dynamics` | 3 450 | timeseries 2020-period × 4 chart_types | + +**DOM.РФ JSON dashboard** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_region_aggregates` | 439 | (region, room_count_type) → flat_count/area/percent | +| `domrf_developer_aggregates` | 11 063 | (developer × region × room_count) | +| `domrf_flat_area_distribution` | 40 | РФ-уровень бакеты площади | + +**DOM.РФ XLSX/PDF снапшоты** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_developers_full` | 2 563 | реестр девелоперов из XLSX | +| `domrf_escrow_banks` | 28 | банки эскроу (Сбер 61%) | +| `domrf_guaranty_regions` | 99 | РФ + 8 ФО + 89 регионов разбивка | +| `domrf_housing_summary` | 58 | разбивка по механизмам | +| `domrf_planned_commissioning` | 10 | план ввода 2026-2035 | +| `domrf_sold_out` | 9 | РФ + 8 топ-регионов (PDF, ради unique `attracted_funds_mln_rub`) | +| `domrf_mortgage_rates` | 20 | ставки топ-20 банков (PDF) | + +**DOM.РФ launch / commissioning** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_launch_top` | 1 410 | топ-N developer/region/area/fo по запускам/вводу | +| `domrf_launch_obj_class` | 4 | Типовой/Комфорт/Бизнес/Элитный | +| `domrf_launch_monthly` | 75 | timeseries 2020-2026 | +| `domrf_commissioning` | 1 | факт ввод (multifamily/private) | +| `domrf_project_finance` | 99 | ПР финансирование РФ/ФО/регионы | + +**DOM.РФ stat_series (parsed XLSX 254 МБ)** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_stat_series_observations` | **809 236** | long-form panel: 99 территорий × 123 индикатора × ~70 месяцев (2020-2026) | +| `domrf_xlsx_files` | 9 | BYTEA-архив исходных XLSX (8 МБ) | + +**DOM.РФ ipoteka / mortgage** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_mortgage_dashboard` | 1 | агрегат рынка ипотеки РФ | +| `domrf_mortgage_details` | 2 | RUB/OTH валюты | + +**DOM.РФ /сервисы/api/kn/* (от Антона)** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_kn_flats` | **27 563** | поквартирный прайс РФ free/booked/sold с ценами | +| `domrf_kn_objects` | 1 516 | реестр ЖК Свердл (1285 ЕКБ + 28 PRINZIP) с GPS, классом, эскроу | +| `domrf_dev_growth` | 139 | динамика 2024→2025 девелоперов | + +**ЦБ РФ ипотека (от Антона)** +| Таблица | Рядов | Описание | +|---|---:|---| +| `cbr_mortgage_series` | 3 308 | 47 серий × {rf,sverdl} × Jan 2019 - Feb 2026 | +| `cbr_mortgage_files` | 19 | XLSX-источники | + +**Внешние источники** +| Таблица | Рядов | Описание | +|---|---:|---| +| `sber_ddf_metrics` | 20 | Сбер ДомКлик DDF nov2025: affordability, demand index, миграция, повторные ипотечники, price gap | +| `yandex_realty_zk` | 12 | Yandex Realty ЖК ЕКБ snapshot | +| `yandex_realty_class_prices` | 3 | разбивка по классам econom/comfort/elite | + +**Geo Свердл/ЕКБ** +| Таблица | Рядов | Описание | +|---|---:|---| +| `ekb_districts` | 9 | 9 районов ЕКБ с zk_count/flat_count/area | +| `ekb_metro` | 10 | станции метро ЕКБ | +| `sv_cities` | 24 | не-ЕКБ города Свердл (Н.Тагил, Первоуральск, Арамиль) | +| `sv_dev_sales` | 50 | ТОП-50 Свердл девелоперы Март 2026 | + +**Universal raw** +| Таблица | Рядов | Описание | +|---|---:|---| +| `domrf_raw_endpoints` | 349 | universal JSONB store: каждый /api/* response с GIN-index на payload | + +### 🔍 Helper views (9 шт) + +**Базовые (legacy):** +| View | Описание | +|---|---| +| `v_market_pulse_rf` | one-row live-метрика РФ: 118.7M м² стройка / sold 30.91% / Цена 216 316 ₽/м² / ставка 19.9% vs 6.83% | +| `v_developer_master` | полный профиль девелопера (legacy). PRINZIP sold% 48.4% — ЛИДЕР Свердл | +| `v_region_master` | one-row-per-region: квартирография + sold_out + sold_ready + project_finance + guaranty + launch | +| `v_rosreestr_market_by_region` | Rosreestr p10/median/p90 + DOM.РФ имена | +| `v_stat_series_resolved` | stat_series_observations + alias + indicator_name (`WHERE region_id=66` → Свердл) | + +**Связи квартиры↔ЖК↔застройщики (NEW 27.04 evening):** +| View | Описание | +|---|---| +| `v_jk_with_developer` | ЖК Свердл + canonical dev_name + region_name (через FK `fk_kn_obj_developer/region`). 1 516 строк. | +| `v_jk_sales_summary` | flats GROUP BY obj_name: total/sold/booked/free + sold_pct + avg_ppm + avg_area + total_value. Standalone-вид поквартирных снапшотов из `domrf_kn_flats`. | +| `v_developer_portfolio` | Девелопер + все его корпуса/ЖК Свердл: count, flats_total, sqm + фильтры ekb/problem/escrow/completed/in_progress + earliest/latest ready_dt. | +| `v_developer_full_metrics` | **⭐ ULTIMATE QUERY**: portfolio (ЖК) + квартирография (1к/2к/3к/4+) с процентами + realization Свердл sold%/цена. Лучший view для PRINZIP/Брусника/конкурентов сравнения. | + +### 🔗 FK relationships + +**35 FK constraints** (после relations v2): +- `domrf_snapshots`: 24 child FKs (все таблицы со snapshot_date) +- `domrf_regions`: 5 (developer_aggregates, flat_area_distribution, region_aggregates, realization, kn_objects⭐, rent_deals⭐, territory_aliases) +- `domrf_developers`: 3 (developer_aggregates, **kn_objects** ⭐ NEW, **sv_dev_sales** ⭐ NEW) +- `domrf_federal_districts`: 1 (territory_aliases) + +**Цепочка анализа продаж:** +``` +domrf_kn_flats (поквартирно) ─── obj_name ──→ domrf_kn_objects (ЖК) + │ dev_id (FK) + ▼ + domrf_developers (справочник) + │ developer_id (FK) + ▼ + domrf_realization (sold%/price 14 мес) + │ + │ region_code=66 ─→ domrf_regions +``` + +⚠ **Известное ограничение**: `domrf_kn_flats.obj_id` НЕ имеет FK на `domrf_kn_objects.obj_id` — два API endpoint используют разные ID-системы (27 563/27 563 orphans, текстовый match по `obj_name`/`comm_name` тоже даёт только 2 совпадения). Поквартирные снапшоты обрабатываются standalone через `v_jk_sales_summary`. + +### 📜 ETL artefacts (data/sql/) + +``` +01_schema_rosreestr_deals.sql partitioned table + staging +02_load_all_quarters.sh \copy CSV (sep ~ и ;) +03_materialized_views.sql 3 MV для аналитики + +10_schema_domrf.sql domrf_regions/developers/snapshots/aggregates +11_load_domrf.py JSON dashboard loader +20_schema_domrf_extras.sql XLSX/PDF schemas +21_load_domrf_extras.py XLSX/PDF loader + +30_scrape_domrf.py Playwright headless Chromium с per-page direct API triggers +31_schema_domrf_full.sql launch/sold_ready/etc normalized +31_schema_domrf_raw.sql universal JSONB store +32_load_domrf_full.py normalize scraper output +32_load_domrf_raw.py JSONB raw store loader + +33_schema_domrf_lazy.sql domrf_realization + xlsx_files (5 lazy pages) +34_load_domrf_lazy.py realization/xlsx/raw loader +35_schema_domrf_stats.sql stat_series_indicators + observations +36_parse_xlsx_stats.py parse 7 XLSX → 809K observations +37_cleanup_redundant.sql DROP sold_out_by_year/progress + +38_relations_fix.sql +4 FK, миграция -1→0 RF, drop dup indexes, COMMENT'ы +39_territory_aliases.sql federal_districts + territory_aliases + v_stat_series_resolved +40_relations.sql оригинальные FK + 4 helper views (legacy) +40_scrape_history.py 14-month history scraper для realization+sold_ready +41_load_history.py loader с SNAPSHOT_DATE = end of period +42_schema_breakdown_widen.sql drop JSONB extra → 6 typed dimension columns +43_anton_import.sql 13 new tables for Anton's SQLite +44_import_anton_db.py sqlite3 → postgres migration +45_relations_v2.sql +4 FK (kn_obj→regions/developers, sv_dev_sales→developers, rent_deals→regions) + 4 analytical views (jk_with_developer, jk_sales_summary, developer_portfolio, developer_full_metrics) +``` + +### 🎯 Аналитика — что делать с этими данными + +**Готовые отчёты в графе памяти:** +- `Sverdl_Quartirography_Report_Apr27` — полный отчёт по квартирографии Свердл (что строится, что продаётся, тренды 14 мес, топ-15 девелоперов) +- `PRINZIP_Strategy_Apr27` — стратегические рекомендации для PRINZIP (что строить, куда смотреть, BENCHMARK с Брусникой/Форум-групп) + +**Использование (паттерны):** +```sql +-- Свердл realization timeseries за год +SELECT TO_CHAR(snapshot_date,'YYYY-MM') AS p, total_square, sold_perc, price_avg +FROM domrf_realization +WHERE region_code=66 AND endpoint_type='total' AND type_square='total' +ORDER BY snapshot_date; + +-- ЕКБ ДДУ-сделки по площадным сегментам +SELECT + CASE WHEN area<30 THEN 'studio' WHEN area<45 THEN '1k' ... END, + COUNT(*), AVG(price_per_sqm) +FROM rosreestr_deals +WHERE region_code=66 AND city ILIKE '%катеринбург%' + AND period_start_date >= '2025-04-01' AND doc_type='ДДУ'; + +-- Свердл stat_series (МКД 214-ФЗ, временной ряд) через resolved view +SELECT obs_date, value +FROM v_stat_series_resolved +WHERE region_id=66 AND sheet_code='01_01_01'; + +-- Поквартирный прайс ЕКБ (Антон) +SELECT obj_name, rooms, total_area, price_per_m2, status +FROM domrf_kn_flats WHERE city='Город Екатеринбург' AND status='free'; + +-- Топ ЖК ЕКБ (Антон) +SELECT comm_name, dev_name, flat_count, square_living, ready_dt +FROM domrf_kn_objects WHERE is_ekb=true ORDER BY flat_count DESC; +``` + --- ## Структура репозитория @@ -325,7 +570,16 @@ git push ⚠️ Эти документы — **снимок состояния на момент написания**. Свежее состояние стратегии — в графе памяти. При расхождении доверяем графу. -Релевантные сущности графа на 26.04.2026: +Релевантные сущности графа на 27.04.2026: + +**Аналитика и отчёты (27.04):** +- `DomRF_Lazy_Loaded_Apr27_2026` ⭐ — итог дня: 5 lazy-load страниц + stat_series + cleanup + territory aliases + MCP +- `DomRF_History_Loaded_Apr27_2026` ⭐ — 14 месяцев истории realization+sold_ready (143K рядов realization) +- `Anton_Imported_Apr27_2026` ⭐ — 13 таблиц от Антона + merge developers (8 629 итого) +- `Sverdl_Quartirography_Report_Apr27` 📊 — полный отчёт по Свердл квартирографии +- `PRINZIP_Strategy_Apr27` 📊 — стратегия PRINZIP: что строить и куда смотреть + +Сущности 26.04.2026: **Стратегия / план:** - `Project_GenDesign` — общая шапка diff --git a/data/sql/30_scrape_domrf.py b/data/sql/30_scrape_domrf.py index 9bb4aeff..fb769c7a 100644 --- a/data/sql/30_scrape_domrf.py +++ b/data/sql/30_scrape_domrf.py @@ -60,6 +60,330 @@ def upgrade_size(url: str) -> str: return urlunsplit((parts.scheme, parts.netloc, parts.path, new_q, parts.fragment)) +# --------------------------------------------------------------------------- +# Page-specific lazy-load triggers +# --------------------------------------------------------------------------- +# Most DOM.RF analytics pages render Material-UI tabs/selects; data fetches +# fire on tab/option switch. The "kitchen sink" trigger walks every tab and +# every Select option, with networkidle waits in between. Pages with custom +# UI (tree pickers, multi-step filters) get dedicated triggers below. + +async def _wait_quiet(page, ms=2500): + try: + await page.wait_for_load_state('networkidle', timeout=8000) + except Exception: + pass + await page.wait_for_timeout(ms) + + +async def _click_all_tabs(page): + """Click every Material-UI tab one-by-one, waiting between to capture XHR.""" + tab_count = await page.evaluate('''() => document.querySelectorAll( + '[role="tab"], .MuiTab-root, button[data-tab]' + ).length''') + print(f' tabs found: {tab_count}') + for i in range(tab_count): + try: + await page.evaluate( + '''(i) => { + const tabs = document.querySelectorAll( + '[role="tab"], .MuiTab-root, button[data-tab]' + ); + if (tabs[i]) tabs[i].click(); + }''', + i, + ) + await _wait_quiet(page, 2000) + except Exception as e: + print(f' tab[{i}] err: {e}') + + +async def _iterate_selects(page, max_options=20): + """Open every MUI Select / native