from __future__ import annotations from datetime import datetime from decimal import Decimal from sqlalchemy import DateTime, ForeignKey, Index, JSON, Numeric, String from sqlalchemy.orm import Mapped, mapped_column from app.enums.llm_billing import LlmBillingExecutionStatus from app.models.base import Base, TimestampMixin class LlmBillingExecution(Base, TimestampMixin): __tablename__ = "llm_billing_executions" __table_args__ = ( Index( "uq_llm_billing_executions_business_attempt", "user_id", "scene_code", "owner_type", "owner_id", "business_attempt_no", unique=True, ), Index("uq_llm_billing_executions_credit_record", "credit_record_id", unique=True), Index("ix_llm_billing_executions_status_time", "status", "created_at"), ) id: Mapped[str] = mapped_column(String(32), primary_key=True) user_id: Mapped[str] = mapped_column( String(32), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True ) scene_code: Mapped[str] = mapped_column(String(64), nullable=False, index=True) scene_name_snapshot: Mapped[str] = mapped_column(String(128), nullable=False) owner_type: Mapped[str] = mapped_column(String(64), nullable=False) owner_id: Mapped[str] = mapped_column(String(64), nullable=False) business_attempt_no: Mapped[int] = mapped_column(nullable=False) model_config_id: Mapped[str | None] = mapped_column( String(32), ForeignKey("model_configs.id", ondelete="SET NULL"), nullable=True ) model_name_snapshot: Mapped[str | None] = mapped_column(String(128), nullable=True) provider_snapshot: Mapped[str | None] = mapped_column(String(64), nullable=True) model_parameters_snapshot: Mapped[dict | None] = mapped_column(JSON, nullable=True) billing_policy_id: Mapped[str | None] = mapped_column( String(32), ForeignKey("llm_billing_policies.id", ondelete="SET NULL"), nullable=True ) billing_policy_version: Mapped[int | None] = mapped_column(nullable=True) request_time: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) pre_deduct_credits: Mapped[Decimal] = mapped_column(Numeric(20, 2), nullable=False) credit_record_id: Mapped[str] = mapped_column( String(32), ForeignKey("credit_records.id", ondelete="RESTRICT"), nullable=False ) status: Mapped[str] = mapped_column( String(32), nullable=False, default=LlmBillingExecutionStatus.PRE_DEDUCTED.value, server_default=LlmBillingExecutionStatus.PRE_DEDUCTED.value, ) total_call_count: Mapped[int] = mapped_column(nullable=False, default=0, server_default="0") successful_call_count: Mapped[int] = mapped_column(nullable=False, default=0, server_default="0") failed_call_count: Mapped[int] = mapped_column(nullable=False, default=0, server_default="0") total_input_tokens: Mapped[int] = mapped_column(nullable=False, default=0, server_default="0") total_output_tokens: Mapped[int] = mapped_column(nullable=False, default=0, server_default="0") total_tokens: Mapped[int] = mapped_column(nullable=False, default=0, server_default="0") refund_available_credits: Mapped[Decimal] = mapped_column( Numeric(20, 2), nullable=False, default=Decimal("0.00"), server_default="0" ) refund_expired_credits: Mapped[Decimal] = mapped_column( Numeric(20, 2), nullable=False, default=Decimal("0.00"), server_default="0" ) final_error_message: Mapped[str | None] = mapped_column(String(1000), nullable=True) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) refunded_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)