"""多情景预测模拟引擎 — 管理会计OS P2-1 从战略地图KPI输入变量出发,按类别映射到财务影响, 输出乐观/基准/保守三情景数值+曲线数据 """ import math import logging from typing import List, Dict, Optional from datetime import datetime logger = logging.getLogger("cma.scenario") # KPI类别 → 财务影响映射系数 # 降本类: 每变化1% → 成本节省系数 # 增收类: 每变化1% → 收入增长系数 KPI_CATEGORY_MAP = { # 增收类 "revenue_growth": {"type": "revenue", "factor": 0.8, "desc": "收入增长"}, "customer_scale": {"type": "revenue", "factor": 0.6, "desc": "客户规模→收入"}, # 降本类 "cost_control": {"type": "cost", "factor": -0.7, "desc": "成本节约"}, "asset_efficiency": {"type": "cost", "factor": -0.3, "desc": "资产效率→成本"}, # 利润类 "profitability": {"type": "profit", "factor": 0.5, "desc": "直接利润影响"}, # 现金流类 "cash_risk": {"type": "cash", "factor": 0.4, "desc": "现金流影响"}, # 客户类→收入 "customer_satisfaction": {"type": "revenue", "factor": 0.3, "desc": "满意度→收入"}, "customer_concentration": {"type": "revenue", "factor": -0.2, "desc": "集中度→风险"}, # 流程类→成本 "delivery_quality": {"type": "cost", "factor": -0.3, "desc": "交付质量→成本"}, "supply_chain": {"type": "cost", "factor": -0.2, "desc": "供应链→成本"}, # 学习类→长期收入 "talent_pipeline": {"type": "revenue", "factor": 0.15, "desc": "人才→收入"}, "employee_engagement": {"type": "cost", "factor": -0.1, "desc": "敬业度→成本"}, "innovation": {"type": "revenue", "factor": 0.2, "desc": "创新→收入"}, } # 默认基准财务数据(万元/月) DEFAULT_BASE_REVENUE = 1000.0 # 基准收入 DEFAULT_BASE_COST = 700.0 # 基准成本 DEFAULT_BASE_PROFIT = 300.0 # 基准利润 def calculate_scenario( variables: List[Dict], scenario_type: str = "base", # "optimistic" / "base" / "pessimistic" base_revenue: float = DEFAULT_BASE_REVENUE, base_cost: float = DEFAULT_BASE_COST, ) -> Dict: """根据KPI变量列表和三情景系数计算财务影响 Args: variables: [{"kpi_code", "kpi_name", "category", "value", "step_optimistic", "step_base", "step_pessimistic"}, ...] scenario_type: 情景类型 base_revenue: 基准收入 base_cost: 基准成本 Returns: {revenue, cost, profit, profit_margin, kpi_impacts, details} """ step_key = { "optimistic": "step_optimistic", "base": "step_base", "pessimistic": "step_pessimistic", }.get(scenario_type, "step_base") total_revenue_impact = 0.0 total_cost_impact = 0.0 total_profit_impact = 0.0 total_cash_impact = 0.0 base_profit_val = base_revenue - base_cost details = [] for var in variables: kpi_code = var.get("kpi_code", "") kpi_name = var.get("kpi_name", "") category = var.get("category", "") current_value = var.get("value", 0) step_value = var.get(step_key, 0) # 变化百分比 (当前值变化 / 当前值) if current_value and current_value != 0: change_pct = step_value / abs(current_value) * 100 else: change_pct = 0 # 查找类别映射 mapping = KPI_CATEGORY_MAP.get(category, {"type": "revenue", "factor": 0.5, "desc": "通用影响"}) impact_type = mapping["type"] factor = mapping["factor"] impact_desc = mapping["desc"] # 计算财务影响 = 变化率 × 系数 × 基准值 financial_impact = change_pct / 100 * factor if impact_type == "revenue": impact_amount = financial_impact * base_revenue total_revenue_impact += impact_amount elif impact_type == "cost": impact_amount = financial_impact * base_cost total_cost_impact += impact_amount elif impact_type == "profit": impact_amount = financial_impact * base_profit_val total_profit_impact += impact_amount elif impact_type == "cash": impact_amount = financial_impact * base_profit_val total_cash_impact += impact_amount else: impact_amount = 0 details.append({ "kpi_code": kpi_code, "kpi_name": kpi_name, "category": category, "current_value": current_value, "scenario_value": step_value, "change_pct": round(change_pct, 2), "impact_type": impact_type, "impact_desc": impact_desc, "impact_amount": round(impact_amount, 2), }) # 合成最终财务数据 final_revenue = base_revenue + total_revenue_impact final_cost = base_cost + total_cost_impact # 重新计算利润(考虑所有影响) final_profit = (final_revenue - final_cost) + total_profit_impact profit_margin = round(final_profit / final_revenue * 100, 2) if final_revenue else 0 return { "scenario_type": scenario_type, "base_revenue": base_revenue, "base_cost": base_cost, "base_profit": base_revenue - base_cost, "revenue": round(final_revenue, 2), "cost": round(final_cost, 2), "profit": round(final_profit, 2), "profit_margin": profit_margin, "revenue_impact": round(total_revenue_impact, 2), "cost_impact": round(total_cost_impact, 2), "profit_impact": round(total_profit_impact, 2), "cash_impact": round(total_cash_impact, 2), "kpi_impacts": details, } def run_three_scenarios( variables: List[Dict], base_revenue: float = DEFAULT_BASE_REVENUE, base_cost: float = DEFAULT_BASE_COST, months: int = 12, ) -> Dict: """运行三情景模拟,生成曲线数据 Args: variables: KPI变量列表,每个包含step_optimistic/step_base/step_pessimistic base_revenue: 基准月度收入 base_cost: 基准月度成本 months: 预测月数 Returns: {scenarios: [...], chart_data: {months, optimistic, base, pessimistic}, summary} """ optimistic = calculate_scenario(variables, "optimistic", base_revenue, base_cost) base = calculate_scenario(variables, "base", base_revenue, base_cost) pessimistic = calculate_scenario(variables, "pessimistic", base_revenue, base_cost) # 生成月度曲线数据(按月线性趋近情景值) start_revenue = base_revenue start_cost = base_cost start_profit = base_revenue - base_cost chart_data = { "months": [], "optimistic": {"revenue": [], "cost": [], "profit": []}, "base": {"revenue": [], "cost": [], "profit": []}, "pessimistic": {"revenue": [], "cost": [], "profit": []}, } for m in range(1, months + 1): progress = m / months # 从0到1线性趋近 label = f"第{m}月" if months <= 12 else f"M{m}" for scenario_type, scenario_data in [ ("optimistic", optimistic), ("base", base), ("pessimistic", pessimistic), ]: rev = start_revenue + (scenario_data["revenue"] - start_revenue) * progress cst = start_cost + (scenario_data["cost"] - start_cost) * progress prf = start_profit + (scenario_data["profit"] - start_profit) * progress chart_data[scenario_type]["revenue"].append(round(rev, 2)) chart_data[scenario_type]["cost"].append(round(cst, 2)) chart_data[scenario_type]["profit"].append(round(prf, 2)) chart_data["months"].append(label) # 汇总 base_profit_val = base["profit"] scenarios_list = [] for label, data in [ ("乐观", optimistic), ("基准", base), ("保守", pessimistic), ]: deviation = data["profit"] - base_profit_val scenarios_list.append({ "scenario": label, "revenue": data["revenue"], "cost": data["cost"], "profit": data["profit"], "profit_margin": data["profit_margin"], "deviation_from_base": round(deviation, 2), "deviation_pct": round(deviation / base_profit_val * 100, 2) if base_profit_val else 0, "kpi_impacts": data["kpi_impacts"], }) summary = { "expected_profit": round((optimistic["profit"] + base["profit"] + pessimistic["profit"]) / 3, 2), "best_profit": optimistic["profit"], "worst_profit": pessimistic["profit"], "base_profit": base["profit"], "variance": round( ((optimistic["profit"] - base["profit"]) ** 2 + (base["profit"] - base["profit"]) ** 2 + (pessimistic["profit"] - base["profit"]) ** 2) / 3, 2 ), "base_revenue": base_revenue, "base_cost": base_cost, "months": months, } return { "scenarios": scenarios_list, "chart_data": chart_data, "summary": summary, }