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:
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -52,14 +52,25 @@ def run_auto_verify():
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# 查最新KPI值
|
# 查最新KPI值
|
||||||
kpi = db.query(KPIDefinition).filter(
|
# 缺陷2残留修复(对齐 verify.py):从 plan 关联 KPI 向上取 entity_id,kpi_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=True(plan 已处于验证通过状态且 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:
|
||||||
current = obj.progress or 0
|
return {"updated": False, "reason": "objective_not_found"}
|
||||||
obj.progress = min(current + delta, 100)
|
if already_verified:
|
||||||
db.add(obj)
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user