-- 149_zhkh_priority_denominator.sql
--
-- CONTEXT: страница «доля квартир дома в продаже» (/trade-in/sale-share). Знаменатель
--   «квартир в доме» = flat_count_effective из view v_building_sale_share.
--   Было (мигр. 146/148): приоритет COALESCE = ГАР первый
--       COALESCE(gar_flat_count, zhkh_flat_count, NULLIF(total_units,0), NULLIF(flat_count,0)).
--   Проблема: ГАР ДИКО недосчитывает квартиры в МКД (прод-замер):
--       · ул. Крауля 89А — ГАР=11 vs ЖКХ=429;
--       · 27 из 347 домов — ГАР ≥2× недосчёт (gar=7→zhkh=639, gar=2→zhkh=376).
--   ГИС ЖКХ (houses.zhkh_flat_count) точнее — заполняется отдельным loader'ом
--   (app/tasks/zhkh_flats_load.py), резолвит ~90% вторички.
--
-- WHAT:
--   1. houses — две ЖКХ-провенанс-колонки: zhkh_year (smallint), zhkh_floors_raw (text)
--      — заполняются тем же loader'ом, schema-slot здесь.
--   2. v_building_sale_share — ИНВЕРТИРУЕМ приоритет знаменателя: ЖКХ ПЕРВЫЙ
--          COALESCE(zhkh_flat_count, gar_flat_count, NULLIF(total_units,0), NULLIF(flat_count,0))
--      во ВСЕХ 7 местах (flat_count_effective + оба плаузибилити-гейта + оба деления +
--      оба numerator-guard). Плюс две новые transparency-колонки в конце SELECT:
--      zhkh_flat_count (сырой ЖКХ-счёт) + flat_count_source ('zhkh'|'gar'|'total_units'|
--      'flat_count'|NULL — какой источник реально дал знаменатель). CREATE OR REPLACE
--      VIEW допускает только ДОБАВЛЕНИЕ колонок в конец → существующие позиции/типы/
--      имена не трогаем (включая h.gar_flat_count, h.gar_match_method).
--
-- DEPENDENCIES: 143 (view + houses.gar_*), 144 (canon match → gar_flat_count),
--   145 (плаузибилити-гейт знаменателя), 146 (listings_45d + sale_share_pct_45d + zhkh
--   в COALESCE + houses.zhkh_flat_count), 147 (canon strip geo-prefixes),
--   148 (дедуп кросс-площадочных дублей — числители count(DISTINCT (rooms,round(area_m2),
--   floor))). Базируется на текущем (148) определении view — меняем ТОЛЬКО приоритет
--   COALESCE + добавляем 2 колонки в конец. houses.zhkh_flat_count — из мигр. 146.
--
-- SAFETY / IDEMPOTENCY: только ADD COLUMN IF NOT EXISTS + CREATE OR REPLACE VIEW
--   (колонки только добавляются в конец → структура совместима) + COMMENT. Повторный
--   прогон — no-op. Деплой-раннер гонит файл через psql -v ON_ERROR_STOP=on БЕЗ
--   --single-transaction → транзакцию открывает САМ файл (BEGIN/COMMIT ниже), как 146/148.
--
-- NB по нумерации: последний занятый = 148; следующий свободный sequential = 149
--   (проверено `ls tradein-mvp/backend/data/sql | grep '^14'` + открытые PR — дубля basename нет).
--
-- Deploy order: после 148_dedup_apartments_in_sale_share.sql.

BEGIN;

ALTER TABLE houses ADD COLUMN IF NOT EXISTS zhkh_year       smallint;
ALTER TABLE houses ADD COLUMN IF NOT EXISTS zhkh_floors_raw text;

CREATE OR REPLACE VIEW v_building_sale_share AS
 WITH listing_agg AS (
         SELECT l.house_id_fk AS house_id,
            count(DISTINCT (l.rooms, round(l.area_m2), l.floor)) FILTER (WHERE l.is_active AND l.listing_segment = 'vtorichka'::text) AS active_secondary,
            count(DISTINCT (l.rooms, round(l.area_m2), l.floor)) 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.zhkh_flat_count, 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.zhkh_flat_count, h.gar_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.zhkh_flat_count, h.gar_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))
            THEN round(100.0 * la.active_secondary::numeric
                       / COALESCE(h.zhkh_flat_count, h.gar_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.zhkh_flat_count, h.gar_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.zhkh_flat_count, h.gar_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))
            THEN round(100.0 * la.listings_45d::numeric
                       / COALESCE(h.zhkh_flat_count, h.gar_flat_count, NULLIF(h.total_units, 0), NULLIF(h.flat_count, 0))::numeric, 1)
            ELSE NULL::numeric
        END AS sale_share_pct_45d,
    h.zhkh_flat_count,
    CASE
        WHEN h.zhkh_flat_count IS NOT NULL THEN 'zhkh'
        WHEN h.gar_flat_count IS NOT NULL THEN 'gar'
        WHEN NULLIF(h.total_units, 0) IS NOT NULL THEN 'total_units'
        WHEN NULLIF(h.flat_count, 0) IS NOT NULL THEN 'flat_count'
        ELSE NULL::text
    END AS flat_count_source
   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; дедуп кросс-площадочных '
  'дублей мигр. 148; ЖКХ-приоритет знаменателя мигр. 149). flat_count_effective = '
  'COALESCE(zhkh_flat_count, gar_flat_count, NULLIF(total_units,0), NULLIF(flat_count,0)) — '
  'ЖКХ ПРИОРИТЕТ (ГИС ЖКХ точнее ГАР, который дико недосчитывает квартиры в МКД; мигр. 149). '
  'Колонки zhkh_flat_count (сырой ЖКХ-счёт) + flat_count_source (zhkh|gar|total_units|flat_count|'
  'NULL — какой источник реально дал знаменатель) добавлены для прозрачности. Оба числителя '
  'считают УНИКАЛЬНЫЕ КВАРТИРЫ по сигнатуре count(DISTINCT (rooms, round(area_m2), floor)), а не '
  'объявления: одна квартира на avito+cian+domclick = разные dedup_hash, но одна сигнатура. '
  'active_secondary = FILTER (is_active AND vtorichka); listings_45d = FILTER (vtorichka AND '
  'last_seen_at>=now()-45d). sale_share_pct = active_secondary/denom; sale_share_pct_45d = '
  'listings_45d/denom. Оба под плаузибилити-гейтом (denom>=GREATEST(total_floors,8) AND '
  'числитель<=denom; мигр. 145), иначе NULL. Частично-NULL сигнатуры считаются раздельно '
  '(count(DISTINCT ROW) игнорит лишь полностью-NULL строку). Фильтр: geom NOT NULL AND '
  '(active_secondary>0 OR listings_45d>0) — churn-only дома (0 активных, но есть листинги за '
  '45д) тоже видны.';

COMMIT;
