[tradein] Houses Catalog: Cian bti_data/valuation house_info распарсены, но выбрасываются (никогда не пишутся в houses) #2435

Closed
opened 2026-07-04 19:38:20 +00:00 by lekss361 · 2 comments
Owner

Контекст

Пользователь запросил закрыть разрыв "Houses у Cian/Yandex (только Avito)" по аналогии с Avito. Полная разведка (Explore-агент, 2026-07-04) показала: houses/house_reviews/house_placement_history — provider-agnostic с самого начала (source text, колонки под cian/yandex уже добавлены в 020_houses_alter_cian.sql/031_houses_alter_yandex.sql), houses матчатся по адресу/гео межисточниково (match_or_create_house), не по listing source — т.е. разрыв это в основном UX-полнота HouseInfoCard.tsx (пусто для домов, тронутых только Cian/Yandex-листингами), НЕ точность оценки (estimator.py не использует rating/amenity-поля вообще, только price-history, который уже частично cross-source через yandex_valuation).

Cian — конкретный, узкий разрыв (эта issue)

  1. providers/cian/detail.py:59-60,175-182DetailEnrichment.bti_data (series_name/entrances/flat_count/is_emergency/heat_supply_type/gas_supply_type/overlap_type) парсится из того же _cianConfig state, что и сам оффер (без доп. запроса) — но save_detail_enrichment (:316) явно комментирует "не сохраняется здесь — задача Stage 6 (houses)" и просто отбрасывает. Колонки под это уже есть (020_houses_alter_cian.sql).
  2. providers/cian/valuation.py:78-112CianValuationResult.house_info (15 полей: год/тип/этажность), .management_company, .external_house_id (= houses.cian_internal_house_id) — вычисляются, но _save_to_cache (:467-560) пишет только в external_valuations (price cache), house-поля на пол. HOUSE_FIELD_PRIORITY["management_company_id"] = ["cian_valuation"] уже существует именно под это.

Cian ЖК (newbuildings) — уже работает (newbuilding.py → real UPDATE houses), не трогать/не дублировать.

DoD

  • bti_data резолвится через match_or_create_house (адрес/гео, как в avito/houses.py::_persist_house) и пишется в соответствующие колонки houses
  • Cian Valuation house_info/management_company/external_house_id аналогично доходят до houses (не только external_valuations)
  • Не трогает существующий newbuilding-путь (newbuilding.py, newbuilding_enrich_backfill.py)
  • Учесть открытый риск #1772 (дом дробится на несколько houses) — писать строго через match_or_create_house, не создавать альтернативный путь
  • Тесты по аналогии с существующими save_detail_enrichment/_save_to_cache тестами

Оценка: S (обе части, данные уже распарсены и типизированы — только write-path).

Yandex — отдельно, НЕ в этой issue

Аггрегатный ЖК rating/developer/text_reviews_count уже осел в отдельной market.yandex_jk_enrichment, но не долит до houses/house_reviews (S-M, паттерн есть — _save_cian_reviews). Для вторичных домов Yandex — в providers/yandex/detail.py нет embedded-state богатства (DOM/JSON-LD only, в отличие от Cian/Avito MFE-блобов) — по коду похоже, что сайт просто не отдаёт эту информацию для вторички. Нужен ~1-2ч Playwright-recon на живом realty.yandex.ru ПЕРЕД тем как оценивать объём (может оказаться, что там просто нечего парсить). Заведу отдельную issue после recon.

Refs: разведка от Explore-агента в этой сессии (2026-07-04), issue history #2011/#1791/#1792 (закрыты, но задокументировали историю этого же класса разрывов).

## Контекст Пользователь запросил закрыть разрыв "Houses❌ у Cian/Yandex (только Avito✅)" по аналогии с Avito. Полная разведка (Explore-агент, 2026-07-04) показала: `houses`/`house_reviews`/`house_placement_history` — provider-agnostic с самого начала (`source text`, колонки под cian/yandex уже добавлены в `020_houses_alter_cian.sql`/`031_houses_alter_yandex.sql`), houses матчатся по адресу/гео **межисточниково** (`match_or_create_house`), не по listing source — т.е. разрыв это в основном UX-полнота `HouseInfoCard.tsx` (пусто для домов, тронутых только Cian/Yandex-листингами), НЕ точность оценки (`estimator.py` не использует rating/amenity-поля вообще, только price-history, который уже частично cross-source через `yandex_valuation`). ## Cian — конкретный, узкий разрыв (эта issue) 1. **`providers/cian/detail.py:59-60,175-182`** — `DetailEnrichment.bti_data` (series_name/entrances/flat_count/is_emergency/heat_supply_type/gas_supply_type/overlap_type) парсится из того же `_cianConfig` state, что и сам оффер (без доп. запроса) — но `save_detail_enrichment` (:316) явно комментирует "не сохраняется здесь — задача Stage 6 (houses)" и просто отбрасывает. Колонки под это уже есть (`020_houses_alter_cian.sql`). 2. **`providers/cian/valuation.py:78-112`** — `CianValuationResult.house_info` (15 полей: год/тип/этажность), `.management_company`, `.external_house_id` (= `houses.cian_internal_house_id`) — вычисляются, но `_save_to_cache` (:467-560) пишет только в `external_valuations` (price cache), house-поля на пол. `HOUSE_FIELD_PRIORITY["management_company_id"] = ["cian_valuation"]` уже существует именно под это. Cian ЖК (newbuildings) — **уже работает** (`newbuilding.py` → real `UPDATE houses`), не трогать/не дублировать. ## DoD - [ ] `bti_data` резолвится через `match_or_create_house` (адрес/гео, как в `avito/houses.py::_persist_house`) и пишется в соответствующие колонки `houses` - [ ] Cian Valuation `house_info`/`management_company`/`external_house_id` аналогично доходят до `houses` (не только `external_valuations`) - [ ] Не трогает существующий newbuilding-путь (`newbuilding.py`, `newbuilding_enrich_backfill.py`) - [ ] Учесть открытый риск #1772 (дом дробится на несколько houses) — писать строго через `match_or_create_house`, не создавать альтернативный путь - [ ] Тесты по аналогии с существующими `save_detail_enrichment`/`_save_to_cache` тестами Оценка: **S** (обе части, данные уже распарсены и типизированы — только write-path). ## Yandex — отдельно, НЕ в этой issue Аггрегатный ЖК rating/developer/text_reviews_count уже осел в отдельной `market.yandex_jk_enrichment`, но не долит до `houses`/`house_reviews` (S-M, паттерн есть — `_save_cian_reviews`). Для **вторичных** домов Yandex — в `providers/yandex/detail.py` **нет** embedded-state богатства (DOM/JSON-LD only, в отличие от Cian/Avito MFE-блобов) — по коду похоже, что сайт просто не отдаёт эту информацию для вторички. Нужен ~1-2ч Playwright-recon на живом realty.yandex.ru ПЕРЕД тем как оценивать объём (может оказаться, что там просто нечего парсить). Заведу отдельную issue после recon. Refs: разведка от Explore-агента в этой сессии (2026-07-04), issue history #2011/#1791/#1792 (закрыты, но задокументировали историю этого же класса разрывов).
Collaborator

Работаю над этим в PR #2668.

Важно: сама #2435 была реализована и смержена ещё в PR #2437 (04.07) — обе половины (bti_data через match_or_create_house и valuation house_info/management_company/external_house_id), с тестами и прокинутым matcher во всех 4 call-site.

Но на проде это дало ноль строк: 9361 дом, 0 с series_name/entrances/flat_count/is_emergency/heat_supply_type/gas_supply_type/overlap_type — при 628 detail-обогащённых Cian-листингах за 05–22.07 (589 из них с house_id_fk, 0 без адреса).

Причина: bti читался как ключ, соседний с defaultState в контейнере frontend-offer-card, а Cian отдаёт его ВНУТРИ defaultState — offerData.bti. bti_data всегда оставался None. Старые тесты этого не ловили — они кормят bti_data напрямую в save_detail_enrichment, минуя fetch_detail. PR #2668 чинит извлечение и добавляет тест на реальном сохранённом HTML.

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

Valuation-половину на проде подтвердить не удалось: cian_valuation в external_valuations — 139 строк, все с house_id IS NULL, последний fetch 29.06 (до мержа #2437); management_companies пуста. Код выглядит корректно, но после фикса ни разу не исполнялся.

Работаю над этим в PR #2668. Важно: сама #2435 была реализована и смержена ещё в PR #2437 (04.07) — обе половины (bti_data через match_or_create_house и valuation house_info/management_company/external_house_id), с тестами и прокинутым matcher во всех 4 call-site. Но на проде это дало **ноль строк**: 9361 дом, 0 с series_name/entrances/flat_count/is_emergency/heat_supply_type/gas_supply_type/overlap_type — при 628 detail-обогащённых Cian-листингах за 05–22.07 (589 из них с house_id_fk, 0 без адреса). Причина: bti читался как ключ, соседний с defaultState в контейнере frontend-offer-card, а Cian отдаёт его ВНУТРИ defaultState — offerData.bti. bti_data всегда оставался None. Старые тесты этого не ловили — они кормят bti_data напрямую в save_detail_enrichment, минуя fetch_detail. PR #2668 чинит извлечение и добавляет тест на реальном сохранённом HTML. Колонки под поля на месте — миграция не нужна. Valuation-половину на проде подтвердить не удалось: cian_valuation в external_valuations — 139 строк, все с house_id IS NULL, последний fetch 29.06 (до мержа #2437); management_companies пуста. Код выглядит корректно, но после фикса ни разу не исполнялся.
Collaborator

Задача уже была сделана месяц назад — PR #2437 от 04.07, обе половины, через match_or_create_house, с 11 тестами. Issue осталась открытой из-за Refs вместо Closes. Реализовывать было нечего.

Но прод-проверка показала, что тот код дал ноль строк, и вот это исправлено в PR #2668.

Почему не работало

Парсер читал ключ bti как соседний с состоянием страницы, а Циан отдаёт его внутриofferData.bti. Значит bti_data всегда был пустым, и весь путь записи, реализованный в #2437, был мёртв с самого начала.

Проверено офлайн на сохранённой копии страницы (к площадке не ходил): среди 143 контейнеров состояния ключа bti нет, а внутри данных оффера лежит ровно то, что нужно — подъезды, число квартир, тип перекрытий.

Фикс — одна строка.

Почему это не поймали 11 тестов

Все 11 остаются зелёными и с багом. Они кормят данные напрямую в функцию сохранения, минуя парсер — то есть проверяют половину пути, начиная с той точки, где данные уже есть. А сломана была ровно первая половина.

Это ровно та слепая зона, из-за которой дефект прожил месяц: тесты написаны на тот случай, который автор себе представлял, и подтверждали, что запись работает — при том, что записывать было нечего.

Новый тест берёт настоящую сохранённую страницу и проверяет весь путь целиком.

Охват — числами с прода

  • домов всего 9361, с полями из BTI — ноль по всем семи полям;
  • 628 объявлений Циана прошли детальное обогащение 05–22.07 (589 с привязкой к дому) → и всё равно ноль домов;
  • 5188 домов касались объявления Циана — это потолок обогащения; у них 4153 без типа дома, 1337 без этажности, 444 без года постройки.

Реально будет меньше и не сразу: данные приходят только по вторичке, и детальный проход Циана стоит с 22 июля — до его возобновления эффекта не будет. Почему он встал — отдельный вопрос, не копал.

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

Вторую половину подтвердить не удалось

Оценочный модуль Циана: 139 строк в кэше цен, все без привязки к дому, последний запрос 29.06 — то есть до мержа #2437. Код выглядит корректным, но после того фикса ни разу не исполнялся. Таблица управляющих компаний пуста.

Попутно найдено и заведено отдельно

views_total пуст у всех 21 682 объявлений Циана — парсер вызывает int() на строке «146 просмотров, 8 за сегодня». Это #2669.

Задача **уже была сделана месяц назад** — PR #2437 от 04.07, обе половины, через `match_or_create_house`, с 11 тестами. Issue осталась открытой из-за `Refs` вместо `Closes`. Реализовывать было нечего. Но прод-проверка показала, что тот код дал **ноль строк**, и вот это исправлено в PR #2668. ## Почему не работало Парсер читал ключ `bti` как **соседний** с состоянием страницы, а Циан отдаёт его **внутри** — `offerData.bti`. Значит `bti_data` всегда был пустым, и весь путь записи, реализованный в #2437, был мёртв с самого начала. Проверено офлайн на сохранённой копии страницы (к площадке не ходил): среди 143 контейнеров состояния ключа `bti` нет, а внутри данных оффера лежит ровно то, что нужно — подъезды, число квартир, тип перекрытий. Фикс — одна строка. ## Почему это не поймали 11 тестов **Все 11 остаются зелёными и с багом.** Они кормят данные напрямую в функцию сохранения, минуя парсер — то есть проверяют половину пути, начиная с той точки, где данные уже есть. А сломана была ровно первая половина. Это ровно та слепая зона, из-за которой дефект прожил месяц: тесты написаны на тот случай, который автор себе представлял, и подтверждали, что запись работает — при том, что записывать было нечего. Новый тест берёт настоящую сохранённую страницу и проверяет весь путь целиком. ## Охват — числами с прода - домов всего 9361, с полями из BTI — **ноль по всем семи полям**; - 628 объявлений Циана прошли детальное обогащение 05–22.07 (589 с привязкой к дому) → и всё равно ноль домов; - **5188 домов** касались объявления Циана — это потолок обогащения; у них 4153 без типа дома, 1337 без этажности, 444 без года постройки. Реально будет меньше и не сразу: данные приходят только по вторичке, и **детальный проход Циана стоит с 22 июля** — до его возобновления эффекта не будет. Почему он встал — отдельный вопрос, не копал. Колонки под все поля на месте, миграция не понадобилась — утверждение issue подтвердилось. ## Вторую половину подтвердить не удалось Оценочный модуль Циана: 139 строк в кэше цен, **все без привязки к дому**, последний запрос **29.06** — то есть до мержа #2437. Код выглядит корректным, но после того фикса ни разу не исполнялся. Таблица управляющих компаний пуста. ## Попутно найдено и заведено отдельно `views_total` пуст у **всех 21 682** объявлений Циана — парсер вызывает `int()` на строке «146 просмотров, 8 за сегодня». Это #2669.
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#2435
No description provided.