fix(tradein/dedup): остаток схлопывания домов становится измеряемым числом, а не оценкой (#2690) #2820

Merged
bot-backend merged 1 commit from fix/2690-dedup-residual into main 2026-08-10 11:18:20 +00:00

1 commit

Author SHA1 Message Date
bot-backend
e1586d1307 fix(tradein/dedup): остаток схлопывания домов становится измеряемым числом, а не оценкой
All checks were successful
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m58s
#2690 просил ключ, независимый от нормализованного адреса. Проверил на живой таблице
все поля houses — независимого наблюдения нет ни одного, и это вывод, а не пауза.

  cadastral_number  2648 заполнено, ВСЕ 2648 значений различны → схлопывает ноль. Все 2648
                    несут dadata_enriched_at и house_fias_id, то есть это ответ DaData на
                    нашу же строку адреса. Второй кадастр (KNN-подсказка листингов) отвергнут
                    ещё в #2674: 20.1% значений накрывают >1 здания ГАР.
  house_fias_id     3678 заполнено, все различны → ФИАС-проход сегодня сливает 0 строк.
  gar_house_guid    перемерено: из 458 пар с общим guid 441 делят канон (guid его повторяет),
                    17 нет — и 5 из них дальше 250 м, худшая 5064 км. Круговой как был.
  zhkh_house_guid   выглядит внешним реестром и им не является: loader ставит его
                    WHERE gar_house_guid = <guid>, т.е. это И ЕСТЬ ГАР-guid у 4268 из 4663.
                    Из 194 пар с РАЗНЫМ каноном 193 приходят кадастровым фолбэком (та же
                    KNN-подсказка), и 30 из 31 пары дальше 250 м — тоже он.
  source+ext_house_id / cian_internal_house_id / yandex_jk_id — различны по построению / 39 / 0.
  координаты        настоящее независимое наблюдение, но не идентичность: у соседей общий двор.
                    Уже используются единственным осмысленным способом — как страж.
  год+этажность     ложный свидетель: из 391 пары, которую страж判 не может рассудить, оба поля
                    совпадают у 18 (у 357 есть NULL), зато у 306 пар, отвергнутых стражем
                    дальше 250 м, они совпадают — признак подтвердил бы заведомо неверное.

Поэтому ключ не усиливаю. Вместо этого фиксирую остаток числом, которое живёт: перепись
после обоих проходов пишет в counters прогона, сколько однокононных строк осталось и ПОЧЕМУ.
Разовый замер протухает быстро — «781 лишняя строка» из шапки задачи через четыре дня стала
963, после того как прогон удалил 821.

Корзины намеренно НЕ складываются в один «остаток»: «страж молчит» (нет координат у стороны)
и «страж отверг» (дальше 250 м) — противоположные факты. Прод 2026-08-10, 963 строки:
568 из них дальше 250 м, медиана 1084 м — это вообще не дубли, канон-ключ о них врёт.
Отдельная корзина residual_mergeable — растяжка на сам проход: прошло все стражи и не слилось,
ожидание 0.

Перепись делит с канон-проходом один и тот же префикс кластеризации (_ranked_cte) — своя копия
разъехалась бы с проходом, который описывает, и разъезд был бы невидим. Падение переписи не
роняет слияние: слияние — продукт, счётчик — прибор.

Прод-сверка ДО мержа: отрендеренный _RESIDUAL_SQL на боевой БД даёт ровно то, что независимо
намерено вручную — 963 / 1765 объявлений / 326 молчит / 568 отверг / 8 cross-fias / 61 сольётся.

Попутно две правки честности шапки:
  * снято утверждение «cadastral_number is 100% NULL on prod» — неверно с 2648 строк, и вывод
    про адресный ключ на нём больше не держится;
  * записан результат проверки правила выбора победителя (#2690 п.3). Правило было починено
    в #2674 (listing_cnt DESC NULLS LAST) и проверено задним числом по house_merge_log: из 821
    слияния 08.08 ноль выбрали победителя беднее проигравшего. Контрфактика старого правила на
    тех же кластерах — 6 из 762 забрали бы пустого победителя. Там же предупреждение: мерить
    победителя до слияния по listings.scraped_at нельзя, #2206 двигает его при ре-подтверждении
    и порождает 207 несуществующих «худших победителей».

Гео-ограждение 250 м не тронуто. Расшивка уже слитого не предлагается.
Тесты красные на старом коде: 4 из 4 (KeyError residual_rows / нет _RESIDUAL_SQL).

Refs #2690
2026-08-10 16:12:28 +05:00