feat(profitability): real ROI (op profit / invested capital), enable EVA, add RI
ci/woodpecker/push/main Pipeline failed
ci/woodpecker/push/main Pipeline failed
Kaliber: wecom-finance 2026-10-02 ruling (Boss 11:59 CST "li; but (1) add real ROI"). - kpi_definitions seed scripts/sql/profitability_kpi_defs_20261003.sql: F_ROI renamed to F_ROA (same kpi_id=20, all 8 historical values kept), new F_ROI (real IMA caliber = operating profit / invested capital), F_EVA activated with unit and thresholds aligned to 万元, new F_RI (residual income). - parameters are NOT hardcoded in formula text: they live in kpi_definitions.data_source_config.params (tax_rate / wacc / min_return_rate / include_factory_subsidy / include_channel_subsidy / include_sales_discount / annualize / capital_basis); single reader = app/utils/profitability_params.py. - deriver scripts/kpi_profitability_deriver.py: YTD operating profit from voucher_details (carry_forward=0 net), invested capital from subject_balances level-1 rows only, idempotent upsert into kpi_values (source_type=derived, no data_status downgrade, fail-closed when invested capital is 0). - read-only endpoint GET /reports/value-management reads the derived rows only (single source of truth, no recomputation) and exposes the parameter block. - DupontAnalysis.vue: value management section (ROI / ROA / ROE / EVA / RI). - caliber_units.py: RATIO_CALIBER_TABLE key F_ROI renamed to F_ROA, new F_ROI row. - tests: backend/tests/test_profitability_indicators.py (21 cases).
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""盈利能力三指标派生器 —— 真 ROI / EVA / RI(cma-profitability 2026-10-03)
|
||||
|
||||
任务:`finance-20261002-profitability-indicators-roi-eva-ri-fullstack.md`(P1,提出者 wecom-finance)
|
||||
放行出处:任总 2026-10-02 11:59 CST DM「立;但①要补真ROI」
|
||||
|
||||
口径(财务裁定,万元):
|
||||
营业利润(旧口径) = 5001 − 5401 − 5403 − (5601 + 5602 + 5603)
|
||||
经营口径营业利润 = 旧口径 + 厂补(530103) − 渠补(571103) [参数开关可回旧口径]
|
||||
(可选 +571112 商品折扣(销售),默认不计入)
|
||||
投入资本 = 有息负债(2001 + 2501) + 所有者权益(3xxx 一级科目净额)
|
||||
真 ROI = 经营口径营业利润 ÷ 投入资本 × 100
|
||||
NOPAT = 经营口径营业利润 × (1 − 所得税率)
|
||||
EVA = NOPAT − 投入资本 × WACC
|
||||
RI = 经营口径营业利润 − 投入资本 × 最低报酬率
|
||||
|
||||
取数:
|
||||
· 损益科目 → `voucher_details`(净额 = 借−贷,`carry_forward=0` 行域,同 kpi_bep_deriver)
|
||||
· 投入资本 → `subject_balances`(期末余额 cb_cr−cb_dr;3xxx 仅取 `level=1` 一级科目,防跨层双计)
|
||||
· 期间口径 = **YTD 累计(年初至目标月)**;是否年化由参数 `annualize` 控制(默认 False,
|
||||
即 ROI/EVA/RI 均为 YTD 非年化口径,已写进 kpi_definitions.formula_desc 与 remark)
|
||||
|
||||
参数:`app/utils/profitability_params.py`(唯一读取入口;落 `kpi_definitions.data_source_config.params`)
|
||||
幂等:同 kpi_id + entity + period + source_type='derived' 存在则原位更新;**不降级 data_status**
|
||||
(ASK-20260924-10 口径:保留既有状态,含人工放行的 verified)
|
||||
边界:只写 `source_type='derived'` 行;不改任何 kpi_definitions(定义由种子 SQL 负责);无 DDL。
|
||||
|
||||
用法:
|
||||
venv/bin/python3 scripts/kpi_profitability_deriver.py --period 2026-08 # 落库
|
||||
venv/bin/python3 scripts/kpi_profitability_deriver.py --period 2026-08 --dry-run # 只算不写
|
||||
venv/bin/python3 scripts/kpi_profitability_deriver.py --entity 1 --period 2026-08
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sqlalchemy import func, text
|
||||
|
||||
from app.database import get_session_local
|
||||
from app.models import KPIDefinition, KPIValue, VoucherDetail
|
||||
from app.utils.profitability_params import (
|
||||
EVA_KPI, PROFITABILITY_KPIS, RI_KPI, ROI_KPI, load_profitability_params,
|
||||
)
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
logger = logging.getLogger("cma.profitability_deriver")
|
||||
|
||||
#: 损益科目口径(与任务单 §一 逐字一致)
|
||||
SALES_CODE = "5001" # 主营业务收入(贷方)
|
||||
COST_CODE = "5401" # 主营业务成本
|
||||
TAX_SURCHARGE_CODE = "5403" # 营业税金及附加
|
||||
EXPENSE_PREFIXES = ("5601", "5602", "5603") # 销售/管理/财务费用
|
||||
FACTORY_SUBSIDY_CODE = "530103" # 厂补(贷方)
|
||||
CHANNEL_SUBSIDY_CODE = "571103" # 渠补(借方)
|
||||
SALES_DISCOUNT_CODE = "571112" # 商品折扣(销售),默认不计入
|
||||
|
||||
#: 有息负债(一级科目)
|
||||
INTEREST_BEARING_CODES = ("2001", "2501")
|
||||
#: 所有者权益前缀(一级科目 3xxx,恰 4 位;用 LIKE 保证 MySQL/SQLite 双引擎可跑)
|
||||
EQUITY_PREFIX = "3"
|
||||
EQUITY_LEVEL1_LIKE = "3___"
|
||||
|
||||
|
||||
# ────────────────────────── 取数 ──────────────────────────
|
||||
|
||||
def ytd_window(period: str) -> tuple:
|
||||
"""目标期间 → YTD 起止(年初 .. 当期)"""
|
||||
year = period[:4]
|
||||
return f"{year}-01", period
|
||||
|
||||
|
||||
def sum_voucher(db, entity_id: int, period_from: str, period_to: str,
|
||||
field: str, subject_filter=None) -> float:
|
||||
"""聚合 voucher_details 金额**元**。field: 'net'(借−贷) / 'debit_amount' / 'credit_amount'。
|
||||
|
||||
净额口径只在 `carry_forward=0` 行域成立(含结转腿时损益类净额恒 0)——见
|
||||
/srv/hermes/wisdom/_decisions/cma-expense-profit-net-caliber-20261002.md。
|
||||
"""
|
||||
expr = (VoucherDetail.debit_amount - VoucherDetail.credit_amount
|
||||
if field == "net" else getattr(VoucherDetail, field))
|
||||
q = db.query(func.sum(expr)).filter(
|
||||
VoucherDetail.entity_id == entity_id,
|
||||
VoucherDetail.carry_forward == 0,
|
||||
VoucherDetail.period >= period_from,
|
||||
VoucherDetail.period <= period_to,
|
||||
)
|
||||
if subject_filter:
|
||||
kind, code = subject_filter
|
||||
if kind == "eq":
|
||||
q = q.filter(VoucherDetail.subject_code == code)
|
||||
elif kind == "like":
|
||||
q = q.filter(VoucherDetail.subject_code.like(code + "%"))
|
||||
return float(q.scalar() or 0.0)
|
||||
|
||||
|
||||
def operating_profit(db, entity_id: int, period: str, params: Dict[str, Any]) -> Dict[str, float]:
|
||||
"""YTD 营业利润(万元):旧口径 / 经营口径 + 各项明细,全部留痕供复核。"""
|
||||
p_from, p_to = ytd_window(period)
|
||||
|
||||
sales = sum_voucher(db, entity_id, p_from, p_to, "credit_amount", ("eq", SALES_CODE))
|
||||
cost = sum_voucher(db, entity_id, p_from, p_to, "net", ("eq", COST_CODE))
|
||||
tax_surcharge = sum_voucher(db, entity_id, p_from, p_to, "net", ("eq", TAX_SURCHARGE_CODE))
|
||||
expenses = sum(sum_voucher(db, entity_id, p_from, p_to, "net", ("like", p))
|
||||
for p in EXPENSE_PREFIXES)
|
||||
|
||||
op_legacy = (sales - cost - tax_surcharge - expenses) / 10000.0
|
||||
|
||||
factory_subsidy = -sum_voucher(db, entity_id, p_from, p_to, "net", ("eq", FACTORY_SUBSIDY_CODE)) / 10000.0
|
||||
channel_subsidy = sum_voucher(db, entity_id, p_from, p_to, "net", ("eq", CHANNEL_SUBSIDY_CODE)) / 10000.0
|
||||
sales_discount = sum_voucher(db, entity_id, p_from, p_to, "net", ("eq", SALES_DISCOUNT_CODE)) / 10000.0
|
||||
|
||||
op = op_legacy
|
||||
if params["include_factory_subsidy"]:
|
||||
op += factory_subsidy
|
||||
if params["include_channel_subsidy"]:
|
||||
op -= channel_subsidy
|
||||
if params["include_sales_discount"]:
|
||||
op -= sales_discount
|
||||
|
||||
return {
|
||||
"sales_w": round(sales / 10000.0, 4),
|
||||
"cost_w": round(cost / 10000.0, 4),
|
||||
"tax_surcharge_w": round(tax_surcharge / 10000.0, 4),
|
||||
"expenses_w": round(expenses / 10000.0, 4),
|
||||
"op_profit_legacy_w": round(op_legacy, 4),
|
||||
"factory_subsidy_w": round(factory_subsidy, 4),
|
||||
"channel_subsidy_w": round(channel_subsidy, 4),
|
||||
"sales_discount_w": round(sales_discount, 4),
|
||||
"op_profit_w": round(op, 4),
|
||||
}
|
||||
|
||||
|
||||
def _equity_sql():
|
||||
return text("""
|
||||
SELECT COALESCE(SUM(cb_cr - cb_dr), 0)
|
||||
FROM subject_balances
|
||||
WHERE entity_id = :eid AND period = :period AND `level` = 1
|
||||
AND subject_code LIKE :eq_like
|
||||
""")
|
||||
|
||||
|
||||
def _debt_sql():
|
||||
return text("""
|
||||
SELECT COALESCE(SUM(cb_cr - cb_dr), 0)
|
||||
FROM subject_balances
|
||||
WHERE entity_id = :eid AND period = :period AND `level` = 1
|
||||
AND subject_code IN ('2001', '2501')
|
||||
""")
|
||||
|
||||
|
||||
def invest_capital(db, entity_id: int, period: str, basis: str = "ending") -> Dict[str, Any]:
|
||||
"""投入资本(万元)= 有息负债 + 所有者权益(`subject_balances` 期末/平均)。
|
||||
|
||||
3xxx **只取 `level=1`(一级科目)**:`row_kind` 有 subtotal_level1/detail 两层,
|
||||
含明细层会把 `3104 利润分配` 与其子科目 `310415` 双计(实测 +598.55 万虚增)。
|
||||
"""
|
||||
def _query(p: str) -> Dict[str, float]:
|
||||
debt = float(db.execute(_debt_sql(), {"eid": entity_id, "period": p}).scalar() or 0.0)
|
||||
equity = float(db.execute(_equity_sql(), {"eid": entity_id, "period": p,
|
||||
"eq_like": EQUITY_LEVEL1_LIKE}).scalar() or 0.0)
|
||||
return {"debt": debt / 10000.0, "equity": equity / 10000.0}
|
||||
|
||||
cur = _query(period)
|
||||
out = {
|
||||
"interest_bearing_debt_w": round(cur["debt"], 4),
|
||||
"equity_w": round(cur["equity"], 4),
|
||||
"basis": basis,
|
||||
"ending_w": round(cur["debt"] + cur["equity"], 4),
|
||||
}
|
||||
total = out["ending_w"]
|
||||
if basis == "average":
|
||||
y, m = int(period[:4]), int(period[5:7])
|
||||
if m == 1:
|
||||
prev_period = f"{y - 1}-12"
|
||||
else:
|
||||
prev_period = f"{y}-{m - 1:02d}"
|
||||
prev = _query(prev_period)
|
||||
prev_ok = (prev["debt"] + prev["equity"]) != 0.0
|
||||
out["prev_period"] = prev_period
|
||||
out["prev_total_w"] = round(prev["debt"] + prev["equity"], 4)
|
||||
if prev_ok:
|
||||
total = (out["ending_w"] + out["prev_total_w"]) / 2.0
|
||||
else:
|
||||
out["average_fallback"] = "上一期 subject_balances 缺数 → 回落期末口径"
|
||||
out["basis"] = "ending(fallback)"
|
||||
out["invest_capital_w"] = round(total, 4)
|
||||
return out
|
||||
|
||||
|
||||
# ────────────────────────── 计算 ──────────────────────────
|
||||
|
||||
def compute_indicators(op_profit_w: float, invest_capital_w: float,
|
||||
months: int, params: Dict[str, Any]) -> Dict[str, Optional[float]]:
|
||||
"""纯计算(无 DB,便于单测):返回三条指标值(None = 不可得,fail-closed)。"""
|
||||
if not invest_capital_w:
|
||||
return {ROI_KPI: None, EVA_KPI: None, RI_KPI: None, "nopat_w": None, "scale": None}
|
||||
scale = (12.0 / months) if (params.get("annualize") and months) else 1.0
|
||||
op = op_profit_w * scale
|
||||
nopat = op * (1.0 - float(params["tax_rate"]))
|
||||
return {
|
||||
ROI_KPI: round(op / invest_capital_w * 100.0, 2),
|
||||
"nopat_w": round(nopat, 4),
|
||||
EVA_KPI: round(nopat - invest_capital_w * float(params["wacc"]), 2),
|
||||
RI_KPI: round(op - invest_capital_w * float(params["min_return_rate"]), 2),
|
||||
"scale": scale,
|
||||
}
|
||||
|
||||
|
||||
# ────────────────────────── 落库 ──────────────────────────
|
||||
|
||||
def run_derive(db, entity_id: int, period: str, write_kpi: bool = True) -> Dict[str, Any]:
|
||||
params = load_profitability_params(db, entity_id)
|
||||
months = int(period[5:7])
|
||||
profit = operating_profit(db, entity_id, period, params)
|
||||
capital = invest_capital(db, entity_id, period, params.get("capital_basis", "ending"))
|
||||
values = compute_indicators(profit["op_profit_w"], capital["invest_capital_w"], months, params)
|
||||
|
||||
defs = {d.kpi_code: d for d in db.query(KPIDefinition).filter(
|
||||
KPIDefinition.entity_id == entity_id,
|
||||
KPIDefinition.kpi_code.in_(list(PROFITABILITY_KPIS)),
|
||||
).all()}
|
||||
|
||||
switch_txt = (f"厂补{'计入' if params['include_factory_subsidy'] else '不计入'}/"
|
||||
f"渠补{'计入' if params['include_channel_subsidy'] else '不计入'}/"
|
||||
f"商品折扣{'计入' if params['include_sales_discount'] else '不计入'}")
|
||||
basis_txt = ("YTD累计(非年化)" if not params.get("annualize")
|
||||
else f"YTD年化(×12/{months})")
|
||||
common = (f"营业利润({switch_txt}; {basis_txt}) {profit['op_profit_w']:.2f}万 / "
|
||||
f"投入资本({capital['basis']}) {capital['invest_capital_w']:.2f}万")
|
||||
|
||||
remarks = {
|
||||
ROI_KPI: (f"{common} × 100 = {values[ROI_KPI]}%"
|
||||
if values[ROI_KPI] is not None else f"{common}|投入资本为 0,ROI 不可得"),
|
||||
EVA_KPI: (f"NOPAT {values['nopat_w']:.2f}万(税率{params['tax_rate']:.0%}) − "
|
||||
f"投入资本 {capital['invest_capital_w']:.2f}万 × WACC {params['wacc']:.2%} "
|
||||
f"= {values[EVA_KPI]}万" if values[EVA_KPI] is not None else f"{common}|投入资本为 0"),
|
||||
RI_KPI: (f"营业利润 {profit['op_profit_w']:.2f}万 − 投入资本 {capital['invest_capital_w']:.2f}万 × "
|
||||
f"最低报酬率 {params['min_return_rate']:.2%} = {values[RI_KPI]}万"
|
||||
if values[RI_KPI] is not None else f"{common}|投入资本为 0"),
|
||||
}
|
||||
|
||||
batch = f"profit-{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
||||
written, changed = {}, []
|
||||
for code in PROFITABILITY_KPIS:
|
||||
kpi = defs.get(code)
|
||||
if kpi is None:
|
||||
written[code] = "no-definition"
|
||||
logger.warning("kpi_definitions 缺 %s(entity=%s)——请先应用种子 SQL", code, entity_id)
|
||||
continue
|
||||
if values[code] is None:
|
||||
written[code] = "unavailable(invest_capital=0)"
|
||||
continue
|
||||
if not write_kpi:
|
||||
written[code] = "dry-run"
|
||||
continue
|
||||
existing = db.query(KPIValue).filter(
|
||||
KPIValue.kpi_id == kpi.id,
|
||||
KPIValue.period == period,
|
||||
KPIValue.source_type == "derived",
|
||||
).first()
|
||||
if existing:
|
||||
old = float(existing.actual_value) if existing.actual_value is not None else None
|
||||
new = float(values[code])
|
||||
existing.actual_value = new
|
||||
existing.source_batch = batch
|
||||
if old is None or abs(old - new) > 1e-9:
|
||||
existing.remark = f"{remarks[code]}|值已更新(batch={batch}, 旧{old} → 新{new}) 待财务复核"
|
||||
changed.append({"code": code, "old": old, "new": new,
|
||||
"data_status": existing.data_status})
|
||||
else:
|
||||
existing.remark = remarks[code]
|
||||
# 不降级 data_status(ASK-20260924-10)
|
||||
written[code] = "upsert"
|
||||
else:
|
||||
db.add(KPIValue(
|
||||
entity_id=entity_id,
|
||||
kpi_id=kpi.id,
|
||||
period=period,
|
||||
actual_value=values[code],
|
||||
source_type="derived",
|
||||
source_batch=batch,
|
||||
data_status="pending",
|
||||
remark=remarks[code],
|
||||
))
|
||||
written[code] = "insert"
|
||||
db.flush()
|
||||
if write_kpi:
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"entity_id": entity_id,
|
||||
"period": period,
|
||||
"months": months,
|
||||
"mode": "write" if write_kpi else "dry-run",
|
||||
"batch": batch,
|
||||
"params": params,
|
||||
"profit": profit,
|
||||
"capital": capital,
|
||||
"nopat_w": values.get("nopat_w"),
|
||||
"values": {c: values[c] for c in PROFITABILITY_KPIS},
|
||||
"remarks": remarks,
|
||||
"written": written,
|
||||
"changed": changed,
|
||||
}
|
||||
|
||||
|
||||
def _pick_period(db, entity_id: int) -> Optional[str]:
|
||||
"""默认目标期间 = subject_balances 中该账套最新的期间(投入资本的唯一可取期间)。"""
|
||||
row = db.execute(text(
|
||||
"SELECT MAX(period) FROM subject_balances WHERE entity_id = :eid"
|
||||
), {"eid": entity_id}).scalar()
|
||||
return str(row) if row else None
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="盈利能力三指标派生器(真 ROI / EVA / RI)")
|
||||
ap.add_argument("--period", default=None, help="目标期间 YYYY-MM(默认取 subject_balances 最新期)")
|
||||
ap.add_argument("--entity", type=int, default=1, help="entity_id(默认 1=陕西酣客)")
|
||||
ap.add_argument("--dry-run", action="store_true", help="只计算不落库")
|
||||
args = ap.parse_args()
|
||||
|
||||
db = get_session_local()()
|
||||
try:
|
||||
period = args.period or _pick_period(db, args.entity)
|
||||
if not period:
|
||||
logger.error("entity=%s 无 subject_balances 记录,无可用期间", args.entity)
|
||||
return 2
|
||||
r = run_derive(db, args.entity, period, write_kpi=not args.dry_run)
|
||||
print(f"盈利能力派生完成 entity={r['entity_id']} period={r['period']} "
|
||||
f"(YTD {r['months']}个月) mode={r['mode']} batch={r['batch']}")
|
||||
p, c = r["profit"], r["capital"]
|
||||
print(f" 销售收入 {p['sales_w']:.2f}万 − 成本 {p['cost_w']:.2f}万 − 税金及附加 {p['tax_surcharge_w']:.2f}万 "
|
||||
f"− 三费 {p['expenses_w']:.2f}万 = 旧口径营业利润 {p['op_profit_legacy_w']:.2f}万")
|
||||
print(f" 厂补 {p['factory_subsidy_w']:.2f}万 / 渠补 {p['channel_subsidy_w']:.2f}万 / "
|
||||
f"商品折扣 {p['sales_discount_w']:.2f}万 → 经营口径营业利润 {p['op_profit_w']:.2f}万")
|
||||
print(f" 投入资本({c['basis']}) = 有息负债 {c['interest_bearing_debt_w']:.2f}万 + "
|
||||
f"所有者权益 {c['equity_w']:.2f}万 = {c['invest_capital_w']:.2f}万")
|
||||
print(f" F_ROI = {r['values'][ROI_KPI]} %")
|
||||
print(f" F_EVA = {r['values'][EVA_KPI]} 万元 (NOPAT {r['nopat_w']:.2f}万)")
|
||||
print(f" F_RI = {r['values'][RI_KPI]} 万元")
|
||||
print(f" 落库: {r['written']}")
|
||||
if r["changed"]:
|
||||
print(f" 值变: {r['changed']}")
|
||||
return 0
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main() or 0)
|
||||
@@ -0,0 +1,80 @@
|
||||
-- CMA 盈利能力三指标(真 ROI / EVA / RI)定义收敛 — 2026-10-03
|
||||
-- 任务: finance-20261002-profitability-indicators-roi-eva-ri-fullstack(部署目标 cma-backend · cma-frontend)
|
||||
-- 口径: 财务Bot 2026-10-02 裁定(任总 11:59 CST DM「立;但①要补真ROI」)
|
||||
-- 动作:
|
||||
-- ① F_ROI 正名 → F_ROA(**沿用原 kpi_id=20 与全部 8 期历史值,不删行**;名称「总资产报酬率(ROA)」不变)
|
||||
-- ② 新增 F_ROI(真口径:经营口径营业利润 ÷ 投入资本)
|
||||
-- ③ F_EVA 启用 status='active' + 单位/阈值对齐「万元」(原 unit='元' 而派生值以万元落库 ⇒ 会恒黄/量级失真)
|
||||
-- ④ 新增 F_RI(剩余收益)
|
||||
-- 参数: 全部落 data_source_config.params(可配置、非硬编码进公式文本);
|
||||
-- 读取入口唯一 = app/utils/profitability_params.py
|
||||
-- 幂等: 条件 UPDATE + NOT EXISTS 守卫,可安全重跑
|
||||
-- ⚠️ 顺序不可换:①必须先于②((entity_id,kpi_code) 唯一键 uk_entity_kpi 会撞)
|
||||
|
||||
-- ① F_ROI 正名 → F_ROA(仅当 F_ROA 尚不存在;历史值随 kpi_id 保留)
|
||||
UPDATE kpi_definitions
|
||||
SET kpi_code = 'F_ROA', updated_at = NOW()
|
||||
WHERE entity_id = 1 AND kpi_code = 'F_ROI'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM (SELECT id FROM kpi_definitions
|
||||
WHERE entity_id = 1 AND kpi_code = 'F_ROA') AS t
|
||||
);
|
||||
|
||||
-- ② 新增 F_ROI(真口径)
|
||||
INSERT INTO kpi_definitions
|
||||
(entity_id, kpi_code, kpi_name, dimension, category, formula, formula_desc,
|
||||
data_source_type, data_source, data_owner, frequency, unit, target_value,
|
||||
target_calc_type, threshold_green, threshold_yellow, threshold_red,
|
||||
kpi_level, status, bot_source, epic, created_by, data_source_config)
|
||||
SELECT 1, 'F_ROI', '投入资本回报率(ROI)', 'finance', 'profitability',
|
||||
'经营口径营业利润 / 投入资本 * 100',
|
||||
'真ROI=营业利润÷投入资本; 投入资本=有息负债(2001+2501)+所有者权益(3xxx一级科目); YTD累计(非年化); '
|
||||
'厂补530103/渠补571103计入经营(开关 include_factory_subsidy/include_channel_subsidy); '
|
||||
'571112商品折扣默认不计入(include_sales_discount); 参数见 data_source_config.params',
|
||||
'derived', '盈利能力派生器 kpi_profitability_deriver.py(subject_balances + voucher_details)',
|
||||
'财务部', 'monthly', '%', 0.00,
|
||||
'ratio', NULL, NULL, NULL,
|
||||
'strategic', 'active', 'finance-bot', 'Epic2', 1,
|
||||
'{"params": {"include_factory_subsidy": true, "include_channel_subsidy": true, "include_sales_discount": false, "annualize": false, "capital_basis": "ending"}, "param_note": "利润口径开关,三条指标(F_ROI/F_EVA/F_RI)共用;读取入口 app/utils/profitability_params.py"}'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM kpi_definitions WHERE entity_id = 1 AND kpi_code = 'F_ROI'
|
||||
);
|
||||
|
||||
-- ③ F_EVA 启用 + 万元口径对齐(阈值语义逐字平移:原 绿>=0元 / 黄>=-100000元 / 红<-100000元
|
||||
-- ⇔ 万元 绿>=0 / 黄>=-10 / 红<-10;不改判定语义,只消除 元↔万元 跨量级失真)
|
||||
UPDATE kpi_definitions
|
||||
SET status = 'active',
|
||||
unit = '万元',
|
||||
target_value = 0.00,
|
||||
target_calc_type = 'accumulate',
|
||||
formula = 'NOPAT - 投入资本 * WACC',
|
||||
formula_desc = 'EVA=NOPAT−投入资本×WACC; NOPAT=经营口径营业利润×(1−所得税率); 单位万元; '
|
||||
'YTD累计(非年化); 资本成本参数见 data_source_config.params(tax_rate/wacc)',
|
||||
data_source_type = 'derived',
|
||||
data_source = '盈利能力派生器 kpi_profitability_deriver.py(subject_balances + voucher_details)',
|
||||
data_owner = '财务部',
|
||||
threshold_green = '>=0', threshold_yellow = '>=-10', threshold_red = '<-10',
|
||||
data_source_config = '{"params": {"tax_rate": 0.25, "wacc": 0.08}}',
|
||||
updated_at = NOW()
|
||||
WHERE entity_id = 1 AND kpi_code = 'F_EVA';
|
||||
|
||||
-- ④ 新增 F_RI(剩余收益)
|
||||
INSERT INTO kpi_definitions
|
||||
(entity_id, kpi_code, kpi_name, dimension, category, formula, formula_desc,
|
||||
data_source_type, data_source, data_owner, frequency, unit, target_value,
|
||||
target_calc_type, threshold_green, threshold_yellow, threshold_red,
|
||||
kpi_level, status, bot_source, epic, created_by, data_source_config)
|
||||
SELECT 1, 'F_RI', '剩余收益(RI)', 'finance', 'profitability',
|
||||
'经营口径营业利润 - 投入资本 * 最低报酬率',
|
||||
'RI=营业利润−投入资本×最低报酬率(同WACC族,EVA为先扣税版本); 单位万元; YTD累计(非年化); '
|
||||
'最低报酬率参数见 data_source_config.params.min_return_rate; 部门级 RI 待部门级资产负债后下沉',
|
||||
'derived', '盈利能力派生器 kpi_profitability_deriver.py(subject_balances + voucher_details)',
|
||||
'财务部', 'monthly', '万元', 0.00,
|
||||
'accumulate', NULL, NULL, NULL,
|
||||
'strategic', 'active', 'finance-bot', 'Epic2', 1,
|
||||
'{"params": {"min_return_rate": 0.08}}'
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM kpi_definitions WHERE entity_id = 1 AND kpi_code = 'F_RI'
|
||||
);
|
||||
Reference in New Issue
Block a user