#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 執行 API Usage Stats 資料表修復 Migration Usage: python migrations/fix_api_usage_stats.py """ import sys from pathlib import Path # 添加專案根目錄到 Python 路徑 project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root)) from app import create_app, db from sqlalchemy import text def run_migration(): """執行資料表結構修復""" app = create_app() with app.app_context(): print("=" * 60) print("API Usage Stats 資料表結構修復") print("=" * 60) try: # 1. 檢查當前結構 print("\n[1/8] 檢查當前資料表結構...") result = db.session.execute(text('SHOW COLUMNS FROM dt_api_usage_stats')) current_columns = {row[0]: row for row in result} print(f" ✓ 當前欄位數量: {len(current_columns)}") # 2. 備份現有資料 print("\n[2/8] 建立資料備份...") db.session.execute(text(''' CREATE TABLE IF NOT EXISTS dt_api_usage_stats_backup_20251001 AS SELECT * FROM dt_api_usage_stats ''')) db.session.commit() backup_count = db.session.execute( text('SELECT COUNT(*) FROM dt_api_usage_stats_backup_20251001') ).scalar() print(f" ✓ 已備份 {backup_count} 筆記錄") # 3. 修改欄位名稱:api_name → api_endpoint if 'api_name' in current_columns: print("\n[3/8] 重新命名 api_name → api_endpoint...") db.session.execute(text(''' ALTER TABLE dt_api_usage_stats CHANGE COLUMN api_name api_endpoint VARCHAR(200) NOT NULL COMMENT 'API端點' ''')) db.session.commit() print(" ✓ 已重新命名 api_name → api_endpoint") else: print("\n[3/8] 跳過(api_name 已不存在或已是 api_endpoint)") # 4. 新增 prompt_tokens 和 completion_tokens print("\n[4/8] 新增 prompt_tokens 和 completion_tokens...") if 'prompt_tokens' not in current_columns: db.session.execute(text(''' ALTER TABLE dt_api_usage_stats ADD COLUMN prompt_tokens INT DEFAULT 0 COMMENT 'Prompt token數' AFTER api_endpoint ''')) if 'completion_tokens' not in current_columns: db.session.execute(text(''' ALTER TABLE dt_api_usage_stats ADD COLUMN completion_tokens INT DEFAULT 0 COMMENT 'Completion token數' AFTER prompt_tokens ''')) db.session.commit() print(" ✓ 已新增 token 細分欄位") # 5. 重新命名 token_used → total_tokens if 'token_used' in current_columns: print("\n[5/8] 重新命名 token_used → total_tokens...") db.session.execute(text(''' ALTER TABLE dt_api_usage_stats CHANGE COLUMN token_used total_tokens INT DEFAULT 0 COMMENT '總token數' ''')) db.session.commit() print(" ✓ 已重新命名 token_used → total_tokens") else: print("\n[5/8] 跳過(token_used 已不存在或已是 total_tokens)") # 6. 新增計費相關欄位 print("\n[6/8] 新增計費相關欄位...") if 'prompt_unit_price' not in current_columns: db.session.execute(text(''' ALTER TABLE dt_api_usage_stats ADD COLUMN prompt_unit_price DECIMAL(10, 8) DEFAULT 0.00000000 COMMENT '單價' AFTER total_tokens ''')) if 'prompt_price_unit' not in current_columns: db.session.execute(text(''' ALTER TABLE dt_api_usage_stats ADD COLUMN prompt_price_unit VARCHAR(20) DEFAULT 'USD' COMMENT '價格單位' AFTER prompt_unit_price ''')) db.session.commit() print(" ✓ 已新增計費欄位") # 7. 替換 status 欄位為 success (BOOLEAN) print("\n[7/8] 更新 status 欄位...") if 'status' in current_columns and 'success' not in current_columns: # 先新增 success 欄位 db.session.execute(text(''' ALTER TABLE dt_api_usage_stats ADD COLUMN success BOOLEAN DEFAULT TRUE COMMENT '是否成功' AFTER response_time_ms ''')) # 將 status 資料轉換到 success db.session.execute(text(''' UPDATE dt_api_usage_stats SET success = (status = 'SUCCESS') ''')) # 刪除舊的 status 欄位 db.session.execute(text(''' ALTER TABLE dt_api_usage_stats DROP COLUMN status ''')) db.session.commit() print(" ✓ 已將 status 轉換為 success (BOOLEAN)") else: print(" ℹ 跳過(已完成或不需要轉換)") # 8. 更新索引 print("\n[8/8] 建立索引...") try: db.session.execute(text(''' ALTER TABLE dt_api_usage_stats ADD INDEX IF NOT EXISTS idx_api_endpoint (api_endpoint) ''')) except Exception as e: if 'Duplicate' not in str(e): raise try: db.session.execute(text(''' ALTER TABLE dt_api_usage_stats ADD INDEX IF NOT EXISTS idx_success (success) ''')) except Exception as e: if 'Duplicate' not in str(e): raise db.session.commit() print(" ✓ 已建立索引") # 9. 驗證最終結構 print("\n" + "=" * 60) print("驗證最終資料表結構") print("=" * 60) result = db.session.execute(text('SHOW COLUMNS FROM dt_api_usage_stats')) final_columns = list(result) print(f"\n最終欄位列表 (共 {len(final_columns)} 個):") for col in final_columns: print(f" - {col[0]:25} {col[1]:20} NULL={col[2]} Default={col[4]}") # 10. 統計資料 print("\n" + "=" * 60) print("資料統計") print("=" * 60) total_records = db.session.execute( text('SELECT COUNT(*) FROM dt_api_usage_stats') ).scalar() print(f"總記錄數: {total_records}") if total_records > 0: stats = db.session.execute(text(''' SELECT api_endpoint, COUNT(*) as count, SUM(total_tokens) as total_tokens, SUM(cost) as total_cost FROM dt_api_usage_stats GROUP BY api_endpoint ''')).fetchall() print("\nAPI 使用統計:") for stat in stats: print(f" {stat[0]:40} | {stat[1]:5} 次 | {stat[2]:10} tokens | ${stat[3]:.4f}") print("\n" + "=" * 60) print("✅ Migration 執行完成!") print("=" * 60) except Exception as e: db.session.rollback() print(f"\n❌ Migration 失敗: {str(e)}") print("\n可以使用備份表還原資料:") print(" DROP TABLE dt_api_usage_stats;") print(" CREATE TABLE dt_api_usage_stats AS SELECT * FROM dt_api_usage_stats_backup_20251001;") raise if __name__ == '__main__': run_migration()