fix(qc-race): 末轮QC在办期间误判轮次用尽+纯技术方案分支缺升级待办自动关闭(2026-09-18银联实锤)——①共用段/标准A2:在办守卫先于轮次用尽判定(start_qc开轮即建passed=0记录,评分完成才UPDATE;旧顺序在末轮审核在办时误发qc_escalation待办,银联16:21:40误发实录,16:24:20第3轮8.60分通过);A2过滤同加_open_qc_type_tasks在办豁免;②纯技术方案分支T2b:补qc_escalation待办自动关闭段(标准流程有A2b,tech_proposal分支reconcile提前return不经过→误发/过期升级待办永久挂角标)

This commit is contained in:
yumoqing 2026-09-18 17:17:22 +08:00
parent 09caa11802
commit cdc87f6a6d

View File

@ -913,6 +913,12 @@ async def reconcile_project(sor, project_id, project_name=""):
if qc_pending:
_kept = []
for _qp in qc_pending:
# 审核任务在办 → 最新一轮记录尚未落定start_qc 开轮即建 passed=0 记录,
# 评分在完成时才 UPDATE不得参与「轮次用尽」判定或派发一律等待
# 2026-09-18 银联末轮误报修复的标准流程同款守卫)。
if await _open_qc_type_tasks(sor, project_id, _qp[0]) > 0:
acts.append("waiting: 类型「%s」审核任务在办,最新一轮尚未落定" % _qp[0])
continue
_d = QC_TYPE_TO_DIM.get(_qp[0], "")
if _d and await _open_dim_tasks(sor, project_id, _d) > 0:
acts.append("waiting: 维度「%s」分析任务在办,%s 暂缓 QC 审核(防审半成品)"
@ -1598,6 +1604,19 @@ async def _reconcile_tech_analysis_qc(sor, project_id, pname, n_items, latest_qc
if latest_qc and str(latest_qc.get("passed")) == "1":
return True, False, False
# 在办守卫必须先于「轮次用尽」判定2026-09-18 银联末轮误报修复):
# start_qc 在末轮审核开轮时即建记录passed=0、未评分此刻任务仍在办
# 旧顺序会先判 rnd>=qc_max_round 命中「轮次用尽」误发 qc_escalation 待办,
# 而实际末轮审核正在跑、随时可能评分通过。QC 任务在办 → 最新轮尚未落定,一律等待。
if await _open_tech_tasks(sor, project_id, "bid_qc") > 0:
acts.append("waiting: 技术评估项 QC 任务在办")
return False, False, True
# 竞态守卫:抽取/重做任务在办 → 不派审核(防对半成品打分),
# 同样不得判「轮次用尽」(重做正在跑,不是最终失败)。
if await _open_tech_tasks(sor, project_id, "bid_tech_analysis") > 0:
acts.append("waiting: 技术评估项抽取任务在办,暂不派 tech QC")
return False, False, True
rnd = to_int(latest_qc.get("round"), 0) if latest_qc else 0
if latest_qc and rnd >= th["qc_max_round"]:
# 轮次用尽:产出未更新则不空转,抛人工(同标准流程逃逸阀)
@ -1614,13 +1633,6 @@ async def _reconcile_tech_analysis_qc(sor, project_id, pname, n_items, latest_qc
await sor.sqlExe("COMMIT", {})
acts.append("escalated: 技术评估项 QC 轮次用尽 → 人工介入")
return False, True, True
if await _open_tech_tasks(sor, project_id, "bid_qc") > 0:
acts.append("waiting: 技术评估项 QC 任务在办")
return False, False, True
# 竞态守卫:抽取/重做任务在办 → 不派审核(防对半成品打分)
if await _open_tech_tasks(sor, project_id, "bid_tech_analysis") > 0:
acts.append("waiting: 技术评估项抽取任务在办,暂不派 tech QC")
return False, False, True
tid = await create_role_task(
sor, project_id, R_QC,
"%s 技术评估项契合度审核(对标招标技术需求原文)" % pname,
@ -1686,6 +1698,22 @@ async def _reconcile_tech_proposal(sor, project_id, pname, stage_table):
qc_passed, _tech_over, _blocked = await _reconcile_tech_analysis_qc(
sor, project_id, pname, n_items, latest_qc, th, _on,
FLOW_KEY_TECH_PROPOSAL, acts)
# ── T2b. qc_escalation 待办自动关闭2026-09-18 银联实锤缺口)──
# 标准流程有 A2b 段,纯技术方案分支在 reconcile_project 提前 return 不经过它 →
# tech_items 轮次用尽发的升级待办在问题解决后QC 通过/人工放行/重做恢复)
# 永远挂着,角标常驻误导。逃逸阀闭环的另一半:问题解决 → 待办自动收。
if not _tech_over:
_ht_esc = await find_human_task(sor, project_id, HT_QC_ESCALATION, status="pending")
if _ht_esc:
await sor.sqlExe(
"UPDATE pipeline_human_tasks SET status='done', qc_status='passed', "
"qc_comment='对账器自动关闭:技术评估项 QC 已通过/放行或流程已恢复', "
"submitted_at=NOW() WHERE id=${h}$ AND status='pending'",
{"h": _ht_esc.get("id")})
await sor.sqlExe("COMMIT", {})
acts.append("auto-closed: qc_escalation 待办tech_items 已通过/流程已恢复)")
if not qc_passed:
return acts or ["idle: 技术需求解析/审核中"]
if _blocked: