Files
cma-management/backend/app/api/predict.py
T

155 lines
6.1 KiB
Python

"""预测模拟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)}")