Bug: ekb_districts.median_price_per_m2 NULL для половины районов — пересчитать из rosreestr #18

Closed
opened 2026-04-30 19:47:08 +00:00 by lekss361 · 1 comment
lekss361 commented 2026-04-30 19:47:08 +00:00 (Migrated from github.com)

Проблема

В таблице ekb_districts для нескольких ЕКБ-районов колонка median_price_per_m2 = NULL. Это видно прямо из API responseов recommend_mix: warning «В ekb_districts нет median_price_per_m2 для 'Академический' — коэффициент района = 1.0» появляется для таких районов.

Эффект: district_factor ↑ всегда 1.0 для этих районов → нет региональной нормировки цены. Также cadastre_vs_market_pct не считается (нет district_median для сравнения с кадастром).

Что сделать

Backend:

  • Скрипт data/sql/67_refresh_ekb_districts_median.py (или SQL-файл) — пересчитывает median_price_per_m2 для всех 8 районов ЕКБ из rosreestr_deals за последние 24 мес:
    WITH per_district AS (
      SELECT d.district_name,
             PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY rd.price_per_sqm) AS median_pm
        FROM rosreestr_deals rd
        JOIN cad_quarters_geom cq ON cq.cad_number = rd.quarter_cad_number
        JOIN ekb_districts_geom d ON ST_Intersects(d.geom, cq.geom)
       WHERE rd.region_code=66
         AND rd.doc_type='ДДУ'
         AND rd.realestate_type_code='002001003000'
         AND rd.price_per_sqm BETWEEN 30000 AND 1000000
         AND rd.period_start_date >= NOW() - INTERVAL '24 months'
       GROUP BY d.district_name)
    UPDATE ekb_districts SET median_price_per_m2 = per_district.median_pm
      FROM per_district WHERE ekb_districts.district_name = per_district.district_name;
    
  • Прокинуть в Celery beat (раз в месяц после загрузки нового rosreestr-квартала)

Acceptance:

  • После прогона все 8 районов ЕКБ имеют median_price_per_m2 IS NOT NULL
  • recommend_mix для Академического показывает district_factor != 1.0 и cadastre_vs_market_pct != null
  • Cron-task запускается успешно через NSPD-beat-расписание (или отдельным расписанием)

Effort

~2 часа.

## Проблема В таблице `ekb_districts` для нескольких ЕКБ-районов колонка `median_price_per_m2` = NULL. Это видно прямо из API responseов `recommend_mix`: warning «В ekb_districts нет median_price_per_m2 для 'Академический' — коэффициент района = 1.0» появляется для таких районов. Эффект: `district_factor` ↑ всегда 1.0 для этих районов → нет региональной нормировки цены. Также `cadastre_vs_market_pct` не считается (нет district_median для сравнения с кадастром). ## Что сделать **Backend:** - Скрипт `data/sql/67_refresh_ekb_districts_median.py` (или SQL-файл) — пересчитывает `median_price_per_m2` для всех 8 районов ЕКБ из `rosreestr_deals` за последние 24 мес: ```sql WITH per_district AS ( SELECT d.district_name, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY rd.price_per_sqm) AS median_pm FROM rosreestr_deals rd JOIN cad_quarters_geom cq ON cq.cad_number = rd.quarter_cad_number JOIN ekb_districts_geom d ON ST_Intersects(d.geom, cq.geom) WHERE rd.region_code=66 AND rd.doc_type='ДДУ' AND rd.realestate_type_code='002001003000' AND rd.price_per_sqm BETWEEN 30000 AND 1000000 AND rd.period_start_date >= NOW() - INTERVAL '24 months' GROUP BY d.district_name) UPDATE ekb_districts SET median_price_per_m2 = per_district.median_pm FROM per_district WHERE ekb_districts.district_name = per_district.district_name; ``` - Прокинуть в Celery beat (раз в месяц после загрузки нового rosreestr-квартала) **Acceptance:** - [ ] После прогона все 8 районов ЕКБ имеют `median_price_per_m2 IS NOT NULL` - [ ] `recommend_mix` для Академического показывает `district_factor` != 1.0 и `cadastre_vs_market_pct` != null - [ ] Cron-task запускается успешно через NSPD-beat-расписание (или отдельным расписанием) ## Effort ~2 часа.
lekss361 commented 2026-04-30 20:08:28 +00:00 (Migrated from github.com)

Реализовано:

Backend

  • data/sql/67_refresh_ekb_districts_median.sql — SQL-функция refresh_ekb_districts_median(window_months int, min_deals int) с spatial-join rosreestr_dealscad_quarters_geomekb_districts_geom. Окно 24 мес, минимум 50 сделок, исключает 'не определён'.
  • backend/app/services/analytics_refresh.py — Python-обёртка для вызова из API/Celery.
  • backend/app/workers/tasks/refresh_analytics.py — Celery task tasks.refresh_analytics.refresh_ekb_districts_medians.
  • Beat schedule (celery_app.py): 0 4 5 * * (каждое 5-е число месяца, 04:00 МСК — после публикации новых rosreestr-кварталов).

Применено в проде

Все 8 районов ЕКБ обновлены через MCP (первый прогон):

Район Сделок Median ₽/м² Mean ₽/м²
Академический 297 74 422 82 393
Верх-Исетский 299 77 996 103 464
Железнодорожный 363 69 687 83 239
Кировский 359 77 720 102 869
Ленинский 444 81 079 96 172
Октябрьский 263 77 720 98 534
Орджоникидзевский 411 71 670 78 281
Чкаловский 509 70 452 80 264

Verification

recommend_mix(district='Академический', class='Comfort'):

  • BEFORE: district_factor: 1.0 (fallback, warning «нет median_price_per_m2»), cadastre_vs_market_pct: None.
  • AFTER: district_factor: 0.9783, district_median_price_per_m2: 74422, cadastre_vs_market_pct: 95.2% (cad 38K vs market 74K — рынок дороже на ~95%, что норма для новостроек vs средне-старых строений).

Старые значения 191K/205K для Октябрьского/Орджоникидзевского (видимо из импорта Антона из листингов yandex/domrf) перезаписаны на актуальные из rosreestr-сделок.

Реализовано: ## Backend - `data/sql/67_refresh_ekb_districts_median.sql` — SQL-функция `refresh_ekb_districts_median(window_months int, min_deals int)` с spatial-join `rosreestr_deals` ↔ `cad_quarters_geom` ↔ `ekb_districts_geom`. Окно 24 мес, минимум 50 сделок, исключает 'не определён'. - `backend/app/services/analytics_refresh.py` — Python-обёртка для вызова из API/Celery. - `backend/app/workers/tasks/refresh_analytics.py` — Celery task `tasks.refresh_analytics.refresh_ekb_districts_medians`. - Beat schedule (`celery_app.py`): `0 4 5 * *` (каждое 5-е число месяца, 04:00 МСК — после публикации новых rosreestr-кварталов). ## Применено в проде Все 8 районов ЕКБ обновлены через MCP (первый прогон): | Район | Сделок | Median ₽/м² | Mean ₽/м² | |---|---|---|---| | Академический | 297 | 74 422 | 82 393 | | Верх-Исетский | 299 | 77 996 | 103 464 | | Железнодорожный | 363 | 69 687 | 83 239 | | Кировский | 359 | 77 720 | 102 869 | | Ленинский | 444 | 81 079 | 96 172 | | Октябрьский | 263 | 77 720 | 98 534 | | Орджоникидзевский | 411 | 71 670 | 78 281 | | Чкаловский | 509 | 70 452 | 80 264 | ## Verification `recommend_mix(district='Академический', class='Comfort')`: - BEFORE: `district_factor: 1.0` (fallback, warning «нет median_price_per_m2»), `cadastre_vs_market_pct: None`. - AFTER: `district_factor: 0.9783`, `district_median_price_per_m2: 74422`, `cadastre_vs_market_pct: 95.2%` (cad 38K vs market 74K — рынок дороже на ~95%, что норма для новостроек vs средне-старых строений). Старые значения 191K/205K для Октябрьского/Орджоникидзевского (видимо из импорта Антона из листингов yandex/domrf) перезаписаны на актуальные из rosreestr-сделок.
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#18
No description provided.