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

146 lines
6.2 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
"""Auto-Verify 定时验证任务 — 扫描到期pending计划,自动验证执行效果
运行: 30 9 * * * cd /root/cma-management/backend && python3 scripts/auto_verify_cron.py
"""
import sys, os
from datetime import datetime, timedelta
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.database import get_db
from app.models import ActionPlan, KPIDefinition, KPIValue, Objective
from app.utils.notifier import send_wecom_message
from app.api.verify import CONDITION_FUNCS
def evaluate_condition(condition, actual, target):
"""按条件判断验证结果(兼容旧表达式)"""
if isinstance(condition, str) and ('<' in condition or '>' in condition):
try:
return eval(condition.replace('value', str(actual)), {"__builtins__": {}})
except Exception:
return False
func = CONDITION_FUNCS.get(condition)
if not func:
return False
return func(actual, target)
def run_auto_verify():
db = next(get_db())
try:
now = datetime.now()
# 扫描到期pending计划(due_date <= now+1天)
plans = db.query(ActionPlan).filter(
ActionPlan.verify_status == "pending",
ActionPlan.status.in_(["pending", "in_progress"]),
ActionPlan.due_date <= now + timedelta(days=1),
).all()
verified_count = 0
escalated_count = 0
messages = []
for plan in plans:
rule = plan.auto_verify_rule
if not rule or not isinstance(rule, dict):
continue
kpi_code = rule.get("kpi_code")
if not kpi_code:
continue
# 查最新KPI值
# 缺陷2残留修复(对齐 verify.py):从 plan 关联 KPI 向上取 entity_idkpi_code 查询带 entity_id 过滤(防跨租户误匹配)
entity_id = None
if plan.kpi_id:
pkpi = db.query(KPIDefinition).filter(KPIDefinition.id == plan.kpi_id).first()
if pkpi:
entity_id = pkpi.entity_id
q = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == kpi_code)
if entity_id is not None:
q = q.filter(KPIDefinition.entity_id == entity_id)
kpi = q.order_by(KPIDefinition.id.desc()).first()
if not kpi:
continue
# 缺陷3残留修复(对齐 verify.py):KPIValue 按 period <= 当前月过滤,跨月验证不取未来期间
period_limit = now.strftime("%Y-%m")
latest = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi.id,
KPIValue.actual_value.isnot(None),
KPIValue.period <= period_limit,
).order_by(KPIValue.period.desc(), KPIValue.calculated_at.desc(), KPIValue.id.desc()).first()
if not latest or latest.actual_value is None:
continue
actual = latest.actual_value
condition = rule.get("condition", "LESS_THAN")
target = rule.get("target_value")
retry_max = rule.get("retry_max", 3)
passed = evaluate_condition(condition, actual, target)
plan.kpi_current_before = rule.get("baseline_value")
plan.kpi_current_after = actual
if passed:
# 缺陷1残留修复(对齐 verify.py):plan 已处于"验证通过"状态(passed + verified_at 非空)则跳过 OKR 累加,防重复累加
already_verified = bool(plan.verify_status == "passed" and plan.verified_at is not None)
plan.verify_status = "passed"
plan.verified_at = now
plan.status = "completed" # 缺陷4残留修复:"done" 不在枚举(pending/in_progress/completed/cancelled),改 completed
plan.progress = 100
# 阶段3: OKR progress更新
update_okr_progress(db, plan, +15, already_verified=already_verified)
messages.append(f"✅ 行动计划#{plan.id}验证通过: {plan.title} ({kpi_code}: {rule.get('baseline_value')}{actual})")
verified_count += 1
else:
plan.verify_attempts = (plan.verify_attempts or 0) + 1
if plan.verify_attempts >= retry_max:
plan.verify_status = "escalated"
messages.append(f"🚨 行动计划#{plan.id}连续{retry_max}次验证失败,升级人工: {plan.title}")
escalated_count += 1
else:
plan.verify_status = "retrying"
messages.append(f"⚠️ 行动计划#{plan.id}验证失败(第{plan.verify_attempts}次),待重试: {plan.title}")
db.commit()
# 汇总通知
if messages:
summary = "\n".join(messages)
try:
send_wecom_message(f"📋 行动计划自动验证日报\n{summary}", title="Auto-Verify")
except Exception:
pass
print(f"[{now.strftime('%Y-%m-%d %H:%M')}] 验证通过{verified_count}个,升级{escalated_count}个,共处理{len(plans)}个到期计划")
return {"verified": verified_count, "escalated": escalated_count, "total": len(plans)}
finally:
db.close()
def update_okr_progress(db, plan, delta, already_verified=False):
"""验证通过→更新所属OKR progress
缺陷1残留修复(对齐 verify.py 2026-08-30):幂等防重复累加
- already_verified=Trueplan 已处于验证通过状态且 verified_at 非空)→ 跳过累加,保持原值
"""
okr_id = getattr(plan, "okr_id", None) or getattr(plan, "objective_id", None)
if not okr_id:
return {"updated": False, "reason": "no_objective"}
obj = db.query(Objective).filter(Objective.id == okr_id).first()
if not obj:
return {"updated": False, "reason": "objective_not_found"}
if already_verified:
return {"updated": False, "reason": "already_verified", "objective_id": obj.id, "progress": obj.progress or 0}
current = obj.progress or 0
obj.progress = min(current + delta, 100)
db.add(obj)
return {"updated": True, "objective_id": obj.id, "progress": obj.progress}
if __name__ == "__main__":
run_auto_verify()