-- 143_building_sale_share_schema.sql
--
-- CONTEXT: схема-фундамент новой страницы «доля квартир дома в продаже» —
--   список домов ВТОРИЧНОГО рынка, где
--       active_vtorichka_listings / total_apartments_in_building >= threshold%.
--   Числитель (активные вторичные объявления на дом) у нас уже есть в `listings`
--   (house_id_fk + is_active + listing_segment='vtorichka'). Знаменатель —
--   «сколько всего квартир в доме» — на проде заполнен слабо (total_units/flat_count
--   неполные/шумные), поэтому отдельно паримся над ГАР/ФИАС-дампом по региону 66 и
--   матчим к houses по НОРМАЛИЗОВАННОМУ адресу (tradein_normalize_short_addr).
--
--   Этот файл — ТОЛЬКО схема (таблица-приёмник ГАР + provenance-колонки в houses +
--   rollup-view). Сам парс/загрузка ГАР и backfill houses.gar_* — отдельная задача;
--   до неё view опирается на fallback-знаменатель (total_units / flat_count).
--
-- WHAT:
--   1. Таблица gar_house_flats — «квартир на дом» из ГАР (region-66, ключ — адрес/GUID).
--   2. houses.gar_* — provenance матча ГАР→houses (guid, flat_count, matched_at, method).
--   3. v_building_sale_share — per-building rollup вторички с эффективным знаменателем
--      COALESCE(gar_flat_count, total_units, flat_count) и sale_share_pct.
--
-- DEPENDENCIES (existing prod objects):
--   - houses (id, total_units, flat_count, geom Point/4326, short_address,
--     full_address, address, lat, lon, year_built, house_type, total_floors,
--     series_name, is_emergency) — core tables.
--   - listings (house_id_fk → houses.id, is_active, listing_segment, price_rub,
--     price_per_m2, days_on_market) — core tables.
--   - tradein_normalize_short_addr(text) — нормализатор адреса (используется
--     loader'ом ГАР для заполнения norm_address; в этом файле прямо не вызывается).
--   - pg_trgm extension — создаётся здесь IF NOT EXISTS (нужен для trgm-GIN на
--     norm_address: fuzzy-матч ГАР↔houses по адресу на следующем шаге).
--
-- SAFETY / IDEMPOTENCY:
--   - НЕ деструктивно: только CREATE TABLE/INDEX IF NOT EXISTS, ADD COLUMN IF NOT
--     EXISTS, CREATE OR REPLACE VIEW. Повторный прогон — no-op.
--   - NB: раннер деплоя (deploy-tradein.yml) гонит файл через psql -v
--     ON_ERROR_STOP=on БЕЗ --single-transaction → транзакцию открывает САМ файл
--     (BEGIN/COMMIT ниже), как 137/134/133.
--
-- NB по нумерации: исходно планировался как «139», но на forgejo/main 139–142
--   уже заняты (139_premium_houses … 142_premium_buildings_curated) → следующий
--   свободный sequential = 143 (проверено `ls | grep '^143'`).
--
-- Deploy order: после 142_premium_buildings_curated.sql.

BEGIN;

-- pg_trgm нужен для GIN-trgm индекса на norm_address (fuzzy ГАР↔houses match).
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- ─────────────────────────────────────────────────────────────────────
-- 1. gar_house_flats — «квартир на дом» из ГАР (region-66), address-keyed
-- ─────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS gar_house_flats (
    house_guid    text PRIMARY KEY,      -- ГАР OBJECTGUID дома
    object_id     bigint,                -- ГАР OBJECTID
    region_code   text NOT NULL,
    flat_count    int,                   -- # помещений-квартир под домом
    full_address  text,
    norm_address  text,                  -- tradein_normalize_short_addr(full_address)
    street_name   text,
    house_num     text,
    loaded_at     timestamptz NOT NULL DEFAULT now(),
    gar_version   text
);

CREATE INDEX IF NOT EXISTS gar_house_flats_norm_address_idx
    ON gar_house_flats (norm_address);

CREATE INDEX IF NOT EXISTS gar_house_flats_norm_address_trgm_idx
    ON gar_house_flats USING gin (norm_address gin_trgm_ops);

CREATE INDEX IF NOT EXISTS gar_house_flats_region_code_idx
    ON gar_house_flats (region_code);

COMMENT ON TABLE gar_house_flats IS
  'ГАР/ФИАС: квартир на дом (region-66), ключ house_guid (OBJECTGUID). '
  'norm_address = tradein_normalize_short_addr(full_address) для матча к houses. '
  'Заполняется отдельным loader''ом из self-parsed ГАР-дампа (мигр. 143).';

-- ─────────────────────────────────────────────────────────────────────
-- 2. houses — provenance-колонки матча ГАР→houses
-- ─────────────────────────────────────────────────────────────────────
ALTER TABLE houses ADD COLUMN IF NOT EXISTS gar_house_guid   text;
ALTER TABLE houses ADD COLUMN IF NOT EXISTS gar_flat_count   int;
ALTER TABLE houses ADD COLUMN IF NOT EXISTS gar_matched_at   timestamptz;
ALTER TABLE houses ADD COLUMN IF NOT EXISTS gar_match_method text;

-- partial: индексируем только реально заматченные дома (gar_flat_count populated).
CREATE INDEX IF NOT EXISTS houses_gar_flat_count_idx
    ON houses (gar_flat_count)
    WHERE gar_flat_count IS NOT NULL;

-- ─────────────────────────────────────────────────────────────────────
-- 3. v_building_sale_share — per-building rollup вторички
--    Эффективный знаменатель = COALESCE(ГАР, total_units, flat_count).
--    Строки с NULL-знаменателем НЕ отбрасываем (sale_share_pct=NULL) —
--    coverage-запрос должен уметь их посчитать.
-- ─────────────────────────────────────────────────────────────────────
CREATE OR REPLACE VIEW v_building_sale_share AS
WITH listing_agg AS (
    -- агрегат активных вторичных объявлений на дом (keyed на house_id_fk)
    SELECT
        l.house_id_fk AS house_id,
        count(*) FILTER (
            WHERE l.is_active AND l.listing_segment = 'vtorichka'
        ) AS active_secondary,
        percentile_cont(0.5) WITHIN GROUP (ORDER BY l.price_rub)
            FILTER (
                WHERE l.is_active AND l.listing_segment = 'vtorichka'
                  AND l.price_rub IS NOT NULL
            ) AS median_price_rub,
        percentile_cont(0.5) WITHIN GROUP (ORDER BY l.price_per_m2)
            FILTER (
                WHERE l.is_active AND l.listing_segment = 'vtorichka'
                  AND l.price_per_m2 IS NOT NULL
            ) AS median_price_per_m2,
        avg(l.days_on_market) FILTER (
            WHERE l.is_active AND l.listing_segment = 'vtorichka'
              AND l.days_on_market IS NOT NULL
        ) AS avg_days_on_market
    FROM listings l
    WHERE l.house_id_fk IS NOT NULL
    GROUP BY l.house_id_fk
)
SELECT
    h.id            AS house_id,
    h.short_address,
    h.full_address,
    h.address,
    h.lat,
    h.lon,
    h.year_built,
    h.house_type,
    h.total_floors,
    h.series_name,
    h.is_emergency,
    COALESCE(h.gar_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))
        AS flat_count_effective,
    h.gar_flat_count,
    h.gar_match_method,
    la.active_secondary,
    la.median_price_rub,
    la.median_price_per_m2,
    la.avg_days_on_market,
    CASE
        WHEN COALESCE(h.gar_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0)) > 0
        THEN round(
            100.0 * la.active_secondary
            / COALESCE(h.gar_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0)),
            1
        )
        ELSE NULL
    END AS sale_share_pct
FROM houses h
JOIN listing_agg la ON la.house_id = h.id
WHERE h.geom IS NOT NULL
  AND la.active_secondary > 0;

COMMENT ON VIEW v_building_sale_share IS
  'Per-building rollup вторички для страницы «доля квартир дома в продаже» (мигр. 143). '
  'flat_count_effective = COALESCE(gar_flat_count, NULLIF(total_units,0), NULLIF(flat_count,0)); '
  'sale_share_pct = round(100*active_secondary/flat_count_effective,1) только при denom>0 '
  '(иначе NULL — строка сохраняется для coverage-подсчёта). Фильтр: geom NOT NULL AND '
  'active_secondary>0.';

COMMIT;
