feat: 预测性成本智能·宏观敏感性因素联动(IMA 2026.7完整版)
- 内置3宏观因素: 原油价格/美元汇率/CPI通胀率 - 敏感性引擎: 按KPI类别推断弹性(成本类油价0.15/利润类0.12/营收类0.08), 方向+因素涨KPI涨 - 负值KPI(亏损)方向反转修复: 油价涨→净利更亏 - API: GET /predict/kpi-forecast/sensitivity?pct=10 → KPI×因素矩阵(±pct调整后预测) - 前端: 敏感性幅度选择(±5/10/20%) + 敏感性矩阵表(同向/反向+↑↓调整值) - 诚实标注: 模型弹性(规则推断,非历史回归), 后续可用宏观历史数据回归校准 - 验证: Chrome实测页面+API矩阵, pytest 46 passed
This commit is contained in:
@@ -293,3 +293,92 @@ def forecast_finance_kpis(entity_id: int, db: Session,
|
||||
score = {"high": 3, "medium": 2, "low": 1}
|
||||
results.sort(key=lambda r: (score.get(r["confidence"], 0), r["history_count"]), reverse=True)
|
||||
return results
|
||||
|
||||
|
||||
# ════════════════════════════════════════════════════════════
|
||||
# 宏观敏感性因素联动(IMA 2026.7 Predictive Cost Intelligence 完整版)
|
||||
# 内置宏观因素 → 按KPI类型推断弹性系数 → 调整预测值
|
||||
# MVP:弹性系数为规则推断+可调,非历史回归(诚实标注"模型弹性")
|
||||
# ════════════════════════════════════════════════════════════
|
||||
|
||||
MACRO_FACTORS = [
|
||||
{"key": "oil", "name": "原油价格", "unit": "美元/桶",
|
||||
"desc": "油价↑ → 运输/能源成本↑ → 成本类KPI↑、利润类KPI↓"},
|
||||
{"key": "usd", "name": "美元汇率", "unit": "USD/CNY",
|
||||
"desc": "美元↑ → 进口成本↑(成本类↑)、出口收入↑(营收类↑)"},
|
||||
{"key": "cpi", "name": "CPI通胀率", "unit": "%",
|
||||
"desc": "CPI↑ → 成本↑、名义营收↑"},
|
||||
]
|
||||
|
||||
# KPI 类别关键词 → 因素方向/弹性 (direction: +因素涨KPI涨, -因素涨KPI跌)
|
||||
FACTOR_RULES = {
|
||||
"cost": { # 成本/费用类: 宏观涨 → 成本涨
|
||||
"oil": {"direction": "+", "elasticity": 0.15},
|
||||
"usd": {"direction": "+", "elasticity": 0.10},
|
||||
"cpi": {"direction": "+", "elasticity": 0.10},
|
||||
},
|
||||
"revenue": { # 营收类: 通胀涨→名义营收涨
|
||||
"oil": {"direction": "-", "elasticity": 0.05},
|
||||
"usd": {"direction": "+", "elasticity": 0.08},
|
||||
"cpi": {"direction": "+", "elasticity": 0.08},
|
||||
},
|
||||
"profit": { # 利润类: 宏观涨 → 成本挤压利润
|
||||
"oil": {"direction": "-", "elasticity": 0.12},
|
||||
"usd": {"direction": "-", "elasticity": 0.08},
|
||||
"cpi": {"direction": "-", "elasticity": 0.08},
|
||||
},
|
||||
"cash": { # 现金流类
|
||||
"oil": {"direction": "-", "elasticity": 0.06},
|
||||
"usd": {"direction": "-", "elasticity": 0.04},
|
||||
"cpi": {"direction": "-", "elasticity": 0.05},
|
||||
},
|
||||
}
|
||||
|
||||
# 类别关键词匹配(长词优先)
|
||||
CATEGORY_KEYWORDS = [
|
||||
("profit", ["净利润", "净利", "利润", "毛利", "ROE", "ROI", "EVA", "收益率", "报酬率"]),
|
||||
("revenue", ["营收", "收入", "销售额", "销售", "产值", "客单"]),
|
||||
("cost", ["费用率", "成本率", "费用", "成本", "费率", "应付", "返利", "渠补", "税"]),
|
||||
("cash", ["现金流", "现金", "回款", "FCF", "资金"]),
|
||||
]
|
||||
|
||||
|
||||
def infer_kpi_category(kpi_name: str, kpi_code: str = "") -> str:
|
||||
"""按KPI名称/编码推断类别: profit/revenue/cost/cash,兜底 profit(保守)"""
|
||||
n = (kpi_name or "") + " " + (kpi_code or "")
|
||||
for cat, kws in CATEGORY_KEYWORDS:
|
||||
if any(kw in n for kw in kws):
|
||||
return cat
|
||||
return "profit"
|
||||
|
||||
|
||||
def factor_sensitivity_for_kpi(kpi_name: str, kpi_code: str = "") -> list:
|
||||
"""返回该KPI对3个宏观因素的敏感性(方向+弹性)"""
|
||||
cat = infer_kpi_category(kpi_name, kpi_code)
|
||||
rules = FACTOR_RULES.get(cat, FACTOR_RULES["profit"])
|
||||
out = []
|
||||
for f in MACRO_FACTORS:
|
||||
r = rules.get(f["key"], {"direction": "-", "elasticity": 0.05})
|
||||
out.append({
|
||||
"factor_key": f["key"],
|
||||
"factor_name": f["name"],
|
||||
"factor_unit": f["unit"],
|
||||
"factor_desc": f["desc"],
|
||||
"direction": r["direction"],
|
||||
"elasticity": r["elasticity"],
|
||||
"category": cat,
|
||||
})
|
||||
return out
|
||||
|
||||
|
||||
def adjusted_next_with_factor(next_target: Optional[float], pct: float,
|
||||
direction: str, elasticity: float) -> Optional[float]:
|
||||
"""因素变动 pct% → 调整后预测值: 方向+ 因素涨预测涨; 方向- 因素涨预测跌
|
||||
负值KPI(亏损)方向反转: 方向- 时因素涨 → 更亏(更负)"""
|
||||
if next_target is None:
|
||||
return None
|
||||
factor_change = pct * 0.01 # ±5% → 0.05
|
||||
sign = 1.0 if direction == "+" else -1.0
|
||||
if next_target < 0:
|
||||
sign = -sign # 负值(亏损): 因素涨 → 更亏
|
||||
return round(next_target * (1 + sign * factor_change * elasticity), 2)
|
||||
|
||||
Reference in New Issue
Block a user