gendesign/_wt-cadval/data/sql/80_complexes_backfill_nulls.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

223 lines
9.7 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.

-- 80_complexes_backfill_nulls.sql
-- Quick-win backfill для public.complexes через spatial joins + lookups.
-- Закрывает NULL'ы без новых scrapers.
--
-- Что делает:
-- A. cad_quarter — spatial join ST_Contains(cad_quarters_geom.geom, complexes.geom)
-- B. cad_building_num — колонка 100% NULL, никто не пишет в неё.
-- Выбрана Опция A: колонка дропается, relation живёт через
-- cad_buildings.complex_id (FK 1:N, уже существует).
-- Создаётся VIEW v_complex_buildings.
-- v_complex_full пересоздаётся без cad_building_num + с
-- cad_building_nums TEXT[] из агрегации по cad_buildings.
-- C. district_name — spatial join ST_Contains(ekb_districts.geom, complexes.geom)
-- (только EKB-покрытие, ~9 районов)
-- D. developer_id — fuzzy match lower(trim(developer_name)) с domrf_developers.
-- Ambiguous (>1 match по имени) — пропускаем, берём только
-- уникальные совпадения.
-- E. obj_class — источник данных domrf_kn_objects.obj_class = NULL для
-- всех 4548 строк; objective_lots.class даёт только 2 complexes.
-- Шаг выполняется (закрывает эти 2), но большого эффекта нет.
-- F. wall_type — domrf_kn_objects.wall_type = NULL для всех строк;
-- objective_lots не содержит wall_type.
-- Источника данных нет — шаг пропускается.
--
-- Dependencies:
-- - public.complexes (таблица)
-- - public.cad_quarters_geom (GIST индекс cad_quarters_geom_gist)
-- - public.ekb_districts (GIST индекс ekb_districts_geom_idx)
-- - public.domrf_developers (btree на developer_id PK)
-- - public.objective_lots (FK complex_id, поле class)
-- - public.cad_buildings (FK complex_id)
-- - public.v_complex_full (пересоздаётся — DROP CASCADE + CREATE)
--
-- Apply order: после 78_drop_use_rosreestr2coord.sql
--
-- Идемпотентность:
-- - UPDATE ... WHERE column IS NULL — повторный запуск не перезапишет NOT NULL.
-- - DROP TABLE IF EXISTS / CREATE OR REPLACE VIEW — безопасны.
-- - DROP COLUMN IF EXISTS — безопасен.
BEGIN;
-- ============================================================
-- A. cad_quarter — spatial join
-- ============================================================
-- Spatial indexes используются (EXPLAIN: Index Scan on cad_quarters_geom).
-- LIMIT 1 защищает от boundary случаев когда точка лежит на двух кварталах.
UPDATE complexes c
SET cad_quarter = (
SELECT q.cad_number
FROM cad_quarters_geom q
WHERE ST_Contains(q.geom, c.geom)
LIMIT 1
)
WHERE c.cad_quarter IS NULL AND c.geom IS NOT NULL;
-- ============================================================
-- B. cad_building_num — Опция A: дропаем колонку, relation через cad_buildings.complex_id
-- ============================================================
-- Шаг B.1: пересоздать v_complex_full без cad_building_num.
-- Вместо неё добавляем cad_building_nums TEXT[] (агрегация из cad_buildings).
-- DROP CASCADE нужен потому что PostgreSQL не позволяет CREATE OR REPLACE VIEW
-- если изменяется список/порядок колонок (cad_building_num убираем, cad_building_nums добавляем).
DROP VIEW IF EXISTS v_complex_full CASCADE;
CREATE OR REPLACE VIEW v_complex_full AS
SELECT
c.id AS complex_id,
c.canonical_name,
c.developer_name,
c.region_id,
c.district_name,
c.obj_class,
c.address,
c.latitude,
c.longitude,
(c.geom IS NOT NULL) AS has_geom,
c.flat_count,
c.square_living,
c.site_status,
c.cad_quarter,
-- cad_building_nums заменяет cad_building_num: агрегат из cad_buildings.cad_num
(
SELECT array_agg(b.cad_num ORDER BY b.cad_num)
FROM cad_buildings b
WHERE b.complex_id = c.id
) AS cad_building_nums,
(
SELECT COUNT(*)
FROM cad_buildings b
WHERE b.complex_id = c.id
) AS cad_buildings_n,
(
SELECT array_agg(DISTINCT cs.source ORDER BY cs.source)
FROM complex_sources cs
WHERE cs.complex_id = c.id
) AS sources,
(
SELECT COUNT(*)
FROM complex_sources cs
WHERE cs.complex_id = c.id
) AS source_links_n,
(
SELECT COUNT(DISTINCT cs.source)
FROM complex_sources cs
WHERE cs.complex_id = c.id
) AS source_kinds_n,
(
SELECT array_agg(cs.source_external_id ORDER BY cs.source_external_id)
FROM complex_sources cs
WHERE cs.complex_id = c.id
AND cs.source = 'domrf_kn'
) AS domrf_obj_ids,
(
SELECT cs.source_id
FROM complex_sources cs
WHERE cs.complex_id = c.id
AND cs.source = 'objective'
LIMIT 1
) AS objective_project_name,
(
SELECT COUNT(*)
FROM domrf_kn_flats f
WHERE f.obj_id IN (
SELECT cs.source_external_id
FROM complex_sources cs
WHERE cs.complex_id = c.id
AND cs.source = 'domrf_kn'
AND cs.source_external_id IS NOT NULL
)
) AS kn_flats_n,
(
SELECT COUNT(*)
FROM objective_lots ol
WHERE ol.complex_id = c.id
) AS objective_lots_n,
c.primary_source,
c.created_at,
c.updated_at
FROM complexes c;
-- Шаг B.2: дропаем колонку cad_building_num (100% NULL, индекс дропается автоматически)
ALTER TABLE complexes DROP COLUMN IF EXISTS cad_building_num;
-- Шаг B.3: VIEW v_complex_buildings — удобный агрегат complex → buildings
CREATE OR REPLACE VIEW v_complex_buildings AS
SELECT
c.id AS complex_id,
c.canonical_name,
array_agg(DISTINCT b.cad_num ORDER BY b.cad_num) AS cad_building_nums,
COUNT(b.cad_num) AS buildings_count
FROM complexes c
LEFT JOIN cad_buildings b ON b.complex_id = c.id
GROUP BY c.id, c.canonical_name;
-- ============================================================
-- C. district_name — spatial join (EKB coverage only)
-- ============================================================
-- EXPLAIN: Index Scan on ekb_districts_geom_idx — GIST index используется.
-- Закрывает только complexes внутри 9 районов EKB (geom IS NOT NULL).
-- Complexes без geom или за пределами EKB остаются NULL.
UPDATE complexes c
SET district_name = (
SELECT d.district_name
FROM ekb_districts d
WHERE d.geom IS NOT NULL
AND ST_Contains(d.geom, c.geom)
LIMIT 1
)
WHERE c.district_name IS NULL AND c.geom IS NOT NULL;
-- ============================================================
-- D. developer_id — fuzzy name match с domrf_developers
-- ============================================================
-- Берём только случаи с ровно одним совпадением по нормализованному имени
-- (lower + trim). Ambiguous (29 complexes с ≥2 candidates) — пропускаем.
UPDATE complexes c
SET developer_id = (
SELECT d.developer_id
FROM domrf_developers d
WHERE lower(trim(d.developer_name)) = lower(trim(c.developer_name))
LIMIT 1
)
WHERE c.developer_id IS NULL
AND c.developer_name IS NOT NULL
AND (
-- только если ровно одна запись в domrf_developers с таким именем
SELECT COUNT(DISTINCT d2.developer_id)
FROM domrf_developers d2
WHERE lower(trim(d2.developer_name)) = lower(trim(c.developer_name))
) = 1;
-- ============================================================
-- E. obj_class — via objective_lots.class (2 complexes покрывается)
-- ============================================================
-- domrf_kn_objects.obj_class = NULL для всех строк (источник не заполнен).
-- objective_lots.class — единственный источник; покрывает ~2 complexes с NULL obj_class.
-- Берём mode (наиболее частый класс) по complex_id.
UPDATE complexes c
SET obj_class = (
SELECT ol.class
FROM objective_lots ol
WHERE ol.complex_id = c.id
AND ol.class IS NOT NULL
GROUP BY ol.class
ORDER BY COUNT(*) DESC
LIMIT 1
)
WHERE c.obj_class IS NULL
AND EXISTS (
SELECT 1 FROM objective_lots ol2
WHERE ol2.complex_id = c.id AND ol2.class IS NOT NULL
);
-- ============================================================
-- F. wall_type — источника данных нет
-- ============================================================
-- domrf_kn_objects.wall_type = NULL для всех 4548 строк.
-- objective_lots не содержит wall_type.
-- Шаг пропускается — нет источника для backfill.
COMMIT;