交易流水对账明细 | AI引擎计价规则
This commit is contained in:
@@ -0,0 +1,582 @@
|
||||
"""add model pricing rules and credit pricing snapshots
|
||||
|
||||
Revision ID: 1c93b40133f0
|
||||
Revises: 2026070902
|
||||
Create Date: 2026-07-10 15:10:44.983521
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "1c93b40133f0"
|
||||
down_revision: Union[str, None] = "2026070902"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
FK_CREDIT_RECORD_PRICING_RULE = (
|
||||
"fk_credit_records_pricing_rule_id_model_pricing_rules"
|
||||
)
|
||||
|
||||
|
||||
def _json_type() -> sa.types.TypeEngine:
|
||||
"""Use JSONB on PostgreSQL and JSON on other supported development DBs."""
|
||||
return sa.JSON().with_variant(
|
||||
postgresql.JSONB(astext_type=sa.Text()),
|
||||
"postgresql",
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 1. Versioned model pricing rules.
|
||||
op.create_table(
|
||||
"model_pricing_rules",
|
||||
sa.Column("id", sa.String(length=32), nullable=False),
|
||||
sa.Column("provider", sa.String(length=32), nullable=False),
|
||||
sa.Column("model_name", sa.String(length=128), nullable=False),
|
||||
sa.Column("model_category", sa.String(length=16), nullable=False),
|
||||
sa.Column("billing_mode", sa.String(length=48), nullable=False),
|
||||
sa.Column("calculator_version", sa.String(length=64), nullable=False),
|
||||
sa.Column("version_code", sa.String(length=64), nullable=False),
|
||||
sa.Column("effective_from", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("effective_to", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("publish_status", sa.String(length=16), nullable=False),
|
||||
sa.Column("currency", sa.String(length=8), nullable=False),
|
||||
sa.Column("rule_schema_version", sa.Integer(), nullable=False),
|
||||
sa.Column("rule_json", _json_type(), nullable=False),
|
||||
sa.Column("rule_content_hash", sa.String(length=64), nullable=False),
|
||||
sa.Column("source_url", sa.Text(), nullable=True),
|
||||
sa.Column("source_updated_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("remark", sa.Text(), nullable=True),
|
||||
sa.Column("created_by", sa.String(length=32), nullable=True),
|
||||
sa.Column("updated_by", sa.String(length=32), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
# A disabled future rule is represented by an empty interval where
|
||||
# effective_to == effective_from, so equality must be allowed.
|
||||
sa.CheckConstraint(
|
||||
"effective_to IS NULL OR effective_to >= effective_from",
|
||||
name="ck_model_pricing_rules_effective_range",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
"uq_model_pricing_rules_provider_model_version",
|
||||
"model_pricing_rules",
|
||||
["provider", "model_name", "version_code"],
|
||||
unique=True,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_resolve",
|
||||
"model_pricing_rules",
|
||||
[
|
||||
"provider",
|
||||
"model_name",
|
||||
"publish_status",
|
||||
"effective_from",
|
||||
"effective_to",
|
||||
],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_category_status",
|
||||
"model_pricing_rules",
|
||||
["model_category", "publish_status"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_provider",
|
||||
"model_pricing_rules",
|
||||
["provider"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_model_name",
|
||||
"model_pricing_rules",
|
||||
["model_name"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_model_category",
|
||||
"model_pricing_rules",
|
||||
["model_category"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_billing_mode",
|
||||
"model_pricing_rules",
|
||||
["billing_mode"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_calculator_version",
|
||||
"model_pricing_rules",
|
||||
["calculator_version"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_effective_from",
|
||||
"model_pricing_rules",
|
||||
["effective_from"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_effective_to",
|
||||
"model_pricing_rules",
|
||||
["effective_to"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_publish_status",
|
||||
"model_pricing_rules",
|
||||
["publish_status"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_model_pricing_rules_rule_content_hash",
|
||||
"model_pricing_rules",
|
||||
["rule_content_hash"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
# 2. Bind provider callbacks to the exact billing attempt.
|
||||
op.add_column(
|
||||
"chat_generation_tasks",
|
||||
sa.Column("current_billing_attempt_no", sa.Integer(), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_chat_generation_tasks_current_billing_attempt_no",
|
||||
"chat_generation_tasks",
|
||||
["current_billing_attempt_no"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
# 3. Immutable pricing, usage, attachment, and output snapshots.
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_rule_id", sa.String(length=32), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_version_code", sa.String(length=64), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_billing_mode", sa.String(length=48), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"pricing_calculator_version",
|
||||
sa.String(length=64),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_usage_source", sa.String(length=32), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_reference_at", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_effective_from", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_effective_to", sa.DateTime(timezone=True), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_snapshot_schema_version", sa.Integer(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_snapshot_hash", sa.String(length=64), nullable=True),
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("provider_cost_currency", sa.String(length=8), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"provider_cost_amount",
|
||||
sa.Numeric(precision=20, scale=8),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("provider_cost_status", sa.String(length=32), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"provider_cost_calculated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"provider_cost_finalized_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"provider_usage_primary",
|
||||
sa.Boolean(),
|
||||
server_default=sa.text("true"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"provider_cost_is_estimated",
|
||||
sa.Boolean(),
|
||||
server_default=sa.text("false"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"attachment_image_count",
|
||||
sa.Integer(),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"attachment_video_count",
|
||||
sa.Integer(),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"attachment_audio_count",
|
||||
sa.Integer(),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"attachment_total_count",
|
||||
sa.Integer(),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"attachment_video_duration_seconds",
|
||||
sa.Numeric(precision=20, scale=6),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"attachment_audio_duration_seconds",
|
||||
sa.Numeric(precision=20, scale=6),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"requested_output_count",
|
||||
sa.Integer(),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"generated_image_count",
|
||||
sa.Integer(),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"generated_video_count",
|
||||
sa.Integer(),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column(
|
||||
"generated_total_count",
|
||||
sa.Integer(),
|
||||
server_default=sa.text("0"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("pricing_snapshot_json", _json_type(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("usage_snapshot_json", _json_type(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("attachment_snapshot_json", _json_type(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"credit_records",
|
||||
sa.Column("generation_snapshot_json", _json_type(), nullable=True),
|
||||
)
|
||||
|
||||
op.create_index(
|
||||
"ix_credit_records_pricing_rule_id",
|
||||
"credit_records",
|
||||
["pricing_rule_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_credit_records_pricing_version_code",
|
||||
"credit_records",
|
||||
["pricing_version_code"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_credit_records_pricing_usage_source",
|
||||
"credit_records",
|
||||
["pricing_usage_source"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_credit_records_pricing_reference_at",
|
||||
"credit_records",
|
||||
["pricing_reference_at"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_credit_records_provider_cost_status",
|
||||
"credit_records",
|
||||
["provider_cost_status"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_credit_records_pricing_status_time",
|
||||
"credit_records",
|
||||
["provider_cost_status", "created_at"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_credit_records_pricing_model_time",
|
||||
"credit_records",
|
||||
["engine_provider", "engine_model_name", "pricing_reference_at"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_credit_records_pricing_version",
|
||||
"credit_records",
|
||||
["pricing_version_code", "pricing_rule_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_foreign_key(
|
||||
FK_CREDIT_RECORD_PRICING_RULE,
|
||||
"credit_records",
|
||||
"model_pricing_rules",
|
||||
["pricing_rule_id"],
|
||||
["id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
|
||||
# 4. Lock the engine and billing attempt for legacy project generations.
|
||||
op.add_column(
|
||||
"generation_records",
|
||||
sa.Column("engine_id", sa.String(length=32), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"generation_records",
|
||||
sa.Column("engine_snapshot_json", sa.Text(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"generation_records",
|
||||
sa.Column("provider_response_json", sa.Text(), nullable=True),
|
||||
)
|
||||
op.add_column(
|
||||
"generation_records",
|
||||
sa.Column("current_billing_attempt_no", sa.Integer(), nullable=True),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_generation_records_engine_id",
|
||||
"generation_records",
|
||||
["engine_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_generation_records_current_billing_attempt_no",
|
||||
"generation_records",
|
||||
["current_billing_attempt_no"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Reverse legacy generation record extensions.
|
||||
op.drop_index(
|
||||
"ix_generation_records_current_billing_attempt_no",
|
||||
table_name="generation_records",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_generation_records_engine_id",
|
||||
table_name="generation_records",
|
||||
)
|
||||
op.drop_column("generation_records", "current_billing_attempt_no")
|
||||
op.drop_column("generation_records", "provider_response_json")
|
||||
op.drop_column("generation_records", "engine_snapshot_json")
|
||||
op.drop_column("generation_records", "engine_id")
|
||||
|
||||
# Reverse credit pricing snapshots before dropping the referenced rule table.
|
||||
op.drop_constraint(
|
||||
FK_CREDIT_RECORD_PRICING_RULE,
|
||||
"credit_records",
|
||||
type_="foreignkey",
|
||||
)
|
||||
op.drop_index("ix_credit_records_pricing_version", table_name="credit_records")
|
||||
op.drop_index("ix_credit_records_pricing_model_time", table_name="credit_records")
|
||||
op.drop_index("ix_credit_records_pricing_status_time", table_name="credit_records")
|
||||
op.drop_index("ix_credit_records_provider_cost_status", table_name="credit_records")
|
||||
op.drop_index("ix_credit_records_pricing_reference_at", table_name="credit_records")
|
||||
op.drop_index("ix_credit_records_pricing_usage_source", table_name="credit_records")
|
||||
op.drop_index("ix_credit_records_pricing_version_code", table_name="credit_records")
|
||||
op.drop_index("ix_credit_records_pricing_rule_id", table_name="credit_records")
|
||||
|
||||
op.drop_column("credit_records", "generation_snapshot_json")
|
||||
op.drop_column("credit_records", "attachment_snapshot_json")
|
||||
op.drop_column("credit_records", "usage_snapshot_json")
|
||||
op.drop_column("credit_records", "pricing_snapshot_json")
|
||||
op.drop_column("credit_records", "generated_total_count")
|
||||
op.drop_column("credit_records", "generated_video_count")
|
||||
op.drop_column("credit_records", "generated_image_count")
|
||||
op.drop_column("credit_records", "requested_output_count")
|
||||
op.drop_column("credit_records", "attachment_audio_duration_seconds")
|
||||
op.drop_column("credit_records", "attachment_video_duration_seconds")
|
||||
op.drop_column("credit_records", "attachment_total_count")
|
||||
op.drop_column("credit_records", "attachment_audio_count")
|
||||
op.drop_column("credit_records", "attachment_video_count")
|
||||
op.drop_column("credit_records", "attachment_image_count")
|
||||
op.drop_column("credit_records", "provider_cost_is_estimated")
|
||||
op.drop_column("credit_records", "provider_usage_primary")
|
||||
op.drop_column("credit_records", "provider_cost_finalized_at")
|
||||
op.drop_column("credit_records", "provider_cost_calculated_at")
|
||||
op.drop_column("credit_records", "provider_cost_status")
|
||||
op.drop_column("credit_records", "provider_cost_amount")
|
||||
op.drop_column("credit_records", "provider_cost_currency")
|
||||
op.drop_column("credit_records", "pricing_snapshot_hash")
|
||||
op.drop_column("credit_records", "pricing_snapshot_schema_version")
|
||||
op.drop_column("credit_records", "pricing_effective_to")
|
||||
op.drop_column("credit_records", "pricing_effective_from")
|
||||
op.drop_column("credit_records", "pricing_reference_at")
|
||||
op.drop_column("credit_records", "pricing_usage_source")
|
||||
op.drop_column("credit_records", "pricing_calculator_version")
|
||||
op.drop_column("credit_records", "pricing_billing_mode")
|
||||
op.drop_column("credit_records", "pricing_version_code")
|
||||
op.drop_column("credit_records", "pricing_rule_id")
|
||||
|
||||
# Reverse exact billing-attempt binding.
|
||||
op.drop_index(
|
||||
"ix_chat_generation_tasks_current_billing_attempt_no",
|
||||
table_name="chat_generation_tasks",
|
||||
)
|
||||
op.drop_column("chat_generation_tasks", "current_billing_attempt_no")
|
||||
|
||||
# Reverse pricing-rule storage.
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_rule_content_hash",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_publish_status",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_effective_to",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_effective_from",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_calculator_version",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_billing_mode",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_model_category",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_model_name",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_provider",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_category_status",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"ix_model_pricing_rules_resolve",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_index(
|
||||
"uq_model_pricing_rules_provider_model_version",
|
||||
table_name="model_pricing_rules",
|
||||
)
|
||||
op.drop_table("model_pricing_rules")
|
||||
Reference in New Issue
Block a user