Блокер Москвы: город входит в ценовую формулу текстом, нормализованный адрес считается уникальным по всей стране #2996

Open
opened 2026-08-20 17:49:45 +00:00 by lekss361 · 4 comments
Owner

Эпик: #2989 · Проверяющий счёл это критичнее FDW — промах даёт не пустой экран, а неверную цену.

Находка 1 — город в формуле как строка

backend/app/services/estimator.py:1728 и :1809LEFT JOIN deal_city_price_bands b ON b.city = d.city. _load_city_price_bands (estimator.py:329-348) грузит словарь {city: (ppm2_min, ppm2_max)} строками.

Сегодня distinct city в listings равен 7 и это работает. В Москве и области будет «Москва», «г. Москва», «Химки», «Московская область, Химки» — текстовый джойн промахнётся молча, и результатом промаха будет неверная цена, а не заметный сбой.

Нужен city_fias_id сквозняком: listings, deals, deal_city_price_bands. Текстовое имя — только для показа.

Находка 2 — адрес уникален глобально

  • house_address_aliases имеет UNIQUE (normalized_address) без региона
  • geocode_cachePRIMARY KEY (address_normalized) без региона

То есть нормализованный адрес считается уникальным по всей стране. Код уже борется костылём _TIER2B_GUARD_M = 3000 в backend/app/services/matching/houses.py, и в Московской агломерации этот костыль не работает по построению — там улицы с одинаковыми названиями лежат ближе трёх километров.

Поправка проверяющего: гард заменять не радиусом, а принадлежностью полигону населённого пункта — ST_Within плюс совпадение city_fias_id. Тогда радиус перестаёт быть механизмом вообще.

Находка 3 — справочник домов не знает свой регион

У houses нет ни city, ни region_code — в отличие от listings и deals, где обе есть. Регион определим только по geom, гарда на приёме нет: 23 дома лежат вне области 66 с разбросом координат от 43 до 59 по широте и от 18 до 131 по долготе.

gar_house_flats — 919 341 строка, все с region_code = '66', загрузчик по умолчанию режет по city_filter="Екатеринбург". И это огрызок ГАР: только срез house_guid → flat_count, без адресной иерархии ADDR_OBJ, которая и делает ключи региононейтральными.

Находка 4 — обогащение ходит в чужую базу

Четыре тира — геокодер, метаданные дома, квартальный ценовой индекс и сам источник сделок Росреестра — идут через SERVER gendesign_remote в базу Site Finder: gendesign_cad_buildings, gendesign_rosreestr_deals, quarter_price_index, gendesign_osm_poi_ekb, gendesign_ekb_districts_geom. Для Москвы ни одного из них не существует, и деградация тихая — estimator.py:1318 логирует warning и продолжает считать цену без квартального индекса.

Отдельно: FDW привязан к docker-сети по имени (host=gendesign-postgres), при переезде связь рвётся и сделки перестают приезжать. Решение принять до окна: переезжать обоими стеками, переписать на публичный адрес с TLS (тогда connect_timeout=3 поднять до 15), либо уйти на батч-выгрузку.

Хорошая новость

Мины МСК-66 в базе tradein нет: все 10 гео-колонок в geometry_columns имеют SRID 4326, grep по ST_Transform / SRID= / 3857 даёт ноль попаданий. Местная система координат живёт в проекте gendesign, а не здесь.

Acceptance

  • city_fias_id в listings, deals, deal_city_price_bands; джойн по ключу, не по строке
  • UNIQUE (region_code, normalized_address) в house_address_aliases, то же для geocode_cache
  • region_code NOT NULL в houses; дом без выводимого региона уходит в карантин, а не получает выдуманный
  • Гард по полигону вместо _TIER2B_GUARD_M
  • Решение по FDW принято и зафиксировано
  • Платный DaData включён до заливки Москвы — gar_house_guid заполнен у 52 % домов, house_fias_id у 37 %, а ФИАС-идентификатор это фундамент всех региональных ключей выше

Смежное: #2583 (квартальный индекс нормирован на медиану ЕКБ — та же корневая причина), #2777 (108 домов сшиты из разных населённых пунктов).

Scope: backend/app/services/estimator.py, backend/app/services/matching/houses.py, backend/data/sql/.

Эпик: #2989 · **Проверяющий счёл это критичнее FDW — промах даёт не пустой экран, а неверную цену.** ## Находка 1 — город в формуле как строка `backend/app/services/estimator.py:1728` и `:1809` — `LEFT JOIN deal_city_price_bands b ON b.city = d.city`. `_load_city_price_bands` (`estimator.py:329-348`) грузит словарь `{city: (ppm2_min, ppm2_max)}` **строками**. Сегодня `distinct city` в `listings` равен 7 и это работает. В Москве и области будет «Москва», «г. Москва», «Химки», «Московская область, Химки» — текстовый джойн промахнётся **молча**, и результатом промаха будет неверная цена, а не заметный сбой. Нужен `city_fias_id` сквозняком: `listings`, `deals`, `deal_city_price_bands`. Текстовое имя — только для показа. ## Находка 2 — адрес уникален глобально - `house_address_aliases` имеет `UNIQUE (normalized_address)` **без региона** - `geocode_cache` — `PRIMARY KEY (address_normalized)` **без региона** То есть нормализованный адрес считается уникальным по всей стране. Код уже борется костылём `_TIER2B_GUARD_M = 3000` в `backend/app/services/matching/houses.py`, и в Московской агломерации этот костыль не работает по построению — там улицы с одинаковыми названиями лежат ближе трёх километров. Поправка проверяющего: гард заменять **не радиусом**, а принадлежностью полигону населённого пункта — `ST_Within` плюс совпадение `city_fias_id`. Тогда радиус перестаёт быть механизмом вообще. ## Находка 3 — справочник домов не знает свой регион У `houses` нет ни `city`, ни `region_code` — в отличие от `listings` и `deals`, где обе есть. Регион определим только по `geom`, гарда на приёме нет: **23 дома лежат вне области 66** с разбросом координат от 43 до 59 по широте и от 18 до 131 по долготе. `gar_house_flats` — 919 341 строка, **все с `region_code = '66'`**, загрузчик по умолчанию режет по `city_filter="Екатеринбург"`. И это огрызок ГАР: только срез `house_guid → flat_count`, без адресной иерархии ADDR_OBJ, которая и делает ключи региононейтральными. ## Находка 4 — обогащение ходит в чужую базу Четыре тира — геокодер, метаданные дома, квартальный ценовой индекс и сам источник сделок Росреестра — идут через `SERVER gendesign_remote` в базу Site Finder: `gendesign_cad_buildings`, `gendesign_rosreestr_deals`, `quarter_price_index`, `gendesign_osm_poi_ekb`, `gendesign_ekb_districts_geom`. Для Москвы ни одного из них не существует, и деградация тихая — `estimator.py:1318` логирует warning и продолжает считать цену без квартального индекса. Отдельно: FDW привязан к docker-сети по имени (`host=gendesign-postgres`), при переезде связь рвётся и сделки перестают приезжать. Решение принять **до окна**: переезжать обоими стеками, переписать на публичный адрес с TLS (тогда `connect_timeout=3` поднять до 15), либо уйти на батч-выгрузку. ## Хорошая новость Мины МСК-66 в базе `tradein` **нет**: все 10 гео-колонок в `geometry_columns` имеют SRID 4326, grep по `ST_Transform` / `SRID=` / `3857` даёт ноль попаданий. Местная система координат живёт в проекте gendesign, а не здесь. ## Acceptance - [ ] `city_fias_id` в `listings`, `deals`, `deal_city_price_bands`; джойн по ключу, не по строке - [ ] `UNIQUE (region_code, normalized_address)` в `house_address_aliases`, то же для `geocode_cache` - [ ] `region_code NOT NULL` в `houses`; дом без выводимого региона уходит в карантин, а не получает выдуманный - [ ] Гард по полигону вместо `_TIER2B_GUARD_M` - [ ] Решение по FDW принято и зафиксировано - [ ] Платный DaData включён до заливки Москвы — `gar_house_guid` заполнен у 52 % домов, `house_fias_id` у 37 %, а ФИАС-идентификатор это фундамент всех региональных ключей выше Смежное: #2583 (квартальный индекс нормирован на медиану ЕКБ — та же корневая причина), #2777 (108 домов сшиты из разных населённых пунктов). Scope: `backend/app/services/estimator.py`, `backend/app/services/matching/houses.py`, `backend/data/sql/`.
lekss361 added the
bug
data
priority/p1
scope/backend
scope/db
tradein
labels 2026-08-20 17:51:00 +00:00
Collaborator

Находка 3: нашёл, ОТКУДА эти 23 дома. Это не живой путь, а разовый бэкфилл — и «precision: exact» у них стоит

Проверил на проде 21.08. Число подтвердилось (23 дома вне рамки 55.5–62.5 с.ш. / 56.5–67.5 в.д. из 9 429 с координатами), и добавились две вещи, которых в постановке не было: масштаб последствий и причина.

Масштаб

К этим домам привязано 591 объявление:

 id     широта  долгота  объявл.  адрес
 12912  56.805   53.173     132   Ул. Шаумяна/Фурманова/Московская, стр. 5
 12615  59.393   24.674     113   Ул. Студенческая, секц. 1
  7899  48.540   39.277      83   Кв-л "Щербакова/ул. Благодатская, стр. 6
 10562  44.766   20.556      48   Ул. 8 Марта, 1 этап
 12137  58.013   56.253      48   Ул. Ландау/Екатерининская/Вавилова, стр. 49
 ...                                                    (всего 23 дома)

Адреса — екатеринбургские. Координаты — Ижевск, Таллин, Луганск, Белград, Пермь.

Причина

22 из 23 — source='derived', и у каждого в raw_payload один и тот же след:

{"yandex_geocode": {"batch": "full_backfill_2026-05-27",
                    "source": "yandex_geocoder_api",
                    "precision": "exact",
                    "address": "Россия, Удмуртская Республика, Ижевск, улица Шаумяна, 5"}}

Разовый бэкфилл 27.05 звал Yandex-геокодер без ограничения по региону. Куда он разрешил екатеринбургские ЖК:

Польша, Варшава, улица Александровская, 3A          exact
Сербия, округ Белград, Гроцка                       exact
Эстония, Таллин, путь Юлиыпиласте, 1                exact
Приморский край, Владивосток, улица Ладыгина, 15А   exact
Удмуртия, Ижевск, улица Шаумяна, 5                  exact
Брянск, улица Куйбышева (два дома)                  exact
Пермь, Екатерининская улица, 49с2                   number
Луганск, квартал Щербакова, 6                       exact
Кременчуг, Полтавская область                       exact
Гданьск, улица Амундсена, 5A                        number
Иркутск, Байкальский тракт                          exact
...

17 из 22 помечены precision: "exact". Это и есть главный урок: precision отвечает на вопрос «нашёлся ли точный номер дома», а не «в том ли городе». Как критерий приёмки бэкфилла он бесполезен — все 17 «точных» неверны.

Механизм именно тот, что описан в Находке 2 («нормализованный адрес считается уникальным по всей стране»), но канал другой: не geocode_cache, а прямой вызов API из разового скрипта. Адреса при этом составные («Ул. Шаумяна/Фурманова/Московская, стр. 5») — геокодер берёт первую улицу и находит её где угодно.

Живой путь при этом чист

Проверил обе гипотезы, которые напрашивались, и обе не подтвердились:

geocode_cache:  0 записей вне рамки из 10 448
listings:       2 записи вне рамки из 88 544 (обе неактивны)

То есть _resolve_city_for_geocode в живом геокодере работает, и скрипта full_backfill_2026-05-27 в репозитории нет — он не повторится сам. Это исторический осадок, а не текущая утечка.

Что из этого следует для постановки

  • Гард на приёме нужен не столько живому пути, сколько следующему разовому скрипту. Тот обошёл живую защиту, потому что звал API напрямую. Инвариант должен жить у таблицы (или в тесте-стороже), а не в одной из процедур записи.
  • precision от геокодера нельзя использовать как критерий приёмки — ни в бэкфилле, ни в живом пути. Нужна проверка принадлежности, а не «точности». Это ровно то, что поправка проверяющего говорит про Находку 2: не радиус, а ST_Within + совпадение города.
  • Осадок надо убрать отдельно — 591 объявление сейчас привязано к домам в Варшаве и Таллине, и любой поиск сопоставимых вокруг них бессмыслен. Готов сделать миграцию, но выбор между «обнулить координаты» и «удалить дома с пере-сопоставлением» — решение владельца: первое честнее (координат нет), второе чище (дом пересоздастся с правильными).
## Находка 3: нашёл, ОТКУДА эти 23 дома. Это не живой путь, а разовый бэкфилл — и «precision: exact» у них стоит Проверил на проде 21.08. Число подтвердилось (23 дома вне рамки 55.5–62.5 с.ш. / 56.5–67.5 в.д. из 9 429 с координатами), и добавились две вещи, которых в постановке не было: масштаб последствий и причина. ### Масштаб К этим домам привязано **591 объявление**: ``` id широта долгота объявл. адрес 12912 56.805 53.173 132 Ул. Шаумяна/Фурманова/Московская, стр. 5 12615 59.393 24.674 113 Ул. Студенческая, секц. 1 7899 48.540 39.277 83 Кв-л "Щербакова/ул. Благодатская, стр. 6 10562 44.766 20.556 48 Ул. 8 Марта, 1 этап 12137 58.013 56.253 48 Ул. Ландау/Екатерининская/Вавилова, стр. 49 ... (всего 23 дома) ``` Адреса — екатеринбургские. Координаты — Ижевск, Таллин, Луганск, Белград, Пермь. ### Причина 22 из 23 — `source='derived'`, и у каждого в `raw_payload` один и тот же след: ```json {"yandex_geocode": {"batch": "full_backfill_2026-05-27", "source": "yandex_geocoder_api", "precision": "exact", "address": "Россия, Удмуртская Республика, Ижевск, улица Шаумяна, 5"}} ``` Разовый бэкфилл 27.05 звал Yandex-геокодер **без ограничения по региону**. Куда он разрешил екатеринбургские ЖК: ``` Польша, Варшава, улица Александровская, 3A exact Сербия, округ Белград, Гроцка exact Эстония, Таллин, путь Юлиыпиласте, 1 exact Приморский край, Владивосток, улица Ладыгина, 15А exact Удмуртия, Ижевск, улица Шаумяна, 5 exact Брянск, улица Куйбышева (два дома) exact Пермь, Екатерининская улица, 49с2 number Луганск, квартал Щербакова, 6 exact Кременчуг, Полтавская область exact Гданьск, улица Амундсена, 5A number Иркутск, Байкальский тракт exact ... ``` **17 из 22 помечены `precision: "exact"`.** Это и есть главный урок: `precision` отвечает на вопрос «нашёлся ли точный номер дома», а не «в том ли городе». Как критерий приёмки бэкфилла он бесполезен — все 17 «точных» неверны. Механизм именно тот, что описан в Находке 2 («нормализованный адрес считается уникальным по всей стране»), но канал другой: не `geocode_cache`, а прямой вызов API из разового скрипта. Адреса при этом составные («Ул. Шаумяна/Фурманова/Московская, стр. 5») — геокодер берёт первую улицу и находит её где угодно. ### Живой путь при этом чист Проверил обе гипотезы, которые напрашивались, и обе не подтвердились: ``` geocode_cache: 0 записей вне рамки из 10 448 listings: 2 записи вне рамки из 88 544 (обе неактивны) ``` То есть `_resolve_city_for_geocode` в живом геокодере работает, и скрипта `full_backfill_2026-05-27` в репозитории нет — он не повторится сам. Это исторический осадок, а не текущая утечка. ### Что из этого следует для постановки - **Гард на приёме нужен не столько живому пути, сколько следующему разовому скрипту.** Тот обошёл живую защиту, потому что звал API напрямую. Инвариант должен жить у таблицы (или в тесте-стороже), а не в одной из процедур записи. - **`precision` от геокодера нельзя использовать как критерий приёмки** — ни в бэкфилле, ни в живом пути. Нужна проверка принадлежности, а не «точности». Это ровно то, что поправка проверяющего говорит про Находку 2: не радиус, а `ST_Within` + совпадение города. - **Осадок надо убрать отдельно** — 591 объявление сейчас привязано к домам в Варшаве и Таллине, и любой поиск сопоставимых вокруг них бессмыслен. Готов сделать миграцию, но выбор между «обнулить координаты» и «удалить дома с пере-сопоставлением» — решение владельца: первое честнее (координат нет), второе чище (дом пересоздастся с правильными).
Author
Owner

Решение по FDW принято (пункт acceptance «Решение по FDW принято и зафиксировано»)

Владелец 2026-08-20 зафиксировал целевую топологию переезда. Из трёх вариантов, перечисленных в находке 4, выбран первый: переезжать обоими стеками на одну машину.

Что это значит для этого issue. Postgres Меры и Postgres Птицы селятся на одном хосте (Selectel, выделенный сервер). Следовательно:

  • SERVER gendesign_remote остаётся локальным, привязка host=gendesign-postgres по имени docker-сети продолжает работать;
  • переписывать FDW на публичный адрес с TLS не нужно, connect_timeout=3 не трогаем;
  • батч-выгрузка не нужна;
  • лишняя точка отказа (FDW через интернет) не появляется.

То есть блокер переезда из находки 4 закрывается бесплатно, самим фактом совместного размещения. Разнесение баз по разным машинам явно отвергнуто именно поэтому — плюс потому, что раздельное размещение теряет общий page cache.

Что из находки 4 НЕ закрыто и остаётся в этом issue: для Москвы ни одного из четырёх тиров обогащения (геокодер, метаданные дома, квартальный индекс, сделки Росреестра) не существует, а деградация тихая — estimator.py:1318 логирует warning и продолжает считать цену. Это отдельная проблема, к переезду не привязанная.

Что проверить в окне миграции: после переноса убедиться, что оба контейнера Postgres в одной docker-сети и host=gendesign-postgres резолвится — это единственное, что может сломаться при смене хоста.

Остальные пункты acceptance (city_fias_id, UNIQUE (region_code, normalized_address), region_code NOT NULL, гард по полигону, платный DaData) не затронуты и остаются открытыми — они относятся к пересборке схемы, а не к окну переезда.

Refs #2989

## Решение по FDW принято (пункт acceptance «Решение по FDW принято и зафиксировано») Владелец 2026-08-20 зафиксировал целевую топологию переезда. Из трёх вариантов, перечисленных в находке 4, выбран **первый: переезжать обоими стеками на одну машину**. **Что это значит для этого issue.** Postgres Меры и Postgres Птицы селятся на одном хосте (Selectel, выделенный сервер). Следовательно: - `SERVER gendesign_remote` остаётся **локальным**, привязка `host=gendesign-postgres` по имени docker-сети продолжает работать; - переписывать FDW на публичный адрес с TLS не нужно, `connect_timeout=3` не трогаем; - батч-выгрузка не нужна; - лишняя точка отказа (FDW через интернет) не появляется. То есть блокер переезда из находки 4 закрывается **бесплатно**, самим фактом совместного размещения. Разнесение баз по разным машинам явно отвергнуто именно поэтому — плюс потому, что раздельное размещение теряет общий page cache. **Что из находки 4 НЕ закрыто и остаётся в этом issue:** для Москвы ни одного из четырёх тиров обогащения (геокодер, метаданные дома, квартальный индекс, сделки Росреестра) не существует, а деградация тихая — `estimator.py:1318` логирует warning и продолжает считать цену. Это отдельная проблема, к переезду не привязанная. **Что проверить в окне миграции:** после переноса убедиться, что оба контейнера Postgres в одной docker-сети и `host=gendesign-postgres` резолвится — это единственное, что может сломаться при смене хоста. Остальные пункты acceptance (`city_fias_id`, `UNIQUE (region_code, normalized_address)`, `region_code NOT NULL`, гард по полигону, платный DaData) не затронуты и остаются открытыми — они относятся к пересборке схемы, а не к окну переезда. Refs #2989
Author
Owner

Замеры 2026-08-22: два блокера сверх четырёх находок, и смена приоритета на 77-без-50

Блокер 5 — bbox покрытия отвергнет Москву до всякой цены

location_index.py:65-73_EKB_BBOX 56.70–56.95 / 60.50–60.75, точка вне него получает статус out_of_coverage. Москва (55.75 / 37.62) лежит снаружи. Это не про точность — оценка просто не начнётся.

Рядом то же самое в geocoder.py:75-76 (EKB_BBOX_TIGHT, EKB_BBOX_WIDE) и :105 (OBLAST66_BBOX).

Блокер 6 — тип документа теряется при загрузке, ДДУ войдут как продажи

В deals Меры нет колонки doc_type, хотя весь оценщик в комментариях оперирует «ДКП-сделками». Наверху типы разделены, и для Москвы разница решающая:

Год ДКП медиана ДКП ДДУ медиана ДДУ
2026 53 327 316 096 1 371 409 672
2025 113 522 276 388 3 072 296 152
2024 107 005 256 250 30 627 112 743

Тридцать тысяч ДДУ 2024 года по ценам котлована. Без фильтра они войдут в ценовой коридор как обычные продажи и занизят Москву. По региону 66 перекос меньше (17 911 ДДУ против 254 089 ДКП), поэтому дефект и не проявлялся.

Поправка к находке 4

В задаче сказано, что для Москвы не существует ни одного из источников обогащения. Проверено — верно для трёх из четырёх:

Источник Москва
rosreestr_deals есть: 273 854 ДКП по 77, 367 181 по 50
cad_buildings нет — данные начинаются с долготы 39.24, Москва на 37.62
osm_poi_ekb нет — 4 845 записей по ЕКБ
ekb_districts_geom нет — 8 районов ЕКБ

Разница принципиальная: отсутствуют улучшающие тиры, а сам ценовой якорь по Москве есть. Формулировка «ни одного» читается как «данных нет вообще» и ведёт к неверному выводу о невозможности запуска.

Решение по объёму: запускаем 77, область 50 откладываем

Регион ДКП с улицей Городов Улиц
77 Москва 273 854 96.9 % 612 4 208
66 Свердловская 254 089 85.8 % 1 540 6 381
50 Московская обл. 367 181 68.8 % 10 121 22 708

Москва проще Екатеринбурга: улица заполнена у 96.9 % против 85.8 %, различных значений города 612 против 1 540. Вся мина текстовых имён сосредоточена в области — 10 121 значение города и 22 708 улиц.

Запуск только 77 сжимает проблему ключей города с десяти тысяч значений до шестисот, которые можно перечислить и проверить перед заливкой. Область добавляется следующим шагом, уже с настоящим city_fias_id.

Инвентаризация зашитого ЕКБ

109 совпадений в 25 файлах, три класса:

Жёсткие гейтыlocation_index.py:65-73, geocoder.py:75-76,105,129,292,298,319, normalize.py:117.

Параметризуемоеscheduler.py:309, yandex_address_backfill.py:82, zhkh_flats_loader.py:368, gar_flats_load.py:98, sber_index.py:72,453.

Калибровочный перекосestimator.py:2308 (константы вторички ЕКБ применяются ко всему движку) и estimator.py:4884-4887, где premium_buildings_curated активно депромоутит не-ЕКБ описания как шум. Для Москвы это прямой вред, а не просто неточность.

Образец решения уже есть в коде: house_imv_backfill.py:143 держит _REGION_BBOX списком регионов. Его надо поднять в общий реестр и заставить потреблять всех остальных.

Разбиение

Задача раскладывается на три и дробится дальше по первой части: #3051 (ключи регионов), #3052 (гард по полигону), #3053 (решение по FDW).

## Замеры 2026-08-22: два блокера сверх четырёх находок, и смена приоритета на 77-без-50 ### Блокер 5 — bbox покрытия отвергнет Москву до всякой цены `location_index.py:65-73` — `_EKB_BBOX` 56.70–56.95 / 60.50–60.75, точка вне него получает статус `out_of_coverage`. Москва (55.75 / 37.62) лежит снаружи. Это не про точность — оценка просто не начнётся. Рядом то же самое в `geocoder.py:75-76` (`EKB_BBOX_TIGHT`, `EKB_BBOX_WIDE`) и `:105` (`OBLAST66_BBOX`). ### Блокер 6 — тип документа теряется при загрузке, ДДУ войдут как продажи В `deals` Меры **нет колонки `doc_type`**, хотя весь оценщик в комментариях оперирует «ДКП-сделками». Наверху типы разделены, и для Москвы разница решающая: | Год | ДКП | медиана ДКП | ДДУ | медиана ДДУ | |---|---|---|---|---| | 2026 | 53 327 | 316 096 | 1 371 | 409 672 | | 2025 | 113 522 | 276 388 | 3 072 | 296 152 | | 2024 | 107 005 | 256 250 | **30 627** | **112 743** | Тридцать тысяч ДДУ 2024 года по ценам котлована. Без фильтра они войдут в ценовой коридор как обычные продажи и занизят Москву. По региону 66 перекос меньше (17 911 ДДУ против 254 089 ДКП), поэтому дефект и не проявлялся. ### Поправка к находке 4 В задаче сказано, что для Москвы не существует **ни одного** из источников обогащения. Проверено — верно для трёх из четырёх: | Источник | Москва | |---|---| | `rosreestr_deals` | **есть**: 273 854 ДКП по 77, 367 181 по 50 | | `cad_buildings` | нет — данные начинаются с долготы 39.24, Москва на 37.62 | | `osm_poi_ekb` | нет — 4 845 записей по ЕКБ | | `ekb_districts_geom` | нет — 8 районов ЕКБ | Разница принципиальная: отсутствуют улучшающие тиры, а сам ценовой якорь по Москве есть. Формулировка «ни одного» читается как «данных нет вообще» и ведёт к неверному выводу о невозможности запуска. ### Решение по объёму: запускаем 77, область 50 откладываем | Регион | ДКП | с улицей | Городов | Улиц | |---|---|---|---|---| | 77 Москва | 273 854 | **96.9 %** | 612 | 4 208 | | 66 Свердловская | 254 089 | 85.8 % | 1 540 | 6 381 | | 50 Московская обл. | 367 181 | 68.8 % | **10 121** | 22 708 | Москва проще Екатеринбурга: улица заполнена у 96.9 % против 85.8 %, различных значений города 612 против 1 540. Вся мина текстовых имён сосредоточена в области — 10 121 значение города и 22 708 улиц. Запуск только 77 сжимает проблему ключей города с десяти тысяч значений до шестисот, которые можно перечислить и проверить перед заливкой. Область добавляется следующим шагом, уже с настоящим `city_fias_id`. ### Инвентаризация зашитого ЕКБ 109 совпадений в 25 файлах, три класса: **Жёсткие гейты** — `location_index.py:65-73`, `geocoder.py:75-76,105,129,292,298,319`, `normalize.py:117`. **Параметризуемое** — `scheduler.py:309`, `yandex_address_backfill.py:82`, `zhkh_flats_loader.py:368`, `gar_flats_load.py:98`, `sber_index.py:72,453`. **Калибровочный перекос** — `estimator.py:2308` (константы вторички ЕКБ применяются ко всему движку) и `estimator.py:4884-4887`, где `premium_buildings_curated` **активно депромоутит не-ЕКБ описания как шум**. Для Москвы это прямой вред, а не просто неточность. Образец решения уже есть в коде: `house_imv_backfill.py:143` держит `_REGION_BBOX` списком регионов. Его надо поднять в общий реестр и заставить потреблять всех остальных. ### Разбиение Задача раскладывается на три и дробится дальше по первой части: #3051 (ключи регионов), #3052 (гард по полигону), #3053 (решение по FDW).
Collaborator

Перепроверка 27.08.2026 (после пересборки базы 25-26.08 и переезда продукта на Poincare). Тело тикета писалось 20-24.08 по старой инфраструктуре.

Посылка держится, две поправки по фактам.

Подтверждено: город входит в формулу текстом — estimator.py:1819 и :1900, LEFT JOIN deal_city_price_bands b ON b.city = d.city, плюс LOWER(d.city) = CAST(:target_city AS text). Колонки city_fias_id нет ни в одной таблице (проверено по information_schema.columns).

Поправки: у houses теперь есть region_code (он же в deals, listings, gar_house_flats, gendesign_rosreestr_deals) — отсутствует только city. Масштаб текстовых ключей больше заявленного: 384 в deals и 383 в deal_city_price_bands, а не 7 городов.

**Перепроверка 27.08.2026** (после пересборки базы 25-26.08 и переезда продукта на Poincare). Тело тикета писалось 20-24.08 по старой инфраструктуре. Посылка держится, две поправки по фактам. Подтверждено: город входит в формулу текстом — `estimator.py:1819` и `:1900`, `LEFT JOIN deal_city_price_bands b ON b.city = d.city`, плюс `LOWER(d.city) = CAST(:target_city AS text)`. Колонки `city_fias_id` нет **ни в одной** таблице (проверено по `information_schema.columns`). Поправки: у `houses` теперь **есть** `region_code` (он же в `deals`, `listings`, `gar_house_flats`, `gendesign_rosreestr_deals`) — отсутствует только `city`. Масштаб текстовых ключей больше заявленного: 384 в `deals` и 383 в `deal_city_price_bands`, а не 7 городов.
Sign in to join this conversation.
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#2996
No description provided.