310 lines
11 KiB
Python
310 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
AI分析API端点
|
||
"""
|
||
|
||
from fastapi import APIRouter, HTTPException, Depends
|
||
from fastapi.responses import JSONResponse
|
||
from typing import Dict, Any
|
||
import asyncio
|
||
from datetime import datetime
|
||
import logging
|
||
|
||
from ai_service import AIService, get_ai_config
|
||
from api import get_db_connection
|
||
|
||
# 创建路由
|
||
router = APIRouter(prefix="/api/ai", tags=["AI分析"])
|
||
|
||
# 配置日志
|
||
logger = logging.getLogger(__name__)
|
||
|
||
@router.post("/analyze")
|
||
async def analyze_production():
|
||
"""
|
||
分析生产数据
|
||
返回AI生成的生产报表
|
||
"""
|
||
try:
|
||
# 获取数据库连接
|
||
conn = await get_db_connection()
|
||
|
||
# 获取最近30天的数据
|
||
query = """
|
||
SELECT
|
||
platform,
|
||
ts_cn,
|
||
batch,
|
||
mac,
|
||
note
|
||
FROM audit_records
|
||
WHERE ts_cn >= datetime('now', '-30 days')
|
||
ORDER BY ts_cn DESC
|
||
"""
|
||
|
||
cursor = await conn.execute(query)
|
||
rows = await cursor.fetchall()
|
||
await conn.close()
|
||
|
||
# 整理数据
|
||
pdd_data = []
|
||
yt_data = []
|
||
|
||
for row in rows:
|
||
record = {
|
||
"ts_cn": row[1],
|
||
"batch": row[2],
|
||
"mac": row[3],
|
||
"note": row[4]
|
||
}
|
||
|
||
if row[0] == "pdd":
|
||
pdd_data.append(record)
|
||
elif row[0] == "yt":
|
||
yt_data.append(record)
|
||
|
||
# 获取实时生产数据(从Redis,类似dashboard的逻辑)
|
||
from datetime import timezone, timedelta
|
||
beijing_tz = timezone(timedelta(hours=8))
|
||
now_bj = datetime.now(beijing_tz)
|
||
today_bj = now_bj.strftime('%Y-%m-%d')
|
||
|
||
# 尝试从Redis获取实时数据
|
||
real_pdd_count = 0
|
||
real_yt_count = 0
|
||
week_pdd_count = 0
|
||
week_yt_count = 0
|
||
|
||
try:
|
||
import sys
|
||
sys.path.append('/home/hyx/work/生产管理系统')
|
||
from server import parse_audit_line, get_redis
|
||
|
||
r = get_redis()
|
||
|
||
# 获取拼多多审计数据
|
||
pdd_items = []
|
||
for key in ['mac_batch_audit_pdd', 'audit:pdd', 'pdd:audit']:
|
||
if r.exists(key) and r.type(key) == 'list':
|
||
pdd_items = r.lrange(key, 0, -1)
|
||
break
|
||
|
||
# 获取圆通审计数据
|
||
yt_items = []
|
||
for key in ['mac_batch_audit_yt', 'audit:yt', 'yt:audit']:
|
||
if r.exists(key) and r.type(key) == 'list':
|
||
yt_items = r.lrange(key, 0, -1)
|
||
break
|
||
|
||
# 解析拼多多数据
|
||
today_pdd_macs = set()
|
||
week_pdd_macs = set()
|
||
|
||
for item in pdd_items:
|
||
try:
|
||
parsed = parse_audit_line(item)
|
||
ts_str = parsed.get('ts_cn') or ''
|
||
mac = parsed.get('mac') or ''
|
||
if ts_str and mac:
|
||
# 今日产量
|
||
if ts_str.startswith(today_bj):
|
||
today_pdd_macs.add(mac)
|
||
|
||
# 本周产量(最近7天)
|
||
item_date = datetime.strptime(ts_str.split(' ')[0], '%Y-%m-%d')
|
||
if (now_bj - item_date).days <= 7:
|
||
week_pdd_macs.add(mac)
|
||
except:
|
||
pass
|
||
|
||
# 解析圆通数据
|
||
today_yt_macs = set()
|
||
week_yt_macs = set()
|
||
|
||
for item in yt_items:
|
||
try:
|
||
parsed = parse_audit_line(item)
|
||
ts_str = parsed.get('ts_cn') or ''
|
||
mac = parsed.get('mac') or ''
|
||
if ts_str and mac:
|
||
# 今日产量
|
||
if ts_str.startswith(today_bj):
|
||
today_yt_macs.add(mac)
|
||
|
||
# 本周产量(最近7天)
|
||
item_date = datetime.strptime(ts_str.split(' ')[0], '%Y-%m-%d')
|
||
if (now_bj - item_date).days <= 7:
|
||
week_yt_macs.add(mac)
|
||
except:
|
||
pass
|
||
|
||
real_pdd_count = len(today_pdd_macs)
|
||
real_yt_count = len(today_yt_macs)
|
||
week_pdd_count = len(week_pdd_macs)
|
||
week_yt_count = len(week_yt_macs)
|
||
|
||
# 添加调试日志
|
||
print(f"AI分析获取的实时数据: 今日PDD={real_pdd_count}, 今日YT={real_yt_count}, 本周PDD={week_pdd_count}, 本周YT={week_yt_count}")
|
||
|
||
except Exception as e:
|
||
print(f"Redis error in AI: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
# 获取发货数据统计
|
||
shipments_stats = {'total': 0, 'by_platform': {}}
|
||
try:
|
||
# 使用同一个redis连接
|
||
shipments_count = r.hlen('shipment_sn_mapping')
|
||
shipments_stats['total'] = shipments_count
|
||
|
||
# 获取各平台发货数量(简化处理,假设主要来自拼多多)
|
||
shipments_stats['by_platform'] = {'pdd': shipments_count * 0.919, 'yt': shipments_count * 0.054, 'other': shipments_count * 0.027}
|
||
|
||
except Exception as e:
|
||
print(f"Redis shipment error in AI: {e}")
|
||
# 如果Redis失败,尝试重新连接
|
||
try:
|
||
import sys
|
||
sys.path.append('/home/hyx/work/生产管理系统')
|
||
from server import get_redis
|
||
r = get_redis()
|
||
shipments_count = r.hlen('shipment_sn_mapping')
|
||
shipments_stats['total'] = shipments_count
|
||
shipments_stats['by_platform'] = {'pdd': shipments_count * 0.919, 'yt': shipments_count * 0.054, 'other': shipments_count * 0.027}
|
||
except:
|
||
pass
|
||
|
||
# 获取其他统计数据
|
||
bom_stats = {'count': 0, 'products': 0}
|
||
inventory_stats = {'count': 0, 'total_qty': 0}
|
||
purchase_demand_stats = {'count': 0, 'total_required': 0}
|
||
customer_order_stats = {'count': 0, 'total_qty': 0, 'completed': 0}
|
||
reconciliation_stats = {'count': 0, 'total_qty': 0}
|
||
|
||
# 准备AI分析数据,包含实时数据
|
||
print(f"\n=== AI分析数据准备 ===")
|
||
print(f"数据库记录: PDD={len(pdd_data)}, YT={len(yt_data)}")
|
||
print(f"实时数据: 今日PDD={real_pdd_count}, 今日YT={real_yt_count}")
|
||
print(f"实时数据: 本周PDD={week_pdd_count}, 本周YT={week_yt_count}")
|
||
print("========================\n")
|
||
|
||
data = {
|
||
"pdd": pdd_data,
|
||
"yt": yt_data,
|
||
"realtime": {
|
||
"today_pdd": real_pdd_count,
|
||
"today_yt": real_yt_count,
|
||
"week_pdd": week_pdd_count,
|
||
"week_yt": week_yt_count,
|
||
"total_today": real_pdd_count + real_yt_count,
|
||
"total_week": week_pdd_count + week_yt_count
|
||
},
|
||
"shipments": shipments_stats,
|
||
"bom": bom_stats,
|
||
"inventory": inventory_stats,
|
||
"purchase_demand": purchase_demand_stats,
|
||
"customer_orders": customer_order_stats,
|
||
"reconciliations": reconciliation_stats,
|
||
"analysis_time": datetime.now().isoformat()
|
||
}
|
||
|
||
# 调用AI服务
|
||
config = get_ai_config()
|
||
async with AIService(config) as ai_service:
|
||
result = await ai_service.analyze_production_data(data)
|
||
|
||
# 添加元数据
|
||
result["metadata"] = {
|
||
"generated_at": datetime.now().isoformat(),
|
||
"data_period": "最近30天",
|
||
"total_records": len(pdd_data) + len(yt_data),
|
||
"ai_provider": config.provider,
|
||
"realtime_data": {
|
||
"today_total": real_pdd_count + real_yt_count,
|
||
"week_total": week_pdd_count + week_yt_count
|
||
}
|
||
}
|
||
|
||
return JSONResponse(content=result)
|
||
|
||
except Exception as e:
|
||
logger.error(f"AI分析失败: {str(e)}")
|
||
raise HTTPException(status_code=500, detail=f"AI分析失败: {str(e)}")
|
||
|
||
@router.get("/config")
|
||
async def get_ai_config_info():
|
||
"""获取AI配置信息(不包含敏感信息)"""
|
||
try:
|
||
config = get_ai_config()
|
||
return {
|
||
"provider": config.provider,
|
||
"model": config.model,
|
||
"configured": bool(config.api_key or config.provider == "local")
|
||
}
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
@router.post("/test")
|
||
async def test_ai_connection():
|
||
"""测试AI连接"""
|
||
try:
|
||
config = get_ai_config()
|
||
|
||
# 测试数据
|
||
test_data = {
|
||
"pdd": [{"ts_cn": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "batch": "TEST", "mac": "TEST001", "note": "测试数据"}],
|
||
"yt": []
|
||
}
|
||
|
||
async with AIService(config) as ai_service:
|
||
result = await ai_service.analyze_production_data(test_data)
|
||
|
||
return {
|
||
"success": True,
|
||
"message": "AI连接测试成功",
|
||
"provider": config.provider,
|
||
"model": config.model
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"AI连接测试失败: {str(e)}")
|
||
return {
|
||
"success": False,
|
||
"message": f"AI连接测试失败: {str(e)}",
|
||
"provider": config.provider if 'config' in locals() else "unknown"
|
||
}
|
||
|
||
@router.get("/providers")
|
||
async def get_supported_providers():
|
||
"""获取支持的AI提供商列表"""
|
||
return {
|
||
"providers": [
|
||
{
|
||
"id": "openai",
|
||
"name": "OpenAI",
|
||
"models": ["gpt-3.5-turbo", "gpt-4", "gpt-4-turbo"],
|
||
"description": "OpenAI GPT模型,需要API Key"
|
||
},
|
||
{
|
||
"id": "qwen",
|
||
"name": "通义千问",
|
||
"models": ["qwen-turbo", "qwen-plus", "qwen-max"],
|
||
"description": "阿里云通义千问,需要API Key"
|
||
},
|
||
{
|
||
"id": "wenxin",
|
||
"name": "文心一言",
|
||
"models": ["ERNIE-Bot", "ERNIE-Bot-turbo", "ERNIE-Bot-4"],
|
||
"description": "百度文心一言,需要API Key"
|
||
},
|
||
{
|
||
"id": "local",
|
||
"name": "本地模型",
|
||
"models": ["llama2", "llama2:13b", "codellama", "qwen:7b"],
|
||
"description": "本地部署的模型(如Ollama),无需API Key"
|
||
}
|
||
]
|
||
}
|