-- 146_sale_share_45d_and_zhkh_denom.sql
--
-- CONTEXT: страница «доля квартир дома в продаже» получает ВТОРОЙ процент —
--   trailing-45-дневное окно — и второй источник знаменателя (ГИС ЖКХ).
--
--   Было (мигр. 143/145): один метрик sale_share_pct =
--       active_vtorichka_listings / flat_count_effective (только АКТИВНЫЕ объявления).
--   Стало: добавляем sale_share_pct_45d =
--       distinct vtorichka-листинги (активные ИЛИ недавно закрытые), увиденные за
--       последние 45 дней / flat_count_effective. Дом попадает в выборку, если ЛЮБОЙ
--       из двух процентов проходит порог (фильтрация — на стороне API).
--
--   Мы храним ~27k неактивных листингов с last_seen_at (история скрейпа ~36 дней),
--   поэтому окно 45д сейчас ≈ «всё, что когда-либо видели» — это ожидаемо и нормально
--   (по мере накопления истории окно станет действительно скользящим).
--
--   Второй источник знаменателя: houses.zhkh_flat_count (ГИС ЖКХ) — заполняется
--   ОТДЕЛЬНЫМ loader'ом позже; здесь только schema-slot + приоритет в COALESCE
--   (после ГАР, перед total_units/flat_count).
--
-- WHAT:
--   1. houses.zhkh_* — provenance-колонки матча ГИС ЖКХ → houses
--      (zhkh_flat_count, zhkh_house_guid, zhkh_matched_at). Partial-индекс на
--      zhkh_flat_count WHERE NOT NULL.
--   2. v_building_sale_share — добавляем listings_45d + sale_share_pct_45d,
--      zhkh как 2-й приоритет знаменателя, WHERE расширяем на churn-only дома
--      (0 активных, но есть листинги за 45д).
--
-- DEPENDENCIES: 143 (view + houses.gar_*), 144 (canon match populates gar_flat_count),
--   145 (плаузибилити-гейт знаменателя). listings.dedup_hash / listings.last_seen_at —
--   существующие колонки (dedup-merge мигр. 135 + scrape-tracking).
--
-- SAFETY / IDEMPOTENCY: только ADD COLUMN IF NOT EXISTS, CREATE INDEX IF NOT EXISTS,
--   CREATE OR REPLACE VIEW. Повторный прогон — no-op. Деплой-раннер гонит файл через
--   psql -v ON_ERROR_STOP=on БЕЗ --single-transaction → транзакцию открывает САМ файл
--   (BEGIN/COMMIT ниже), как 143/145.
--
-- NB по нумерации: 143–145 заняты building_sale_share_*; следующий свободный
--   sequential = 146 (проверено `ls tradein-mvp/backend/data/sql | grep '^14'`).
--
-- Deploy order: после 145_building_sale_share_plausible_denom.sql.

BEGIN;

-- ─────────────────────────────────────────────────────────────────────
-- 1. houses — provenance-колонки 2-го источника знаменателя (ГИС ЖКХ)
-- ─────────────────────────────────────────────────────────────────────
ALTER TABLE houses ADD COLUMN IF NOT EXISTS zhkh_flat_count  int;
ALTER TABLE houses ADD COLUMN IF NOT EXISTS zhkh_house_guid  text;
ALTER TABLE houses ADD COLUMN IF NOT EXISTS zhkh_matched_at  timestamptz;

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

-- ─────────────────────────────────────────────────────────────────────
-- 2. v_building_sale_share — два процента (now + 45д) + zhkh-знаменатель
--    flat_count_effective = COALESCE(ГАР, ЖКХ, total_units, flat_count).
--    sale_share_pct       = active_secondary / denom (как было).
--    sale_share_pct_45d   = distinct vtorichka за 45д / denom (новое).
--    Оба процента — под одним плаузибилити-гейтом (denom>=GREATEST(floors,8) AND
--    числитель<=denom), иначе NULL. WHERE расширен на churn-only дома.
-- ─────────────────────────────────────────────────────────────────────
CREATE OR REPLACE VIEW v_building_sale_share AS
 WITH listing_agg AS (
         SELECT l.house_id_fk AS house_id,
            count(*) FILTER (WHERE l.is_active AND l.listing_segment = 'vtorichka'::text) AS active_secondary,
            count(DISTINCT l.dedup_hash) FILTER (
                WHERE l.listing_segment = 'vtorichka'::text
                  AND l.last_seen_at >= (now() - interval '45 days')
            ) AS listings_45d,
            percentile_cont(0.5::double precision) WITHIN GROUP (ORDER BY (l.price_rub::double precision))
                FILTER (WHERE l.is_active AND l.listing_segment = 'vtorichka'::text AND l.price_rub IS NOT NULL) AS median_price_rub,
            percentile_cont(0.5::double precision) WITHIN GROUP (ORDER BY (l.price_per_m2::double precision))
                FILTER (WHERE l.is_active AND l.listing_segment = 'vtorichka'::text 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'::text 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, h.zhkh_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, h.zhkh_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))
                     >= GREATEST(COALESCE(h.total_floors, 0), 8)
             AND la.active_secondary <= COALESCE(h.gar_flat_count, h.zhkh_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))
            THEN round(100.0 * la.active_secondary::numeric
                       / COALESCE(h.gar_flat_count, h.zhkh_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))::numeric, 1)
            ELSE NULL::numeric
        END AS sale_share_pct,
    la.listings_45d,
        CASE
            WHEN COALESCE(h.gar_flat_count, h.zhkh_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))
                     >= GREATEST(COALESCE(h.total_floors, 0), 8)
             AND la.listings_45d <= COALESCE(h.gar_flat_count, h.zhkh_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))
            THEN round(100.0 * la.listings_45d::numeric
                       / COALESCE(h.gar_flat_count, h.zhkh_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))::numeric, 1)
            ELSE NULL::numeric
        END AS sale_share_pct_45d
   FROM houses h
     JOIN listing_agg la ON la.house_id = h.id
  WHERE h.geom IS NOT NULL AND (la.active_secondary > 0 OR la.listings_45d > 0);

COMMENT ON VIEW v_building_sale_share IS
  'Per-building rollup вторички для «доли квартир дома в продаже» (мигр. 143; знаменатель — '
  'ГАР canon-match мигр. 144; 2-й источник ЖКХ + окно 45д мигр. 146). flat_count_effective = '
  'COALESCE(gar_flat_count, zhkh_flat_count, NULLIF(total_units,0), NULLIF(flat_count,0)). '
  'sale_share_pct = active_secondary/denom; sale_share_pct_45d = count(DISTINCT dedup_hash) '
  'vtorichka за last_seen_at>=now()-45d / denom (активные ИЛИ недавно закрытые). Оба под '
  'плаузибилити-гейтом (denom>=GREATEST(total_floors,8) AND числитель<=denom; мигр. 145), '
  'иначе NULL. Фильтр: geom NOT NULL AND (active_secondary>0 OR listings_45d>0) — churn-only '
  'дома (0 активных, но есть листинги за 45д) тоже видны.';

COMMIT;
