155 lines
6.1 KiB
Python
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)}")
|