gendesign/data/sql/94_mv_layout_velocity.sql
lekss361 144970aadd feat(analytics): mv_layout_velocity MV + refresh helper (#113 PR B)
Add materialized view aggregating objective_corpus_room_month JOIN
objective_complex_mapping (matched via objective_complex_name) за last
24 months, grouped by (domrf_obj_id, room_bucket). Cyrillic 'студия'
normalised к ASCII 'studio' для consistency с layout_signature.py (PR A).

Tables / cardinality (verified live на проде):
- objective_corpus_room_month: 15 738 EKB rows, monthly 2025-05..2026-05
- objective_complex_mapping: 114 mapped EKB objects (7.5% coverage)
- Expected MV size: ~459 rows (114 objs × ~4 room_buckets)
- EXPLAIN: Hash Join 19k×114 → 8k pre-agg → 459 groups, no extra indexes needed

UNIQUE INDEX (obj_id, room_bucket) enables REFRESH CONCURRENTLY (used
by Python helper layout_velocity_refresh.py — НЕ scheduled в этом PR,
будет triggered manually или через celery beat в follow-up).

Migration applied auto-через deploy.yml (see .claude/rules/sql.md).

Smoke verification — qa-tester after deploy (test_layout_velocity_mv.py
removed because conftest.py / db fixture infra не существует в проекте;
проще smoke через прод SELECT COUNT после migration apply).
2026-05-16 11:00:02 +03:00

72 lines
3.1 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 94_mv_layout_velocity.sql
-- Issue #113 PR B — Materialized view per (obj_id, room_bucket) for top-layouts ranking.
--
-- Sources:
-- objective_corpus_room_month — monthly per-corpus-per-room deals (19 738 rows, 2025-05..2026-05)
-- objective_complex_mapping — project_name → domrf_obj_id mapping (114 mapped objects)
--
-- Join key:
-- objective_corpus_room_month.project_name = objective_complex_mapping.objective_complex_name
-- (verified: one project_name → at most one domrf_obj_id)
--
-- room_bucket normalisation:
-- cyrillic 'студия' → ASCII 'studio' for consistency with layout_signature.py (PR A, Issue #113)
-- values '1', '2', '3', '4+' are kept as-is
--
-- Window: last 24 months (rolling, anchored to NOW() at refresh time)
-- Expected rows after aggregation: ~459 (obj_id × room_bucket combinations)
--
-- REFRESH CONCURRENTLY is safe after this migration because a UNIQUE index
-- on (obj_id, room_bucket) is created immediately.
-- Subsequent refreshes: layout_velocity_refresh.py helper (PR B) — not scheduled yet.
--
-- Deploy: auto-applied by deploy.yml via _schema_migrations tracking.
-- Dependencies: none (no views depend on this MV at creation time).
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,
AVG(l.deals_total_avg_area_m2)::numeric(10, 2) AS avg_area_m2,
AVG(l.deals_total_avg_price_thousand_rub_per_m2)::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
GROUP BY cm.domrf_obj_id, l.room_bucket;
-- UNIQUE index required for REFRESH CONCURRENTLY (future periodic refreshes)
CREATE UNIQUE INDEX mv_layout_velocity_pk
ON mv_layout_velocity (obj_id, room_bucket);
-- Lookup index used by /best-layouts endpoint (PR C)
CREATE INDEX mv_layout_velocity_obj_idx
ON mv_layout_velocity (obj_id);
COMMIT;