CREATE TABLE IF NOT EXISTS model_pricing_rule_sets ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), rule_set_key text NOT NULL UNIQUE, name text NOT NULL, description text, category text NOT NULL DEFAULT 'general', currency text NOT NULL DEFAULT 'resource', status text NOT NULL DEFAULT 'active', metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); ALTER TABLE IF EXISTS model_pricing_rules ADD COLUMN IF NOT EXISTS rule_set_id uuid REFERENCES model_pricing_rule_sets(id) ON DELETE CASCADE, ADD COLUMN IF NOT EXISTS rule_key text NOT NULL DEFAULT ('rule_' || replace(gen_random_uuid()::text, '-', '')), ADD COLUMN IF NOT EXISTS display_name text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS calculator_type text NOT NULL DEFAULT 'unit_weight', ADD COLUMN IF NOT EXISTS dimension_schema jsonb NOT NULL DEFAULT '{}'::jsonb, ADD COLUMN IF NOT EXISTS formula_config jsonb NOT NULL DEFAULT '{}'::jsonb, ADD COLUMN IF NOT EXISTS priority integer NOT NULL DEFAULT 100, ADD COLUMN IF NOT EXISTS status text NOT NULL DEFAULT 'active', ADD COLUMN IF NOT EXISTS metadata jsonb NOT NULL DEFAULT '{}'::jsonb; ALTER TABLE IF EXISTS integration_platforms ADD COLUMN IF NOT EXISTS pricing_rule_set_id uuid REFERENCES model_pricing_rule_sets(id) ON DELETE SET NULL; ALTER TABLE IF EXISTS platform_models ADD COLUMN IF NOT EXISTS pricing_rule_set_id uuid REFERENCES model_pricing_rule_sets(id) ON DELETE SET NULL; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint c JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey) WHERE c.contype = 'f' AND c.conrelid = 'integration_platforms'::regclass AND a.attname = 'pricing_rule_set_id' ) THEN ALTER TABLE integration_platforms ADD CONSTRAINT fk_integration_platforms_pricing_rule_set FOREIGN KEY (pricing_rule_set_id) REFERENCES model_pricing_rule_sets(id) ON DELETE SET NULL; END IF; IF NOT EXISTS ( SELECT 1 FROM pg_constraint c JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey) WHERE c.contype = 'f' AND c.conrelid = 'platform_models'::regclass AND a.attname = 'pricing_rule_set_id' ) THEN ALTER TABLE platform_models ADD CONSTRAINT fk_platform_models_pricing_rule_set FOREIGN KEY (pricing_rule_set_id) REFERENCES model_pricing_rule_sets(id) ON DELETE SET NULL; END IF; END $$; CREATE INDEX IF NOT EXISTS idx_model_pricing_rule_set ON model_pricing_rules(rule_set_id, resource_type, priority); CREATE UNIQUE INDEX IF NOT EXISTS idx_model_pricing_rule_set_key ON model_pricing_rules(rule_set_id, rule_key) WHERE rule_set_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_integration_platforms_pricing_rule_set ON integration_platforms(pricing_rule_set_id); CREATE INDEX IF NOT EXISTS idx_platform_models_pricing_rule_set ON platform_models(pricing_rule_set_id); INSERT INTO model_pricing_rule_sets (rule_set_key, name, description, category, currency, status, metadata) VALUES ( 'default-multimodal-v1', '默认多模态计价规则', '覆盖文本、图像和视频的默认计价规则,可作为平台或平台模型的基础价格模板。', 'default', 'resource', 'active', '{"source":"gateway.default","version":1}'::jsonb ) ON CONFLICT (rule_set_key) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, category = EXCLUDED.category, currency = EXCLUDED.currency, status = EXCLUDED.status, metadata = EXCLUDED.metadata, updated_at = now(); WITH default_set AS ( SELECT id FROM model_pricing_rule_sets WHERE rule_set_key = 'default-multimodal-v1' ) INSERT INTO model_pricing_rules ( rule_set_id, rule_key, display_name, scope_type, scope_id, resource_type, unit, base_price, currency, base_weight, dynamic_weight, calculator_type, dimension_schema, formula_config, priority, status, metadata ) SELECT default_set.id, item.rule_key, item.display_name, 'rule_set', default_set.id, item.resource_type, item.unit, item.base_price, 'resource', item.base_weight, item.dynamic_weight, item.calculator_type, item.dimension_schema, item.formula_config, item.priority, 'active', '{"source":"gateway.default"}'::jsonb FROM default_set CROSS JOIN ( VALUES ( 'text_input_tokens', '文本输入 Token', 'text_input', '1k_tokens', 0.01::numeric, '{"meter":"input_tokens"}'::jsonb, '{}'::jsonb, 'token_usage', '{"metrics":["input_tokens"],"unitScale":1000}'::jsonb, '{"formula":"ceil(input_tokens / 1000) * base_price"}'::jsonb, 10 ), ( 'text_output_tokens', '文本输出 Token', 'text_output', '1k_tokens', 0.03::numeric, '{"meter":"output_tokens"}'::jsonb, '{}'::jsonb, 'token_usage', '{"metrics":["output_tokens"],"unitScale":1000}'::jsonb, '{"formula":"ceil(output_tokens / 1000) * base_price"}'::jsonb, 20 ), ( 'image', '图像', 'image', 'image', 10::numeric, '{"meter":"count"}'::jsonb, '{"resolutionFactors":{"1K":1,"2K":1.5,"3K":1.75,"4K":2,"8K":4},"qualityFactors":{"low":0.5,"medium":1,"high":1.5}}'::jsonb, 'unit_weight', '{"dimensions":["count","resolution","quality"],"defaults":{"count":1,"quality":"medium","resolution":"1K"}}'::jsonb, '{"formula":"count * base_price * resolution_factor * quality_factor"}'::jsonb, 30 ), ( 'video_generation', '视频生成', 'video', '5s', 100::numeric, '{"meter":"duration","unitSeconds":5}'::jsonb, '{"resolutionWeights":{"480p":0.75,"720p":1,"1080p":1.5,"2160p":2},"audioWeights":{"true":2,"false":1},"referenceVideoWeights":{"true":1.5,"false":1},"voiceSpecifiedWeights":{"true":1.2,"false":1}}'::jsonb, 'duration_weight', '{"dimensions":["duration_seconds","resolution","audio","reference_video","voice_specified","count"],"defaults":{"count":1,"duration_seconds":5,"resolution":"720p","audio":false,"reference_video":false,"voice_specified":false}}'::jsonb, '{"formula":"count * ceil(duration_seconds / 5) * base_price * resolution_weight * audio_weight * reference_video_weight * voice_specified_weight"}'::jsonb, 50 ) ) AS item(rule_key, display_name, resource_type, unit, base_price, base_weight, dynamic_weight, calculator_type, dimension_schema, formula_config, priority) ON CONFLICT (rule_set_id, rule_key) WHERE rule_set_id IS NOT NULL DO UPDATE SET display_name = EXCLUDED.display_name, scope_type = EXCLUDED.scope_type, scope_id = EXCLUDED.scope_id, resource_type = EXCLUDED.resource_type, unit = EXCLUDED.unit, base_price = EXCLUDED.base_price, currency = EXCLUDED.currency, base_weight = EXCLUDED.base_weight, dynamic_weight = EXCLUDED.dynamic_weight, calculator_type = EXCLUDED.calculator_type, dimension_schema = EXCLUDED.dimension_schema, formula_config = EXCLUDED.formula_config, priority = EXCLUDED.priority, status = EXCLUDED.status, metadata = EXCLUDED.metadata, updated_at = now();