feat: AI事前预警 — 现金流预测+预警扩展+准确率+情景建议

This commit is contained in:
Hermes CI Fix
2026-07-21 18:22:04 +08:00
parent 270d7758f8
commit c4e91f28ef
9 changed files with 1101 additions and 6 deletions
+106 -1
View File
@@ -1,10 +1,16 @@
"""预测模拟API — 管理会计OS"""
import logging
from fastapi import APIRouter, HTTPException
from fastapi import APIRouter, HTTPException, Depends
from app.utils.predict_engine import (
cvp_analysis, npv, irr,
sensitivity_analysis, scenario_analysis,
)
from app.utils.cash_forecast_engine import (
forecast_cash_flow, save_forecast_to_db,
calculate_accuracy, generate_scenario_suggestion,
)
from app.database import get_db
from sqlalchemy.orm import Session
logger = logging.getLogger("cma.predict")
router = APIRouter(prefix="/api/cma/predict", tags=["预测模拟"])
@@ -154,6 +160,105 @@ def api_cvp_detailed(data: dict):
raise HTTPException(400, f"CVP详细分析失败: {str(e)}")
# ── 现金流预测(AI事前预警) ────────────────────────────────────
@router.post("/cash-forecast")
def api_cash_forecast(data: dict, db: Session = Depends(get_db)):
"""现金流预测 — 根据历史KPI推算未来30天现金流"""
try:
entity_id = int(data.get("entity_id", 1))
days = int(data.get("days", 30))
current_cash = float(data["current_cash"]) if data.get("current_cash") else None
result = forecast_cash_flow(entity_id, db, days, current_cash)
# 保存到数据库
try:
save_forecast_to_db(entity_id, result, db)
except Exception as e:
logger.warning(f"保存预测结果失败: {e}")
return result
except Exception as e:
raise HTTPException(400, f"现金流预测失败: {str(e)}")
@router.get("/cash-forecast/history")
def api_cash_forecast_history(
entity_id: int = 1,
days: int = 30,
db: Session = Depends(get_db),
):
"""获取已保存的现金流预测历史"""
from app.models import CashForecast
forecasts = db.query(CashForecast).filter(
CashForecast.entity_id == entity_id,
).order_by(CashForecast.forecast_date.desc()).limit(days).all()
return {
"data": [{
"id": f.id,
"forecast_date": f.forecast_date.isoformat(),
"predicted_cash": f.predicted_cash,
"lower_bound": f.lower_bound,
"upper_bound": f.upper_bound,
"alert_status": f.alert_status,
} for f in forecasts]
}
@router.get("/accuracy")
def api_forecast_accuracy(
entity_id: int = 1,
db: Session = Depends(get_db),
):
"""预测准确率报表 — 上期预测 vs 本期实际"""
try:
results = calculate_accuracy(entity_id, db)
# 计算整体MAE/MAPE
if results:
total_mae = sum(r["mae"] for r in results) / len(results)
total_mape = sum(r["mape"] for r in results) / len(results)
else:
total_mae = 0
total_mape = 0
return {
"data": results,
"summary": {
"total_periods": len(results),
"avg_mae": round(total_mae, 2),
"avg_mape": round(total_mape, 2),
},
}
except Exception as e:
raise HTTPException(400, f"获取准确率失败: {str(e)}")
@router.get("/scenario-suggestions")
def api_scenario_suggestions(alert_type: str = None):
"""获取情景建议模板"""
types = ["cash_low", "cash_critical", "cost_high", "revenue_drop"]
results = []
for at in types:
if alert_type and at != alert_type:
continue
sug = generate_scenario_suggestion(at, "")
results.append({"alert_type": at, **sug})
return {"data": results}
@router.post("/scenario-suggestion/generate")
def api_generate_suggestion(data: dict):
"""根据预警信息动态生成情景建议"""
try:
alert_type = data.get("alert_type", "cash_low")
kpi_name = data.get("kpi_name", "未知KPI")
extra = data.get("extra", {})
sug = generate_scenario_suggestion(alert_type, kpi_name, extra)
return sug
except Exception as e:
raise HTTPException(400, f"生成建议失败: {str(e)}")
# ── 实物期权计算器 ─────────────────────────────────────────────
import math