This commit is contained in:
2026-07-11 13:00:06 +08:00
parent 6cc1655c69
commit 344fb789a4
82 changed files with 2594 additions and 7930 deletions
+8 -55
View File
@@ -1,22 +1,14 @@
from __future__ import annotations
from datetime import datetime
from decimal import Decimal
from typing import Any
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Index, Integer, JSON, Numeric, String
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy import Float, ForeignKey, Index, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
JsonType = JSON().with_variant(JSONB, "postgresql")
class CreditRecord(Base, TimestampMixin):
__tablename__ = "credit_records"
__table_args__ = (
# 正式计费幂等键:同一用户同一个业务流水只能写入一次。
# PostgreSQL/MySQL/SQLite 对 nullable unique 的处理都允许多条 NULL,兼容历史数据。
Index("uq_credit_records_user_biz_key", "user_id", "biz_key", unique=True),
Index("ix_credit_records_user_refund_for_biz_key", "user_id", "refund_for_biz_key"),
Index("ix_credit_records_related_type", "related_id", "type"),
@@ -26,13 +18,12 @@ class CreditRecord(Base, TimestampMixin):
Index("ix_credit_records_user_kind_time", "user_type_snapshot", "frontend_user_kind_snapshot", "created_at"),
Index("ix_credit_records_team_time", "team_id_snapshot", "created_at"),
Index("ix_credit_records_user_kind_team_time", "user_type_snapshot", "frontend_user_kind_snapshot", "team_id_snapshot", "created_at"),
Index("ix_credit_records_pricing_status_time", "provider_cost_status", "created_at"),
Index("ix_credit_records_pricing_model_time", "engine_provider", "engine_model_name", "pricing_reference_at"),
Index("ix_credit_records_pricing_version", "pricing_version_code", "pricing_rule_id"),
)
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str] = mapped_column(String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True)
user_id: Mapped[str] = mapped_column(
String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True
)
type: Mapped[str] = mapped_column(String(16), index=True)
amount: Mapped[float] = mapped_column(Float)
balance_after: Mapped[float] = mapped_column(Float)
@@ -68,53 +59,15 @@ class CreditRecord(Base, TimestampMixin):
output_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
total_tokens: Mapped[int | None] = mapped_column(Integer, nullable=True)
engine_type: Mapped[str | None] = mapped_column(String(16), nullable=True)
engine_id: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
engine_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
engine_provider: Mapped[str | None] = mapped_column(String(64), nullable=True)
engine_model_name: Mapped[str | None] = mapped_column(String(128), nullable=True)
# 不可变计价版本快照。后续调价不得基于规则表重新计算历史流水。
pricing_rule_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("model_pricing_rules.id", ondelete="RESTRICT"), nullable=True, index=True
)
pricing_version_code: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
pricing_billing_mode: Mapped[str | None] = mapped_column(String(48), nullable=True)
pricing_calculator_version: Mapped[str | None] = mapped_column(String(64), nullable=True)
pricing_usage_source: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
pricing_reference_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
pricing_effective_from: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
pricing_effective_to: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
pricing_snapshot_schema_version: Mapped[int | None] = mapped_column(Integer, nullable=True)
pricing_snapshot_hash: Mapped[str | None] = mapped_column(String(64), nullable=True)
provider_cost_currency: Mapped[str | None] = mapped_column(String(8), nullable=True)
provider_cost_amount: Mapped[Decimal | None] = mapped_column(Numeric(20, 8), nullable=True)
provider_cost_status: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
provider_cost_calculated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
provider_cost_finalized_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
provider_usage_primary: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default="true")
provider_cost_is_estimated: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default="false")
# 财务高频聚合字段平铺,避免列表/导出时逐条解析 JSON 或回查任务链。
attachment_image_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
attachment_video_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
attachment_audio_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
attachment_total_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
attachment_video_duration_seconds: Mapped[Decimal] = mapped_column(Numeric(20, 6), nullable=False, default=0, server_default="0")
attachment_audio_duration_seconds: Mapped[Decimal] = mapped_column(Numeric(20, 6), nullable=False, default=0, server_default="0")
requested_output_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
generated_image_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
generated_video_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
generated_total_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
pricing_snapshot_json: Mapped[dict[str, Any] | None] = mapped_column(JsonType, nullable=True)
usage_snapshot_json: Mapped[dict[str, Any] | None] = mapped_column(JsonType, nullable=True)
attachment_snapshot_json: Mapped[dict[str, Any] | None] = mapped_column(JsonType, nullable=True)
generation_snapshot_json: Mapped[dict[str, Any] | None] = mapped_column(JsonType, nullable=True)
user_type_snapshot: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
frontend_user_kind_snapshot: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
# 交易流水发生时的团队归属冷备快照;用户后续改团队不影响历史流水展示与筛选。
team_id_snapshot: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
team_name_snapshot: Mapped[str | None] = mapped_column(String(128), nullable=True)