feat(sf): newbuilding_listings house-grain таблица для future-ЖК слоя (#975)
All checks were successful
CI / changes (push) Successful in 7s
CI / changes (pull_request) Successful in 7s
CI / backend-tests (push) Successful in 5m48s
CI / backend-tests (pull_request) Successful in 5m50s

This commit is contained in:
bot-backend 2026-06-07 00:46:15 +03:00
parent dee7d64ac8
commit 0a24818bc6

View file

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