All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 8s
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 / frontend-checks (pull_request) Successful in 1m2s
CI Trade-In / backend-tests (pull_request) Successful in 2m58s
Ревью PR #2684 — четыре MINOR. 1. Починка фильтра открыла кнопку отмены на все 53 источника. Раньше таблица была пуста на каждой вкладке, поэтому кнопка не рендерилась НИ РАЗУ и дыра не проявлялась: ручки отмены source не проверяют вовсе. Оператор на вкладке Авито мог бы «отменить» refresh_search_matview — задача продолжила бы работать под статусом 'cancelled' (ещё один врущий статус ровно в тот день, когда их вычищаем), а has_running_run перестал бы держать single-run guard, который существует из-за инцидента с двойным свипом и баном (2026-05-31). Гейт поставлен на общем узле всех пяти ручек — scrape_runs.honors_cancel + отказ в mark_cancelled, — а не в UI: иначе ручной POST по-прежнему снимал бы guard. Флаг cancellable отдаётся в строке, UI по нему прячет кнопку. Состав набора выведен из call-site'ов runs.is_cancelled: city-sweep'ы (все площадки и города), full-load'ы, avito_newbuilding_sweep, rosreestr_dkp_import. Правило НЕ «любой *_sweep»: yandex_newbuilding_sweep отмену не опрашивает. 2. Комментарий пересозданного v_data_quality утверждал, что его обновляет /api/v1/admin/data-quality. Читателей у view нет ни одного — живая ручка строит свой запрос. PR с тезисом «ложный показатель хуже отсутствующего» не имеет права переносить в прод ложное утверждение о читателе. 3. Лимит выдачи 20 → 50: первые 20 строк по started_at на три четверти — сердцебиение proxy_healthcheck (1631 из 3245), часовой сбор мог не поместиться. Привязка к вкладке НЕ возвращается. 4. Тест «действующее определение view» искал маркер подстрокой с OR REPLACE — миграция с обычным CREATE VIEW или парой DROP+CREATE была бы невидима, и тест проверял бы 214, пока показатель уже вернулся. Заменено регуляркой на обе формы. Фальсификация трёх новых тестов патч-методом — все три красные. Полный прогон 3490 passed / 9 skipped, tsc --noEmit чистый.
116 lines
8.5 KiB
PL/PgSQL
116 lines
8.5 KiB
PL/PgSQL
-- 214_drop_dead_run_metrics.sql
|
||
-- Purpose (#2674): удалить показатели, которые не могут быть ненулевыми.
|
||
--
|
||
-- Один класс находок: колонка есть, админка её показывает, писателя нет ни одного —
|
||
-- и ноль читается как «всё чисто», а не как «мы это не считаем». Такой показатель
|
||
-- хуже отсутствующего: он создаёт ложную уверенность. Числа с прода 2026-08-06.
|
||
--
|
||
-- 1. listings.is_outlier + v_data_quality.outliers_flagged
|
||
-- 93 408 строк listings, is_outlier = false у ВСЕХ, NULL у нуля — то есть
|
||
-- только DEFAULT из 002, ни одного UPDATE за всю историю. Писателя нет и не
|
||
-- задумывалось: «выброс» у эстиматора вычисляется Tukey-фильтром по КОНКРЕТНОЙ
|
||
-- подборке аналогов (estimator._filter_outliers) и живёт ровно один запрос —
|
||
-- один и тот же лот выброс для одной оценки и нормальный аналог для соседней.
|
||
-- Persist-флаг на объявлении такое отношение выразить не может в принципе,
|
||
-- поэтому реализовать «пометку» нечем: это остаток отменённой затеи
|
||
-- («помечено модели после aggregation», 002_core_tables.sql), а не недоделка.
|
||
--
|
||
-- 2. scrape_runs.run_type
|
||
-- 3244 прогона, ровно одно значение 'city_sweep' — DEFAULT из 051; ни одно
|
||
-- место кода run_type не задаёт. Колонка подписывала «city_sweep» прогоны,
|
||
-- которые никаким sweep не были: proxy_healthcheck (1630 шт.),
|
||
-- deactivate_stale_*, sber_index_pull, rosreestr_dkp_import. Что именно бежало,
|
||
-- честно называет source — второй, вырожденной оси не нужно.
|
||
--
|
||
-- 3. scrape_runs.http_requests / http_errors / returning_count / disappeared_count
|
||
-- Ноль во всех 3244 прогонах с миграции 015, писателя нет вообще.
|
||
-- HTTP-запросы не считает никто ни в одном фетчере — заполнить нечем без
|
||
-- сквозной инструментации всех путей. Ошибки и «пропало/вернулось» УЖЕ
|
||
-- считает тот, кто их знает, и кладёт в counters jsonb: errors_count у
|
||
-- pipeline, deactivated/revived у deactivate_stale_*. Отдельные колонки были
|
||
-- бы вторым, иначе определённым счётчиком того же слова.
|
||
--
|
||
-- ⚠️ View-зависимость: v_data_quality (последний DDL — 095_dead_schema.sql) читает
|
||
-- listings.is_outlier и содержит CTE `SELECT * FROM listings`, что фиксирует
|
||
-- column-level зависимость на все колонки. Порядок тот же, что в 095:
|
||
-- DROP VIEW → DROP COLUMN → CREATE VIEW (уже без outliers_flagged).
|
||
--
|
||
-- Dependencies: 002_core_tables.sql, 015_scrape_runs.sql, 051_scrape_runs_extend.sql,
|
||
-- 095_dead_schema.sql (последний DDL v_data_quality).
|
||
-- Идемпотентно: DROP VIEW IF EXISTS / DROP COLUMN IF EXISTS / CREATE OR REPLACE VIEW.
|
||
|
||
BEGIN;
|
||
|
||
-- ── 1. is_outlier: снять зависимый view, дропнуть колонку, пересоздать view ───
|
||
|
||
DROP VIEW IF EXISTS v_data_quality;
|
||
|
||
ALTER TABLE IF EXISTS listings DROP COLUMN IF EXISTS is_outlier;
|
||
|
||
-- DDL идентичен 095, минус строка outliers_flagged (см. п.1 шапки).
|
||
CREATE OR REPLACE VIEW v_data_quality AS
|
||
WITH active_listings AS (
|
||
SELECT * FROM listings WHERE is_active = true
|
||
)
|
||
SELECT
|
||
(SELECT count(*) FROM houses) AS houses_total,
|
||
(SELECT count(*) FROM houses h
|
||
WHERE EXISTS (SELECT 1 FROM house_sources hs WHERE hs.house_id = h.id)) AS houses_with_source,
|
||
(SELECT count(*) FROM houses h
|
||
WHERE EXISTS (SELECT 1 FROM house_sources hs
|
||
WHERE hs.house_id = h.id AND hs.ext_source = 'avito')) AS houses_with_avito,
|
||
(SELECT count(*) FROM houses h
|
||
WHERE EXISTS (SELECT 1 FROM house_sources hs
|
||
WHERE hs.house_id = h.id AND hs.ext_source LIKE 'cian%')) AS houses_with_cian,
|
||
(SELECT count(*) FROM houses h
|
||
WHERE EXISTS (SELECT 1 FROM house_sources hs
|
||
WHERE hs.house_id = h.id AND hs.ext_source = 'yandex')) AS houses_with_yandex,
|
||
(SELECT count(*) FROM (
|
||
SELECT house_id FROM house_sources GROUP BY house_id HAVING count(*) >= 2
|
||
) sub) AS houses_2plus_sources,
|
||
(SELECT count(*) FROM (
|
||
SELECT house_id FROM house_sources GROUP BY house_id HAVING count(*) >= 3
|
||
) sub) AS houses_3plus_sources,
|
||
(SELECT count(*) FROM active_listings) AS listings_active,
|
||
(SELECT count(*) FROM (
|
||
SELECT listing_id FROM listing_sources
|
||
WHERE listing_id IN (SELECT id FROM active_listings)
|
||
GROUP BY listing_id HAVING count(*) >= 2
|
||
) sub) AS listings_dedup_2sources,
|
||
(SELECT count(*) FROM active_listings WHERE lat IS NOT NULL) * 100.0
|
||
/ NULLIF((SELECT count(*) FROM active_listings), 0) AS pct_geocoded,
|
||
(SELECT count(*) FROM active_listings WHERE cadastral_number IS NOT NULL) * 100.0
|
||
/ NULLIF((SELECT count(*) FROM active_listings), 0) AS pct_cadastr,
|
||
(SELECT count(*) FROM active_listings WHERE description IS NOT NULL) * 100.0
|
||
/ NULLIF((SELECT count(*) FROM active_listings), 0) AS pct_description,
|
||
(SELECT count(*) FROM active_listings l
|
||
JOIN houses h ON h.id = l.house_id_fk
|
||
WHERE h.year_built IS NOT NULL) * 100.0
|
||
/ NULLIF((SELECT count(*) FROM active_listings), 0) AS pct_year_built,
|
||
NOW() - (SELECT max(scraped_at) FROM listings WHERE source = 'avito') AS avito_last_scrape_ago,
|
||
NOW() - (SELECT max(scraped_at) FROM listings WHERE source = 'cian') AS cian_last_scrape_ago,
|
||
NOW() - (SELECT max(scraped_at) FROM listings WHERE source = 'yandex') AS yandex_last_scrape_ago,
|
||
(SELECT count(*) FROM v_price_divergence) AS price_disagreements_count;
|
||
|
||
-- Комментарий из 095 утверждал, что view «refreshed on-demand by /api/v1/admin/
|
||
-- data-quality endpoint». Это неправда с момента переписывания ручки: живой
|
||
-- /api/v1/admin/scraper/data-quality строит собственный запрос по listings/houses и
|
||
-- этого view не касается, читателей в коде нет ни одного (проверено #2674). PR,
|
||
-- тезис которого «ложный показатель хуже отсутствующего», не имеет права нести
|
||
-- ложное утверждение о читателе — пишем как есть.
|
||
COMMENT ON VIEW v_data_quality IS
|
||
'KPI-снимок для РУЧНЫХ psql-запросов. Читателей в коде нет (проверено #2674): '
|
||
'/api/v1/admin/scraper/data-quality считает свои метрики сам и этот view не трогает. '
|
||
'#2674: outliers_flagged убран — is_outlier не писал никто, «выброс» определён только '
|
||
'внутри одной подборки аналогов (estimator._filter_outliers), не на объявлении.';
|
||
|
||
-- ── 2-3. scrape_runs: вырожденный run_type + четыре счётчика без писателя ─────
|
||
|
||
ALTER TABLE IF EXISTS scrape_runs
|
||
DROP COLUMN IF EXISTS run_type,
|
||
DROP COLUMN IF EXISTS http_requests,
|
||
DROP COLUMN IF EXISTS http_errors,
|
||
DROP COLUMN IF EXISTS returning_count,
|
||
DROP COLUMN IF EXISTS disappeared_count;
|
||
|
||
COMMIT;
|