fix(tradein/cian): читать bti из offerData — BTI-персист в houses писал ноль строк (#2435) #2668

Merged
bot-backend merged 1 commit from feat/2435-cian-house-enrichment into main 2026-08-05 18:54:36 +00:00
Collaborator

Что было

#2435 уже был реализован и смержен — PR #2437 (04.07): bti_data пишется в houses через match_or_create_house, valuation house_info/management_company/external_house_id доходят до дома, 11 тестов, 4 call-site'а прокидывают matcher. Issue осталась открытой (Refs, не Closes).

При прод-проверке выяснилось, что записи не появилось ни одной:

метрика (прод, 05.08) значение
домов всего 9361
из них с series_name / entrances / flat_count / is_emergency / heat_supply_type / gas_supply_type / overlap_type 0 / 0 / 0 / 0 / 0 / 0 / 0
Cian-листингов detail-обогащено с момента мержа (05–22.07) 628
из них с house_id_fk (дом резолвится) 589
без адреса (matcher бы отказал) 0
домов, которых вообще касались Cian-объявления 5188

Колонки на месте — утверждение issue подтверждено, миграция не нужна.

Причина

bti читался только как ключ, соседний с defaultState внутри контейнера frontend-offer-card. Cian отдаёт его внутри defaultStateofferData.bti. extract_all_states() на реальной странице возвращает 143 ключа, bti среди них нет → bti_data всегда None → весь write-path #2435 мёртв.

Проверено офлайн на сохранённом реальном HTML (fixtures/cian_flat_330982715.html), без обращения к площадке:

offer-card containers: [... 143 ключа ...]   BTI present: False
offer_data['bti']['houseData'] = {"yearRelease": 1957, "entrances": 3, "flatCount": 24,
  "isEmergency": false, "houseHeatSupplyType": "central",
  "houseGasSupplyType": "central", "houseOverlapType": "concrete", ...}

Почему не поймали тестами: все 11 тестов #2435 кормят bti_data напрямую в save_detail_enrichment, минуя fetch_detail — персист покрыт, извлечение нет.

Что стало

bti_state = offer_data.get("bti") or offer_card_states.get("bti") — старое место оставлено фоллбэком. Плюс поправлен докстринг модуля, который и утверждал неверное («sister containers in same _cianConfig»).

Новый тест гоняет реальный сохранённый HTML через настоящий fetch_detail (мок browser_fetcher, сети нет) и проверяет, что bti_data заполнен ожидаемыми полями; второй тест закрепляет фоллбэк.

Охват

Потолок — 5188 домов, которых касались Cian-объявления (сейчас у них 4153 без house_type, 1337 без total_floors, 444 без year_built). Реально обогатится меньше и не сразу: BTI отдаётся только по вторичке, и заполнение идёт по мере повторных detail-проходов. Прямо сейчас Cian detail-обогащение стоит с 22.07 — до его возобновления эффекта на проде не будет.

Это полнота карточки дома, не точность оценки — оценщик эти поля не использует.

Что НЕ входит

  • Valuation-половина #2435 остаётся непроверяемой на проде: external_valuations для cian_valuation — 139 строк, все с house_id IS NULL, последний fetch 29.06, то есть до мержа #2437; management_companies пуста (0 строк). Код выглядит корректно (estimator передаёт house_id=target_house_id), но ни разу не исполнялся после фикса. Почему прекратились cian_valuation-фетчи — отдельный вопрос, не трогал.
  • listings.views_total — NULL у всех 21 682 Cian-листингов. Тот же класс («распарсили и потеряли»), но другое поле и другая причина: stats.totalViewsFormattedString = "146 просмотров, 8 за сегодня", а _parse_views() делает int(...) после срезания пробелов → всегда ValueErrorNone. Вне scope #2435, стоит отдельной issue.
  • cian_price_history.py: save_detail_enrichment вызывается только когда price_changes непустой — оффер с BTI, но без изменений цены, BTI теряет. Сейчас последствий нет (626 из 628 имели price-history), менять семантику джобы в этом PR не стал.
  • yearRelease / houseMaterialType / floorMax из BTI по-прежнему не пишутся — так было решено в #2437, не расширял.

Test plan

  • test_cian_bti_house_persist.py + test_cian_state_parser.py + test_pipeline_browser_routing.py + test_cian_city_sweep.py + test_cian_history_backfill_listings_rollback.py — 44 passed
  • Фальсификация: без фикса test_fetch_detail_extracts_bti_from_offer_data_on_real_page краснеет (bti_data is None); 11 старых тестов #2435 остаются зелёными — та самая слепая зона
  • ruff check + format (конфиг репо) — чисто
  • После деплоя и возобновления Cian detail-прохода: SELECT count(entrances) FROM houses должен стать > 0

Refs #2435

## Что было #2435 уже был реализован и смержен — PR #2437 (04.07): `bti_data` пишется в `houses` через `match_or_create_house`, valuation `house_info`/`management_company`/`external_house_id` доходят до дома, 11 тестов, 4 call-site'а прокидывают matcher. Issue осталась открытой (`Refs`, не `Closes`). При прод-проверке выяснилось, что **записи не появилось ни одной**: | метрика (прод, 05.08) | значение | |---|---| | домов всего | 9361 | | из них с `series_name` / `entrances` / `flat_count` / `is_emergency` / `heat_supply_type` / `gas_supply_type` / `overlap_type` | **0 / 0 / 0 / 0 / 0 / 0 / 0** | | Cian-листингов detail-обогащено с момента мержа (05–22.07) | 628 | | из них с `house_id_fk` (дом резолвится) | 589 | | без адреса (matcher бы отказал) | 0 | | домов, которых вообще касались Cian-объявления | 5188 | Колонки на месте — утверждение issue подтверждено, миграция не нужна. ## Причина `bti` читался только как ключ, соседний с `defaultState` внутри контейнера `frontend-offer-card`. Cian отдаёт его **внутри** `defaultState` — `offerData.bti`. `extract_all_states()` на реальной странице возвращает 143 ключа, `bti` среди них нет → `bti_data` всегда `None` → весь write-path #2435 мёртв. Проверено офлайн на сохранённом реальном HTML (`fixtures/cian_flat_330982715.html`), без обращения к площадке: ``` offer-card containers: [... 143 ключа ...] BTI present: False offer_data['bti']['houseData'] = {"yearRelease": 1957, "entrances": 3, "flatCount": 24, "isEmergency": false, "houseHeatSupplyType": "central", "houseGasSupplyType": "central", "houseOverlapType": "concrete", ...} ``` Почему не поймали тестами: все 11 тестов #2435 кормят `bti_data` напрямую в `save_detail_enrichment`, минуя `fetch_detail` — персист покрыт, извлечение нет. ## Что стало `bti_state = offer_data.get("bti") or offer_card_states.get("bti")` — старое место оставлено фоллбэком. Плюс поправлен докстринг модуля, который и утверждал неверное («sister containers in same _cianConfig»). Новый тест гоняет реальный сохранённый HTML через настоящий `fetch_detail` (мок `browser_fetcher`, сети нет) и проверяет, что `bti_data` заполнен ожидаемыми полями; второй тест закрепляет фоллбэк. ## Охват Потолок — **5188 домов**, которых касались Cian-объявления (сейчас у них 4153 без `house_type`, 1337 без `total_floors`, 444 без `year_built`). Реально обогатится меньше и не сразу: BTI отдаётся только по вторичке, и заполнение идёт по мере повторных detail-проходов. Прямо сейчас Cian detail-обогащение стоит с 22.07 — до его возобновления эффекта на проде не будет. Это **полнота карточки дома, не точность оценки** — оценщик эти поля не использует. ## Что НЕ входит - **Valuation-половина #2435 остаётся непроверяемой на проде**: `external_valuations` для `cian_valuation` — 139 строк, **все с `house_id IS NULL`**, последний fetch **29.06**, то есть до мержа #2437; `management_companies` пуста (0 строк). Код выглядит корректно (estimator передаёт `house_id=target_house_id`), но ни разу не исполнялся после фикса. Почему прекратились cian_valuation-фетчи — отдельный вопрос, не трогал. - **`listings.views_total` — NULL у всех 21 682 Cian-листингов.** Тот же класс («распарсили и потеряли»), но другое поле и другая причина: `stats.totalViewsFormattedString` = `"146 просмотров, 8 за сегодня"`, а `_parse_views()` делает `int(...)` после срезания пробелов → всегда `ValueError` → `None`. Вне scope #2435, стоит отдельной issue. - **`cian_price_history.py`**: `save_detail_enrichment` вызывается только когда `price_changes` непустой — оффер с BTI, но без изменений цены, BTI теряет. Сейчас последствий нет (626 из 628 имели price-history), менять семантику джобы в этом PR не стал. - `yearRelease` / `houseMaterialType` / `floorMax` из BTI по-прежнему не пишутся — так было решено в #2437, не расширял. ## Test plan - [x] `test_cian_bti_house_persist.py` + `test_cian_state_parser.py` + `test_pipeline_browser_routing.py` + `test_cian_city_sweep.py` + `test_cian_history_backfill_listings_rollback.py` — 44 passed - [x] Фальсификация: без фикса `test_fetch_detail_extracts_bti_from_offer_data_on_real_page` краснеет (`bti_data is None`); 11 старых тестов #2435 остаются зелёными — та самая слепая зона - [x] ruff check + format (конфиг репо) — чисто - [ ] После деплоя и возобновления Cian detail-прохода: `SELECT count(entrances) FROM houses` должен стать > 0 Refs #2435
bot-backend added 1 commit 2026-08-05 18:41:45 +00:00
fix(tradein/cian): читать bti из offerData — BTI-персист в houses писал ноль строк (#2435)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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 / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 2m43s
c232772e70
#2435 (PR #2437) завёл запись BTI-полей дома через match_or_create_house, но на
проде она не дала ни одной строки: 9361 дом, 0 с series_name/entrances/flat_count/
is_emergency/heat_supply_type/gas_supply_type/overlap_type — при 628 detail-
обогащённых Cian-листингах за Jul 5-22 и 5188 домах, которых Cian вообще касался.

Причина: bti читался только как соседний с defaultState ключ контейнера
frontend-offer-card, а Cian отдаёт его ВНУТРИ defaultState — offerData.bti.
extract_all_states() исправно возвращает 143 ключа, но bti среди них нет,
поэтому bti_data всегда оставался None и весь write-path был мёртвым.

Существующие тесты этого не ловили: они кормят bti_data прямо в
save_detail_enrichment, минуя fetch_detail. Новый тест гоняет реальный
сохранённый HTML (fixtures/cian_flat_330982715.html) через настоящий
fetch_detail — без фикса краснеет.

Старое место оставлено фоллбэком.
bot-backend merged commit ef8609d725 into main 2026-08-05 18:54:36 +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#2668
No description provided.