"""预测模拟API — 管理会计OS""" import logging from fastapi import APIRouter, HTTPException from app.utils.predict_engine import ( cvp_analysis, npv, irr, sensitivity_analysis, scenario_analysis, ) logger = logging.getLogger("cma.predict") router = APIRouter(prefix="/api/cma/predict", tags=["预测模拟"]) @router.post("/cvp") def api_cvp_analysis(data: dict): """CVP本量利分析""" try: result = cvp_analysis( unit_price=float(data.get("unit_price", 0)), unit_variable_cost=float(data.get("unit_variable_cost", 0)), fixed_cost=float(data.get("fixed_cost", 0)), target_profit=float(data["target_profit"]) if data.get("target_profit") else None, actual_volume=float(data["actual_volume"]) if data.get("actual_volume") else None, ) return result except Exception as e: raise HTTPException(400, f"CVP计算失败: {str(e)}") @router.post("/investment") def api_investment_analysis(data: dict): """投资决策分析(NPV/IRR/回收期)""" try: initial = float(data.get("initial_investment", 0)) rate = float(data.get("discount_rate", 10)) cash_flows = [float(cf) for cf in data.get("cash_flows", [])] if not cash_flows: raise HTTPException(400, "现金流列表不能为空") npv_result = npv(initial, cash_flows, rate) irr_result = irr(initial, cash_flows) return { "npv_analysis": npv_result, "irr_analysis": irr_result, } except HTTPException: raise except Exception as e: raise HTTPException(400, f"投资决策计算失败: {str(e)}") @router.post("/sensitivity") def api_sensitivity_analysis(data: dict): """敏感性分析""" try: result = sensitivity_analysis( base_revenue=float(data.get("base_revenue", 0)), base_cost=float(data.get("base_cost", 0)), base_profit=float(data["base_profit"]) if data.get("base_profit") else None, step=int(data.get("step", 5)), max_step=int(data.get("max_step", 20)), ) return result except Exception as e: raise HTTPException(400, f"敏感性分析失败: {str(e)}") @router.post("/scenario") def api_scenario_analysis(data: dict): """情景模拟""" try: optimistic = data.get("optimistic", {}) pessimistic = data.get("pessimistic", {}) base = data.get("base", {}) if not all([optimistic, pessimistic, base]): raise HTTPException(400, "需要提供乐观/中性/悲观三个情景的参数") result = scenario_analysis( optimistic={ "revenue": float(optimistic.get("revenue", 0)), "cost": float(optimistic.get("cost", 0)), }, pessimistic={ "revenue": float(pessimistic.get("revenue", 0)), "cost": float(pessimistic.get("cost", 0)), }, base={ "revenue": float(base.get("revenue", 0)), "cost": float(base.get("cost", 0)), }, ) return result except HTTPException: raise except Exception as e: raise HTTPException(400, f"情景模拟失败: {str(e)}") @router.post("/cvp-detailed") def api_cvp_detailed(data: dict): """CVP本量利详细分析 — 含改善方案推演和保本图数据 (CMA P2)""" try: fixed_cost = float(data.get("fixed_cost", 617)) variable_cost_rate = float(data.get("variable_cost_rate", 0.4862)) unit_price = float(data.get("unit_price", 228)) current_volume = float(data.get("current_volume", 5300)) contribution_margin_rate = 1 - variable_cost_rate breakeven_revenue = round(fixed_cost / contribution_margin_rate, 2) breakeven_units = round(breakeven_revenue * 10000 / unit_price, 0) current_revenue = round(current_volume * unit_price / 10000, 2) current_profit = round(current_revenue * (1 - variable_cost_rate) - fixed_cost, 2) safety_margin = round((current_revenue - breakeven_revenue) / current_revenue * 100, 2) if current_revenue > 0 else 0 scenarios = [ {"name": "降固定费用至300万", "fixed_cost": 300, "variable_cost_rate": variable_cost_rate, "breakeven_revenue": round(300 / contribution_margin_rate, 2), "breakeven_units": round(300 / contribution_margin_rate * 10000 / unit_price, 0)}, {"name": "降变动成本率至30%", "fixed_cost": fixed_cost, "variable_cost_rate": 0.3, "breakeven_revenue": round(fixed_cost / 0.7, 2), "breakeven_units": round(fixed_cost / 0.7 * 10000 / unit_price, 0)}, {"name": "两者同时改善", "fixed_cost": 300, "variable_cost_rate": 0.3, "breakeven_revenue": round(300 / 0.7, 2), "breakeven_units": round(300 / 0.7 * 10000 / unit_price, 0)}, ] # 保本图数据点 chart_data = [] max_volume = int(max(breakeven_units * 2, current_volume * 3)) step = max(1, int(max_volume / 20)) for vol in range(0, int(max_volume) + step, step): rev = round(vol * unit_price / 10000, 2) tc = round(fixed_cost + rev * variable_cost_rate, 2) chart_data.append({"volume": vol, "revenue": rev, "total_cost": tc, "profit": round(rev - tc, 2)}) return { "fixed_cost": fixed_cost, "variable_cost_rate": round(variable_cost_rate * 100, 2), "unit_price": unit_price, "contribution_margin_rate": round(contribution_margin_rate * 100, 2), "breakeven_revenue": breakeven_revenue, "breakeven_units": int(breakeven_units), "current_revenue": current_revenue, "current_profit": current_profit, "current_volume": int(current_volume), "safety_margin": safety_margin, "scenarios": scenarios, "chart_data": chart_data, } except Exception as e: raise HTTPException(400, f"CVP详细分析失败: {str(e)}")