fix(tradein/admin): убрать показатели, которые не могут быть ненулевыми, и брать список источников из данных (#2674) #2684
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2684
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2674-admin-metrics-honesty"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Четыре находки #2674 одного класса: админка показывает числа, которые никогда не бывают ненулевыми, и подаёт это как результат. Ноль читается оператором как «всё чисто», а не как «мы это не считаем» — такой показатель хуже отсутствующего, поэтому по каждой находке выбор был «заполнить или честно убрать», а не «как бы не удалять код».
Числа с прода (
tradein-postgres, 2026-08-06, до фикса).1. «Помечено выбросов» — УБРАН (
v_data_quality.outliers_flagged+ колонкаlistings.is_outlier)Было:
is_outlier = trueу 0 из 93 408 объявлений,NULL— у 0. То есть в колонке толькоDEFAULT falseиз миграции 002 и ни одногоUPDATEза всю историю; писателя нет нигде.Почему убрать, а не реализовать. Механизм не «не доделан» — он невыразим в этой колонке. «Выброс» у эстиматора считается Tukey-фильтром по конкретной подборке аналогов (
estimator._filter_outliers, k=1.5 / 1.0 на малых выборках) и живёт ровно один запрос: один и тот же лот — выброс для оценки соседней однушки и нормальный аналог для оценки трёшки этажом выше. Persist-флаг на объявлении такое отношение выразить не может в принципе. Комментарий в 002 («помечено модели после aggregation») описывает отменённую затею batch-разметки, которую вытеснила фильтрация во время запроса.Проверено, что нигде не читается: во всём репозитории
outliers_flagged/v_data_qualityвстречаются только в четырёхdata/sql/*.sql; ни один Python/TS не читает ни колонку, ни view (сам view — hand-tool под psql: его COMMENT ссылается на/api/v1/admin/data-quality, но эндпоинт давно переписан и view не трогает). View сохранён, из него убрана одна строка.2. Счётчики HTTP-запросов / ошибок / вернувшихся / исчезнувших — УБРАНЫ (4 колонки
scrape_runs)Было:
http_requests,http_errors,returning_count,disappeared_count— 0 во всех 3244 прогонах с миграции 015, писателя нет вообще, и ни API, ни UI их даже не отдают.Почему убрать, включая «дешёвые в заполнении»:
http_requests— HTTP-запросы не считает ни один фетчер (curl_cffi/BrowserFetcher). Заполнить нечем без сквозной инструментации всех путей сбора — это проект, а не фикс, и заказчика у числа нет.http_errors— то, что оператор реально хочет («сколько ошибок»), уже пишется тем, кто их считает:errors_countвcountersjsonb (579 прогонов), иRunsTableего уже показывает. Отдельная колонка была бы вторым, иначе определённым счётчиком того же слова.returning_count/disappeared_count— та же история:deactivate_stale_*пишетdeactivated(187) иrevived(169) вcounters, вычисляя их там, где данные есть. Дублировать это колонкой, которая ненулевая у 3 источников из 53, — сделать хуже.Сигнал не потерян: он живёт в
countersjsonb у джоба, который его знает.3. Тип прогона
run_type— УБРАН (API + таблица админки + колонка)Было: 3244 строки, ровно одно значение
'city_sweep'—DEFAULTиз миграции 051; ни одно место кодаrun_typeне задаёт. Колонка «Тип» в UI подписывала «city_sweep» прогоны, которые никаким sweep не были:proxy_healthcheck(1630 шт.),deactivate_stale_*,sber_index_pull,rosreestr_dkp_import.Почему убрать: вид прогона честно называет
source— вторая, вырожденная ось не нужна. Колонка «Тип» в таблице заменена на «Источник» (r.source) — то же место на экране, но со значением, которое различает строки. Симметрично снятSELECT run_typeв обеих копияхlist_all(app.services.scrape_runsиscraper_kit.orchestration.runs) — иначе после DROP админка отдаёт 500.4. Фильтр источников — из данных, а не из литерала
Было:
RUN_SOURCE_FILTERS = ["", "avito", "cian", "yandex"], аlist_allсравнивает точно (source = :source). Строк сsource='avito'/'cian'/'yandex'в таблице 0 — то есть каждый пункт фильтра давал пустую выдачу, и пустой выбор «Все» тоже: он молча подставлялsourceвкладки. В данных 53 источника; 2466 прогонов (76%) вне трёх площадок, включая весь Домклик (domclick_city_sweep46,domclick_detail_backfill30,deactivate_stale_domklik25).Стало:
GET /api/v1/admin/scrape/runs/sources→SELECT DISTINCT source ORDER BY source(фильтры прогонов игнорирует — иначе выбор источника вырезал бы из списка остальные), отдельный query-key на фронте (список не схлопывается при смене фильтра), «Все» = буквально все. Новый источник появляется в фильтре сам, без правки кода.Тесты
tradein-mvp/backend/tests/test_2674_dead_admin_metrics.py(8 тестов) + правкаtest_scraper_admin_apis.py.Фальсификация патч-методом (impl откачен, тесты оставлены) — красные все 9, зелёных без фикса нет:
Тест на «новый источник появляется сам» кормит фейковую БД заведомо несуществующим
newmarket_2031_sweep— если он доезжает до ответа, список действительно из данных. Тестtest_latest_v_data_quality_no_longer_reports_outliersсмотрит не на конкретный файл, а на действующее определение view (последняя миграция, которая его создаёт) — поймает и повторное добавление показателя.Полный прогон:
3487 passed, 9 skipped(--deselect tests/test_search_api.py::test_search_cache_hit, как в CI). Фронт:tsc --noEmitчистый.Test plan
214_drop_dead_run_metrics.sql(strict):\d scrape_runs— нетrun_type,http_requests,http_errors,returning_count,disappeared_count;\d listings— нетis_outlier;SELECT * FROM v_data_quality— нет колонкиoutliers_flaggedи view не сломан.docker exec tradein-backend curl -s -H "X-Authenticated-User: admin" localhost:8000/api/v1/admin/scrape/runs/sources— 53 источника, среди нихdomclick_city_sweep..../scrape/runs?limit=5— 200, в строках нетrun_type./scrapers, любая вкладка: «Источник» в фильтре — длинный список реальных значений; выборdomclick_city_sweepдаёт непустую выдачу; при «Все» таблица не пустая (раньше была пустой на всех вкладках); колонка «Источник» в таблице различает строки.done,counters/total_seenпишутся как раньше (окно между применением SQL и подъёмом нового образа — админский/scrape/runsможет кратко отдать 500 на старом коде, самоустраняется).Refs #2674
Второй коммит
3c5f535e— по всем четырём пунктам ревью.1. Кнопка отмены — гард, не отдельная задача. Поставил его в бэкенде, а не в UI, потому что дыра шире, чем рендер кнопки: ручки отмены source не проверяют вовсе, и ручной POST снимал бы
has_running_run-guard даже при спрятанной кнопке. Гейт — на общем узле всех пяти ручек:scrape_runs.honors_cancel(source)+ отказ вmark_cancelled(лог +False, без вранья в статусе). Флагcancellableотдаётся в строке, UI по нему прячет кнопку.Состав набора вывел из call-site'ов
runs.is_cancelled, а не из слова «свип»:*city_sweep*(все площадки и города, включая_nizhniy_tagil),*full_load*(включая_exhaustive),avito_newbuilding_sweep,rosreestr_dkp_import(он тоже опрашивает — вscheduler.py, per-batch).yandex_newbuilding_sweepв набор НЕ входит — его таск отмену не опрашивает, поэтому правило «любой*_sweep» было бы ровно тем же багом с другой стороны. Это зафиксировано тестом.2. Комментарий view — заменил на честный: «KPI-снимок для РУЧНЫХ psql-запросов, читателей в коде нет (проверено #2674):
/api/v1/admin/scraper/data-qualityсчитает свои метрики сам и этот view не трогает».3. Лимит 20 → 50 в
useScraperRuns+ подпись под заголовком. Привязку к вкладке не возвращал.4. Тест «действующее определение» — маркер стал регуляркой
CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+v_data_quality\b, ловит иCREATE VIEW, и пару DROP+CREATE.Тесты. Три новых (
honors_cancelнабор,cancellableв строке, отказmark_cancelled) — фальсификация патч-методом, все три красные без фикса. Правил один существующий:test_city_sweep.py::test_scrape_runs_mark_cancelled_returns_boolкормил голыйMagicMock(), у которого.source— тоже мок; теперьsource="avito_city_sweep"с комментарием про новую предпосылку. Полный прогон3490 passed, 9 skipped,tsc --noEmitчистый.Отдельно, для протокола:
npm run lintлокально не запускается —@eslint/eslintrcотсутствует в node_modules и в главном дереве тоже (состояние pre-existing, не от этого PR). В CI ставится заново, там и проверится.