Ревью справедливо поймало два места, где текст после снятия предиката стал
неточным:
1. Якорь в deploy/import-rosreestr.sh обещал «потребителей, фильтрующих по
d.rooms, больше нет» — это верно только про предикаты РАВЕНСТВА.
app/tasks/asking_to_sold_ratio.py:148,152 по-прежнему КЛЮЧУЕТСЯ этим
бакетом (GROUP BY LEAST(GREATEST(rooms,0),4)) и намеренно зеркалит ту же
синтетику на листинговой стороне (#2620). Прежняя формулировка сказала бы
будущему редактору, что проверять некого, — а в сценарии «поменяли CASE на
реальную комнатность» вернулся бы именно #2620.
2. Докстринг GET /sales-vs-listings обещал listing «с такими же rooms». После
снятия предиката это верно для пары запрос↔объявление, но не для пары
сделка↔объявление: deal_rooms может не совпадать с запрошенным rooms.
Кода правка не касается. Полный сьют: 5946 passed, 35 skipped; ruff чист.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`street_sales_vs_listings()` фильтровала сделки по `d.rooms`, а `deals.rooms` у
источника 'rosreestr' — не комнатность, а бакет площади: импортёр пишет туда
`CASE WHEN area < 30 THEN 0 ... ELSE 4 END`, и на проде 321 559 строк из 321 560
удовлетворяют `rooms == area_bucket(area_m2)`. Предикат работал вторым фильтром
по площади поверх полосы ±15 %, которую функция считает сама: клиенту 49 м² / 1к
полоса 41.7–56.4 м² урезалась до «меньше 44 м²».
Ровно эта патология снята в #3256 (PR #3445) на четырёх сделочных площадках
эстиматора. Здесь — последний оставшийся потребитель, и ключ асимметричный:
`d.rooms` снят, `l.rooms` ОСТАВЛЕН (у объявлений комнатность настоящая, это
единственный признак ассортимента на листинговой стороне).
Миграция 300 = тело 211 минус ровно одна строка; сигнатура и RETURNS TABLE
побайтово те же (иначе CREATE OR REPLACE создал бы вторую перегрузку, #2627),
сегментный гард #2660/#1186 и city-предикаты #2583 H4 перенесены дословно.
Прод-замер (2026-09-12, 1160 реальных клиентских запросов из trade_in_estimates,
улица извлеклась у 954; тем же путём, что у продукта — extract_street_name /
_resolve_target_city):
- непустой ответ /sales-vs-listings: 805 (84.4 %) → 899 (94.2 %), впервые
непустых 94 клиента;
- сделок в выборке: 68 147 → 88 816;
- из них с подобранным объявлением (то, что показывается парами): 30 830 → 39 193;
- выборка не сократилась ни у кого (0 из 954) — предикат умел только резать.
Прогноз в issue был «те же 180 клиентов»; измерено 94 — оценка 180 бралась по
коридору эстиматора с другими period/tolerance, в файл положено измеренное.
Тело проверено EXPLAIN'ом на боевой БД (только чтение) — планировщик принимает.
Тесты: tests/test_migration_300_sales_vs_listings_deals_rooms.py — статические
гарды. Фальсификация обоих направлений: вернул `d.rooms = p_rooms` → красные
test_deals_side_has_no_rooms_predicate + test_only_the_rooms_predicate_differs_from_211;
снял заодно `l.rooms = p_rooms` («починил симметрично») → красный
test_listings_side_keeps_rooms_predicate. Полный сьют: 5946 passed, 35 skipped.
Якорь в deploy/import-rosreestr.sh обновлён: потребителей, фильтрующих по
d.rooms, больше нет.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>