fix(tradein/houses): camelCase-типы домов приводятся к канону у источника (#2678) #2719

Merged
bot-backend merged 1 commit from fix/2678-house-type-canon into main 2026-08-06 09:55:55 +00:00
Collaborator

Что чинит

Хвост #2675 (пункты 1 и 6 из #2678). Миграция 141 привела тип дома к канону только в объявлениях; в таблице домов camelCase-вокабуляр Циана остался, и каждый читатель чинил его у себя — #2675 починил домовую оценку, остальные потребители продолжали сравнивать monolithBrick с monolith_brick и не совпадать (ложный штраф при подборе аналогов, пропуск оценки на поштучном пути).

Замер прода 2026-08-06 (SELECT source, house_type, count(*) FROM houses GROUP BY 1,2)

значение строк где
monolithBrick 56 derived 48 + cian_newbuilding 8
other 18 derived
stalin 3 derived
aerocreteBlock / gasSilicateBlock / wireframe 1 / 1 / 1 derived
канон (brick/panel/monolith/monolith_brick/block/wood) 984 все источники
NULL 8440

Числа совпали с оценкой в #2674 (там «75 домов с camelCase» — фактически 56 camelCase + 3 stalin + 18 other + 1 wireframe = 78 неканоничных, из них мапится 61).

Проверка смысла ПЕРЕД склейкой (контрольная группа в своих же данных)

Для каждой неканоничной строки взяты типы её ЖЕ объявлений (listings.house_id_fk), уже нормализованных миграцией 141:

значение в houses что говорят его объявления вывод
monolithBrick ×56 monolith_brick присутствует у всех 56 одно и то же → склейка безопасна
stalin ×3 brick совпадает с решением 141 («сталинка» = кирпич)
aerocreteBlock ×1 block склейка безопасна
gasSilicateBlock ×1 block склейка безопасна
other ×18 разброс monolith / monolith_brick / brick / block «неизвестно», а не материал → НЕ трогаем
wireframe ×1 wireframe и у объявлений материала в каноне нет → НЕ трогаем

Что сделано

  1. Миграция 224 (twin 141 для houses): monolithBrick→monolith_brick, gasSilicateBlock/aerocreteBlock/foamConcreteBlock→block, stalin→brick. Ожидаемо тронута 61 строка (56+3+1+1+0, счётчики сняты на проде до применения). Идемпотентна: повторный прогон 0 строк. Плюс COMMENT ON COLUMN с каноном и с тем, что осталось вне его намеренно.
  2. Нормализация у источника. Живого писателя camelCase в houses нет — у всех 80 неканоничных строк одна и та же метка last_scraped_at = 2026-05-24 14:04:20.012209, т.е. единственный прогон backfill'а 063 (промоут из listings ДО 141). Единственный живой писатель houses.house_type — avito-каталог домов (providers/avito/houses.py), и он был вторым источником неканона: незнакомую подпись превращал в 'other'. Теперь идёт через общий normalize_house_type, неизвестное → NULL.
  3. #2678 п.6: в _RAW_TO_CANON добавлен канонический токен monolith_brick — единственный, у которого не было регистронезависимой страховки.

Третьего словаря не заводим: тест сверяет CASE миграции с картой кода.

Чего тут НЕТ (и почему)

  • п.2 (monolithic vs monolith_brick в запросах к Авито) — закрывается зондирующим запросом к площадке; живые запросы к площадкам в этой сессии запрещены. Остаётся открытым в #2678.
  • п.3 (мода ремонта на одном объявлении), п.4 (369 замороженных строк), п.5 (сон за пропущенный дом) — вне scope этого PR, остаются в #2678.

Test plan

  • tests/test_2678_house_type_canon.py — каждый ФАКТИЧЕСКИЙ вариант с прода → канон/честный None; писатель avito больше не изобретает 'other'; регистр канона; CASE миграции == карта кода; WHERE не задевает other/wireframe
  • полный прогон tradein-backend: 3664 passed, 9 skipped, 1 deselected (известный test_search_cache_hit)
  • на коде до правки 4 из 22 падают
  • после мержа: SELECT house_type, count(*) FROM houses GROUP BY 1 — camelCase 0, monolith_brick +56, brick +3, block +2

Refs #2678

## Что чинит Хвост #2675 (пункты 1 и 6 из #2678). Миграция 141 привела тип дома к канону только в **объявлениях**; в таблице **домов** camelCase-вокабуляр Циана остался, и каждый читатель чинил его у себя — #2675 починил домовую оценку, остальные потребители продолжали сравнивать `monolithBrick` с `monolith_brick` и не совпадать (ложный штраф при подборе аналогов, пропуск оценки на поштучном пути). ## Замер прода 2026-08-06 (`SELECT source, house_type, count(*) FROM houses GROUP BY 1,2`) | значение | строк | где | |---|---|---| | `monolithBrick` | **56** | derived 48 + cian_newbuilding 8 | | `other` | 18 | derived | | `stalin` | 3 | derived | | `aerocreteBlock` / `gasSilicateBlock` / `wireframe` | 1 / 1 / 1 | derived | | канон (brick/panel/monolith/monolith_brick/block/wood) | 984 | все источники | | NULL | 8440 | — | Числа совпали с оценкой в #2674 (там «75 домов с camelCase» — фактически 56 camelCase + 3 stalin + 18 other + 1 wireframe = 78 неканоничных, из них мапится 61). ## Проверка смысла ПЕРЕД склейкой (контрольная группа в своих же данных) Для каждой неканоничной строки взяты типы её ЖЕ объявлений (`listings.house_id_fk`), уже нормализованных миграцией 141: | значение в houses | что говорят его объявления | вывод | |---|---|---| | `monolithBrick` ×56 | `monolith_brick` присутствует у **всех 56** | одно и то же → склейка безопасна | | `stalin` ×3 | `brick` | совпадает с решением 141 («сталинка» = кирпич) | | `aerocreteBlock` ×1 | `block` | склейка безопасна | | `gasSilicateBlock` ×1 | `block` | склейка безопасна | | `other` ×18 | разброс monolith / monolith_brick / brick / block | **«неизвестно», а не материал → НЕ трогаем** | | `wireframe` ×1 | `wireframe` и у объявлений | материала в каноне нет → **НЕ трогаем** | ## Что сделано 1. **Миграция 224** (twin 141 для `houses`): `monolithBrick→monolith_brick`, `gasSilicateBlock`/`aerocreteBlock`/`foamConcreteBlock→block`, `stalin→brick`. **Ожидаемо тронута 61 строка** (56+3+1+1+0, счётчики сняты на проде до применения). Идемпотентна: повторный прогон 0 строк. Плюс `COMMENT ON COLUMN` с каноном и с тем, что осталось вне его намеренно. 2. **Нормализация у источника.** Живого писателя camelCase в `houses` нет — у всех 80 неканоничных строк одна и та же метка `last_scraped_at = 2026-05-24 14:04:20.012209`, т.е. единственный прогон backfill'а 063 (промоут из listings ДО 141). Единственный живой писатель `houses.house_type` — avito-каталог домов (`providers/avito/houses.py`), и он был **вторым** источником неканона: незнакомую подпись превращал в `'other'`. Теперь идёт через общий `normalize_house_type`, неизвестное → NULL. 3. **#2678 п.6:** в `_RAW_TO_CANON` добавлен канонический токен `monolith_brick` — единственный, у которого не было регистронезависимой страховки. Третьего словаря не заводим: тест сверяет `CASE` миграции с картой кода. ## Чего тут НЕТ (и почему) - **п.2 (`monolithic` vs `monolith_brick` в запросах к Авито)** — закрывается зондирующим запросом к площадке; живые запросы к площадкам в этой сессии запрещены. Остаётся открытым в #2678. - **п.3 (мода ремонта на одном объявлении), п.4 (369 замороженных строк), п.5 (сон за пропущенный дом)** — вне scope этого PR, остаются в #2678. ## Test plan - [x] `tests/test_2678_house_type_canon.py` — каждый ФАКТИЧЕСКИЙ вариант с прода → канон/честный None; писатель avito больше не изобретает `'other'`; регистр канона; CASE миграции == карта кода; WHERE не задевает `other`/`wireframe` - [x] полный прогон tradein-backend: 3664 passed, 9 skipped, 1 deselected (известный `test_search_cache_hit`) - [x] на коде до правки 4 из 22 падают - [ ] после мержа: `SELECT house_type, count(*) FROM houses GROUP BY 1` — camelCase 0, `monolith_brick` +56, `brick` +3, `block` +2 Refs #2678
bot-backend added 1 commit 2026-08-06 09:43:53 +00:00
fix(tradein/houses): camelCase-типы домов приводятся к канону у источника (#2678)
All checks were successful
CI / changes (pull_request) Successful in 10s
CI Trade-In / changes (pull_request) Successful in 10s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m9s
ebffadcfa7
Хвост #2675. Миграция 141 нормализовала тип дома только в объявлениях; в самой
таблице домов camelCase-вокабуляр Циана остался, и каждый читатель чинил его у
себя. #2675 починил домовую оценку — остальные потребители продолжали сравнивать
'monolithBrick' с 'monolith_brick' и не совпадать.

Замер прода 2026-08-06 (GROUP BY source, house_type): monolithBrick 56 (48 derived
+ 8 cian_newbuilding) · other 18 · stalin 3 · aerocreteBlock 1 · gasSilicateBlock 1
· wireframe 1 · канон 984 · NULL 8440.

1. Миграция 224 — twin 141 для houses: monolithBrick→monolith_brick,
   gasSilicateBlock/aerocreteBlock/foamConcreteBlock→block, stalin→brick.
   Ожидаемо тронуто 61 строка (счётчики сняты на проде ДО применения).
   Маппинг тождественен house_type_normalizer._RAW_TO_CANON — третьего словаря нет,
   тест сторожит совпадение.

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

2. Нормализация у источника. Живого писателя camelCase в houses нет: у всех 80
   неканоничных строк одна и та же метка last_scraped_at 2026-05-24 14:04:20 —
   единственный прогон backfill'а 063. Единственный живой писатель
   houses.house_type — avito-каталог домов, и он был вторым источником неканона:
   незнакомую подпись превращал в 'other'. Теперь ходит через общий
   normalize_house_type, неизвестное → NULL (нейтрально для soft-penalty
   эстиматора, в отличие от гарантированно несовпадающего 'other').

3. #2678 п.6: в карту нормализатора добавлен канонический токен monolith_brick —
   единственный, у которого не было регистронезависимой страховки.

Refs #2678
bot-backend merged commit 0144092856 into main 2026-08-06 09:55:55 +00:00
bot-backend deleted branch fix/2678-house-type-canon 2026-08-06 09:55:55 +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#2719
No description provided.