gendesign/_wt-cadval/data/sql/165_velocity_mapping_reviewed_gate.sql
bot-backend 542ff1c9ca docs(tradein/domclick): два комментария описывали пул, которого нет с миграции 253
Оба места утверждали, что у Домклика один выделенный резидентный прокси и
пула нет. Это перестало быть правдой ещё в #2800 (миграция 253 сняла
резервацию узла), но текст остался — и именно на него опирался тикет #3189,
поставленный под «калибровку» ограничения, которого не существует.
Свип к тому же ходит через пул давно (serp.py:359), а бэкфилл подключён к
нему в PR #3222.

Заодно докстринг называл не тот ограничитель: свип кладёт не счётчик
провалов lease, а break по первому DomClickBlockedError (#2854) — до
ротации дело не доходит ни при каком счётчике, отсюда buckets_completed=0.

Только комментарии, поведение не меняется. Миграция 175 уже применена, а
_schema_migrations трекает по имени файла без checksum — правка текста
её не перезапустит.
2026-08-29 18:57:25 +03:00

93 lines
5 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 165_velocity_mapping_reviewed_gate.sql
-- #307 OBJ-2 — gate objective_complex_mapping by confidence в mv_layout_velocity.
--
-- Контекст: fuzzy-trgm backfill (#1331/#1333, миграции 155/156/116/117/150) добавил
-- ~115+ auto-matched строк в objective_complex_mapping с is_reviewed=false и низким
-- match_score (вплоть до 0.50-0.625). Они с высокой вероятностью false-positive
-- (Objective project name ≠ тот же ЖК что domrf_obj_id) и искажали velocity-агрегаты
-- Site Finder, попадая в MV наравне с проверенными маппингами.
--
-- Fix: redefine MV с confidence-gate в JOIN — принимаем маппинг только если
-- is_reviewed = TRUE (человек подтвердил), ИЛИ
-- match_method = 'manual' (ручной маппинг, score обычно NULL), ИЛИ
-- match_score >= 0.85 (AUTO_ACCEPT_THRESHOLD из objective_backfill.py —
-- high-confidence auto, fuzzy_trgm 0.85+ надёжен).
--
-- Строгий gate только на is_reviewed=true дал бы 2 строки из 303 EKB-маппингов →
-- обнулил бы velocity. Порог 0.85 сохраняет ~264/303, отбрасывая ~39 низкоуверенных.
-- Тот же предикат применён в runtime-пути backend/app/services/site_finder/velocity.py.
--
-- Сохраняет weighted-average формулу из #21 (100_fix_mv_layout_velocity_weighted_avg.sql) —
-- меняется ТОЛЬКО WHERE/JOIN-условие на objective_complex_mapping.
--
-- Dependencies: mv_layout_velocity не имеет зависимых VIEW/MV (проверено 2026-05-17/100_fix).
-- Idempotency: DROP IF EXISTS → безопасен при повторном запуске.
-- Deploy order: SQL migration → app deploy. Auto-applied через _schema_migrations.
-- ВАЖНО: DROP + CREATE + REFRESH (non-concurrent, блокировка ~30 с на ~19 738 source rows).
BEGIN;
DROP MATERIALIZED VIEW IF EXISTS mv_layout_velocity CASCADE;
CREATE MATERIALIZED VIEW mv_layout_velocity AS
WITH last24mo AS (
SELECT
ocm.project_name,
CASE
WHEN ocm.room_bucket = 'студия' THEN 'studio'
ELSE ocm.room_bucket
END AS room_bucket,
ocm.deals_total_count,
ocm.deals_total_avg_area_m2,
ocm.deals_total_avg_price_thousand_rub_per_m2,
ocm.deals_total_vol_m2,
ocm.report_month
FROM objective_corpus_room_month ocm
WHERE ocm.report_month >= (NOW() - INTERVAL '24 months')::date
)
SELECT
cm.domrf_obj_id AS obj_id,
l.room_bucket,
SUM(l.deals_total_count)::int AS total_deals_24mo,
-- weighted average (#21) — нули из месяцев без сделок не тянут вниз.
(SUM(l.deals_total_avg_area_m2 * l.deals_total_count)
/ NULLIF(SUM(l.deals_total_count), 0))::numeric(10, 2) AS avg_area_m2,
(SUM(l.deals_total_avg_price_thousand_rub_per_m2 * l.deals_total_count)
/ NULLIF(SUM(l.deals_total_count), 0))::numeric(12, 2) AS avg_price_thousand_rub_per_m2,
SUM(l.deals_total_vol_m2)::numeric(12, 2) AS total_vol_m2,
MIN(l.report_month) AS window_start,
MAX(l.report_month) AS window_end,
COUNT(DISTINCT l.report_month)::int AS months_with_data
FROM last24mo l
JOIN objective_complex_mapping cm
ON cm.objective_complex_name = l.project_name
WHERE l.room_bucket IS NOT NULL
AND cm.domrf_obj_id IS NOT NULL
AND cm.objective_group = 'Екатеринбург'
-- #307 OBJ-2: confidence-gate — исключаем unreviewed low-score auto-matches.
AND (cm.is_reviewed = TRUE OR cm.match_method = 'manual' OR cm.match_score >= 0.85)
GROUP BY cm.domrf_obj_id, l.room_bucket
WITH NO DATA;
-- UNIQUE index: required for REFRESH CONCURRENTLY (periodic via layout_velocity_refresh.py).
CREATE UNIQUE INDEX mv_layout_velocity_pk
ON mv_layout_velocity (obj_id, room_bucket);
-- Lookup index for /best-layouts endpoint queries by obj_id.
CREATE INDEX mv_layout_velocity_obj_idx
ON mv_layout_velocity (obj_id);
-- Initial populate (non-concurrent — MV just created, CONCURRENTLY requires populated MV).
REFRESH MATERIALIZED VIEW mv_layout_velocity;
COMMENT ON MATERIALIZED VIEW mv_layout_velocity IS
'Per-(obj_id, room_bucket) deals aggregation за last 24 months. '
'WEIGHTED average площади и цены (SF Bug #21). '
'Confidence-gated mapping: is_reviewed/manual/score>=0.85 (#307 OBJ-2). '
'Source: objective_corpus_room_month × objective_complex_mapping (EKB only). '
'Refresh via layout_velocity_refresh.py (CONCURRENTLY после initial populate).';
COMMIT;