forked from niuniu/llm-intelligence
chore: prepare repository for publishing
This commit is contained in:
362
db/migrations/002_sprint1_complete_schema.sql
Normal file
362
db/migrations/002_sprint1_complete_schema.sql
Normal file
@@ -0,0 +1,362 @@
|
||||
-- Sprint 1: 数据层补全
|
||||
-- 将数据库从 3 张表升级到 8 张完整表 + audit_log
|
||||
-- 日期: 2026-05-10
|
||||
-- 负责人: 宰相
|
||||
|
||||
-- ============================================================
|
||||
-- 一、新表创建
|
||||
-- ============================================================
|
||||
|
||||
-- 1.1 model_provider: 模型厂商表
|
||||
CREATE TABLE IF NOT EXISTS model_provider (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE, -- "OpenAI", "百度", "DeepSeek"
|
||||
name_cn TEXT, -- 中文名
|
||||
country TEXT NOT NULL DEFAULT 'unknown', -- "US" / "CN" / "EU"
|
||||
website TEXT,
|
||||
founded_year INTEGER,
|
||||
description TEXT,
|
||||
logo_url TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active', -- active / deprecated
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by TEXT DEFAULT 'system',
|
||||
updated_by TEXT DEFAULT 'system',
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_provider_country ON model_provider(country);
|
||||
CREATE INDEX IF NOT EXISTS idx_provider_status ON model_provider(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_provider_deleted ON model_provider(deleted_at) WHERE deleted_at IS NOT NULL;
|
||||
|
||||
COMMENT ON TABLE model_provider IS '模型厂商/开发商信息';
|
||||
|
||||
-- 1.2 operator: 运营商/云平台表
|
||||
CREATE TABLE IF NOT EXISTS operator (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE, -- "阿里云", "AWS", "OpenRouter"
|
||||
name_cn TEXT,
|
||||
country TEXT NOT NULL DEFAULT 'unknown',
|
||||
website TEXT,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by TEXT DEFAULT 'system',
|
||||
updated_by TEXT DEFAULT 'system',
|
||||
deleted_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_operator_country ON operator(country);
|
||||
CREATE INDEX IF NOT EXISTS idx_operator_status ON operator(status);
|
||||
|
||||
COMMENT ON TABLE operator IS '模型运营平台/云服务商';
|
||||
|
||||
-- 1.3 region_pricing: 区域定价表(替代 model_prices)
|
||||
CREATE TABLE IF NOT EXISTS region_pricing (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
model_id BIGINT NOT NULL REFERENCES models(id) ON DELETE CASCADE,
|
||||
operator_id BIGINT REFERENCES operator(id) ON DELETE SET NULL,
|
||||
region TEXT NOT NULL DEFAULT 'global', -- global / cn / us / eu
|
||||
currency TEXT NOT NULL DEFAULT 'USD',
|
||||
input_price_per_mtok REAL NOT NULL DEFAULT 0,
|
||||
output_price_per_mtok REAL NOT NULL DEFAULT 0,
|
||||
request_price REAL, -- 按请求计费
|
||||
effective_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
is_free BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
source_url TEXT,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by TEXT DEFAULT 'system',
|
||||
updated_by TEXT DEFAULT 'system',
|
||||
UNIQUE(model_id, operator_id, region, currency, effective_date),
|
||||
-- CHECK 约束
|
||||
CONSTRAINT chk_price_non_negative CHECK (input_price_per_mtok >= 0 AND output_price_per_mtok >= 0),
|
||||
CONSTRAINT chk_currency_valid CHECK (currency IN ('CNY', 'USD', 'EUR'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_region_pricing_model_id ON region_pricing(model_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_region_pricing_region ON region_pricing(region);
|
||||
CREATE INDEX IF NOT EXISTS idx_region_pricing_currency ON region_pricing(currency);
|
||||
CREATE INDEX IF NOT EXISTS idx_region_pricing_is_free ON region_pricing(is_free);
|
||||
CREATE INDEX IF NOT EXISTS idx_region_pricing_effective ON region_pricing(effective_date);
|
||||
|
||||
COMMENT ON TABLE region_pricing IS '模型区域定价信息(含CNY/USD/EUR)';
|
||||
|
||||
-- 1.4 pricing_history: 价格变动历史
|
||||
CREATE TABLE IF NOT EXISTS pricing_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
model_id BIGINT NOT NULL REFERENCES models(id) ON DELETE CASCADE,
|
||||
region TEXT NOT NULL DEFAULT 'global',
|
||||
currency TEXT NOT NULL DEFAULT 'USD',
|
||||
old_input_price REAL,
|
||||
new_input_price REAL,
|
||||
old_output_price REAL,
|
||||
new_output_price REAL,
|
||||
change_percent REAL, -- 变动百分比
|
||||
changed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
reason TEXT, -- 变动原因(如官方公告)
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pricing_history_model ON pricing_history(model_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_pricing_history_changed ON pricing_history(changed_at);
|
||||
|
||||
COMMENT ON TABLE pricing_history IS '模型价格变动历史追踪';
|
||||
|
||||
-- 1.5 free_tier: 免费政策库
|
||||
CREATE TABLE IF NOT EXISTS free_tier (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
model_id BIGINT NOT NULL REFERENCES models(id) ON DELETE CASCADE,
|
||||
operator_id BIGINT REFERENCES operator(id) ON DELETE SET NULL,
|
||||
free_type TEXT NOT NULL DEFAULT 'limited', -- unlimited / limited / trial
|
||||
max_requests INTEGER, -- 免费请求次数上限
|
||||
max_tokens BIGINT, -- 免费Token上限
|
||||
expiration_date DATE, -- 免费政策到期日
|
||||
description TEXT,
|
||||
source_url TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(model_id, operator_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_free_tier_model ON free_tier(model_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_free_tier_type ON free_tier(free_type);
|
||||
|
||||
COMMENT ON TABLE free_tier IS '模型免费政策/额度信息';
|
||||
|
||||
-- 1.6 daily_report: 日报存储(替代 report_runs)
|
||||
CREATE TABLE IF NOT EXISTS daily_report (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
report_date DATE NOT NULL UNIQUE,
|
||||
status TEXT NOT NULL DEFAULT 'pending', -- pending / generated / failed
|
||||
model_count INTEGER, -- 当日模型总数
|
||||
new_models INTEGER DEFAULT 0, -- 新上线模型数
|
||||
price_changes INTEGER DEFAULT 0, -- 价格变动数
|
||||
free_models INTEGER DEFAULT 0, -- 免费模型数
|
||||
summary_md TEXT, -- Markdown摘要
|
||||
output_path TEXT,
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_daily_report_date ON daily_report(report_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_daily_report_status ON daily_report(status);
|
||||
|
||||
COMMENT ON TABLE daily_report IS '每日自动报告运行记录';
|
||||
|
||||
-- 1.7 user_subscription: 用户订阅(Phase 2 基础)
|
||||
CREATE TABLE IF NOT EXISTS user_subscription (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
subscribe_type TEXT NOT NULL DEFAULT 'daily', -- daily / price_alert / new_model
|
||||
model_id BIGINT REFERENCES models(id) ON DELETE SET NULL,
|
||||
provider_id BIGINT REFERENCES model_provider(id) ON DELETE SET NULL,
|
||||
threshold REAL, -- 价格变动阈值(%)
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, subscribe_type, model_id, provider_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_subscription_user ON user_subscription(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscription_active ON user_subscription(is_active);
|
||||
|
||||
COMMENT ON TABLE user_subscription IS '用户订阅配置(Phase 2)';
|
||||
|
||||
-- 1.8 audit_log: 审计日志【新增】
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
table_name TEXT NOT NULL,
|
||||
record_id BIGINT NOT NULL,
|
||||
field_name TEXT,
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
operation TEXT NOT NULL, -- INSERT / UPDATE / DELETE
|
||||
operator TEXT DEFAULT 'system', -- 操作人/采集器标识
|
||||
batch_id TEXT, -- 采集批次号
|
||||
source_url TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_table ON audit_log(table_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_record ON audit_log(record_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_batch ON audit_log(batch_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_log(created_at);
|
||||
|
||||
COMMENT ON TABLE audit_log IS '数据变更审计日志(血缘追踪)';
|
||||
|
||||
-- ============================================================
|
||||
-- 二、现有表字段扩充
|
||||
-- ============================================================
|
||||
|
||||
-- 2.1 修改 models 表,添加 TECHNICAL_DESIGN 中定义的字段
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 添加 provider_id 外键字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='provider_id') THEN
|
||||
ALTER TABLE models ADD COLUMN provider_id BIGINT REFERENCES model_provider(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
|
||||
-- 添加 version 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='version') THEN
|
||||
ALTER TABLE models ADD COLUMN version TEXT;
|
||||
END IF;
|
||||
|
||||
-- 添加 modality 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='modality') THEN
|
||||
ALTER TABLE models ADD COLUMN modality TEXT NOT NULL DEFAULT 'text';
|
||||
END IF;
|
||||
|
||||
-- 添加 release_date 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='release_date') THEN
|
||||
ALTER TABLE models ADD COLUMN release_date DATE;
|
||||
END IF;
|
||||
|
||||
-- 添加 elo_score 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='elo_score') THEN
|
||||
ALTER TABLE models ADD COLUMN elo_score REAL;
|
||||
END IF;
|
||||
|
||||
-- 添加 benchmark_scores 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='benchmark_scores') THEN
|
||||
ALTER TABLE models ADD COLUMN benchmark_scores JSONB;
|
||||
END IF;
|
||||
|
||||
-- 添加 data_confidence 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='data_confidence') THEN
|
||||
ALTER TABLE models ADD COLUMN data_confidence TEXT DEFAULT 'official';
|
||||
END IF;
|
||||
|
||||
-- 添加 retrieved_at 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='retrieved_at') THEN
|
||||
ALTER TABLE models ADD COLUMN retrieved_at TIMESTAMP;
|
||||
END IF;
|
||||
|
||||
-- 添加 batch_id 字段(血缘追踪)
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='batch_id') THEN
|
||||
ALTER TABLE models ADD COLUMN batch_id TEXT;
|
||||
END IF;
|
||||
|
||||
-- 添加 collector_version 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='collector_version') THEN
|
||||
ALTER TABLE models ADD COLUMN collector_version TEXT DEFAULT 'v1.0';
|
||||
END IF;
|
||||
|
||||
-- 添加 source_url 字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='source_url') THEN
|
||||
ALTER TABLE models ADD COLUMN source_url TEXT;
|
||||
END IF;
|
||||
|
||||
-- 添加审计字段
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='created_by') THEN
|
||||
ALTER TABLE models ADD COLUMN created_by TEXT DEFAULT 'system';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='updated_by') THEN
|
||||
ALTER TABLE models ADD COLUMN updated_by TEXT DEFAULT 'system';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name='models' AND column_name='deleted_at') THEN
|
||||
ALTER TABLE models ADD COLUMN deleted_at TIMESTAMP;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 2.2 为 models 表添加 CHECK 约束
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='chk_models_context_length') THEN
|
||||
ALTER TABLE models ADD CONSTRAINT chk_models_context_length CHECK (context_length IS NULL OR context_length <= 10000000);
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='chk_models_modality') THEN
|
||||
ALTER TABLE models ADD CONSTRAINT chk_models_modality CHECK (modality IN ('text', 'vision', 'audio', 'video', 'code', 'multimodal'));
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='chk_models_data_confidence') THEN
|
||||
ALTER TABLE models ADD CONSTRAINT chk_models_data_confidence CHECK (data_confidence IN ('official', 'inferred', 'unverified', 'stale'));
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 2.3 创建 models 表的新索引
|
||||
CREATE INDEX IF NOT EXISTS idx_models_provider_id ON models(provider_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_models_modality ON models(modality);
|
||||
CREATE INDEX IF NOT EXISTS idx_models_data_confidence ON models(data_confidence);
|
||||
CREATE INDEX IF NOT EXISTS idx_models_retrieved_at ON models(retrieved_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_models_batch_id ON models(batch_id);
|
||||
|
||||
-- ============================================================
|
||||
-- 三、审计触发器(自动更新 updated_at)
|
||||
-- ============================================================
|
||||
|
||||
-- 3.1 创建触发器函数
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- 3.2 为所有业务表创建触发器
|
||||
DO $$
|
||||
DECLARE
|
||||
tbl TEXT;
|
||||
tables TEXT[] := ARRAY['models', 'model_provider', 'operator', 'region_pricing',
|
||||
'pricing_history', 'free_tier', 'daily_report',
|
||||
'user_subscription'];
|
||||
BEGIN
|
||||
FOREACH tbl IN ARRAY tables
|
||||
LOOP
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = tbl || '_updated_at') THEN
|
||||
EXECUTE format('CREATE TRIGGER %I_updated_at BEFORE UPDATE ON %I FOR EACH ROW EXECUTE FUNCTION update_updated_at_column()', tbl, tbl);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END $$;
|
||||
|
||||
-- ============================================================
|
||||
-- 四、数据迁移(model_prices → region_pricing)
|
||||
-- ============================================================
|
||||
|
||||
-- 4.1 先创建默认 operator(OpenRouter)
|
||||
INSERT INTO operator (name, name_cn, country, description)
|
||||
VALUES ('OpenRouter', 'OpenRouter', 'US', 'OpenRouter API聚合平台')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
-- 4.2 迁移 model_prices 数据到 region_pricing
|
||||
INSERT INTO region_pricing (
|
||||
model_id, operator_id, region, currency,
|
||||
input_price_per_mtok, output_price_per_mtok,
|
||||
is_free, effective_date, source_url, created_at
|
||||
)
|
||||
SELECT
|
||||
mp.model_id,
|
||||
(SELECT id FROM operator WHERE name = 'OpenRouter' LIMIT 1),
|
||||
'global',
|
||||
mp.currency,
|
||||
COALESCE(mp.input_price_per_mtok, 0),
|
||||
COALESCE(mp.output_price_per_mtok, 0),
|
||||
COALESCE(m.is_free, FALSE),
|
||||
COALESCE(mp.effective_date, CURRENT_DATE),
|
||||
mp.source_url,
|
||||
mp.created_at
|
||||
FROM model_prices mp
|
||||
JOIN models m ON mp.model_id = m.id
|
||||
ON CONFLICT (model_id, operator_id, region, currency, effective_date) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 五、迁移 report_runs → daily_report
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO daily_report (report_date, status, output_path, error_message, created_at)
|
||||
SELECT report_date, status, output_path, error_message, created_at
|
||||
FROM report_runs
|
||||
ON CONFLICT (report_date) DO NOTHING;
|
||||
|
||||
-- ============================================================
|
||||
-- 六、完成标记
|
||||
-- ============================================================
|
||||
|
||||
SELECT 'Sprint 1 Schema Migration Complete' AS status;
|
||||
74
db/migrations/003_phase2_region_pricing_metadata.sql
Normal file
74
db/migrations/003_phase2_region_pricing_metadata.sql
Normal file
@@ -0,0 +1,74 @@
|
||||
-- Phase 2: region_pricing 扩展来源区分与免费额度元数据
|
||||
|
||||
ALTER TABLE region_pricing
|
||||
ADD COLUMN IF NOT EXISTS source_type TEXT NOT NULL DEFAULT 'official',
|
||||
ADD COLUMN IF NOT EXISTS free_quota TEXT,
|
||||
ADD COLUMN IF NOT EXISTS free_limitations TEXT NOT NULL DEFAULT '[]',
|
||||
ADD COLUMN IF NOT EXISTS rate_limit TEXT NOT NULL DEFAULT '{}';
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'chk_region_pricing_source_type'
|
||||
) THEN
|
||||
ALTER TABLE region_pricing
|
||||
ADD CONSTRAINT chk_region_pricing_source_type
|
||||
CHECK (source_type IN ('official', 'reseller', 'free_tier'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
UPDATE region_pricing rp
|
||||
SET
|
||||
source_type = CASE
|
||||
WHEN rp.is_free THEN 'free_tier'
|
||||
WHEN lower(coalesce(o.name, '')) IN (
|
||||
'openrouter',
|
||||
'siliconflow',
|
||||
'together ai',
|
||||
'groq',
|
||||
'baidu qianfan',
|
||||
'alibaba bailian',
|
||||
'tencent cloud',
|
||||
'huawei cloud'
|
||||
) THEN 'reseller'
|
||||
ELSE 'official'
|
||||
END,
|
||||
free_quota = CASE
|
||||
WHEN rp.is_free AND coalesce(rp.free_quota, '') = '' THEN 'Imported free-tier pricing entry'
|
||||
ELSE rp.free_quota
|
||||
END,
|
||||
free_limitations = CASE
|
||||
WHEN coalesce(rp.free_limitations, '') = '' THEN '[]'
|
||||
ELSE rp.free_limitations
|
||||
END,
|
||||
rate_limit = CASE
|
||||
WHEN coalesce(rp.rate_limit, '') = '' THEN '{}'
|
||||
ELSE rp.rate_limit
|
||||
END
|
||||
FROM operator o
|
||||
WHERE rp.operator_id = o.id;
|
||||
|
||||
UPDATE region_pricing
|
||||
SET
|
||||
source_type = CASE
|
||||
WHEN is_free THEN 'free_tier'
|
||||
ELSE source_type
|
||||
END,
|
||||
free_quota = CASE
|
||||
WHEN is_free AND coalesce(free_quota, '') = '' THEN 'Imported free-tier pricing entry'
|
||||
ELSE free_quota
|
||||
END,
|
||||
free_limitations = CASE
|
||||
WHEN coalesce(free_limitations, '') = '' THEN '[]'
|
||||
ELSE free_limitations
|
||||
END,
|
||||
rate_limit = CASE
|
||||
WHEN coalesce(rate_limit, '') = '' THEN '{}'
|
||||
ELSE rate_limit
|
||||
END
|
||||
WHERE operator_id IS NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_region_pricing_source_type ON region_pricing(source_type);
|
||||
5
db/migrations/004_backfill_models_batch_id.sql
Normal file
5
db/migrations/004_backfill_models_batch_id.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- 回填历史手工导入模型的 batch_id,避免血缘字段为空
|
||||
|
||||
UPDATE models
|
||||
SET batch_id = 'manual-seed'
|
||||
WHERE batch_id IS NULL;
|
||||
39
db/migrations/005_subscription_plan.sql
Normal file
39
db/migrations/005_subscription_plan.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
-- Phase 2: 腾讯云 / 订阅型套餐价格模型
|
||||
|
||||
CREATE TABLE IF NOT EXISTS subscription_plan (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
provider_id BIGINT NOT NULL REFERENCES model_provider(id) ON DELETE CASCADE,
|
||||
operator_id BIGINT REFERENCES operator(id) ON DELETE SET NULL,
|
||||
plan_family TEXT NOT NULL CHECK (plan_family IN ('token_plan', 'coding_plan')),
|
||||
plan_code TEXT NOT NULL,
|
||||
plan_name TEXT NOT NULL,
|
||||
tier TEXT NOT NULL,
|
||||
billing_cycle TEXT NOT NULL DEFAULT 'monthly',
|
||||
currency TEXT NOT NULL DEFAULT 'CNY',
|
||||
list_price REAL NOT NULL CHECK (list_price >= 0),
|
||||
price_unit TEXT NOT NULL,
|
||||
quota_value BIGINT,
|
||||
quota_unit TEXT,
|
||||
context_window INTEGER,
|
||||
plan_scope TEXT,
|
||||
model_scope TEXT NOT NULL DEFAULT '[]',
|
||||
source_url TEXT NOT NULL,
|
||||
published_at TIMESTAMP,
|
||||
effective_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_by TEXT DEFAULT 'system',
|
||||
updated_by TEXT DEFAULT 'system',
|
||||
UNIQUE (provider_id, plan_code, effective_date),
|
||||
CONSTRAINT chk_subscription_plan_currency CHECK (currency IN ('CNY', 'USD', 'EUR')),
|
||||
CONSTRAINT chk_subscription_plan_quota_non_negative CHECK (quota_value IS NULL OR quota_value >= 0),
|
||||
CONSTRAINT chk_subscription_plan_context_non_negative CHECK (context_window IS NULL OR context_window >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_subscription_plan_provider_id ON subscription_plan(provider_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscription_plan_operator_id ON subscription_plan(operator_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscription_plan_family ON subscription_plan(plan_family);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscription_plan_effective_date ON subscription_plan(effective_date);
|
||||
|
||||
COMMENT ON TABLE subscription_plan IS '订阅型套餐价格信息(如腾讯云 Token Plan / Coding Plan)';
|
||||
Reference in New Issue
Block a user