fix(cma): cashflow statements importer + auto_verify/alert_generator observability
- add backend/scripts/import_cashflow_statements.py: load zxt cashflow stat (CNY) into cashflow_statements in 10k CNY, dry-run default, --apply gated - auto_verify_cron: per-reason skip counters + legacy-condition rule now resolves kpi_code via plan.kpi_id (was unreachable) + --dry-run - alert_generator: coverage self-check warns when current-month KPI values are absent (0 alerts != no trigger) task: finance-20260916-cma-health-gaps-3items (items 1/2/3)
This commit is contained in:
@@ -217,6 +217,38 @@ def generate_and_push(db_session) -> dict:
|
||||
new_count = run_alert_check(db_session, period)
|
||||
logger.info(f"预警检查完成: 新增 {new_count} 条")
|
||||
|
||||
# 1a. 覆盖度自检(2026-09-16 新增,任务 finance-20260916-cma-health-gaps-3items · 项3):
|
||||
# run_alert_check 的期间口径 = 当月(period=current month),若当月尚无任何 KPIValue,
|
||||
# 则「0 条新预警」并不代表"确无阈值触发",而是"本次检查未覆盖任何数据"——
|
||||
# 该区别原实现不可见(日志与汇总行都只说 0 条),导致连续多日 0 新增无法归因。
|
||||
try:
|
||||
kpis_with_values = 0
|
||||
active_kpis = db_session.query(KPIDefinition).filter(KPIDefinition.status == "active").all()
|
||||
for k in active_kpis:
|
||||
if not any((k.threshold_red, k.threshold_yellow, k.threshold_green)):
|
||||
continue
|
||||
hit = db_session.query(KPIValue).filter(
|
||||
KPIValue.kpi_id == k.id, KPIValue.period == period,
|
||||
KPIValue.actual_value.isnot(None),
|
||||
).first()
|
||||
if hit:
|
||||
kpis_with_values += 1
|
||||
coverage = {"period": period, "kpis_with_threshold_and_value": kpis_with_values}
|
||||
if kpis_with_values == 0:
|
||||
latest_period = db_session.query(KPIValue.period).filter(
|
||||
KPIValue.actual_value.isnot(None),
|
||||
KPIValue.period.op("REGEXP")("^[0-9]{4}-[0-9]{2}$"), # 只取 YYYY-MM 月期间(排除 2026-H1/H2 伪期间)
|
||||
).order_by(KPIValue.period.desc()).first()
|
||||
logger.warning(
|
||||
f"覆盖度告警: 当月({period})无任何 KPI 实际值 → 本次预警检查未覆盖任何 KPI,"
|
||||
f"「0 条新预警」= 无数据可判,非「确无阈值触发」。库内最新期间="
|
||||
f"{latest_period[0] if latest_period else 'N/A'}"
|
||||
)
|
||||
coverage["latest_period_in_db"] = latest_period[0] if latest_period else None
|
||||
except Exception as e:
|
||||
logger.error(f"覆盖度自检失败: {e}")
|
||||
coverage = {"error": str(e)}
|
||||
|
||||
# 1b. 规则引擎检查(预测偏差 / 客户集中度,P2 2026-09-10)
|
||||
rule_stats = run_rule_engine_checks(db_session)
|
||||
|
||||
@@ -231,7 +263,20 @@ def generate_and_push(db_session) -> dict:
|
||||
logger.error(f"推送失败: {e}")
|
||||
|
||||
return {"period": period, "new_alerts": new_count, "pushed": pushed,
|
||||
"cleaned_green": cleaned, "rule_engine": rule_stats}
|
||||
"cleaned_green": cleaned, "rule_engine": rule_stats,
|
||||
"coverage": coverage}
|
||||
|
||||
|
||||
def coverage_note(result: dict) -> str:
|
||||
"""汇总行追加提示:当月无 KPI 值时显式说明「0 条 = 无数据可判」而非「确无触发」
|
||||
|
||||
2026-09-16 新增(项3)。独立成函数便于回查(无需触发真实写路径即可断言文案)。
|
||||
"""
|
||||
cov = result.get("coverage") or {}
|
||||
if cov.get("kpis_with_threshold_and_value") == 0:
|
||||
return (f" | ⚠️ 当月({result['period']})无KPI值: 本次检查未覆盖任何KPI"
|
||||
f"(库内最新月期间 {cov.get('latest_period_in_db')})→ 0 条不等于确无触发")
|
||||
return ""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -244,6 +289,6 @@ if __name__ == "__main__":
|
||||
try:
|
||||
result = generate_and_push(db)
|
||||
print(f"预警检查: {result['new_alerts']}条新预警 / {result['pushed']}条已推送"
|
||||
f" / 规则引擎{result['rule_engine']}")
|
||||
f" / 规则引擎{result['rule_engine']}{coverage_note(result)}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
Reference in New Issue
Block a user