import logging from datetime import datetime, timedelta, timezone from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.models.api.api_usage_log import ApiUsageLog from app.utils.id_gen import generate_id logger = logging.getLogger("videogen") async def record_usage( db: AsyncSession, api_key_id: str, request_type: str, model_name: str, gen_type: str, status: str, task_id: str | None = None, credits_cost: float = 0.0, tokens_used: int = 0, request_duration_ms: int = 0, error_message: str | None = None, error_code: str | None = None, request_payload_json: str | None = None, price_action: str | None = None, resolution: str | None = None, duration: int | None = None, refund_amount: float | None = None, quota_before: float | None = None, quota_after: float | None = None, price_detail_json: str | None = None, ) -> ApiUsageLog: """记录一次 API 调用日志。""" # 确定 price_action if price_action: action = price_action elif status == "failed": action = "refund" else: action = "deduct" log = ApiUsageLog( id=generate_id(), api_key_id=api_key_id, api_generation_task_id=task_id, price_action=action, request_type=request_type, model_name=model_name, gen_type=gen_type, resolution=resolution, duration=duration, credits_cost=credits_cost, refund_amount=refund_amount or 0.0, quota_before=quota_before, quota_after=quota_after, tokens_used=tokens_used, request_duration_ms=request_duration_ms, price_detail_json=price_detail_json, status=status, error_message=error_message, error_code=error_code, request_payload_json=request_payload_json, ) db.add(log) await db.flush() return log async def list_usage_logs( db: AsyncSession, api_key_id: str | None = None, skip: int = 0, limit: int = 50, start_date: datetime | None = None, end_date: datetime | None = None, ) -> tuple[int, list[ApiUsageLog]]: """查询使用日志(分页+筛选)。""" query = select(ApiUsageLog) count_query = select(func.count(ApiUsageLog.id)) filters = [] if api_key_id: filters.append(ApiUsageLog.api_key_id == api_key_id) if start_date: filters.append(ApiUsageLog.created_at >= start_date) if end_date: filters.append(ApiUsageLog.created_at <= end_date) for f in filters: query = query.where(f) count_query = count_query.where(f) total_result = await db.execute(count_query) total = total_result.scalar_one() query = query.order_by(ApiUsageLog.created_at.desc()).offset(skip).limit(limit) result = await db.execute(query) logs = list(result.scalars().all()) return total, logs async def get_usage_summary( db: AsyncSession, api_key_id: str | None = None, days: int = 30, ) -> dict: """获取使用汇总统计。""" now = datetime.now(timezone.utc) start = now - timedelta(days=days) query = select( func.count(ApiUsageLog.id).label("total_requests"), func.coalesce(func.sum(ApiUsageLog.credits_cost), 0).label("total_credits"), func.coalesce(func.sum(ApiUsageLog.tokens_used), 0).label("total_tokens"), func.coalesce(func.avg(ApiUsageLog.request_duration_ms), 0).label("avg_duration"), ).where(ApiUsageLog.created_at >= start) if api_key_id: query = query.where(ApiUsageLog.api_key_id == api_key_id) result = await db.execute(query) row = result.one() # 成功/失败统计 success_query = select(func.count(ApiUsageLog.id)).where( ApiUsageLog.created_at >= start, ApiUsageLog.status == "success", ) failed_query = select(func.count(ApiUsageLog.id)).where( ApiUsageLog.created_at >= start, ApiUsageLog.status == "failed", ) if api_key_id: success_query = success_query.where(ApiUsageLog.api_key_id == api_key_id) failed_query = failed_query.where(ApiUsageLog.api_key_id == api_key_id) success_result = await db.execute(success_query) failed_result = await db.execute(failed_query) return { "total_requests": row.total_requests or 0, "total_credits_cost": float(row.total_credits or 0), "total_tokens_used": int(row.total_tokens or 0), "avg_duration_ms": int(row.avg_duration or 0), "success_count": success_result.scalar_one() or 0, "failed_count": failed_result.scalar_one() or 0, }