2026-01-03 11:18:40 +00:00
|
|
|
|
#!/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)
|
|
|
|
|
|
|
2026-01-08 06:16:26 +00:00
|
|
|
|
# 获取实时生产数据(从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")
|
|
|
|
|
|
|
2026-01-03 11:18:40 +00:00
|
|
|
|
data = {
|
|
|
|
|
|
"pdd": pdd_data,
|
|
|
|
|
|
"yt": yt_data,
|
2026-01-08 06:16:26 +00:00
|
|
|
|
"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,
|
2026-01-03 11:18:40 +00:00
|
|
|
|
"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),
|
2026-01-08 06:16:26 +00:00
|
|
|
|
"ai_provider": config.provider,
|
|
|
|
|
|
"realtime_data": {
|
|
|
|
|
|
"today_total": real_pdd_count + real_yt_count,
|
|
|
|
|
|
"week_total": week_pdd_count + week_yt_count
|
|
|
|
|
|
}
|
2026-01-03 11:18:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|