tradein: offer_price_history.diff_percent у domklik — колонка смешанных единиц (рубли и проценты вперемешку) #3225

Closed
opened 2026-08-29 13:02:30 +00:00 by bot-backend · 1 comment
Collaborator

Найдено при замере для #3223 (статья МЕРЫ, прод poincare 29.08.2026).

Что не так

offer_price_history.diff_percent для источника domklik содержит РУБЛИ, а не проценты.

Загрузчик tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/domclick/detail.py:413 кладёт поле источника diff как есть, а clamp_diff_percent только зажимает диапазон в ±999999.99 — то есть заведомо неправдоподобное значение молча проходит вместо того, чтобы упасть.

Замер на проде:

  • перцентили diff_percent у domklik: p05 = −600 000, p50 = −50 010, p95 = +300 000;
  • 8900 из 11 075 непустых значений имеют |diff| > 50 — то есть заведомо не проценты;
  • у одного и того же listing_id 406163 в колонке соседствуют −200000.00 / −300000.00 / +99000.00 (рубли, из бэкфилла) и −0.82 / +0.83 (настоящие проценты, из триггера listings_price_change_trg).

Почему это важно

Колонка выглядит пригодной для аналитики и по имени, и по типу (numeric(8,2)). Любой, кто напишет avg(diff_percent) или where diff_percent < -10, получит правдоподобный мусор без единого признака ошибки. При замере для #3223 пришлось считать проценты заново из price_rub — но следующий это правило не унаследует.

Что сделать

  1. Починить загрузчик domklik: считать процент из соседних price_rub, а не брать diff источника.
  2. Бэкфилл: пересчитать diff_percent для существующих domklik-строк из price_rub (соседние записи по (listing_id, change_time)).
  3. Защита: не «зажимать» неправдоподобное значение, а отвергать его. |diff_percent| > 100 для процента — не крайний случай, а сигнал, что в колонку кладут другую величину. Гейт должен краснеть, а не клампить.

Сопутствующее (отдельными задачами, не здесь)

  • Стартовая цена объявления не пишется НИКОГДА для avito/yandex: триггер record_listing_price_change() пишет только изменение. У avito самая ранняя цена в listing_source_snapshots выше первой цены в offer_price_history у 3117 из 4827 листингов — «первая → последняя» по этой таблице систематически занижает падение.
  • yandex_price_history.py сеет синтетическую пару с change_time = now − 1 день: 6072 фальшивых «однодневных» пары, у 4052 из них цена «выросла».
  • offer_price_history.listing_source_id = NULL во всех 64 327 строках — джойн на listing_sources не работает, колонка мёртвая.
Найдено при замере для #3223 (статья МЕРЫ, прод poincare 29.08.2026). ## Что не так `offer_price_history.diff_percent` для источника **domklik** содержит РУБЛИ, а не проценты. Загрузчик `tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/domclick/detail.py:413` кладёт поле источника `diff` как есть, а `clamp_diff_percent` только зажимает диапазон в ±999999.99 — то есть заведомо неправдоподобное значение молча проходит вместо того, чтобы упасть. Замер на проде: * перцентили `diff_percent` у domklik: p05 = **−600 000**, p50 = **−50 010**, p95 = **+300 000**; * **8900 из 11 075** непустых значений имеют `|diff| > 50` — то есть заведомо не проценты; * у одного и того же `listing_id 406163` в колонке соседствуют `−200000.00 / −300000.00 / +99000.00` (рубли, из бэкфилла) и `−0.82 / +0.83` (настоящие проценты, из триггера `listings_price_change_trg`). ## Почему это важно Колонка выглядит пригодной для аналитики и по имени, и по типу (`numeric(8,2)`). Любой, кто напишет `avg(diff_percent)` или `where diff_percent < -10`, получит правдоподобный мусор без единого признака ошибки. При замере для #3223 пришлось считать проценты заново из `price_rub` — но следующий это правило не унаследует. ## Что сделать 1. Починить загрузчик domklik: считать процент из соседних `price_rub`, а не брать `diff` источника. 2. Бэкфилл: пересчитать `diff_percent` для существующих domklik-строк из `price_rub` (соседние записи по `(listing_id, change_time)`). 3. Защита: не «зажимать» неправдоподобное значение, а отвергать его. `|diff_percent| > 100` для процента — не крайний случай, а сигнал, что в колонку кладут другую величину. Гейт должен краснеть, а не клампить. ## Сопутствующее (отдельными задачами, не здесь) * Стартовая цена объявления не пишется НИКОГДА для avito/yandex: триггер `record_listing_price_change()` пишет только изменение. У avito самая ранняя цена в `listing_source_snapshots` выше первой цены в `offer_price_history` у **3117 из 4827** листингов — «первая → последняя» по этой таблице систематически занижает падение. * `yandex_price_history.py` сеет синтетическую пару с `change_time = now − 1 день`: 6072 фальшивых «однодневных» пары, у 4052 из них цена «выросла». * `offer_price_history.listing_source_id` = NULL во **всех** 64 327 строках — джойн на `listing_sources` не работает, колонка мёртвая.
Author
Collaborator

Прод-приёмка PR #3370 / миграции 285 (05.09, ~20:30 UTC, BUILD_SHA=f129d52):

  • строк загрузчика domklik с |diff_percent| > 100: 10 335 → 42; оставшиеся 42 — не рубли, а скачки ровно ×10 в цене источника (потерянный разряд: 420 000 → 4 200 000), все совпадают с честным пересчётом → отдельный #3376 (гейт лоадера такие занулит, миграция пересчитала честно — расхождение поведений);
  • триггерные строки нетронуты: снимок 3 381 строки (id, diff_percent) до и после — идентичен байт-в-байт;
  • идемпотентность: повторный пересчёт изменил бы 0 строк;
  • изменено ~10 378 строк загрузчика (предзамер до мержа).

Закрыты пп.1 (лоадер считает процент из соседних price_rub), 2 (backfill), 3 (гейт отвергает, не клампит; общий с cian). Открыто: #3376 (×10-выбросы), старые cian-строки с |x|>100 не трогались (миграция только domklik).

Прод-приёмка PR #3370 / миграции 285 (05.09, ~20:30 UTC, `BUILD_SHA=f129d52`): - строк загрузчика domklik с `|diff_percent| > 100`: **10 335 → 42**; оставшиеся 42 — не рубли, а скачки ровно ×10 в цене источника (потерянный разряд: `420 000 → 4 200 000`), все совпадают с честным пересчётом → отдельный #3376 (гейт лоадера такие занулит, миграция пересчитала честно — расхождение поведений); - **триггерные строки нетронуты**: снимок 3 381 строки `(id, diff_percent)` до и после — идентичен байт-в-байт; - идемпотентность: повторный пересчёт изменил бы **0** строк; - изменено ~10 378 строк загрузчика (предзамер до мержа). Закрыты пп.1 (лоадер считает процент из соседних `price_rub`), 2 (backfill), 3 (гейт отвергает, не клампит; общий с cian). Открыто: #3376 (×10-выбросы), старые cian-строки с |x|>100 не трогались (миграция только domklik).
Sign in to join this conversation.
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#3225
No description provided.