fix: auto-verify cron残留缺陷修复+第三阶段接入 verify-cron-fix (auto_verify_cron 4项对齐verify.py: entity_id过滤/period过滤/OKR幂等/status completed; action_plan_weekly done->completed; action_plans创建自动生成auto_verify_rule)

This commit is contained in:
Hermes CI Fix
2026-08-31 07:44:00 +08:00
parent 27d8269667
commit 136fe3811f
3 changed files with 54 additions and 16 deletions
+17 -1
View File
@@ -9,7 +9,8 @@ from calendar import monthrange
from app.database import get_db from app.database import get_db
from app.deps import get_entity_id from app.deps import get_entity_id
from app.auth_middleware import require_role, require_auth from app.auth_middleware import require_role, require_auth
from app.models import ActionPlan, KPIAlert, KPIDefinition, User, Objective, KR from app.models import ActionPlan, KPIAlert, KPIDefinition, KPIValue, User, Objective, KR
from app.api.verify import build_auto_verify_rule
logger = logging.getLogger("cma.action_plans") logger = logging.getLogger("cma.action_plans")
@@ -183,7 +184,22 @@ def create_plan(
status="pending", status="pending",
progress=0, progress=0,
created_by=current_user.name or current_user.username, created_by=current_user.name or current_user.username,
auto_verify_rule=data.get("auto_verify_rule"), # 显式规则原样保存;None 时下面自动生成
) )
# 第三阶段接入(2026-08-31):创建 ActionPlan 自动生成验证规则
# 请求体未传 auto_verify_rule 且关联 KPI 存在 → 复用 verify.build_auto_verify_rule 生成默认规则(不改变现有创建行为;auto_close 默认 false
if not data.get("auto_verify_rule"):
baseline_value = data.get("baseline_value")
if baseline_value is None:
# 基线缺省时取 KPI 当前最新值(period <= 当前月),供 kpi_current_before 回填
latest_kpi = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi_ent.id,
KPIValue.actual_value.isnot(None),
KPIValue.period <= datetime.now().strftime("%Y-%m"),
).order_by(KPIValue.period.desc(), KPIValue.calculated_at.desc(), KPIValue.id.desc()).first()
if latest_kpi:
baseline_value = latest_kpi.actual_value
plan.auto_verify_rule = build_auto_verify_rule(kpi_ent, baseline_value=baseline_value)
db.add(plan) db.add(plan)
db.commit() db.commit()
db.refresh(plan) db.refresh(plan)
+1 -1
View File
@@ -27,7 +27,7 @@ def generate_weekly_report():
failed = sum(1 for p in plans if p.verify_status == "failed") failed = sum(1 for p in plans if p.verify_status == "failed")
escalated = sum(1 for p in plans if p.verify_status == "escalated") escalated = sum(1 for p in plans if p.verify_status == "escalated")
pending = sum(1 for p in plans if p.verify_status in ("pending", "retrying")) pending = sum(1 for p in plans if p.verify_status in ("pending", "retrying"))
done = sum(1 for p in plans if p.status == "done") done = sum(1 for p in plans if p.status == "completed") # done 不在枚举(pending/in_progress/completed/cancelled),改 completed
msg = ( msg = (
f"📋 行动计划执行周报({datetime.now().strftime('%Y-%m-%d')}\n" f"📋 行动计划执行周报({datetime.now().strftime('%Y-%m-%d')}\n"
+33 -11
View File
@@ -52,14 +52,25 @@ def run_auto_verify():
continue continue
# 查最新KPI值 # 查最新KPI值
kpi = db.query(KPIDefinition).filter( # 缺陷2残留修复(对齐 verify.py):从 plan 关联 KPI 向上取 entity_idkpi_code 查询带 entity_id 过滤(防跨租户误匹配)
KPIDefinition.kpi_code == kpi_code entity_id = None
).order_by(KPIDefinition.id.desc()).first() 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: if not kpi:
continue continue
# 缺陷3残留修复(对齐 verify.py):KPIValue 按 period <= 当前月过滤,跨月验证不取未来期间
period_limit = now.strftime("%Y-%m")
latest = db.query(KPIValue).filter( latest = db.query(KPIValue).filter(
KPIValue.kpi_id == kpi.id KPIValue.kpi_id == kpi.id,
).order_by(KPIValue.calculated_at.desc()).first() 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: if not latest or latest.actual_value is None:
continue continue
@@ -74,11 +85,14 @@ def run_auto_verify():
plan.kpi_current_after = actual plan.kpi_current_after = actual
if passed: 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.verify_status = "passed"
plan.verified_at = now plan.verified_at = now
plan.status = "done" plan.status = "completed" # 缺陷4残留修复:"done" 不在枚举(pending/in_progress/completed/cancelled),改 completed
plan.progress = 100
# 阶段3: OKR progress更新 # 阶段3: OKR progress更新
update_okr_progress(db, plan, +15) update_okr_progress(db, plan, +15, already_verified=already_verified)
messages.append(f"✅ 行动计划#{plan.id}验证通过: {plan.title} ({kpi_code}: {rule.get('baseline_value')}{actual})") messages.append(f"✅ 行动计划#{plan.id}验证通过: {plan.title} ({kpi_code}: {rule.get('baseline_value')}{actual})")
verified_count += 1 verified_count += 1
else: else:
@@ -107,16 +121,24 @@ def run_auto_verify():
db.close() db.close()
def update_okr_progress(db, plan, delta): def update_okr_progress(db, plan, delta, already_verified=False):
"""验证通过→更新所属OKR progress""" """验证通过→更新所属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) okr_id = getattr(plan, "okr_id", None) or getattr(plan, "objective_id", None)
if not okr_id: if not okr_id:
return return {"updated": False, "reason": "no_objective"}
obj = db.query(Objective).filter(Objective.id == okr_id).first() obj = db.query(Objective).filter(Objective.id == okr_id).first()
if obj: 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 current = obj.progress or 0
obj.progress = min(current + delta, 100) obj.progress = min(current + delta, 100)
db.add(obj) db.add(obj)
return {"updated": True, "objective_id": obj.id, "progress": obj.progress}
if __name__ == "__main__": if __name__ == "__main__":