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:
Hermes CI Fix
2026-09-16 23:46:48 +08:00
parent 27d3e1cd73
commit 9b3a131070
3 changed files with 275 additions and 21 deletions
+47 -2
View File
@@ -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()