-- 120_fix_mv_layout_velocity_weighted_avg.sql
-- Fix #295 — mv_layout_velocity used naive AVG() over pre-aggregated per-month rows.
-- objective_corpus_room_month has rows even for months with 0 deals; those zero rows
-- dragged the average down 2-10x depending on how sparse the project's deal history is.
-- (e.g. a project with 1 deal in 1 month out of 24 reported months: AVG divided by 24,
--  weighted AVG divides by 1 → correct value.)
--
-- Fix: replace AVG() with count-weighted formula
--   SUM(val * deals_total_count) / NULLIF(SUM(deals_total_count), 0)
-- applied to both avg_area_m2 and avg_price_thousand_rub_per_m2.
--
-- Prod verification (2026-05-28, Vitamin-квартал на Титова, last 24 mo):
--   room  | buggy_area | wtd_area | buggy_price | wtd_price
--   studio|   19.11    |  26.03   |   129.83    |  175.83
--   1-rm  |   31.72    |  37.15   |   145.68    |  174.21
--   2-rm  |   41.44    |  55.89   |    98.52    |  134.97
--   3-rm  |   27.14    |  81.43   |    45.40    |  136.20
-- Weighted values match expected (studio ≈28m², 1-rm ≈38m², 2-rm ≈57m², 3-rm ≈81m²,
-- prices 110-180k ₽/m²). Sparse 3-rm (5 deals / 15 months) had 3x undercount.
--
-- All indexes recreated identically to 94_mv_layout_velocity.sql:
--   mv_layout_velocity_pk  (UNIQUE on obj_id, room_bucket) — required for REFRESH CONCURRENTLY
--   mv_layout_velocity_obj_idx (btree on obj_id)
--
-- CASCADE check (2026-05-28): no dependent views or tables found → no extra DDL needed.
--
-- Deploy: auto-applied by deploy.yml via _schema_migrations tracking.
-- After apply: REFRESH MATERIALIZED VIEW CONCURRENTLY mv_layout_velocity is safe
-- (MV is populated WITH DATA in this migration).
--
-- WARN: Re-applying this file will DROP + recreate the MV (CASCADE-safe as no dependents),
--       but will cause a brief data gap. Normally prevented by _schema_migrations tracking.

BEGIN;

DROP MATERIALIZED VIEW IF EXISTS mv_layout_velocity CASCADE;

CREATE MATERIALIZED VIEW mv_layout_velocity AS
WITH last24mo AS (
    SELECT
        ocm.project_name,
        CASE
            WHEN ocm.room_bucket = 'студия' THEN 'studio'
            ELSE ocm.room_bucket
        END                                                         AS room_bucket,
        ocm.deals_total_count,
        ocm.deals_total_avg_area_m2,
        ocm.deals_total_avg_price_thousand_rub_per_m2,
        ocm.deals_total_vol_m2,
        ocm.report_month
    FROM objective_corpus_room_month ocm
    WHERE ocm.report_month >= (NOW() - INTERVAL '24 months')::date
)
SELECT
    cm.domrf_obj_id                                                 AS obj_id,
    l.room_bucket,
    SUM(l.deals_total_count)::int                                   AS total_deals_24mo,
    -- Count-weighted average area: avoids zero-deal months dragging value down
    (SUM(l.deals_total_avg_area_m2 * l.deals_total_count)
        / NULLIF(SUM(l.deals_total_count), 0))::numeric(10, 2)     AS avg_area_m2,
    -- Count-weighted average price: same reasoning
    (SUM(l.deals_total_avg_price_thousand_rub_per_m2 * l.deals_total_count)
        / NULLIF(SUM(l.deals_total_count), 0))::numeric(12, 2)     AS avg_price_thousand_rub_per_m2,
    SUM(l.deals_total_vol_m2)::numeric(12, 2)                      AS total_vol_m2,
    MIN(l.report_month)                                             AS window_start,
    MAX(l.report_month)                                             AS window_end,
    COUNT(DISTINCT l.report_month)::int                            AS months_with_data
FROM last24mo l
JOIN objective_complex_mapping cm
    ON cm.objective_complex_name = l.project_name
WHERE l.room_bucket IS NOT NULL
  AND cm.domrf_obj_id IS NOT NULL
  AND cm.objective_group = 'Екатеринбург'  -- защита от cross-region Cartesian при future multi-city
GROUP BY cm.domrf_obj_id, l.room_bucket
WITH DATA;

-- UNIQUE index required for REFRESH CONCURRENTLY (periodic refreshes via layout_velocity_refresh.py)
CREATE UNIQUE INDEX mv_layout_velocity_pk
    ON mv_layout_velocity (obj_id, room_bucket);

-- Lookup index used by /best-layouts endpoint
CREATE INDEX mv_layout_velocity_obj_idx
    ON mv_layout_velocity (obj_id);

COMMENT ON MATERIALIZED VIEW mv_layout_velocity IS
  'Per-(obj_id, room_bucket) deals aggregation за last 24 months. '
  'Source: objective_corpus_room_month × objective_complex_mapping (EKB only). '
  'avg_area_m2 and avg_price_thousand_rub_per_m2 are COUNT-WEIGHTED to avoid '
  'zero-deal months skewing the average (fix #295). '
  'Refresh via layout_velocity_refresh.py (concurrently=True — MV is pre-populated).';

COMMIT;
