-- 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;