ОТКРЫТИЕ: stdlib urllib проходит WAF nspd.gov.ru (TLS-fingerprint stdlib отличается от mainstream HTTP-clients). Это позволяет полностью убрать Playwright + Chromium для NSPD-задач. * nspd_lite.py: urllib + ssl._create_unverified_context(). 4 публичных fetcher'а (geoportal/quarter/parcel/building) + fetch_via_rosreestr2coord fallback на community-lib * schema 77: nspd_geo_jobs (журнал) + nspd_geo_targets (cad-номера со статусом pending/done/failed). Resume-state в БД. * tasks/nspd_geo.py: Celery task process_nspd_geo_job(job_id). Heartbeat каждые 5 items, WAF backoff 30s × 2^N (max 8 → paused). UPSERT в cad_quarters_geom / cad_buildings → идемпотентно. * worker_ready hook: stale jobs (>10min без heartbeat) автоматически re-enqueue после redeploy. Никаких потерь прогресса. * 4 admin endpoint + UI /admin/scrape/geo с формой запуска (manual_list / rosreestr_pending для авто-наполнения из ДДУ-кварталов), progress-bars и cancel/resume кнопками per job. * settings: use_nspd_lite=True, nspd_lite_rate_ms=600. * dep: rosreestr2coord>=4.0.0 (community lib для fallback). TODO следующих шагов: - smoke-test на проде через SSH (validation RU-IP не банит urllib) - feature toggle USE_NSPD_LITE в scrape_nspd.py для переключения с старого Playwright-пути - docker lean image (-300 MB после убирания Chromium) - расширение SCRAPE_NSPD_DEFAULT_REGIONS на 66,74,72,59 (Челябинск/Тюмень/Пермь)
147 lines
7.4 KiB
SQL
147 lines
7.4 KiB
SQL
-- Соединение всех источников с canonical complexes (Phase A.2 продолжение).
|
||
-- Применено в проде через mcp postgres 2026-05-11.
|
||
-- Идемпотентно через ADD COLUMN IF NOT EXISTS / FK с REFERENCES.
|
||
|
||
-- ── 1. complexes.cad_quarter / cad_building_num: связь с rosreestr/NSPD ─────
|
||
ALTER TABLE complexes
|
||
ADD COLUMN IF NOT EXISTS cad_quarter TEXT,
|
||
ADD COLUMN IF NOT EXISTS cad_building_num TEXT;
|
||
CREATE INDEX IF NOT EXISTS complexes_cad_quarter_idx
|
||
ON complexes(cad_quarter) WHERE cad_quarter IS NOT NULL;
|
||
CREATE INDEX IF NOT EXISTS complexes_cad_building_idx
|
||
ON complexes(cad_building_num) WHERE cad_building_num IS NOT NULL;
|
||
|
||
-- Backfill: any non-null snapshot
|
||
WITH best_kn AS (
|
||
SELECT obj_id,
|
||
(array_agg(cad_quarter ORDER BY snapshot_date DESC NULLS LAST)
|
||
FILTER (WHERE cad_quarter IS NOT NULL))[1] AS cad_quarter,
|
||
(array_agg(cad_building_num ORDER BY snapshot_date DESC NULLS LAST)
|
||
FILTER (WHERE cad_building_num IS NOT NULL))[1] AS cad_building_num
|
||
FROM domrf_kn_objects GROUP BY obj_id
|
||
)
|
||
UPDATE complexes c
|
||
SET cad_quarter = COALESCE(c.cad_quarter, bk.cad_quarter),
|
||
cad_building_num = COALESCE(c.cad_building_num, bk.cad_building_num),
|
||
updated_at = NOW()
|
||
FROM complex_sources cs
|
||
JOIN best_kn bk ON bk.obj_id::text = cs.source_id
|
||
WHERE cs.complex_id = c.id AND cs.source = 'domrf_kn';
|
||
-- Эффект: cad_quarter заполнено у 551 из 1740 ЖК (32%).
|
||
-- cad_building_num = 0 (бaг kn-API: не парсит здание-уровень).
|
||
|
||
-- ── 2. objective_lots.complex_id (FK на complexes) ──────────────────────────
|
||
ALTER TABLE objective_lots
|
||
ADD COLUMN IF NOT EXISTS complex_id BIGINT REFERENCES complexes(id) ON DELETE SET NULL;
|
||
ALTER TABLE objective_corpus_room_month
|
||
ADD COLUMN IF NOT EXISTS complex_id BIGINT REFERENCES complexes(id) ON DELETE SET NULL;
|
||
ALTER TABLE objective_lots_history
|
||
ADD COLUMN IF NOT EXISTS complex_id BIGINT REFERENCES complexes(id) ON DELETE SET NULL;
|
||
CREATE INDEX IF NOT EXISTS objective_lots_complex_idx
|
||
ON objective_lots(complex_id) WHERE complex_id IS NOT NULL;
|
||
CREATE INDEX IF NOT EXISTS objective_crm_complex_idx
|
||
ON objective_corpus_room_month(complex_id) WHERE complex_id IS NOT NULL;
|
||
|
||
UPDATE objective_lots ol
|
||
SET complex_id = cs.complex_id
|
||
FROM complex_sources cs
|
||
WHERE cs.source = 'objective' AND cs.source_id = ol.project_name
|
||
AND ol.complex_id IS NULL;
|
||
|
||
UPDATE objective_corpus_room_month ocrm
|
||
SET complex_id = cs.complex_id
|
||
FROM complex_sources cs
|
||
WHERE cs.source = 'objective' AND cs.source_id = ocrm.project_name
|
||
AND ocrm.complex_id IS NULL;
|
||
-- Эффект: 303 677 lots / 19 738 crm — 100% покрытие (все Objective lots
|
||
-- теперь напрямую JOIN'ятся с complex без двойного хопа через source_id).
|
||
|
||
-- ── 3. cad_buildings.complex_id (spatial join 50м) ──────────────────────────
|
||
ALTER TABLE cad_buildings
|
||
ADD COLUMN IF NOT EXISTS complex_id BIGINT REFERENCES complexes(id) ON DELETE SET NULL;
|
||
CREATE INDEX IF NOT EXISTS cad_buildings_complex_idx
|
||
ON cad_buildings(complex_id) WHERE complex_id IS NOT NULL;
|
||
|
||
WITH bld_complex AS (
|
||
SELECT cb.cad_num, c.complex_id_match
|
||
FROM cad_buildings cb
|
||
CROSS JOIN LATERAL (
|
||
SELECT c2.id AS complex_id_match
|
||
FROM complexes c2
|
||
WHERE c2.geom IS NOT NULL
|
||
AND ST_DWithin(c2.geom::geography, cb.geom::geography, 50)
|
||
ORDER BY c2.geom <-> cb.geom LIMIT 1
|
||
) c
|
||
WHERE cb.geom IS NOT NULL AND cb.complex_id IS NULL
|
||
)
|
||
UPDATE cad_buildings cb
|
||
SET complex_id = bc.complex_id_match
|
||
FROM bld_complex bc WHERE cb.cad_num = bc.cad_num;
|
||
-- Эффект: 716 buildings / 10590 (~7%) связаны с 436 уникальными ЖК.
|
||
-- Низкое покрытие т.к. большинство cad_buildings — частные дома в кадастровых
|
||
-- кварталах, не относящиеся к ЖК. Радиус 50м консервативный.
|
||
|
||
-- ── 4. Auto-create stub complex для 283 orphan obj_id из kn_flats ───────────
|
||
WITH orphan_obj_ids AS (
|
||
SELECT DISTINCT obj_id FROM domrf_kn_flats f
|
||
WHERE NOT EXISTS (
|
||
SELECT 1 FROM complex_sources cs
|
||
WHERE cs.source='domrf_kn' AND cs.source_id = f.obj_id::text
|
||
)
|
||
),
|
||
inserted AS (
|
||
INSERT INTO complexes (canonical_name, region_id, primary_source)
|
||
SELECT 'orphan-kn-' || obj_id, 66, 'domrf_kn_flat_orphan' FROM orphan_obj_ids
|
||
RETURNING id, canonical_name
|
||
)
|
||
INSERT INTO complex_sources (
|
||
complex_id, source, source_id, source_external_id,
|
||
match_method, match_score, is_reviewed, note
|
||
)
|
||
SELECT i.id, 'domrf_kn', oo.obj_id::text, oo.obj_id, 'orphan_flats_only', 0.0, FALSE,
|
||
'auto-created stub: flats existed but kn-objects entry deleted/missing'
|
||
FROM orphan_obj_ids oo
|
||
JOIN inserted i ON i.canonical_name = 'orphan-kn-' || oo.obj_id;
|
||
-- Эффект: complexes 1740 → 2023 (+283 stub).
|
||
|
||
-- ── 5. v_complex_full v2: правильные counts с многокорпусными ЖК ────────────
|
||
DROP VIEW IF EXISTS v_complex_full;
|
||
CREATE 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, c.cad_building_num,
|
||
(SELECT array_agg(DISTINCT source ORDER BY source)
|
||
FROM complex_sources WHERE complex_id = c.id) AS sources,
|
||
(SELECT COUNT(*) FROM complex_sources WHERE complex_id = c.id) AS source_links_n,
|
||
(SELECT COUNT(DISTINCT source) FROM complex_sources WHERE complex_id = c.id) AS source_kinds_n,
|
||
(SELECT array_agg(source_external_id ORDER BY source_external_id)
|
||
FROM complex_sources WHERE complex_id = c.id AND source = 'domrf_kn') AS domrf_obj_ids,
|
||
(SELECT source_id FROM complex_sources
|
||
WHERE complex_id = c.id AND source = 'objective' LIMIT 1) AS objective_project_name,
|
||
(SELECT COUNT(*) FROM domrf_kn_flats f
|
||
WHERE f.obj_id IN (SELECT source_external_id FROM complex_sources
|
||
WHERE complex_id = c.id AND source = 'domrf_kn'
|
||
AND source_external_id IS NOT NULL)) AS kn_flats_n,
|
||
(SELECT COUNT(*) FROM objective_lots WHERE complex_id = c.id) AS objective_lots_n,
|
||
(SELECT COUNT(*) FROM cad_buildings WHERE complex_id = c.id) AS cad_buildings_n,
|
||
c.primary_source, c.created_at, c.updated_at
|
||
FROM complexes c;
|
||
|
||
COMMENT ON VIEW v_complex_full IS
|
||
'Карточка ЖК с агрегатами по всем источникам. multi-corpus ЖК (один project '
|
||
'в Objective = N obj_id в kn) корректно объединены: kn_flats_n считает все '
|
||
'корпуса. source_links_n включает дубликаты по корпусам, source_kinds_n — '
|
||
'unique источники.';
|
||
|
||
-- ── 6. ANALYZE для актуализации статистики ──────────────────────────────────
|
||
ANALYZE complexes;
|
||
ANALYZE complex_sources;
|
||
ANALYZE objective_lots;
|
||
ANALYZE objective_corpus_room_month;
|
||
ANALYZE cad_buildings;
|
||
ANALYZE domrf_kn_objects;
|
||
ANALYZE domrf_kn_flats;
|