Коэффициент asking→sold вне ЕКБ считается на согласованной географии #3531

Merged
lekss361 merged 1 commit from fix/ratio-geography-match into main 2026-09-15 17:18:07 +00:00
Owner

Пункт 3 долга: «Коэффициент области 0,891 может быть завышен из-за несовпадения состава муниципалитетов в числителе и знаменателе».

Что было

Для регионов кроме 66 обе стороны фильтровались ТОЛЬКО по region_code и соединялись ТОЛЬКО по бакету комнат (JOIN ask_side a USING (rooms_bucket)). Sold-медиана и ask-медиана считались по разным географическим популяциям одного региона: объявления смещены к дальней дешёвой периферии сильнее, чем сделки. Отсюда 0.891 у области при 0.73 у Москвы и 0.73 у ЕКБ.

Что стало

Обе стороны раскладываются по одной регулярной сетке FLOOR(ST_Y(geom)/0.1) × FLOOR(ST_X(geom)/0.2) (~11 × 12-16 км), медианы берутся внутри ячейки, в итог идут только ячейки, где обе стороны имеют массу (≥10 сделок И ≥10 объявлений), агрегация взвешена числом сделок: ratio = Σ(w·sold)/Σ(w·ask).

Сетка, а не кольца, — потому что у региона 50 нет своего города-центра. Совмещение по названию муниципалитета невозможно: listings.city там пуста (3 строки из 70 996).

Замер на проде

Рендер-SQL исполнён против прод-БД в транзакции с ROLLBACK (15.09.2026):

регион было (пул) стало (сетка) ячеек с обеими сторонами
50 0.8851 0.8136 68
77 0.7100 0.7287 23

Разнонаправленный сдвиг — подпись поправки состава, а не равномерного смещения. По бакетам область: 0.702 / 0.789 / 0.794 / 0.857 / 0.874.

Гарды и деградация

Регион НЕ получает строк, если: покрытие geom любой стороны <50 %, ИЛИ совпавших ячеек <3, ИЛИ в пересечение попало <50 % геокодированных сделок. DELETE при этом идёт в любом случае — устаревший пуловый коэффициент не должен пережить отказ. Потребитель (estimator._get_asking_sold_ratio) видит отсутствие строки как явную деградацию.

Счётчики geo_r<code>_*: cells_deal, cells_ask, cells_matched, cells_dropped, deals_outside_pct, listings_outside_pct, deals_no_geom_pct, listings_no_geom_pct, skipped, плюс geo_regions_written / geo_regions_skipped. WARNING, когда доля без координат >25 % — у области сейчас 24.5 % и упадёт после геокодирования сделок (отдельный PR).

Что НЕ тронуто

_REDERIVE_SQL и _DELETE_SQL региона 66 — байт в байт, это гейтится тестом. district остаётся пустым: estimator._get_asking_sold_ratio читает строго district = '' (разбиение по районам — #647).

70 тестов зелёные.

Пункт 3 долга: «Коэффициент области 0,891 может быть завышен из-за несовпадения состава муниципалитетов в числителе и знаменателе». ## Что было Для регионов кроме 66 обе стороны фильтровались ТОЛЬКО по `region_code` и соединялись ТОЛЬКО по бакету комнат (`JOIN ask_side a USING (rooms_bucket)`). Sold-медиана и ask-медиана считались по разным географическим популяциям одного региона: объявления смещены к дальней дешёвой периферии сильнее, чем сделки. Отсюда 0.891 у области при 0.73 у Москвы и 0.73 у ЕКБ. ## Что стало Обе стороны раскладываются по одной регулярной сетке `FLOOR(ST_Y(geom)/0.1)` × `FLOOR(ST_X(geom)/0.2)` (~11 × 12-16 км), медианы берутся внутри ячейки, в итог идут только ячейки, где обе стороны имеют массу (≥10 сделок И ≥10 объявлений), агрегация взвешена числом **сделок**: `ratio = Σ(w·sold)/Σ(w·ask)`. Сетка, а не кольца, — потому что у региона 50 нет своего города-центра. Совмещение по названию муниципалитета невозможно: `listings.city` там пуста (3 строки из 70 996). ## Замер на проде Рендер-SQL исполнён против прод-БД в транзакции с `ROLLBACK` (15.09.2026): | регион | было (пул) | стало (сетка) | ячеек с обеими сторонами | |---|---|---|---| | 50 | 0.8851 | **0.8136** | 68 | | 77 | 0.7100 | **0.7287** | 23 | Разнонаправленный сдвиг — подпись поправки состава, а не равномерного смещения. По бакетам область: 0.702 / 0.789 / 0.794 / 0.857 / 0.874. ## Гарды и деградация Регион НЕ получает строк, если: покрытие geom любой стороны <50 %, ИЛИ совпавших ячеек <3, ИЛИ в пересечение попало <50 % геокодированных сделок. `DELETE` при этом идёт в любом случае — устаревший пуловый коэффициент не должен пережить отказ. Потребитель (`estimator._get_asking_sold_ratio`) видит отсутствие строки как явную деградацию. Счётчики `geo_r<code>_*`: `cells_deal`, `cells_ask`, `cells_matched`, `cells_dropped`, `deals_outside_pct`, `listings_outside_pct`, `deals_no_geom_pct`, `listings_no_geom_pct`, `skipped`, плюс `geo_regions_written` / `geo_regions_skipped`. WARNING, когда доля без координат >25 % — у области сейчас 24.5 % и упадёт после геокодирования сделок (отдельный PR). ## Что НЕ тронуто `_REDERIVE_SQL` и `_DELETE_SQL` региона 66 — байт в байт, это гейтится тестом. `district` остаётся пустым: `estimator._get_asking_sold_ratio` читает строго `district = ''` (разбиение по районам — #647). 70 тестов зелёные.
lekss361 added 1 commit 2026-09-15 17:08:55 +00:00
fix(tradein): считать asking→sold коэффициент на согласованной географии вне ЕКБ
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
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 5m27s
1c714aeb88
Регионы кроме 66 фильтровали обе стороны ТОЛЬКО по region_code и соединяли их
ТОЛЬКО по бакету комнат — sold-медиана и ask-медиана считались по разным
географическим популяциям одного региона. Объявления смещены к дальней дешёвой
периферии сильнее, чем сделки, поэтому область 50 давала 0.891 при 0.73 у Москвы
и 0.73 у ЕКБ; разложение по кольцам 10 км показывает 0.808/0.847/0.951/0.688
ВНУТРИ колец и 0.81-0.85 в ближних кольцах, где лежит 76% сделок.

Обе стороны теперь раскладываются по одной регулярной сетке 0.1°x0.2° (~11x12-16 км
на широтах 45-60°N — масштаб, на котором замер показал устойчивость отношения),
медианы берутся внутри ячейки, в итог идут только ячейки с обеими сторонами, и
агрегация взвешена числом СДЕЛОК: ratio = Σ(w·sold)/Σ(w·ask). Сетка, а не кольца,
потому что у региона 50 нет своего города-центра; совмещение по названию
муниципалитета невозможно — listings.city там пуста (3 строки из 70 996).

Путь ЕКБ (_REDERIVE_SQL) не тронут — там своя историческая калибровка городской квотой.

Гарды и наблюдаемость: регион не получает НИ ОДНОЙ строки (старые всё равно
удаляются), если сторон без geom больше половины, ячеек с обеими сторонами <3 или
в пересечение попало <50% геокодированных сделок — оценка честно остаётся без
коэффициента вместо неверного. Порегионные счётчики: ячеек на сторону, ячеек
отброшено по порогу, доля сделок/объявлений вне пересечения и доля строк без geom
(>25% — WARNING, чтобы негеокодированные строки не выпадали молча).

district остаётся пустым: ячейка — промежуточная единица расчёта, а не публикации,
а потребитель читает строго district = '' (гео-разрез — отдельная задача #647).
lekss361 merged commit 39996ef3fd into main 2026-09-15 17:18:07 +00:00
lekss361 deleted branch fix/ratio-geography-match 2026-09-15 17:18:08 +00:00
Sign in to join this conversation.
No reviewers
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#3531
No description provided.