feat: add neighborhood asset and scoring api
This commit is contained in:
157
apps/api/migrations/202606230001_neighborhood_scores.sql
Normal file
157
apps/api/migrations/202606230001_neighborhood_scores.sql
Normal file
@@ -0,0 +1,157 @@
|
||||
CREATE TABLE IF NOT EXISTS gold.neighborhood_scores (
|
||||
neighborhood_id TEXT NOT NULL REFERENCES silver.neighborhoods(neighborhood_id),
|
||||
area_id TEXT NOT NULL REFERENCES silver.areas(area_id),
|
||||
name TEXT NOT NULL,
|
||||
area_name TEXT NOT NULL,
|
||||
district TEXT NOT NULL,
|
||||
month TEXT NOT NULL CHECK (month ~ '^[0-9]{4}-[0-9]{2}$'),
|
||||
investment_score DOUBLE PRECISION NOT NULL CHECK (investment_score BETWEEN 0 AND 100),
|
||||
recommendation TEXT NOT NULL,
|
||||
liquidity_score DOUBLE PRECISION NOT NULL CHECK (liquidity_score BETWEEN 0 AND 100),
|
||||
rent_support_score DOUBLE PRECISION NOT NULL CHECK (rent_support_score BETWEEN 0 AND 100),
|
||||
price_safety_score DOUBLE PRECISION NOT NULL CHECK (price_safety_score BETWEEN 0 AND 100),
|
||||
location_score DOUBLE PRECISION NOT NULL CHECK (location_score BETWEEN 0 AND 100),
|
||||
building_age_score DOUBLE PRECISION NOT NULL CHECK (building_age_score BETWEEN 0 AND 100),
|
||||
transaction_count INTEGER NOT NULL CHECK (transaction_count >= 0),
|
||||
transaction_price_psm DOUBLE PRECISION NOT NULL CHECK (transaction_price_psm >= 0),
|
||||
listing_count INTEGER NOT NULL CHECK (listing_count >= 0),
|
||||
listing_price_psm DOUBLE PRECISION NOT NULL CHECK (listing_price_psm >= 0),
|
||||
rent_price_psm DOUBLE PRECISION NOT NULL CHECK (rent_price_psm >= 0),
|
||||
median_days_on_market DOUBLE PRECISION NOT NULL CHECK (median_days_on_market >= 0),
|
||||
annual_rent_yield_pct DOUBLE PRECISION NOT NULL CHECK (annual_rent_yield_pct >= 0),
|
||||
discount_pct DOUBLE PRECISION NOT NULL,
|
||||
available_units INTEGER NOT NULL CHECK (available_units >= 0),
|
||||
model_version TEXT NOT NULL DEFAULT 'sample-v1',
|
||||
computed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (neighborhood_id, month)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_gold_neighborhood_scores_month
|
||||
ON gold.neighborhood_scores(month, investment_score DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_gold_neighborhood_scores_area_month
|
||||
ON gold.neighborhood_scores(area_id, month, investment_score DESC);
|
||||
|
||||
INSERT INTO silver.neighborhoods (
|
||||
neighborhood_id,
|
||||
area_id,
|
||||
name,
|
||||
built_year,
|
||||
property_type,
|
||||
metro_distance_m,
|
||||
school_quality,
|
||||
notes
|
||||
)
|
||||
VALUES
|
||||
('qiantan_a', 'qiantan', '前滩样例小区A', 2018, '商品住宅', 450, 'good', '演示样例'),
|
||||
('qiantan_b', 'qiantan', '前滩样例小区B', 2021, '商品住宅', 650, 'normal', '演示样例'),
|
||||
('xujiahui_a', 'xujiahui', '徐家汇样例小区A', 2008, '商品住宅', 300, 'excellent', '演示样例'),
|
||||
('xujiahui_b', 'xujiahui', '徐家汇样例小区B', 1998, '商品住宅', 520, 'good', '演示样例'),
|
||||
('danning_a', 'danning', '大宁样例小区A', 2015, '商品住宅', 600, 'good', '演示样例'),
|
||||
('danning_b', 'danning', '大宁样例小区B', 2006, '商品住宅', 850, 'normal', '演示样例'),
|
||||
('zhangjiang_a', 'zhangjiang', '张江样例小区A', 2016, '商品住宅', 700, 'normal', '演示样例'),
|
||||
('zhangjiang_b', 'zhangjiang', '张江样例小区B', 2020, '商品住宅', 500, 'good', '演示样例'),
|
||||
('hongqiao_a', 'hongqiao', '大虹桥样例小区A', 2020, '商品住宅', 900, 'normal', '演示样例'),
|
||||
('hongqiao_b', 'hongqiao', '大虹桥样例小区B', 2012, '商品住宅', 1200, 'normal', '演示样例')
|
||||
ON CONFLICT (neighborhood_id) DO UPDATE
|
||||
SET area_id = EXCLUDED.area_id,
|
||||
name = EXCLUDED.name,
|
||||
built_year = EXCLUDED.built_year,
|
||||
property_type = EXCLUDED.property_type,
|
||||
metro_distance_m = EXCLUDED.metro_distance_m,
|
||||
school_quality = EXCLUDED.school_quality,
|
||||
notes = EXCLUDED.notes,
|
||||
updated_at = now();
|
||||
|
||||
INSERT INTO silver.neighborhood_monthly_metrics (
|
||||
neighborhood_id,
|
||||
month,
|
||||
transaction_count,
|
||||
transaction_price_psm,
|
||||
listing_count,
|
||||
listing_price_psm,
|
||||
rent_price_psm,
|
||||
median_days_on_market,
|
||||
available_units,
|
||||
source
|
||||
)
|
||||
VALUES
|
||||
('qiantan_a', '2026-05', 12, 124000, 38, 128000, 195, 50, 38, 'sample'),
|
||||
('qiantan_b', '2026-05', 8, 119500, 42, 124000, 186, 57, 42, 'sample'),
|
||||
('xujiahui_a', '2026-05', 8, 131000, 30, 137000, 215, 62, 30, 'sample'),
|
||||
('xujiahui_b', '2026-05', 6, 119000, 36, 126500, 198, 78, 36, 'sample'),
|
||||
('danning_a', '2026-05', 14, 100000, 45, 104000, 162, 65, 45, 'sample'),
|
||||
('danning_b', '2026-05', 11, 93000, 52, 99000, 151, 74, 52, 'sample'),
|
||||
('zhangjiang_a', '2026-05', 18, 86000, 58, 90500, 143, 58, 58, 'sample'),
|
||||
('zhangjiang_b', '2026-05', 16, 89000, 46, 93000, 148, 52, 46, 'sample'),
|
||||
('hongqiao_a', '2026-05', 9, 77000, 76, 83500, 121, 94, 76, 'sample'),
|
||||
('hongqiao_b', '2026-05', 7, 72000, 88, 79500, 113, 102, 88, 'sample')
|
||||
ON CONFLICT (neighborhood_id, month) DO UPDATE
|
||||
SET transaction_count = EXCLUDED.transaction_count,
|
||||
transaction_price_psm = EXCLUDED.transaction_price_psm,
|
||||
listing_count = EXCLUDED.listing_count,
|
||||
listing_price_psm = EXCLUDED.listing_price_psm,
|
||||
rent_price_psm = EXCLUDED.rent_price_psm,
|
||||
median_days_on_market = EXCLUDED.median_days_on_market,
|
||||
available_units = EXCLUDED.available_units,
|
||||
source = EXCLUDED.source,
|
||||
updated_at = now();
|
||||
|
||||
INSERT INTO gold.neighborhood_scores (
|
||||
neighborhood_id,
|
||||
area_id,
|
||||
name,
|
||||
area_name,
|
||||
district,
|
||||
month,
|
||||
investment_score,
|
||||
recommendation,
|
||||
liquidity_score,
|
||||
rent_support_score,
|
||||
price_safety_score,
|
||||
location_score,
|
||||
building_age_score,
|
||||
transaction_count,
|
||||
transaction_price_psm,
|
||||
listing_count,
|
||||
listing_price_psm,
|
||||
rent_price_psm,
|
||||
median_days_on_market,
|
||||
annual_rent_yield_pct,
|
||||
discount_pct,
|
||||
available_units,
|
||||
model_version
|
||||
)
|
||||
VALUES
|
||||
('zhangjiang_b', 'zhangjiang', '张江样例小区B', '张江', '浦东新区', '2026-05', 76.8, '重点研究', 84.0, 72.0, 58.0, 78.0, 88.0, 16, 89000, 46, 93000, 148, 52, 2.00, 4.30, 46, 'sample-v1'),
|
||||
('zhangjiang_a', 'zhangjiang', '张江样例小区A', '张江', '浦东新区', '2026-05', 73.4, '观察池', 88.0, 70.0, 54.0, 68.0, 76.0, 18, 86000, 58, 90500, 143, 58, 1.99, 4.97, 58, 'sample-v1'),
|
||||
('qiantan_a', 'qiantan', '前滩样例小区A', '前滩', '浦东新区', '2026-05', 71.6, '观察池', 75.0, 63.0, 48.0, 82.0, 82.0, 12, 124000, 38, 128000, 195, 50, 1.89, 3.13, 38, 'sample-v1'),
|
||||
('danning_a', 'danning', '大宁样例小区A', '大宁', '静安区', '2026-05', 69.2, '观察池', 78.0, 66.0, 52.0, 70.0, 78.0, 14, 100000, 45, 104000, 162, 65, 1.94, 3.85, 45, 'sample-v1'),
|
||||
('xujiahui_a', 'xujiahui', '徐家汇样例小区A', '徐家汇', '徐汇区', '2026-05', 66.9, '观察池', 63.0, 68.0, 45.0, 92.0, 58.0, 8, 131000, 30, 137000, 215, 62, 1.97, 4.38, 30, 'sample-v1'),
|
||||
('qiantan_b', 'qiantan', '前滩样例小区B', '前滩', '浦东新区', '2026-05', 65.4, '观察池', 62.0, 61.0, 55.0, 72.0, 94.0, 8, 119500, 42, 124000, 186, 57, 1.87, 3.63, 42, 'sample-v1'),
|
||||
('danning_b', 'danning', '大宁样例小区B', '大宁', '静安区', '2026-05', 62.5, '中性观望', 60.0, 64.0, 62.0, 58.0, 54.0, 11, 93000, 52, 99000, 151, 74, 1.95, 6.06, 52, 'sample-v1'),
|
||||
('xujiahui_b', 'xujiahui', '徐家汇样例小区B', '徐家汇', '徐汇区', '2026-05', 59.8, '中性观望', 48.0, 66.0, 64.0, 76.0, 34.0, 6, 119000, 36, 126500, 198, 78, 2.00, 5.93, 36, 'sample-v1'),
|
||||
('hongqiao_a', 'hongqiao', '大虹桥样例小区A', '大虹桥', '闵行区', '2026-05', 54.7, '谨慎等待', 42.0, 61.0, 70.0, 48.0, 90.0, 9, 77000, 76, 83500, 121, 94, 1.89, 7.78, 76, 'sample-v1'),
|
||||
('hongqiao_b', 'hongqiao', '大虹桥样例小区B', '大虹桥', '闵行区', '2026-05', 49.6, '谨慎等待', 34.0, 58.0, 76.0, 36.0, 64.0, 7, 72000, 88, 79500, 113, 102, 1.88, 9.43, 88, 'sample-v1')
|
||||
ON CONFLICT (neighborhood_id, month) DO UPDATE
|
||||
SET area_id = EXCLUDED.area_id,
|
||||
name = EXCLUDED.name,
|
||||
area_name = EXCLUDED.area_name,
|
||||
district = EXCLUDED.district,
|
||||
investment_score = EXCLUDED.investment_score,
|
||||
recommendation = EXCLUDED.recommendation,
|
||||
liquidity_score = EXCLUDED.liquidity_score,
|
||||
rent_support_score = EXCLUDED.rent_support_score,
|
||||
price_safety_score = EXCLUDED.price_safety_score,
|
||||
location_score = EXCLUDED.location_score,
|
||||
building_age_score = EXCLUDED.building_age_score,
|
||||
transaction_count = EXCLUDED.transaction_count,
|
||||
transaction_price_psm = EXCLUDED.transaction_price_psm,
|
||||
listing_count = EXCLUDED.listing_count,
|
||||
listing_price_psm = EXCLUDED.listing_price_psm,
|
||||
rent_price_psm = EXCLUDED.rent_price_psm,
|
||||
median_days_on_market = EXCLUDED.median_days_on_market,
|
||||
annual_rent_yield_pct = EXCLUDED.annual_rent_yield_pct,
|
||||
discount_pct = EXCLUDED.discount_pct,
|
||||
available_units = EXCLUDED.available_units,
|
||||
model_version = EXCLUDED.model_version,
|
||||
computed_at = now();
|
||||
Reference in New Issue
Block a user