[P0] Аудит точности данных: DaData (ключи/квоты/поля) + скраперы Avito/Cian/Yandex #653

Closed
opened 2026-05-29 13:15:32 +00:00 by lekss361 · 2 comments
Owner

Антон: «крч очень важна точность». Проверить откуда берём данные и насколько они верны.

Сделать

  • DaData (dadata.py): проверить ключи/квоты, какие поля реально приходят (canonical_address, cadnum, fias, metro), не упираемся ли в 429/лимит на проде.
  • Скраперы Avito/Cian/Yandex: на 5-10 реальных адресов ЕКБ сверить площадь/цену/комнаты/класс с сайтами-источниками — где врут поля.
  • Покрытие: % адресов с найденными аналогами, % с house_class, % с IMV/Cian valuation.

Acceptance

  • Краткий отчёт: где теряется точность, что чинить в первую очередь (вход для задачи точности P0).
Антон: «крч очень важна точность». Проверить откуда берём данные и насколько они верны. ## Сделать - DaData (dadata.py): проверить ключи/квоты, какие поля реально приходят (canonical_address, cadnum, fias, metro), не упираемся ли в 429/лимит на проде. - Скраперы Avito/Cian/Yandex: на 5-10 реальных адресов ЕКБ сверить площадь/цену/комнаты/класс с сайтами-источниками — где врут поля. - Покрытие: % адресов с найденными аналогами, % с house_class, % с IMV/Cian valuation. ## Acceptance - Краткий отчёт: где теряется точность, что чинить в первую очередь (вход для задачи точности P0).
lekss361 added this to the Praktika demo — Mon 2026-06-01 milestone 2026-05-29 13:15:32 +00:00
lekss361 added the
tradein
scope/backend
priority/p0
status/ready
labels 2026-05-29 13:15:32 +00:00
bot-backend self-assigned this 2026-05-30 09:45:57 +00:00
bot-backend added
status/wip
and removed
status/ready
labels 2026-05-30 09:45:58 +00:00
bot-backend added
status/done
and removed
status/wip
labels 2026-05-30 09:55:19 +00:00
Collaborator

Аудит точности данных — отчёт (вход для P0 «точность»)

Метод: статический разбор dadata.py + 9 скрейперов (scrapers/*) + количественный анализ прод-БД tradein (18 428 активных listings, 276 estimates за 7 дней, 12 481 houses, 1 354 внешних оценки). Веб-сверка 5–10 адресов не делалась — системные null-rate по 18k объектов дают более сильную картину, чем 10 анекдотов; ручной spot-check headline-цены оставлен человеку (см. конец).

TL;DR — где теряется точность (по убыванию влияния на диапазон цены)

  1. Внешние «оценки» — витрина без числа. Yandex valuation прикреплён к 272/276 (99%) estimate'ов как источник, но 0 из 1346 строк содержат пригодную цену (price_rub/sale_price_rub/low/high все NULL/0). raw_payload непустой (avg 4384 симв.) → страница берётся, но запрашивается по адресу уровня города ("Россия, Свердловская область, Екатеринбург", без улицы) → возвращается generic-история 20 объектов, headline-оценка не извлекается. Cian valuation — 6 строк, прикреплён к 0 estimate'ов. Avito IMV — 2 строки. Фактически estimate держится только на analog-comps (listings), внешние оценки — декоративны/сломаны.
  2. DaData-обогащение работает на 8%. canonical_address у 22/276 estimate'ов, house_cadnum у 11 (4%), qc_geo=0 у 22. Квота demo (100/день) НЕ причина (~40 estimate/день). Следствие каскадное: без канонического адреса Yandex/Cian valuation уходят с city-level адресом → п.1.
  3. Avito: 76% объявлений без координат (lat IS NULL), при этом Avito — крупнейший источник (9302) и используется в 57% estimate'ов. Если analog-подбор опирается на geo-radius — теряется бóльшая часть пула (нужно подтвердить метод матчинга, т.к. house_id_fk заполнен на 99.2% — возможно матчинг по дому смягчает).
  4. Yandex: 82% объявлений без площади (area_m2 IS NULL) → почти не годятся для цены/м². Подтверждает хрупкость SERP-regex площади (берёт первое N м² из плоского текста карточки).
  5. house_class = 0/12481 (0%) — класс дома не заполняется нигде. parse_house_class() есть (yandex_helpers.py:290), но не вызывается. Если в демо-UI показывается «класс дома» — он пустой/берётся не из листинга.
  6. house_type / year / repair почти всюду NULL → когортные фильтры (год ±N, тип дома, корректировка на ремонт) почти не на чем работать → диапазон шире, чем мог бы.

DaData (dadata.py) — код OK, проблема в покрытии

Парсинг и обработка ошибок корректны: 429/401/403/5xx → graceful None/[] (dadata.py:165-181), поля house_cadnum/house_fias_id/metro/qc_* читаются верно. Дефектов кода нет. Проблема — что обогащение вызывается на 8% адресов. Проверить: (a) заданы ли DADATA_API_TOKEN/SECRET на проде, (b) вызывается ли clean_address() в основном estimate-flow или только в отдельной ветке (PR Q1).

Скраперы — точность полей (БД + код)

Покрытие по null-rate активных listings:

source n area NULL price NULL rooms NULL floor NULL year NULL house_type NULL repair NULL без гео placeholder-адрес
avito 9302 0.4% 0% 1.4% 0% 100% 100% 98.1% 75.9% 3.1%
cian 5158 0.3% 0% 0% 0% 25.6% 34.5% 94.6% 0.7% 0.4%
yandex 3704 81.9% 0% 2.7% 1.5% 83.6% 89.4% 100% 12.9% 3.7%
n1 264 0% 0% 0% 100% 100% 100% 100% 40.2% 0%

Качество источников: Cian — лучший (структурный JSON из Redux, цена/площадь/комнаты/этаж почти без пропусков). Avito — цена/площадь/комнаты надёжны, но 0 года/типа дома и 76% без гео. Yandex — самый слабый (DOM-regex по плоскому тексту): 82% без площади.

Ключевые code-риски (silent fallback на успешном 200, когда меняется вёрстка):

  • Площадь, thousands-space: avito_detail.py:606-614 (_FLOAT_RE=[\d.,]+) и yandex_realty.py:151 берут первый прогон цифр / первое N м² → «1 234,5 м²» парсится как «1», либо ловится площадь кухни. Неверная площадь травит цену/м², когорту И входы Avito IMV/Cian valuation.
  • Cian materialType не нормализуется (cian.py:280, raw "monolithBrick"), тогда как downstream _IMV_HOUSE_TYPE_MAP ждёт monolith_brick → house_type теряется в IMV-вызове и когорте. (Объясняет 34.5% house_type NULL у Cian.)
  • Cian/Yandex placeholder-адрес: Cian при пустом адресе ставит "Екатеринбург (Cian)" (cian.py:482), Yandex при смене hashed-класса откатывается на улицу без дома (yandex_realty.py:256). Геокодируется в центроид → неверный radius-подбор без ошибки. Лучше NULL, чем placeholder.
  • Avito rooms одиночная цифра (\d)-к (avito.py:376) — «10-комн» не матчится; «апартаменты» не классифицируются ни в одном источнике (у Cian isApartments есть в raw, но не используется).
  • Yandex SERP цена без lookahead за м² (yandex_helpers.py:94) — латентный риск: по текущим данным цены Yandex выглядят как реальные тоталы (медиана 6.8M, 0 объектов в диапазоне ppm²), активной порчи нет. Но при дрейфе вёрстки regex захватит цену/м² как цену. Valuation-модуль уже решил это через _RE_PRICE_TOKEN с lookahead — переиспользовать.
  • Anti-bot — безопасно: блок/captcha → пустой список или типизированный raise, stale-мусор не пишется (avito.py:203-216, cian.py:106-113, yandex retry). Опасность не в блоке, а в silent-None/0/placeholder на успешном ответе.

Внешние оценки (IMV / Cian / Yandex valuation)

источник строк свежесть пригодная цена прикреплён к estimate
avito_imv 2 2/2 2/276
cian_valuation 6 6/6 0/276
yandex_valuation 1346 сегодня 0/1346 272/276
  • Yandex valuation: история объектов (history_items c last_price/area_m2/rooms) парсится верно, но (а) запрос city-level → не привязан к адресу, (б) headline-оценка не пишется в price_rub/low/high. Сейчас даёт ложное ощущение «есть оценка от Яндекса» в 99% estimate'ов. Либо чинить (передавать street-level адрес из DaData + извлекать headline), либо перестать прикреплять как источник, пока пусто (репутационный риск на демо — Антону важна точность).
  • Avito IMV / Cian valuation — реальные оценки сайтов, но conditioned на НАШИХ скрейпленных входах (площадь/этаж/тип/ремонт). Cian при неизвестном ремонте молча ставит repairTypeCosmetic (cian_valuation.py:171); Avito IMV — or 0 на каждый ключ цены (avito_imv.py:468-471). Объёмы (2 и 6) показывают, что они почти не вызываются — отдельный вопрос «почему не вызываются».

Покрытие estimate'ов (n=276 за 7д)

  • С аналогами n_analogs>0: 83.3% (avg 11.3); 16.7% (46) без аналогов, но median_price стоит у всех 276 (fallback-логику проверить).
  • Confidence: high 92 / medium 127 / low 57.
  • actual_deals (Росреестр) у 173 (63%).
  • Источники аналогов: Cian 197 (71%), Avito 158 (57%), Yandex 70 (25%).

Что чинить в первую очередь

# Фикс Файл/область Влияние
1 Yandex valuation: либо передавать street-level адрес (из DaData canonical) + извлекать headline-оценку, либо НЕ прикреплять как источник пока цена пуста yandex_valuation.py, estimator attach-логика Убирает ложный «источник» в 99% estimate'ов
2 Поднять DaData-обогащение: проверить токены на проде + вызов clean_address в основном flow env, estimator Чинит каскад → корректные адреса для valuation/гео
3 Avito геокодинг 76% NULL: бэкфилл координат (geocode_cache/DaData) + подтвердить, geo-radius или house_id_fk матчинг geocode worker, estimator Возвращает крупнейший источник в пул аналогов
4 Площадь-regex: thousands-space + «первое м²» в Avito detail и Yandex SERP avito_detail.py:606, yandex_realty.py:151 Чинит 82% Yandex без площади, убирает тихую порчу цены/м²
5 Нормализовать Cian materialType → enum при ingest cian.py:280 Восстанавливает house_type для IMV/когорты (34.5%→)
6 house_class: вызвать parse_house_class или решить источник; если UI показывает класс — закрыть 0%-пробел yandex_helpers.py:290, houses ETL Заполняет пустое поле демо-UI
7 Cian/Yandex placeholder-адрес → NULL вместо центроида cian.py:482, yandex_realty.py:256 Убирает тихо-смещённый radius-подбор

Рекомендую завести из пп. 1–4 отдельные scope/backend fix-issue (P0/P1) — это «вход для задачи точности», который просил Антон. Пп. 1–2 — самые дешёвые и самые влияющие на демо.

(Аудит read-only, изменений кода не вносилось.)

## Аудит точности данных — отчёт (вход для P0 «точность») Метод: статический разбор `dadata.py` + 9 скрейперов (`scrapers/*`) + количественный анализ прод-БД `tradein` (18 428 активных listings, 276 estimates за 7 дней, 12 481 houses, 1 354 внешних оценки). Веб-сверка 5–10 адресов не делалась — системные null-rate по 18k объектов дают более сильную картину, чем 10 анекдотов; ручной spot-check headline-цены оставлен человеку (см. конец). ### TL;DR — где теряется точность (по убыванию влияния на диапазон цены) 1. **Внешние «оценки» — витрина без числа.** Yandex valuation прикреплён к **272/276 (99%)** estimate'ов как источник, но **0 из 1346** строк содержат пригодную цену (`price_rub/sale_price_rub/low/high` все NULL/0). raw_payload непустой (avg 4384 симв.) → страница берётся, но запрашивается по **адресу уровня города** (`"Россия, Свердловская область, Екатеринбург"`, без улицы) → возвращается generic-история 20 объектов, headline-оценка не извлекается. Cian valuation — 6 строк, прикреплён к **0** estimate'ов. Avito IMV — 2 строки. **Фактически estimate держится только на analog-comps (listings), внешние оценки — декоративны/сломаны.** 2. **DaData-обогащение работает на 8%.** `canonical_address` у 22/276 estimate'ов, `house_cadnum` у 11 (4%), `qc_geo=0` у 22. Квота demo (100/день) НЕ причина (~40 estimate/день). Следствие каскадное: без канонического адреса Yandex/Cian valuation уходят с city-level адресом → п.1. 3. **Avito: 76% объявлений без координат** (`lat IS NULL`), при этом Avito — крупнейший источник (9302) и используется в 57% estimate'ов. Если analog-подбор опирается на geo-radius — теряется бóльшая часть пула (нужно подтвердить метод матчинга, т.к. house_id_fk заполнен на 99.2% — возможно матчинг по дому смягчает). 4. **Yandex: 82% объявлений без площади** (`area_m2 IS NULL`) → почти не годятся для цены/м². Подтверждает хрупкость SERP-regex площади (берёт первое `N м²` из плоского текста карточки). 5. **`house_class` = 0/12481 (0%)** — класс дома не заполняется нигде. `parse_house_class()` есть (`yandex_helpers.py:290`), но не вызывается. Если в демо-UI показывается «класс дома» — он пустой/берётся не из листинга. 6. **house_type / year / repair почти всюду NULL** → когортные фильтры (год ±N, тип дома, корректировка на ремонт) почти не на чем работать → диапазон шире, чем мог бы. ### DaData (`dadata.py`) — код OK, проблема в покрытии Парсинг и обработка ошибок корректны: 429/401/403/5xx → graceful `None/[]` (`dadata.py:165-181`), поля `house_cadnum/house_fias_id/metro/qc_*` читаются верно. **Дефектов кода нет.** Проблема — что обогащение вызывается на 8% адресов. Проверить: (a) заданы ли `DADATA_API_TOKEN/SECRET` на проде, (b) вызывается ли `clean_address()` в основном estimate-flow или только в отдельной ветке (PR Q1). ### Скраперы — точность полей (БД + код) Покрытие по null-rate активных listings: | source | n | area NULL | price NULL | rooms NULL | floor NULL | year NULL | house_type NULL | repair NULL | без гео | placeholder-адрес | |---|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:| | avito | 9302 | 0.4% | 0% | 1.4% | 0% | **100%** | **100%** | 98.1% | **75.9%** | 3.1% | | cian | 5158 | 0.3% | 0% | 0% | 0% | 25.6% | 34.5% | 94.6% | 0.7% | 0.4% | | yandex | 3704 | **81.9%** | 0% | 2.7% | 1.5% | 83.6% | 89.4% | 100% | 12.9% | 3.7% | | n1 | 264 | 0% | 0% | 0% | 100% | 100% | 100% | 100% | 40.2% | 0% | Качество источников: **Cian — лучший** (структурный JSON из Redux, цена/площадь/комнаты/этаж почти без пропусков). **Avito** — цена/площадь/комнаты надёжны, но 0 года/типа дома и 76% без гео. **Yandex** — самый слабый (DOM-regex по плоскому тексту): 82% без площади. Ключевые code-риски (silent fallback на успешном 200, когда меняется вёрстка): - **Площадь, thousands-space**: `avito_detail.py:606-614` (`_FLOAT_RE=[\d.,]+`) и `yandex_realty.py:151` берут первый прогон цифр / первое `N м²` → «1 234,5 м²» парсится как «1», либо ловится площадь кухни. Неверная площадь травит цену/м², когорту И входы Avito IMV/Cian valuation. - **Cian `materialType` не нормализуется** (`cian.py:280`, raw `"monolithBrick"`), тогда как downstream `_IMV_HOUSE_TYPE_MAP` ждёт `monolith_brick` → house_type теряется в IMV-вызове и когорте. (Объясняет 34.5% house_type NULL у Cian.) - **Cian/Yandex placeholder-адрес**: Cian при пустом адресе ставит `"Екатеринбург (Cian)"` (`cian.py:482`), Yandex при смене hashed-класса откатывается на улицу без дома (`yandex_realty.py:256`). Геокодируется в центроид → неверный radius-подбор без ошибки. Лучше NULL, чем placeholder. - **Avito rooms** одиночная цифра `(\d)-к` (`avito.py:376`) — «10-комн» не матчится; «апартаменты» не классифицируются ни в одном источнике (у Cian `isApartments` есть в raw, но не используется). - **Yandex SERP цена без lookahead `за м²`** (`yandex_helpers.py:94`) — *латентный* риск: по текущим данным цены Yandex выглядят как реальные тоталы (медиана 6.8M, 0 объектов в диапазоне ppm²), активной порчи нет. Но при дрейфе вёрстки regex захватит цену/м² как цену. Valuation-модуль уже решил это через `_RE_PRICE_TOKEN` с lookahead — переиспользовать. - **Anti-bot — безопасно**: блок/captcha → пустой список или типизированный raise, stale-мусор не пишется (avito.py:203-216, cian.py:106-113, yandex retry). Опасность не в блоке, а в silent-None/0/placeholder на успешном ответе. ### Внешние оценки (IMV / Cian / Yandex valuation) | источник | строк | свежесть | пригодная цена | прикреплён к estimate | |---|--:|---|--:|--:| | avito_imv | 2 | 7д | 2/2 | 2/276 | | cian_valuation | 6 | 7д | 6/6 | 0/276 | | yandex_valuation | 1346 | сегодня | **0/1346** | 272/276 | - **Yandex valuation**: история объектов (`history_items` c `last_price/area_m2/rooms`) парсится верно, но (а) запрос city-level → не привязан к адресу, (б) headline-оценка не пишется в `price_rub/low/high`. Сейчас даёт ложное ощущение «есть оценка от Яндекса» в 99% estimate'ов. Либо чинить (передавать street-level адрес из DaData + извлекать headline), либо **перестать прикреплять как источник**, пока пусто (репутационный риск на демо — Антону важна точность). - **Avito IMV / Cian valuation** — реальные оценки сайтов, но conditioned на НАШИХ скрейпленных входах (площадь/этаж/тип/ремонт). Cian при неизвестном ремонте молча ставит `repairTypeCosmetic` (`cian_valuation.py:171`); Avito IMV — `or 0` на каждый ключ цены (`avito_imv.py:468-471`). Объёмы (2 и 6) показывают, что они почти не вызываются — отдельный вопрос «почему не вызываются». ### Покрытие estimate'ов (n=276 за 7д) - С аналогами `n_analogs>0`: **83.3%** (avg 11.3); 16.7% (46) без аналогов, но `median_price` стоит у всех 276 (fallback-логику проверить). - Confidence: high 92 / medium 127 / low 57. - `actual_deals` (Росреестр) у 173 (63%). - Источники аналогов: Cian 197 (71%), Avito 158 (57%), Yandex 70 (25%). ### Что чинить в первую очередь | # | Фикс | Файл/область | Влияние | |---|---|---|---| | 1 | Yandex valuation: либо передавать street-level адрес (из DaData canonical) + извлекать headline-оценку, либо НЕ прикреплять как источник пока цена пуста | `yandex_valuation.py`, estimator attach-логика | Убирает ложный «источник» в 99% estimate'ов | | 2 | Поднять DaData-обогащение: проверить токены на проде + вызов `clean_address` в основном flow | env, estimator | Чинит каскад → корректные адреса для valuation/гео | | 3 | Avito геокодинг 76% NULL: бэкфилл координат (geocode_cache/DaData) + подтвердить, geo-radius или house_id_fk матчинг | geocode worker, estimator | Возвращает крупнейший источник в пул аналогов | | 4 | Площадь-regex: thousands-space + «первое м²» в Avito detail и Yandex SERP | `avito_detail.py:606`, `yandex_realty.py:151` | Чинит 82% Yandex без площади, убирает тихую порчу цены/м² | | 5 | Нормализовать Cian `materialType` → enum при ingest | `cian.py:280` | Восстанавливает house_type для IMV/когорты (34.5%→) | | 6 | house_class: вызвать `parse_house_class` или решить источник; если UI показывает класс — закрыть 0%-пробел | `yandex_helpers.py:290`, houses ETL | Заполняет пустое поле демо-UI | | 7 | Cian/Yandex placeholder-адрес → NULL вместо центроида | `cian.py:482`, `yandex_realty.py:256` | Убирает тихо-смещённый radius-подбор | Рекомендую завести из пп. 1–4 отдельные `scope/backend` fix-issue (P0/P1) — это «вход для задачи точности», который просил Антон. Пп. 1–2 — самые дешёвые и самые влияющие на демо. *(Аудит read-only, изменений кода не вносилось.)*
Collaborator

Closed (auto-analyst milestone-6 sweep): был status/done, но issue оставался open — противоречие (done = completed). Привёл состояние в соответствие label'у. Если аудит ещё не завершён — reopen + сними status/done.

Closed (auto-analyst milestone-6 sweep): был `status/done`, но issue оставался open — противоречие (done = completed). Привёл состояние в соответствие label'у. Если аудит ещё не завершён — reopen + сними `status/done`.
Sign in to join this conversation.
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

2026-06-01

Dependencies

No dependencies set.

Reference: lekss361/gendesign#653
No description provided.