Бэктест калибрует оценщик с выключенным штрафом за тип дома: deals.house_type пуст у 96 974 из 96 974, у источника заполнен на 99.74% #2862

Open
opened 2026-08-13 09:03:07 +00:00 by bot-backend · 0 comments
Collaborator

Гейт калибрует ту сборку оценщика, которой на проде не существует

Найдено при разборе пустых колонок (#2674). В отличие от прочих находок того разбора, эта не про схему, а про достоверность самого замера.

Механизм

Штраф за несовпадение типа дома (estimator.py:5755-5756):

AND house_type IS NOT NULL
AND house_type <> CAST(:target_house_type AS text)

Условие требует непустой тип и у аналога, и у цели. При пустом целевом сравнение house_type <> NULL даёт NULL — то есть штраф не срабатывает никогда, молча.

Перепроверка на истории (scripts/backtest_estimator.py:1325) подаёт house_type=deal.house_type, а deals.house_type пуст у 96 974 из 96 974. Значит на всех 277 сделках гейта признак выключен.

На боевом пути он включён: тип приходит из формы либо из данных дома, а у аналогов заполнен у 25 744 из 99 528 объявлений (25.9%) и у 1045 из 9486 домов.

Итог: регрессионный гейт меряет оценщик с выключенным признаком, а прод работает с включённым. Любое число этого гейта — про сборку, которая не выпускается.

Где обрывается — не у Росреестра

У источника тип дома заполнен у 96 725 из 96 974 = 99.74%, 114 различных кодов. Обрыв на нашей же проекции: внешняя таблица gendesign_rosreestr_deals объявлена в 12 колонок, wall_material_code в них не заведён (072_scrape_schedules_seed_cian_rosreestr.sql:33-48).

Контроль, что атрибуты дома границу вообще переходят: год постройки у источника 94 834 → в deals 94 834. Значит виноват не источник.

Починка — три шага, а не один

  1. Завести колонку во внешнюю таблицу и в список вставки (12 → 13).
  2. Написать соответствие 114 кодов справочника 126-УНСИ семи значениям нашего словаря. В репозитории такого соответствия нет ни одного, плюс схема источника предупреждает о составных значениях вида «код1;код2». Это основная работа, и она требует справочника, а не кода.
  3. Разовый пересчёт: суточный прогон обновляет 10 фактовых колонок, все 96 974 строки каждый день уходят в пропущенные — само не дозаполнится.

Чего делать НЕ надо

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

Как проверить, что починка сработала

Не «колонка заполнилась», а сдвинулись ли метрики гейта. Если после заполнения unrecorded-подобные счётчики и MAPE не изменятся вовсе — значит признак и на покрытых путях ничего не решает, и это тоже ответ, который надо записать.

Связано: #2674.

## Гейт калибрует ту сборку оценщика, которой на проде не существует Найдено при разборе пустых колонок (#2674). В отличие от прочих находок того разбора, эта не про схему, а про **достоверность самого замера**. ## Механизм Штраф за несовпадение типа дома (`estimator.py:5755-5756`): ```sql AND house_type IS NOT NULL AND house_type <> CAST(:target_house_type AS text) ``` Условие требует непустой тип **и у аналога, и у цели**. При пустом целевом сравнение `house_type <> NULL` даёт `NULL` — то есть штраф не срабатывает **никогда**, молча. Перепроверка на истории (`scripts/backtest_estimator.py:1325`) подаёт `house_type=deal.house_type`, а `deals.house_type` пуст у **96 974 из 96 974**. Значит на всех 277 сделках гейта признак выключен. На боевом пути он включён: тип приходит из формы либо из данных дома, а у аналогов заполнен у **25 744 из 99 528 объявлений (25.9%)** и у **1045 из 9486 домов**. **Итог:** регрессионный гейт меряет оценщик с выключенным признаком, а прод работает с включённым. Любое число этого гейта — про сборку, которая не выпускается. ## Где обрывается — не у Росреестра У источника тип дома заполнен у **96 725 из 96 974 = 99.74%**, 114 различных кодов. Обрыв на **нашей же проекции**: внешняя таблица `gendesign_rosreestr_deals` объявлена в 12 колонок, `wall_material_code` в них не заведён (`072_scrape_schedules_seed_cian_rosreestr.sql:33-48`). Контроль, что атрибуты дома границу вообще переходят: год постройки у источника 94 834 → в `deals` 94 834. Значит виноват не источник. ## Починка — три шага, а не один 1. Завести колонку во внешнюю таблицу и в список вставки (12 → 13). 2. **Написать соответствие 114 кодов справочника 126-УНСИ семи значениям нашего словаря.** В репозитории такого соответствия нет ни одного, плюс схема источника предупреждает о составных значениях вида «код1;код2». Это основная работа, и она требует справочника, а не кода. 3. Разовый пересчёт: суточный прогон обновляет 10 фактовых колонок, все 96 974 строки каждый день уходят в пропущенные — само не дозаполнится. ## Чего делать НЕ надо Не подставлять приблизительное соответствие «на глаз», чтобы закрыть пункт. Неверный тип у сделки хуже пустого: пустой честно выключает штраф, неверный включает его в **неправильную** сторону и делает гейт не менее, а более обманчивым. ## Как проверить, что починка сработала Не «колонка заполнилась», а **сдвинулись ли метрики гейта**. Если после заполнения `unrecorded`-подобные счётчики и MAPE не изменятся вовсе — значит признак и на покрытых путях ничего не решает, и это тоже ответ, который надо записать. Связано: #2674.
lekss361 added the
bug
data
priority/p2
scope/backend
scope/db
tradein
вторичка
labels 2026-08-16 10:25:27 +00:00
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#2862
No description provided.