ПТИЦА: цена участка по радиусу (geo_radius_price) берёт лоты чужих ЖК через устаревший objective_lots.complex_id #3583

Open
opened 2026-09-17 10:34:15 +00:00 by bot-backend · 1 comment
Collaborator

Отдельный потребитель того же дефекта, что в #2962. PR #3582 чинит только конкурентов (competitors.py), этот читатель в его рамки не входит.

Что не так

objective_lots.complex_id проставлен один раз миграцией 76 (загрузка 10.05.2026). Еженедельный 70_parse_objective_raw.py делает UPSERT по objective_lot_id, переписывает project_name и не трогает complex_id. Прод 17.09.2026: из 303 677 строк с complex_id у 236 354 проект чужой, то есть под id ближнего ЖК лежат лоты ЖК из других мест.

Читатели устаревшей колонки, кроме конкурентов:

  1. backend/app/api/v1/parcels.py ~3589-3630, geo_radius_price: nearby_cx по complexes в радиусе, затем objective_lots WHERE ol.complex_id IN (SELECT id FROM nearby_cx) → медиана цены м². В медиану попадают лоты чужих проектов. Замер автора #3582 на 100 участках: медиана посчитана у 65, у 38 из них расходится с медианой по complex_sources (source='objective') → project_name больше чем на 10 %, медиана расхождения 15.6 %.
  2. v_complex_full.objective_lots_n (data/sql/92_cad_bulk_layers.sql:636-639, ранее 76): COUNT(*) FROM objective_lots WHERE ol.complex_id = c.id. Читается в analytics_queries.py (286, 3052), нужно проверить, используется ли именно этот столбец.
  3. data/sql/80_complexes_backfill_nulls.sql:136, 204 — разовая миграция, на живые ответы не влияет, только для справки.

Что сделать

  • В geo_radius_price связывать complex → проект через complex_sources (source='objective') и брать лоты по project_name, как в #3582. Учесть, что связи в complex_sources fuzzy (у «ЖК VEER PARK» стоит 'Clever Park', у «ЖК Графит» — 'Гранит'): в радиусном отборе без имени объекта сверять не с чем, решить, нужна ли тут сверка.
  • v_complex_full.objective_lots_n — то же, если столбец где-то читается.
  • Саму колонку не чинить здесь: правка UPSERT + backfill ~3.2 млн строк под триггером истории — запись на прод, решение владельца.

Приёмка

Тест по значению на временных таблицах (как test_2962_competitors_gapfill_bridge.py): под complex_id ближнего ЖК лежат лоты чужого проекта, медиана их не учитывает. На проде после деплоя: повторить замер на тех же 100 участках, расхождение с медианой по project_name = 0.

Отдельный потребитель того же дефекта, что в #2962. PR #3582 чинит только конкурентов (`competitors.py`), этот читатель в его рамки не входит. ### Что не так `objective_lots.complex_id` проставлен один раз миграцией 76 (загрузка 10.05.2026). Еженедельный `70_parse_objective_raw.py` делает UPSERT по `objective_lot_id`, переписывает `project_name` и не трогает `complex_id`. Прод 17.09.2026: из 303 677 строк с `complex_id` у 236 354 проект чужой, то есть под id ближнего ЖК лежат лоты ЖК из других мест. Читатели устаревшей колонки, кроме конкурентов: 1. **`backend/app/api/v1/parcels.py` ~3589-3630, `geo_radius_price`**: `nearby_cx` по `complexes` в радиусе, затем `objective_lots WHERE ol.complex_id IN (SELECT id FROM nearby_cx)` → медиана цены м². В медиану попадают лоты чужих проектов. Замер автора #3582 на 100 участках: медиана посчитана у 65, у 38 из них расходится с медианой по `complex_sources (source='objective') → project_name` больше чем на 10 %, медиана расхождения 15.6 %. 2. **`v_complex_full.objective_lots_n`** (`data/sql/92_cad_bulk_layers.sql:636-639`, ранее 76): `COUNT(*) FROM objective_lots WHERE ol.complex_id = c.id`. Читается в `analytics_queries.py` (286, 3052), нужно проверить, используется ли именно этот столбец. 3. `data/sql/80_complexes_backfill_nulls.sql:136, 204` — разовая миграция, на живые ответы не влияет, только для справки. ### Что сделать - В `geo_radius_price` связывать complex → проект через `complex_sources (source='objective')` и брать лоты по `project_name`, как в #3582. Учесть, что связи в `complex_sources` fuzzy (у «ЖК VEER PARK» стоит 'Clever Park', у «ЖК Графит» — 'Гранит'): в радиусном отборе без имени объекта сверять не с чем, решить, нужна ли тут сверка. - `v_complex_full.objective_lots_n` — то же, если столбец где-то читается. - Саму колонку не чинить здесь: правка UPSERT + backfill ~3.2 млн строк под триггером истории — запись на прод, решение владельца. ### Приёмка Тест по значению на временных таблицах (как `test_2962_competitors_gapfill_bridge.py`): под `complex_id` ближнего ЖК лежат лоты чужого проекта, медиана их не учитывает. На проде после деплоя: повторить замер на тех же 100 участках, расхождение с медианой по `project_name` = 0.
Author
Collaborator

PR #3591 смержен — что проверить и что осталось, 17.09.2026

Ревью перед мержем (независимое, прод только чтением):

  • Воспроизведён пример: 66:41:0702048:27 старый SQL → 130 552 / 13 638 лотов / 18 ЖК (как в analysis_runs 16.09), новый → 138 880 / 13 911 / 16. Ещё 4 участка сошлись с автором.
  • Независимый эталон (JOIN + общий DISTINCT ON) на 5 участках совпал с новым SQL до знака.
  • Дефект подтверждён составом старого набора: лишь 15–27 % лотов принадлежали ЖК в радиусе; остальное — ЖК дальше 3 км (до 18,7 км), ЖК без координат и несопоставленные проекты.
  • Скорость: новый запрос 61–93 мс против 47–81 мс старого, план — гео-индекс + Index Only Scan.

Где меняется число: цена в financial_estimate без квартальной MV (NPV/ROI, PDF/DOCX), optimize_program (показ альтернатив зависит от знака NPV), district.median_price_per_m2 на фронте (обзор, сравнение участков, экспорт). Сохранённые analysis_runs и выгруженные отчёты не пересчитываются — новый анализ того же участка даст другое число.

Приёмка — не раньше 2026-09-18, до загрузки Объектива 22.09:

  1. Маркер _GEO_RADIUS_PRICE_SQL в gendesign-backend-1.
  2. Анализ 66:41:0702048:27geo_radius_price 138 880 / 13 911 / 16.
  3. После загрузки 22.09 повторить EXPLAIN (ANALYZE, BUFFERS): Index Only Scan уже читает heap (5–9 тыс. чтений), UPSERT загрузки сбросит карту видимости до autovacuum.

Остаётся открытым:

  • Тест не ловит три правдоподобные регрессии: снятое обратное включение имени (отпадёт «Южные кварталы»), regexp «только пробелы» (отвергнет ЖК с кавычками: «Татлин», «РАЗУМ на Малышева» и др.), снятый фильтр price_per_m2_rub IS NOT NULL (изменит n и гейт). Нужны три строки фикстуры.
  • Данные complex_sources: 4 ошибочные fuzzy-связи и 2 верные, но отвергаемые («Новая Ботаника-2», «Теплые кварталы (BAZA)» — из-за префикса «ЖК » в имени) — правка данных за владельцем, подробности в #2962.
## PR #3591 смержен — что проверить и что осталось, 17.09.2026 **Ревью перед мержем** (независимое, прод только чтением): - Воспроизведён пример: `66:41:0702048:27` старый SQL → 130 552 / 13 638 лотов / 18 ЖК (как в `analysis_runs` 16.09), новый → **138 880 / 13 911 / 16**. Ещё 4 участка сошлись с автором. - Независимый эталон (JOIN + общий `DISTINCT ON`) на 5 участках совпал с новым SQL до знака. - Дефект подтверждён составом старого набора: лишь **15–27 %** лотов принадлежали ЖК в радиусе; остальное — ЖК дальше 3 км (до 18,7 км), ЖК без координат и несопоставленные проекты. - Скорость: новый запрос 61–93 мс против 47–81 мс старого, план — гео-индекс + Index Only Scan. **Где меняется число:** цена в `financial_estimate` без квартальной MV (NPV/ROI, PDF/DOCX), `optimize_program` (показ альтернатив зависит от знака NPV), `district.median_price_per_m2` на фронте (обзор, сравнение участков, экспорт). Сохранённые `analysis_runs` и выгруженные отчёты не пересчитываются — новый анализ того же участка даст другое число. **Приёмка — не раньше 2026-09-18, до загрузки Объектива 22.09:** 1. Маркер `_GEO_RADIUS_PRICE_SQL` в `gendesign-backend-1`. 2. Анализ `66:41:0702048:27` → `geo_radius_price` 138 880 / 13 911 / 16. 3. После загрузки 22.09 повторить `EXPLAIN (ANALYZE, BUFFERS)`: Index Only Scan уже читает heap (5–9 тыс. чтений), UPSERT загрузки сбросит карту видимости до autovacuum. **Остаётся открытым:** - Тест не ловит три правдоподобные регрессии: снятое обратное включение имени (отпадёт «Южные кварталы»), regexp «только пробелы» (отвергнет ЖК с кавычками: «Татлин», «РАЗУМ на Малышева» и др.), снятый фильтр `price_per_m2_rub IS NOT NULL` (изменит n и гейт). Нужны три строки фикстуры. - Данные `complex_sources`: 4 ошибочные fuzzy-связи и 2 верные, но отвергаемые («Новая Ботаника-2», «Теплые кварталы (BAZA)» — из-за префикса «ЖК » в имени) — правка данных за владельцем, подробности в #2962.
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#3583
No description provided.