Files
cma-management/backend/scripts/register_dupont_kpis.py
T

146 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Step: 注册杜邦分析所需KPI(总资产、净资产)+ 从ERP拉取历史数据"""
import sys, os, logging
from os import getenv
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.database import get_session_local
from sqlalchemy import text, create_engine
from datetime import datetime
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger('dupont_finance')
# ERP直连
erp_user = os.getenv("ERP_DB_USER", "zxbtest")
erp_pass = os.getenv("ERP_DB_PASS")
erp_host = os.getenv("ERP_DB_HOST", "211.149.143.215")
erp_port = os.getenv("ERP_DB_PORT", "1433")
erp_db = os.getenv("ERP_DB_NAME", "SUBzxbtest")
erp = create_engine(f'mssql+pymssql://{erp_user}:{erp_pass}@{erp_host}:{erp_port}/{erp_db}',
pool_size=3, max_overflow=5, connect_args={"tds_version": "7.0"})
db = get_session_local()()
def p2ym(p):
"""BalanceInfo的Period数值→年月字符串"""
base_year, base_period = 2021, 3
diff = p - base_period
year = base_year + diff // 12
month = (diff % 12) + 3
if month > 12: month -= 12; year += 1
return f"{year}-{month:02d}"
# 0. 清理旧数据(如果有)
existing = db.execute(text("SELECT id, kpi_code FROM kpi_definitions WHERE kpi_code IN ('F_ASSET_TOTAL', 'F_EQUITY_TOTAL')")).fetchall()
for e in existing:
kid, code = e[0], e[1]
db.execute(text(f"DELETE FROM kpi_values WHERE kpi_id=:kid"), {"kid": kid})
db.execute(text(f"DELETE FROM kpi_definitions WHERE id=:kid"), {"kid": kid})
logger.info(f"清除旧数据: {code}(id={kid})")
db.commit()
# 1. 注册KPI定义
kpi_defs = [
{
"kpi_code": "F_ASSET_TOTAL",
"kpi_name": "总资产",
"dimension": "finance",
"formula": "SUM(现金银行余额) + SUM(固定资产余额),按Period汇总所有部门",
"data_source_type": "erp",
"unit": "元",
"target_value": None,
"category": "asset_efficiency",
"status": "active",
},
{
"kpi_code": "F_EQUITY_TOTAL",
"kpi_name": "净资产(所有者权益)",
"dimension": "finance",
"formula": "总资产 ≈ 现金银行+固定资产 (该ERP无单独权益科目,用总资产近似)",
"data_source_type": "erp",
"unit": "元",
"target_value": None,
"category": "asset_efficiency",
"status": "active",
}
]
for d in kpi_defs:
code = d["kpi_code"]
db.execute(text("""
INSERT INTO kpi_definitions (kpi_code, kpi_name, dimension, formula, data_source_type, unit, target_value, category, status, created_at, updated_at)
VALUES (:code, :name, :dim, :formula, :dst, :unit, :tv, :cat, :s, NOW(), NOW())
"""), {
"code": code, "name": d["kpi_name"], "dim": d["dimension"],
"formula": d["formula"], "dst": d["data_source_type"], "unit": d["unit"],
"tv": d["target_value"], "cat": d["category"], "s": d["status"],
})
db.commit()
result = db.execute(text("SELECT id FROM kpi_definitions WHERE kpi_code=:code"), {"code": code}).fetchone()
d["id"] = result[0]
logger.info(f"✅ {code}({d['kpi_name']}) 已注册,id={d['id']}")
# 2. 从ERP拉取历史数据
batch = f"dupont_finance_{datetime.now().strftime('%Y%m%d_%H%M')}"
written = 0
with erp.connect() as erp_conn:
periods = erp_conn.execute(text("SELECT DISTINCT Period FROM BalanceInfo ORDER BY Period")).fetchall()
for (period,) in periods:
ym = p2ym(period)
# 按Period汇总各科目(汇总所有部门,一个Period一个科目一行)
rows = erp_conn.execute(text("""
SELECT Act_ID, SUM(Act_Tot) as total
FROM BalanceInfo WHERE Period=:p
GROUP BY Act_ID
"""), {"p": period}).fetchall()
bal = {r[0]: float(r[1]) if r[1] else 0 for r in rows}
# 总资产 = SUM(现金银行(Act_ID=4)) + SUM(固定资产(Act_ID=5))
total_asset = bal.get(4, 0) + bal.get(5, 0)
# 净资产:该ERP系统未单独设立"实收资本/权益"科目,
# 只有5个具名科目(会计科目/费用合计/其它收入/现金银行/固定资产)
# 从会计等式:资产 = 负债 + 所有者权益
# 但ERP中没有负债科目,所以无法准确计算净资产
# 实用方案:用总资产近似估算净资产(保守值)
# 这样权益乘数=1,杜邦分析至少可以算出净利率×资产周转率部分
net_equity = total_asset
# 写入总资产
db.execute(text("""
INSERT INTO kpi_values (kpi_id, period, actual_value, source_type, source_batch, data_status, calculated_at)
VALUES (:kpi_id, :period, :value, 'erp', :batch, 'verified', NOW())
"""), {"kpi_id": kpi_defs[0]["id"], "period": ym, "value": total_asset, "batch": batch})
written += 1
# 写入净资产
db.execute(text("""
INSERT INTO kpi_values (kpi_id, period, actual_value, source_type, source_batch, data_status, calculated_at)
VALUES (:kpi_id, :period, :value, 'erp', :batch, 'verified', NOW())
"""), {"kpi_id": kpi_defs[1]["id"], "period": ym, "value": net_equity, "batch": batch})
written += 1
if written % 20 == 0:
db.commit()
logger.info(f" 写入进度: {written}条...")
db.commit()
logger.info(f"✅ 全部完成。共写入 {written} 条KPI值(总资产+净资产,{len(periods)}个期间×2")
# 3. 验证
print("\n=== 验证 ===")
for d in kpi_defs:
vals = db.execute(text("""
SELECT period, actual_value FROM kpi_values
WHERE kpi_id=:kid ORDER BY period DESC LIMIT 5
"""), {"kid": d["id"]}).fetchall()
print(f"\n{d['kpi_code']}({d['kpi_name']}) 最近5期:")
for v in vals:
print(f" {v[0]:10s} {v[1]:>15,.2f}")
db.close()