135 lines
6.4 KiB
SQL
135 lines
6.4 KiB
SQL
CREATE SCHEMA IF NOT EXISTS raw;
|
|
CREATE SCHEMA IF NOT EXISTS bronze;
|
|
CREATE SCHEMA IF NOT EXISTS silver;
|
|
CREATE SCHEMA IF NOT EXISTS gold;
|
|
CREATE SCHEMA IF NOT EXISTS app;
|
|
CREATE SCHEMA IF NOT EXISTS audit;
|
|
|
|
CREATE TABLE IF NOT EXISTS audit.data_sources (
|
|
source_id BIGSERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL UNIQUE,
|
|
source_type TEXT NOT NULL,
|
|
url TEXT,
|
|
cadence TEXT,
|
|
reliability TEXT NOT NULL DEFAULT 'unknown',
|
|
notes TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS audit.ingestion_runs (
|
|
run_id BIGSERIAL PRIMARY KEY,
|
|
source_id BIGINT REFERENCES audit.data_sources(source_id),
|
|
status TEXT NOT NULL,
|
|
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
finished_at TIMESTAMPTZ,
|
|
raw_uri TEXT,
|
|
row_count INTEGER,
|
|
error_message TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS silver.areas (
|
|
area_id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
district TEXT NOT NULL,
|
|
segment TEXT NOT NULL,
|
|
notes TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS silver.area_monthly_metrics (
|
|
area_id TEXT NOT NULL REFERENCES silver.areas(area_id),
|
|
month TEXT NOT NULL CHECK (month ~ '^[0-9]{4}-[0-9]{2}$'),
|
|
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),
|
|
median_days_on_market DOUBLE PRECISION NOT NULL CHECK (median_days_on_market >= 0),
|
|
rent_price_psm DOUBLE PRECISION NOT NULL CHECK (rent_price_psm >= 0),
|
|
new_supply_units INTEGER NOT NULL CHECK (new_supply_units >= 0),
|
|
land_residential_gfa_sqm DOUBLE PRECISION NOT NULL CHECK (land_residential_gfa_sqm >= 0),
|
|
mortgage_rate_pct DOUBLE PRECISION NOT NULL CHECK (mortgage_rate_pct >= 0),
|
|
policy_signal INTEGER NOT NULL DEFAULT 0 CHECK (policy_signal BETWEEN -2 AND 2),
|
|
source TEXT NOT NULL DEFAULT 'manual',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (area_id, month)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS silver.neighborhoods (
|
|
neighborhood_id TEXT PRIMARY KEY,
|
|
area_id TEXT NOT NULL REFERENCES silver.areas(area_id),
|
|
name TEXT NOT NULL,
|
|
built_year INTEGER,
|
|
property_type TEXT NOT NULL,
|
|
metro_distance_m INTEGER,
|
|
school_quality TEXT,
|
|
notes TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS silver.neighborhood_monthly_metrics (
|
|
neighborhood_id TEXT NOT NULL REFERENCES silver.neighborhoods(neighborhood_id),
|
|
month TEXT NOT NULL CHECK (month ~ '^[0-9]{4}-[0-9]{2}$'),
|
|
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),
|
|
available_units INTEGER NOT NULL CHECK (available_units >= 0),
|
|
source TEXT NOT NULL DEFAULT 'manual',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (neighborhood_id, month)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS gold.area_scores (
|
|
area_id TEXT NOT NULL REFERENCES silver.areas(area_id),
|
|
name TEXT NOT NULL,
|
|
district TEXT NOT NULL,
|
|
segment 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),
|
|
momentum_score DOUBLE PRECISION NOT NULL CHECK (momentum_score BETWEEN 0 AND 100),
|
|
rent_support_score DOUBLE PRECISION NOT NULL CHECK (rent_support_score BETWEEN 0 AND 100),
|
|
safety_margin_score DOUBLE PRECISION NOT NULL CHECK (safety_margin_score BETWEEN 0 AND 100),
|
|
credit_support_score DOUBLE PRECISION NOT NULL CHECK (credit_support_score BETWEEN 0 AND 100),
|
|
supply_risk_score DOUBLE PRECISION NOT NULL CHECK (supply_risk_score BETWEEN 0 AND 100),
|
|
valuation_pressure_score DOUBLE PRECISION NOT NULL CHECK (valuation_pressure_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),
|
|
listing_pressure_ratio DOUBLE PRECISION NOT NULL CHECK (listing_pressure_ratio >= 0),
|
|
discount_pct DOUBLE PRECISION NOT NULL,
|
|
price_momentum_pct DOUBLE PRECISION NOT NULL,
|
|
volume_momentum_pct DOUBLE PRECISION NOT NULL,
|
|
model_version TEXT NOT NULL DEFAULT 'sample-v1',
|
|
computed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (area_id, month)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS app.watchlist_items (
|
|
watchlist_item_id BIGSERIAL PRIMARY KEY,
|
|
neighborhood_id TEXT REFERENCES silver.neighborhoods(neighborhood_id),
|
|
area_id TEXT REFERENCES silver.areas(area_id),
|
|
target_price_psm DOUBLE PRECISION CHECK (target_price_psm >= 0),
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
notes TEXT NOT NULL DEFAULT '',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
CHECK (neighborhood_id IS NOT NULL OR area_id IS NOT NULL)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_area_monthly_metrics_month ON silver.area_monthly_metrics(month);
|
|
CREATE INDEX IF NOT EXISTS idx_neighborhoods_area ON silver.neighborhoods(area_id);
|
|
CREATE INDEX IF NOT EXISTS idx_gold_area_scores_month ON gold.area_scores(month, investment_score DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_watchlist_status ON app.watchlist_items(status);
|