feat(db): v_bucket_success_score view (success-driven mix #25)
This commit is contained in:
parent
8be539ddc6
commit
369b5a4706
1 changed files with 102 additions and 0 deletions
102
data/sql/86_v_bucket_success_score.sql
Normal file
102
data/sql/86_v_bucket_success_score.sql
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
-- 86_v_bucket_success_score.sql
|
||||||
|
-- Context: Success-driven bucket ranking view for recommend_mix (issue #25).
|
||||||
|
-- Per-(district × class × room_bucket) aggregates from domrf_kn_flats + domrf_kn_objects
|
||||||
|
-- over the last 24 months, with Z-scores normalised per (district, class) group.
|
||||||
|
-- Used in recommend_mix to bias recommendations toward buckets with high velocity + price.
|
||||||
|
--
|
||||||
|
-- Dependencies:
|
||||||
|
-- domrf_kn_flats — total_area, rooms, price_per_m2, obj_id, region_cd, snapshot_date
|
||||||
|
-- domrf_kn_objects — obj_id, district_name, obj_class, region_cd, snapshot_date
|
||||||
|
-- domrf_kn_sale_graph — obj_id, report_month, contracted, snapshot_date
|
||||||
|
--
|
||||||
|
-- Deploy order: standalone, no prior migration needed.
|
||||||
|
-- Idempotent: CREATE OR REPLACE VIEW.
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW v_bucket_success_score AS
|
||||||
|
WITH
|
||||||
|
-- Latest snapshot per object (both tables are time-series)
|
||||||
|
latest_obj AS (
|
||||||
|
SELECT DISTINCT ON (obj_id)
|
||||||
|
obj_id, district_name, obj_class, region_cd
|
||||||
|
FROM domrf_kn_objects
|
||||||
|
WHERE district_name IS NOT NULL
|
||||||
|
ORDER BY obj_id, snapshot_date DESC
|
||||||
|
),
|
||||||
|
|
||||||
|
bucket_aggs AS (
|
||||||
|
SELECT
|
||||||
|
o.district_name,
|
||||||
|
COALESCE(o.obj_class, 'Comfort') AS obj_class,
|
||||||
|
CASE
|
||||||
|
WHEN f.rooms = 0 OR f.total_area < 30 THEN 'Студии 15-30'
|
||||||
|
WHEN f.total_area < 45 THEN '1-к 30-45'
|
||||||
|
WHEN f.total_area < 60 THEN '2-к 45-60'
|
||||||
|
WHEN f.total_area < 80 THEN '3-к 60-80'
|
||||||
|
ELSE '80+ м²'
|
||||||
|
END AS bucket,
|
||||||
|
AVG(NULLIF(f.price_per_m2, 0)) AS avg_price_per_m2,
|
||||||
|
AVG(NULLIF(f.total_area, 0)) AS avg_area_m2,
|
||||||
|
COUNT(*) AS n_deals,
|
||||||
|
-- Velocity proxy: avg monthly contracted units per object, over last 24 months
|
||||||
|
AVG(
|
||||||
|
(SELECT AVG(sg.contracted)
|
||||||
|
FROM domrf_kn_sale_graph sg
|
||||||
|
WHERE sg.obj_id = o.obj_id
|
||||||
|
AND sg.report_month > NOW() - INTERVAL '24 months')
|
||||||
|
) AS avg_velocity_per_month
|
||||||
|
FROM domrf_kn_flats f
|
||||||
|
JOIN latest_obj o ON o.obj_id = f.obj_id
|
||||||
|
WHERE f.total_area BETWEEN 15 AND 200
|
||||||
|
AND f.price_per_m2 BETWEEN 30000 AND 500000
|
||||||
|
AND o.region_cd = 66
|
||||||
|
AND f.snapshot_date > CURRENT_DATE - INTERVAL '24 months'
|
||||||
|
GROUP BY 1, 2, 3
|
||||||
|
HAVING COUNT(*) >= 30 -- статистически значимая выборка
|
||||||
|
),
|
||||||
|
|
||||||
|
z_scores AS (
|
||||||
|
SELECT
|
||||||
|
district_name, obj_class, bucket,
|
||||||
|
n_deals, avg_price_per_m2, avg_area_m2, avg_velocity_per_month,
|
||||||
|
-- Z-score нормированный по группе (district × class)
|
||||||
|
(avg_velocity_per_month
|
||||||
|
- AVG(avg_velocity_per_month) OVER (PARTITION BY district_name, obj_class))
|
||||||
|
/ NULLIF(STDDEV(avg_velocity_per_month) OVER (PARTITION BY district_name, obj_class), 0)
|
||||||
|
AS velocity_z,
|
||||||
|
(avg_price_per_m2
|
||||||
|
- AVG(avg_price_per_m2) OVER (PARTITION BY district_name, obj_class))
|
||||||
|
/ NULLIF(STDDEV(avg_price_per_m2) OVER (PARTITION BY district_name, obj_class), 0)
|
||||||
|
AS price_z,
|
||||||
|
(avg_area_m2
|
||||||
|
- AVG(avg_area_m2) OVER (PARTITION BY district_name, obj_class))
|
||||||
|
/ NULLIF(STDDEV(avg_area_m2) OVER (PARTITION BY district_name, obj_class), 0)
|
||||||
|
AS area_z
|
||||||
|
FROM bucket_aggs
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
district_name,
|
||||||
|
obj_class,
|
||||||
|
bucket,
|
||||||
|
n_deals,
|
||||||
|
avg_price_per_m2,
|
||||||
|
avg_area_m2,
|
||||||
|
avg_velocity_per_month,
|
||||||
|
COALESCE(velocity_z, 0) * 0.5
|
||||||
|
+ COALESCE(price_z, 0) * 0.3
|
||||||
|
- COALESCE(area_z, 0) * 0.2 AS success_score,
|
||||||
|
COALESCE(velocity_z, 0) AS velocity_z,
|
||||||
|
COALESCE(price_z, 0) AS price_z,
|
||||||
|
COALESCE(area_z, 0) AS area_z
|
||||||
|
FROM z_scores
|
||||||
|
ORDER BY district_name, obj_class, success_score DESC;
|
||||||
|
|
||||||
|
COMMENT ON VIEW v_bucket_success_score IS
|
||||||
|
'Success-driven bucket ranking per (district, class). '
|
||||||
|
'Z-scores: velocity (+) приоритет, price (+) приоритет, area (-) приоритет. '
|
||||||
|
'Min 30 сделок в группе. '
|
||||||
|
'Используется recommend_mix для смещения рекомендации (issue #25).';
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
Loading…
Add table
Reference in a new issue