diff --git a/pipeline_bidding/bid_flow.py b/pipeline_bidding/bid_flow.py index 705bb04..a9e9aaf 100644 --- a/pipeline_bidding/bid_flow.py +++ b/pipeline_bidding/bid_flow.py @@ -557,6 +557,13 @@ async def reconcile_project(sor, project_id, project_name=""): for t, _, _, _ in needs_qc: if await _open_qc_type_tasks(sor, project_id, t) > 0: continue + # 竞态守卫(2026-09-02):该维度分析/重做任务在办 → 不派审核。 + # 根治:产出被清空后重做任务还在跑,QC 审核就先派出去对着重写中 + # 的半成品打分(实测 cost_benefit 对空表打 0、qualifications 被连烧两轮)。 + dim = QC_TYPE_TO_DIM.get(t, "") + if dim and await _open_dim_tasks(sor, project_id, dim) > 0: + acts.append("waiting: 维度「%s」分析任务在办,暂不派 %s 审核" % (dim, t)) + continue tid = await create_role_task( sor, project_id, R_QC, "%s 解析产出契合度审核(%s)" % (pname, t), diff --git a/pipeline_bidding/bid_qc_capability.py b/pipeline_bidding/bid_qc_capability.py index f88adfd..c3844ee 100644 --- a/pipeline_bidding/bid_qc_capability.py +++ b/pipeline_bidding/bid_qc_capability.py @@ -119,6 +119,32 @@ async def start_qc(qc_type, project_id="", who=None, agent_id=None, task_id=""): "AND fit_score IS NULL ORDER BY created_at DESC LIMIT 1", {"p": project_id, "t": qc_type}) await sor.sqlExe("COMMIT", {}) + # 已通过守卫(2026-09-02):最新一轮已放行 → 不再开新轮。 + # 根治:任务被引擎通用门禁弹回重跑时再次 start_qc 会白白烧掉一轮 + # (qualifications 实测:第2轮8.80已过,弹回重跑开第3轮对重写中的表打5.6)。 + # 产出若被 QC 清空重做,对账器只在重做完成后才重新派审核,此处不会误挡。 + last_r = await sor.sqlExe( + "SELECT round, fit_score, passed FROM bid_qc_reviews WHERE project_id=${p}$ AND qc_type=${t}$ " + "ORDER BY round DESC LIMIT 1", {"p": project_id, "t": qc_type}) + await sor.sqlExe("COMMIT", {}) + if last_r and not open_r and str(rec_to_dict(last_r[0]).get("passed")) == "1": + _lr = rec_to_dict(last_r[0]) + return True, {"status": "already_passed", "qc_type": qc_type, + "round": _lr.get("round"), "fit_score": _lr.get("fit_score"), + "message": "该类型第 %s 轮已通过契合度审核(%.2f 分 > 通过分),无需重审。" + "直接完成任务即可。" % (_lr.get("round"), to_float(_lr.get("fit_score")))} + # 空产出守卫(2026-09-02):产出被 QC 清空、重做未完成 → 不开新轮。 + # 根治:QC 审核任务比分析重做任务先跑时对着空表开轮打 0 分,白白烧一轮 + # (cost_benefit 实测:第2轮对着零记录表打 0.00)。 + _cnt = await sor.sqlExe( + "SELECT COUNT(*) AS c FROM " + QC_OUTPUT_TABLE[qc_type] + " WHERE project_id=${p}$", + {"p": project_id}) + await sor.sqlExe("COMMIT", {}) + if not open_r and to_int(getattr(_cnt[0], "c", 0) if _cnt else 0) == 0: + return True, {"status": "no_output", "qc_type": qc_type, + "message": "%s 当前无产出记录(被 QC 清空待重做或重做未完成)," + "不开评审轮次。请等待分析重做任务落库后再审," + "本轮任务直接交付说明等待原因即可。" % qc_type} if open_r: rid = getattr(open_r[0], "id", "") rnd = to_int(getattr(open_r[0], "round", 1), 1)