-- 109_asking_to_sold_ratio_segment_filter.sql
-- #1364/#1186 — пересчитать asking_to_sold_ratios с secondary-only asking на ПРОДЕ.
--
-- ПОЧЕМУ ОТДЕЛЬНАЯ МИГРАЦИЯ. Правка derivation в 080_asking_to_sold_ratios.sql
-- (добавлен `listing_segment IS NULL OR = 'vtorichka'`) НЕ долетает до прода: 080 уже
-- в _schema_migrations → пропускается. Ранее посеянные ratio посчитаны ВКЛЮЧАЯ
-- новостройки на asking-стороне (на проде 2026-06-15: novostroyki = 9647 из ~19.5k
-- активных listings — почти половина), что систематически искажает median(asking ppm²)
-- и, как следствие, sold/asking ratio. Эта миграция пере-деривирует коэффициенты на
-- secondary-only asking (080 — для fresh-install).
--
-- ПОЧЕМУ DELETE ПЕРЕД INSERT. ON CONFLICT DO UPDATE обновляет только бакеты, прошедшие
-- порог 30/30 в НОВОЙ выборке. Бакет, у которого после отсева новостроек n_listings
-- падает ниже 30, выпадает из SELECT → стейл-строка (basis='per_rooms') осталась бы.
-- Чистим district='' (в #648 район всегда '') и сеем заново — estimator для выпавшего
-- бакета корректно делает fallback на global -1.
--
-- Derivation байт-в-байт повторяет 080 (CTE deal_side/ask_side/per_bucket/global_row,
-- окно 12 мес, ppm² [30000,600000], бакет LEAST(GREATEST(rooms,0),4)) — с secondary-only
-- guard на обеих asking-сторонах. Idempotent (DELETE+ON CONFLICT DO UPDATE), под gate
-- _schema_migrations (ровно один раз). Stage 4 asking_to_sold_ratio_refresh продолжит
-- держать ratio актуальным той же логикой.
-- ЗАВИСИМОСТИ: deals, listings (002_core_tables.sql), listings.listing_segment (м.011),
--   asking_to_sold_ratios (080).
-- Apply after: 108_clean_avito_addresses_v2.sql

BEGIN;

-- Сброс прежних (novostroyki-загрязнённых) коэффициентов перед чистым пере-севом.
DELETE FROM asking_to_sold_ratios WHERE district = '';

WITH
deal_side AS (
    SELECT
        LEAST(GREATEST(rooms, 0), 4)                                  AS rooms_bucket,
        percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2)     AS sold_median,
        COUNT(*)                                                      AS n_deals
    FROM deals
    WHERE source = 'rosreestr'
      AND rooms IS NOT NULL
      AND price_per_m2 BETWEEN 30000 AND 600000
      AND deal_date >= CURRENT_DATE - INTERVAL '12 months'
    GROUP BY LEAST(GREATEST(rooms, 0), 4)
),
ask_side AS (
    SELECT
        LEAST(GREATEST(rooms, 0), 4)                                  AS rooms_bucket,
        percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2)     AS ask_median,
        COUNT(*)                                                      AS n_listings
    FROM listings
    WHERE is_active
      AND rooms IS NOT NULL
      AND price_per_m2 BETWEEN 30000 AND 600000
      -- novostroyki guard (#1364/#1186): NULL = legacy вторичка до м.011.
      AND (listing_segment IS NULL OR listing_segment = 'vtorichka')
    GROUP BY LEAST(GREATEST(rooms, 0), 4)
),
per_bucket AS (
    SELECT
        d.rooms_bucket,
        ''::text                                  AS district,
        (d.sold_median / a.ask_median)::numeric   AS ratio,
        round(d.sold_median)::bigint              AS sold_median,
        round(a.ask_median)::bigint               AS ask_median,
        d.n_deals::int                            AS n_deals,
        a.n_listings::int                         AS n_listings,
        12                                        AS window_months,
        'per_rooms'::text                         AS basis
    FROM deal_side d
    JOIN ask_side a USING (rooms_bucket)
    WHERE d.n_deals >= 30
      AND a.n_listings >= 30
      AND a.ask_median IS NOT NULL
      AND a.ask_median > 0
      AND d.sold_median IS NOT NULL
      AND d.sold_median > 0
),
deal_global AS (
    SELECT
        percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS sold_median,
        COUNT(*)                                                  AS n_deals
    FROM deals
    WHERE source = 'rosreestr'
      AND rooms IS NOT NULL
      AND price_per_m2 BETWEEN 30000 AND 600000
      AND deal_date >= CURRENT_DATE - INTERVAL '12 months'
),
ask_global AS (
    SELECT
        percentile_cont(0.5) WITHIN GROUP (ORDER BY price_per_m2) AS ask_median,
        COUNT(*)                                                  AS n_listings
    FROM listings
    WHERE is_active
      AND rooms IS NOT NULL
      AND price_per_m2 BETWEEN 30000 AND 600000
      -- novostroyki guard (#1364/#1186): NULL = legacy вторичка до м.011.
      AND (listing_segment IS NULL OR listing_segment = 'vtorichka')
),
global_row AS (
    SELECT
        -1                                        AS rooms_bucket,
        ''::text                                  AS district,
        (d.sold_median / a.ask_median)::numeric   AS ratio,
        round(d.sold_median)::bigint              AS sold_median,
        round(a.ask_median)::bigint               AS ask_median,
        d.n_deals::int                            AS n_deals,
        a.n_listings::int                         AS n_listings,
        12                                        AS window_months,
        'global_fallback'::text                   AS basis
    FROM deal_global d
    CROSS JOIN ask_global a
    WHERE a.ask_median IS NOT NULL
      AND a.ask_median > 0
      AND d.sold_median IS NOT NULL
      AND d.sold_median > 0
)
INSERT INTO asking_to_sold_ratios (
    rooms_bucket, district, ratio, sold_median, ask_median,
    n_deals, n_listings, window_months, basis
)
SELECT rooms_bucket, district, ratio, sold_median, ask_median,
       n_deals, n_listings, window_months, basis FROM global_row
UNION ALL
SELECT rooms_bucket, district, ratio, sold_median, ask_median,
       n_deals, n_listings, window_months, basis FROM per_bucket
ON CONFLICT (rooms_bucket, district) DO UPDATE SET
    ratio         = EXCLUDED.ratio,
    sold_median   = EXCLUDED.sold_median,
    ask_median    = EXCLUDED.ask_median,
    n_deals       = EXCLUDED.n_deals,
    n_listings    = EXCLUDED.n_listings,
    window_months = EXCLUDED.window_months,
    basis         = EXCLUDED.basis,
    computed_at   = now();

COMMIT;
