tradein/houses: хвосты после #2675 — camelCase-типы домов не нормализованы у источника, расхождение двух путей по монолиту, 369 замороженных строк #2678

Closed
opened 2026-08-05 19:56:23 +00:00 by bot-backend · 3 comments
Collaborator

Шесть хвостов из ревью PR #2675. Каждый мелкий, ни один не блокировал мерж, но вместе они про одно: тип дома нормализуется на чтении, а не у источника, и потому чинится по одному читателю за раз.

1. Корень camelCase живёт в таблице домов и не вычищен

Миграция 141 нормализовала типы домов только в объявлениях. В самой таблице домов до сих пор лежат monolithBrick ×56, stalin ×3, aerocreteBlock ×1, gasSilicateBlock ×1, other ×18.

PR #2675 лечит одного читателя — домовую оценку. Остальные потребители типа дома продолжают сравнивать camelCase с каноническими значениями объявлений, получая несовпадение: это ложный штраф при подборе аналогов и пропуск оценки на поштучном пути.

Twin-миграция на три строки по образцу 141 закрыла бы всех читателей разом. Это самый выгодный пункт списка.

2. Два пути шлют Авито разные значения для монолит-кирпичного дома

Домовой путь шлёт monolithic, и это подтверждено эхом ответа площадки: параметр 498 принимает 5244 кирпич / 5245 панель / 5246 блок / 5247 монолит.

Поштучный путь шлёт monolith и monolith_brick. Строк с такими значениями в базе ноль, поэтому прямой проверки нет.

Гипотеза, которую стоит проверить: идентификаторы идут подряд 5244–5247, то есть ровно четыре значения — похоже на полный перечень площадки без гибридного типа. Если так, то monolith_brick в их словаре нет вовсе, и поштучный путь для монолитных домов даёт систематический сдвиг.

Закрывается одним зондирующим запросом после починки прокси. Заодно проверить wood — его мы тоже ни разу не отправляли.

3. Мода ремонта у трети домов опирается на одно объявление

По очереди из 5382 домов: у 32.3% нет ни одного объявления с типом ремонта (уйдут в дефолт), у 29.9% — ровно одно, у 21.3% — два-три, и лишь у 16.5% — четыре и больше.

Плюс выборка не фильтруется по активности и не дедуплицируется: архивные объявления и ре-листинги весят наравне с живыми, а по нашему же дедупу выживает около 11% строк — то есть один ре-листинг может утащить моду.

Решение брать моду всё равно правильное (медианы комнат и площади живут на такой же выборке, а одно реальное наблюдение лучше константы), но стоит ограничить агрегат активными объявлениями и писать в лог размер опоры — чтобы потом было чем спорить.

4. Триста шестьдесят девять замороженных строк

У домов, чей тип теперь признан неизвестным, в базе остаются строки «панель/косметика», которые оценщик продолжает читать. Починка их не лечит, а замораживает в неправильном состоянии.

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

Варианты: пометить провенанс (миграция плюс правка читателя) либо удалить строки. Второе самосогласованно с тем, что 883 дома из очереди теперь маркера не получат вовсе — то есть отсутствие маркера станет нормой, а не исключением.

5. Пять секунд сна за дом, которому мы не отправили запрос

Пауза между запросами стоит безусловно, статус не проверяется. Батч из 50 пропусков — около четырёх минут сна впустую, и PR #2675 добавляет к существующим пропускам ещё примерно четверть. Правка на две строки.

6. monolith_brick не распознаётся общим нормализатором

Сквозной проброс канонических значений регистрозависим, а в словаре соответствий есть все канонические токены кроме monolith_brick. Живого риска сейчас нет — значений в верхнем регистре в базе ноль, — но это ровно та мина, которая ждёт нового источника данных. Одна строка.

Связано: #2675, #2674, #2677, миграция 141.

Шесть хвостов из ревью PR #2675. Каждый мелкий, ни один не блокировал мерж, но вместе они про одно: тип дома нормализуется на **чтении**, а не у источника, и потому чинится по одному читателю за раз. ## 1. Корень camelCase живёт в таблице домов и не вычищен Миграция 141 нормализовала типы домов **только в объявлениях**. В самой таблице домов до сих пор лежат `monolithBrick` ×56, `stalin` ×3, `aerocreteBlock` ×1, `gasSilicateBlock` ×1, `other` ×18. PR #2675 лечит **одного читателя** — домовую оценку. Остальные потребители типа дома продолжают сравнивать camelCase с каноническими значениями объявлений, получая несовпадение: это ложный штраф при подборе аналогов и пропуск оценки на поштучном пути. Twin-миграция на три строки по образцу 141 закрыла бы всех читателей разом. **Это самый выгодный пункт списка.** ## 2. Два пути шлют Авито разные значения для монолит-кирпичного дома Домовой путь шлёт `monolithic`, и это **подтверждено эхом ответа площадки**: параметр 498 принимает 5244 кирпич / 5245 панель / 5246 блок / 5247 монолит. Поштучный путь шлёт `monolith` и `monolith_brick`. Строк с такими значениями в базе **ноль**, поэтому прямой проверки нет. Гипотеза, которую стоит проверить: идентификаторы идут подряд 5244–5247, то есть ровно четыре значения — похоже на полный перечень площадки **без гибридного типа**. Если так, то `monolith_brick` в их словаре нет вовсе, и поштучный путь для монолитных домов даёт систематический сдвиг. Закрывается одним зондирующим запросом после починки прокси. Заодно проверить `wood` — его мы тоже ни разу не отправляли. ## 3. Мода ремонта у трети домов опирается на одно объявление По очереди из 5382 домов: у **32.3%** нет ни одного объявления с типом ремонта (уйдут в дефолт), у **29.9%** — ровно одно, у 21.3% — два-три, и лишь у 16.5% — четыре и больше. Плюс выборка не фильтруется по активности и не дедуплицируется: архивные объявления и ре-листинги весят наравне с живыми, а по нашему же дедупу выживает около 11% строк — то есть один ре-листинг может утащить моду. Решение брать моду всё равно правильное (медианы комнат и площади живут на такой же выборке, а одно реальное наблюдение лучше константы), но стоит ограничить агрегат активными объявлениями и писать в лог размер опоры — чтобы потом было чем спорить. ## 4. Триста шестьдесят девять замороженных строк У домов, чей тип теперь признан неизвестным, в базе остаются строки «панель/косметика», которые оценщик продолжает читать. Починка их не лечит, а **замораживает** в неправильном состоянии. По цене влияние близко к нулю: подмешивание якоря однонаправленное вверх, поэтому заниженный «панельный» якорь оценку вниз не тянет. Но рекомендованная цена **показывается клиенту** как маркер на шкале, и там 369 домов продолжат показывать число, посчитанное по выдуманному типу. Варианты: пометить провенанс (миграция плюс правка читателя) либо удалить строки. Второе самосогласованно с тем, что 883 дома из очереди теперь маркера не получат вовсе — то есть отсутствие маркера станет нормой, а не исключением. ## 5. Пять секунд сна за дом, которому мы не отправили запрос Пауза между запросами стоит безусловно, статус не проверяется. Батч из 50 пропусков — около четырёх минут сна впустую, и PR #2675 добавляет к существующим пропускам ещё примерно четверть. Правка на две строки. ## 6. `monolith_brick` не распознаётся общим нормализатором Сквозной проброс канонических значений регистрозависим, а в словаре соответствий есть все канонические токены **кроме** `monolith_brick`. Живого риска сейчас нет — значений в верхнем регистре в базе ноль, — но это ровно та мина, которая ждёт нового источника данных. Одна строка. Связано: #2675, #2674, #2677, миграция 141.
Author
Collaborator

Пункты 1 и 6 закрыты в PR #2719 — прод-верифицировано.

Замер прода 2026-08-06 (до правки): monolithBrick 56 (48 derived + 8 cian_newbuilding) · other 18 · stalin 3 · aerocreteBlock 1 · gasSilicateBlock 1 · wireframe 1.

Перед склейкой проверено, что значения означают одно и то же — контрольная группа в своих же данных (типы объявлений того же дома, уже нормализованных 141): у всех 56 monolithBrick среди объявлений есть monolith_brick; stalin → brick; aerocreteBlock/gasSilicateBlock → block. А other разбросан по monolith/monolith_brick/brick/block — это «неизвестно», а не материал, поэтому он и wireframe НЕ склеены.

Миграция 224 применена на проде 10:01:49 UTC, тронула ровно 61 строку (совпало с расчётом): monolith_brick 3 → 59, brick 398 → 401, block 92 → 94, camelCase-значений не осталось, other 18 и wireframe 1 на месте, суммарное число непустых типов не изменилось (1068).

Уточнение к формулировке пункта 1: «корень camelCase живёт в таблице домов» — живого писателя camelCase там нет. У всех 80 неканоничных строк одна и та же метка last_scraped_at = 2026-05-24 14:04:20.012209, то есть единственный прогон backfill'а 063 (промоут из listings ДО миграции 141). Зато нашёлся ДРУГОЙ живой источник неканона, которого в issue не было: avito-каталог домов (единственный живой писатель houses.house_type) превращал незнакомую подпись в 'other'. Он переведён на общий normalize_house_type — теперь неизвестное идёт как NULL. Проверено внутри tradein-scraper: Саманный → None, Монолитно-кирпичный → monolith_brick, monolithBrick → monolith_brick, MONOLITH_BRICK → monolith_brick.

Остаются открытыми: 2 (зондирующий запрос к площадке — живые запросы в той сессии были запрещены), 3 (опора моды ремонта), 4 (369 замороженных строк), 5 (сон за пропущенный дом).

Пункты **1** и **6** закрыты в PR #2719 — прод-верифицировано. Замер прода 2026-08-06 (до правки): `monolithBrick` 56 (48 derived + 8 cian_newbuilding) · `other` 18 · `stalin` 3 · `aerocreteBlock` 1 · `gasSilicateBlock` 1 · `wireframe` 1. Перед склейкой проверено, что значения означают одно и то же — контрольная группа в своих же данных (типы объявлений того же дома, уже нормализованных 141): у всех 56 `monolithBrick` среди объявлений есть `monolith_brick`; `stalin` → brick; `aerocreteBlock`/`gasSilicateBlock` → block. А `other` разбросан по monolith/monolith_brick/brick/block — это «неизвестно», а не материал, поэтому он и `wireframe` НЕ склеены. Миграция 224 применена на проде 10:01:49 UTC, **тронула ровно 61 строку** (совпало с расчётом): `monolith_brick` 3 → 59, `brick` 398 → 401, `block` 92 → 94, camelCase-значений не осталось, `other` 18 и `wireframe` 1 на месте, суммарное число непустых типов не изменилось (1068). Уточнение к формулировке пункта 1: «корень camelCase живёт в таблице домов» — живого писателя camelCase там нет. У всех 80 неканоничных строк одна и та же метка `last_scraped_at = 2026-05-24 14:04:20.012209`, то есть единственный прогон backfill'а 063 (промоут из listings ДО миграции 141). Зато нашёлся ДРУГОЙ живой источник неканона, которого в issue не было: avito-каталог домов (единственный живой писатель `houses.house_type`) превращал незнакомую подпись в `'other'`. Он переведён на общий `normalize_house_type` — теперь неизвестное идёт как NULL. Проверено внутри `tradein-scraper`: `Саманный → None`, `Монолитно-кирпичный → monolith_brick`, `monolithBrick → monolith_brick`, `MONOLITH_BRICK → monolith_brick`. Остаются открытыми: **2** (зондирующий запрос к площадке — живые запросы в той сессии были запрещены), **3** (опора моды ремонта), **4** (369 замороженных строк), **5** (сон за пропущенный дом).
Author
Collaborator

Проверка на проде 2026-08-07: п.1 и п.6 подтверждаю, п.2–п.5 открыты

п.1 и п.6 — на проде, независимо перепроверено. В houses не осталось ни одного
camelCase-значения:

brick 401 · panel 331 · monolith 162 · block 94 · monolith_brick 59 · other 18 · wood 2 · wireframe 1

monolithBrick/stalin/aerocreteBlock/gasSilicateBlock — ноль строк. other (18) и
wireframe (1) на месте, как и было задумано: это «неизвестно», а не материал.

п.2, п.3, п.4, п.5 — не сделано. Проверял не по факту упоминания задачи в PR, а по коду:

  • п.2 (monolith vs monolith_brick, зондирующий запрос к площадке) — живых запросов
    агентам по-прежнему нельзя, ничего не изменилось.
  • п.3 (опора моды ремонта) — агрегат в house_imv_backfill.py:193-199 остался
    mode() WITHIN GROUP (ORDER BY repair_state) FROM listings WHERE house_id_fk = :hid AND rooms IS NOT NULL AND area_m2 IS NOT NULL: фильтра активности нет, дедупа нет,
    размер опоры в лог не пишется
    . Ровно то, о чём пункт.
  • п.4 (369 замороженных строк) — миграции, помечающей провенанс или удаляющей их, среди
    применённых на проде (217-234) нет.
  • п.5 (сон за пропущенный дом) — house_imv_backfill.py:568-573: await asyncio.sleep( request_delay_sec) стоит безусловно для всех исходов, кроме auth_error; no_params и
    no_address (те самые 15-16 пропусков на прогон) платят полные 5 секунд каждый.

Оставляю открытой под п.2–п.5. Самый дешёвый из оставшихся — п.5, две строки.

## Проверка на проде 2026-08-07: п.1 и п.6 подтверждаю, п.2–п.5 открыты **п.1 и п.6 — на проде, независимо перепроверено.** В `houses` не осталось ни одного camelCase-значения: ``` brick 401 · panel 331 · monolith 162 · block 94 · monolith_brick 59 · other 18 · wood 2 · wireframe 1 ``` `monolithBrick`/`stalin`/`aerocreteBlock`/`gasSilicateBlock` — ноль строк. `other` (18) и `wireframe` (1) на месте, как и было задумано: это «неизвестно», а не материал. **п.2, п.3, п.4, п.5 — не сделано.** Проверял не по факту упоминания задачи в PR, а по коду: - **п.2** (`monolith` vs `monolith_brick`, зондирующий запрос к площадке) — живых запросов агентам по-прежнему нельзя, ничего не изменилось. - **п.3** (опора моды ремонта) — агрегат в `house_imv_backfill.py:193-199` остался `mode() WITHIN GROUP (ORDER BY repair_state) FROM listings WHERE house_id_fk = :hid AND rooms IS NOT NULL AND area_m2 IS NOT NULL`: **фильтра активности нет, дедупа нет, размер опоры в лог не пишется**. Ровно то, о чём пункт. - **п.4** (369 замороженных строк) — миграции, помечающей провенанс или удаляющей их, среди применённых на проде (217-234) нет. - **п.5** (сон за пропущенный дом) — `house_imv_backfill.py:568-573`: `await asyncio.sleep( request_delay_sec)` стоит безусловно для всех исходов, кроме `auth_error`; `no_params` и `no_address` (те самые 15-16 пропусков на прогон) платят полные 5 секунд каждый. Оставляю открытой под п.2–п.5. Самый дешёвый из оставшихся — п.5, две строки.
lekss361 added the
bug
data
priority/p2
scope/backend
tradein
labels 2026-08-16 10:25:15 +00:00
Owner

Ревизия открытых задач 2026-08-30. Проверено в коде на forgejo/main — сделано, закрываю.

Тип дома канонизируется у источника, словарь один. tests/test_2678_house_type_canon.py, кейсы взяты из фактического замера прода.

Ревизия открытых задач 2026-08-30. Проверено в коде на forgejo/main — сделано, закрываю. Тип дома канонизируется у источника, словарь один. tests/test_2678_house_type_canon.py, кейсы взяты из фактического замера прода.
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#2678
No description provided.