From 0a24818bc6caf22397b73a996d4bf98bb5b488aa Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sun, 7 Jun 2026 00:46:15 +0300 Subject: [PATCH] =?UTF-8?q?feat(sf):=20newbuilding=5Flistings=20house-grai?= =?UTF-8?q?n=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20future-=D0=96=D0=9A=20=D1=81=D0=BB=D0=BE=D1=8F=20(#975?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/sql/135_newbuilding_listings.sql | 94 +++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 data/sql/135_newbuilding_listings.sql diff --git a/data/sql/135_newbuilding_listings.sql b/data/sql/135_newbuilding_listings.sql new file mode 100644 index 00000000..439800d4 --- /dev/null +++ b/data/sql/135_newbuilding_listings.sql @@ -0,0 +1,94 @@ +-- 135_newbuilding_listings.sql +-- #975 (SF-v2 #950 EPIC 6 «3-слойный склад», future-ЖК слой карты B4 / GG-форсайт). +-- +-- На gendesign НЕТ таблицы под листинги новостроек (house-grain). Источник данных — tradein-БД +-- таблица `houses` (cian/yandex_newbuilding скраперы): building-grain, ~13k строк. `objective_lots` +-- на gendesign — LOT-grain (квартиры/лоты), зерно НЕ совпадает → переиспользовать нельзя. +-- +-- РЕШЕНИЕ (objective_lots-reuse vs new, зафиксировано здесь по DoD #975): **новая house-grain +-- таблица**. objective_lots = лот (квартира), houses/newbuilding = дом/ЖК-корпус — разное зерно, +-- разные ключи, разные потребители (lots → market/velocity; newbuilding → future-supply карта). +-- Смешивать = grain-mismatch. Держим раздельно. +-- +-- Зеркалит КЛЮЧЕВЫЕ колонки tradein.houses (не все 60 — только нужное SF future-ЖК слою + cross-ref): +-- идентификация/гео, класс/застройщик/рейтинг, очередность (houses_by_turn), кадастр, source-ids. +-- Наполняется cross-load ETL (#976 950-E5) идемпотентным upsert по uq (source, ext_house_id). +-- +-- Зерно (grain): одна строка = один дом/корпус новостройки (house-grain). Дедуп-ключ — +-- (source, ext_house_id): ext_house_id — id дома в источнике (cian/yandex). re-load UPSERT-ит +-- (latest wins): crossloaded_at = freshness переноса. +-- +-- Deploy: auto-applied deploy.yml через _schema_migrations (ровно один раз по NN). Idempotent: +-- CREATE TABLE/INDEX IF NOT EXISTS + guarded DO для UNIQUE (прецедент м.132/133/134). +-- НИКАКИХ строк здесь не вставляется (данные — из ETL #976). + +BEGIN; + +-- ── 1. Таблица newbuilding_listings ──────────────────────────────────────────── +CREATE TABLE IF NOT EXISTS newbuilding_listings ( + id BIGSERIAL PRIMARY KEY, + source TEXT NOT NULL, -- 'cian' | 'yandex' | ... (источник дома) + ext_house_id TEXT NOT NULL, -- id дома в источнике (houses.ext_house_id) — дедуп + -- ── идентификация / гео ── + url TEXT, -- ссылка ЖК/дома (cian_zhk_url / listing url) + slug TEXT, + address TEXT, + full_address TEXT, + lat DOUBLE PRECISION, + lon DOUBLE PRECISION, + geom geometry(Point, 4326), -- точка дома (для future-ЖК слоя карты B4) + -- ── класс / застройщик / характеристики ── + house_class TEXT, -- класс ЖК (houses.house_class) + developer_name TEXT, + developer_key TEXT, + year_built INTEGER, + commission_year INTEGER, -- год ввода (план/факт) + total_floors INTEGER, + total_units INTEGER, + -- ── рейтинг / очередность / кадастр ── + rating NUMERIC, + reviews_count INTEGER, + houses_by_turn JSONB, -- очередность строительства (houses.houses_by_turn) + cadastral_number TEXT, + -- ── cross-ref на источники ── + cian_internal_house_id BIGINT, + yandex_jk_id TEXT, + raw_payload JSONB, -- полный houses.raw_payload (forward-compat) + source_last_scraped_at timestamptz, -- houses.last_scraped_at (свежесть источника) + crossloaded_at timestamptz NOT NULL DEFAULT now(), -- когда ETL #976 перенёс (freshness) + CONSTRAINT newbuilding_listings_source_chk CHECK (length(source) > 0), + CONSTRAINT newbuilding_listings_ext_id_chk CHECK (length(ext_house_id) > 0) +); + +COMMENT ON TABLE newbuilding_listings IS + 'Листинги новостроек (house-grain, #975 SF-v2 EPIC 6). Зеркало ключевых колонок tradein.houses ' + '(cian/yandex_newbuilding) для future-ЖК слоя карты SF (B4). 1 строка = 1 дом/корпус. UPSERT ' + 'cross-load ETL (#976) по uq_newbuilding_listings (source, ext_house_id). НЕ objective_lots ' + '(тот lot-grain) — отдельное зерно. analyze/карта читают будущее предложение через эту таблицу.'; +COMMENT ON COLUMN newbuilding_listings.ext_house_id IS 'id дома в источнике (tradein.houses.ext_house_id); дедуп-ключ внутри source'; +COMMENT ON COLUMN newbuilding_listings.houses_by_turn IS 'Очередность строительства (JSONB из houses.houses_by_turn) — для future-supply прогноза'; + +-- ── 2. Дедуп/UPSERT-ключ (source, ext_house_id) ──────────────────────────────── +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint WHERE conname = 'uq_newbuilding_listings' + ) THEN + ALTER TABLE newbuilding_listings + ADD CONSTRAINT uq_newbuilding_listings + UNIQUE (source, ext_house_id); + END IF; +END +$$; + +-- ── 3. Индексы ───────────────────────────────────────────────────────────────── +-- Главный read-path future-ЖК слоя: spatial (карта в bbox / overlap участка). +CREATE INDEX IF NOT EXISTS idx_newbuilding_listings_geom + ON newbuilding_listings USING GIST (geom); +-- Фильтры аналитики: по застройщику / классу. +CREATE INDEX IF NOT EXISTS idx_newbuilding_listings_developer + ON newbuilding_listings (developer_key) WHERE developer_key IS NOT NULL; +CREATE INDEX IF NOT EXISTS idx_newbuilding_listings_class + ON newbuilding_listings (house_class) WHERE house_class IS NOT NULL; + +COMMIT;