fix(human_task): 待办归属硬编码owner.superuser清零(2026-09-18银联事故)——bid_common wrapper默认改动态解析(项目真人创建者)+显式expired_at=+7d;bid_flow/qc/tech/tender/kb/score/review共16处调用点去掉硬编码,未显式指派即走resolve_project_owner_assignee

This commit is contained in:
yumoqing 2026-09-18 13:23:44 +08:00
parent 05e5c6bff2
commit 410e814ca1
8 changed files with 32 additions and 22 deletions

View File

@ -372,10 +372,14 @@ async def has_pending_blocking_human_task(sor, project_id):
async def create_human_task(sor, project_id, task_type, title, description="",
assignee_role="owner.superuser", assignee_id="",
assignee_role=None, assignee_id="",
form_schema=None, dedup_by_title=False, task_id=""):
"""创建人类任务pipeline_human_tasks。iteration_id 留空 → 走项目级门禁。
归属2026-09-18 银联事故修复assignee_role/assignee_id 都不传时按项目
真人创建者解析进其我的待办无主项目回退 owner.superuser
旧版默认硬编码 owner.superuser非超管机构创建者永远看不到自己项目的待办
同一事项只发一次待办2026-09-01 机制层不变量同项目+同类型已有
pending 任务 直接返回已有 id不重复创建防止对账器/agent 每轮
重复发同类待办把用户淹没
@ -420,6 +424,15 @@ async def create_human_task(sor, project_id, task_type, title, description="",
task_type, exist.get("id"), project_id)
return exist.get("id")
hid = new_id()
# 归属动态解析2026-09-18 银联事故修复):未显式指派 → 项目真人创建者,
# 无主项目回退 owner.superuser。
if not assignee_role and not assignee_id:
from pipeline_service.human_task_capability import resolve_project_owner_assignee
assignee_role, assignee_id = await resolve_project_owner_assignee(
sor, project_id or "")
# expired_at 显式写(同引擎 create_human_task 的地雷修复):列默认
# current_timestamp() → 不写则创建即过期。
from datetime import datetime as _dt, timedelta as _td
await sor.C("pipeline_human_tasks", {
"id": hid,
"task_id": task_id or "",
@ -433,6 +446,7 @@ async def create_human_task(sor, project_id, task_type, title, description="",
"project_id": project_id or "",
"title": (title or "")[:480],
"description": description or "",
"expired_at": _dt.now() + _td(days=7),
})
logger.info("bid create_human_task type=%s id=%s project=%s", task_type, hid, project_id)
return hid

View File

@ -612,7 +612,7 @@ async def _auto_detect_flow(sor, project_id, pname):
("系统无法自动判断该文件应走哪条流程:%s\n\n"
"请在会话里用 propose_flow_plan(flow_key=...) 指定流程,"
"或联系管理员核对文件内容。" % note),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
return ""
ok, msg = await propose_flow_plan(
@ -651,7 +651,7 @@ async def reconcile_project(sor, project_id, project_name=""):
("以下角色任务执行失败,对账器已暂停自动派发:\n" + tlist +
"\n\n常见原因:机构未配置 LLM 模型(项目缺省模型/角色模型未设置)、"
"模型调用超时、任务内容异常。\n请处理后用 reset_task_retry 恢复,或取消任务。"),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
return ["blocked: %d 个任务执行失败,已抛人工介入,暂停派发" % n_failed]
@ -802,7 +802,7 @@ async def reconcile_project(sor, project_id, project_name=""):
"请人工核对招标文件并修正产出物(对应管理页),"
"或在 QC 审核记录页把对应记录 passed 改为 1 强制放行。"
% (n_redo_done, "\n".join(qc_imp)[:3000])),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
acts.append("escalated: 维度「%s」QC 重做轮次用尽 → 人工介入" % label)
continue
@ -837,7 +837,7 @@ async def reconcile_project(sor, project_id, project_name=""):
("该维度分析任务已完成 %d 次,产出仍为空。\n"
"请人工检查招标文件是否可读bid_tender_files.content_text"
"或人工补录该维度产出后完成本任务。" % first_attempts),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
acts.append("escalated: 维度「%s」重试超限 → 人工介入" % label)
continue
@ -888,7 +888,7 @@ async def reconcile_project(sor, project_id, project_name=""):
"参数 task_kind=analysis_orchestration"
"或人工派发缺失维度。"
% (orch_done, "".join(lb for _, lb in first_run_pending))),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
acts.append("escalated: PM 解析编排掉链 → 人工介入")
else:
@ -943,7 +943,7 @@ async def reconcile_project(sor, project_id, project_name=""):
% (th["qc_max_round"],
"\n".join("- %s(第%s轮):%s" % (t, rnd, (imp or "")[:300])
for t, s, imp, rnd, _on in over))),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
acts.append("escalated: QC 审核轮次用尽(%s),等人工介入;不依赖它的章节照常流转"
% "/".join(sorted(over_types)))
@ -969,7 +969,7 @@ async def reconcile_project(sor, project_id, project_name=""):
("QC 退回后该维度已重做 %d 轮,产出仍未通过契合度审核。\n"
"请人工核对招标文件并修正产出物,或在 QC 审核记录页强制放行。"
% n_redo_done),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
continue
qc_imp = await _dim_qc_improvements(sor, project_id, dim)
@ -1230,7 +1230,7 @@ async def reconcile_project(sor, project_id, project_name=""):
to_int(c.get("revise_count"), 0), _cm[:1500]))
await create_human_task(
sor, project_id, _tt, _ti, _desc,
assignee_role="owner.superuser", dedup_by_title=True)
dedup_by_title=True)
await sor.sqlExe("COMMIT", {})
acts.append("escalated: 章[%s] 超重做上限且无待办记录,补发人工介入待办"
% c.get("chapter_no"))
@ -1386,7 +1386,7 @@ async def reconcile_project(sor, project_id, project_name=""):
"请处理:在对应管理页补录落库,或重跑该任务;落库后流程自动复审。"
"处理完完成本任务。"
% "\n".join("- %s %s" % (tid, ti) for tid, ti in verify_bad)),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
acts.append("escalated: 产出落库核验不通过(%d 个任务标 verify_failed抛人工" % len(verify_bad))
else:
@ -1560,7 +1560,7 @@ async def _reconcile_tech_analysis_qc(sor, project_id, pname, n_items, latest_qc
"请检查招标文件技术部分是否可读bid_tender_files.content_text"
"或用 list_tech_items/add_tech_item 人工补录评估项。"
% (n_first + n_redo, n_first, n_redo)),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
acts.append("escalated: 技术评估项解析轮次超限 → 人工介入")
return False, False, True
@ -1610,7 +1610,7 @@ async def _reconcile_tech_analysis_qc(sor, project_id, pname, n_items, latest_qc
"请人工核对招标文件技术需求修正评估项list_tech_items/add_tech_item"
"或在 QC 审核记录页把该记录 passed 改为 1 强制放行。"
% (rnd, (latest_qc.get("improvement") or "")[:1500])),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
acts.append("escalated: 技术评估项 QC 轮次用尽 → 人工介入")
return False, True, True

View File

@ -222,7 +222,6 @@ async def request_human_docs(project_id, items="", note="", who=None, agent_id=N
hid = await create_human_task(
sor, project_id, HT_HUMAN_DOCS,
"需人工提供的投标资质文件(%d 项)" % len(lines), desc,
assignee_role="owner.superuser",
form_schema={"project_id": project_id, "count": len(lines)})
await sor.sqlExe(
"UPDATE bid_qualifications SET human_task_id=${hid}$ WHERE project_id=${pid}$ "

View File

@ -343,7 +343,7 @@ async def finish_qc(review_id, fit_score, improvement="", comments="",
"处理完成本任务后流程自动恢复。"
% (qc_type, review.get("round"), sc, th["qc_pass_score"],
(improvement or "")[:3000])),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
return True, ("QC 不通过且已达审核轮次上限 %d → 已抛人工介入(阻塞门禁)。"
% th["qc_max_round"])

View File

@ -146,7 +146,7 @@ async def review_chapter(chapter_id, score, max_score="", comments="", detail=""
"如需补材料(证书扫描件/截图/业绩证明),上传到项目工作空间/知识库后"
"可在增补轮重写该章;不补则完成本待办即可。"
% (c.get("chapter_no"), c.get("title"), sc, ms, (comments or "")[:1500])),
assignee_role="owner.superuser", dedup_by_title=True)
dedup_by_title=True)
await sor.sqlExe("COMMIT", {})
await _sync_mirror(sor, pid, chapter_id)
return True, ("章节「%s」评审 %.2f/%.2f:失分全部因实体材料缺失(非写作缺陷),"
@ -198,7 +198,7 @@ async def review_chapter(chapter_id, score, max_score="", comments="", detail=""
"请人工判断:补充素材/调整章节范围/降低目标分,处理完成后完成本任务。"
% (c.get("chapter_no"), c.get("title"), revise, sc, ms,
th["chapter_pass_ratio"] * 100, (comments or "")[:1500])),
assignee_role="owner.superuser", dedup_by_title=True)
dedup_by_title=True)
await sor.sqlExe("COMMIT", {})
return True, ("章节「%s」评审不通过 %.2f/%.2f,已达重做上限 %d 次 → 已抛人工介入任务"
% (c.get("title"), sc, ms, th["max_revise"]))

View File

@ -242,7 +242,7 @@ async def finalize_bid_score(review_id, comments="", who=None, agent_id=None):
("标书评分已达通过门限。\n%s\n\n"
"请人工复核并确认交付(用印/密封/份数/递交方式按招标文件要求执行)。"
% summary[:3000]),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
return True, "整书评分通过:%s\n已发人工交付确认任务。" % summary
@ -254,7 +254,7 @@ async def finalize_bid_score(review_id, comments="", who=None, agent_id=None):
("整书评分已进行 %s 轮仍未达门限:\n%s\n\n"
"请人工决策:调整评分预期/补充关键素材/重构章节,处理完成后完成本任务。"
% (review.get("round"), summary[:3000])),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
return True, ("整书评分不通过且已达轮次上限 %d → 已抛人工介入。\n%s"
% (th["max_round"], summary))
@ -297,7 +297,7 @@ async def finalize_bid_score(review_id, comments="", who=None, agent_id=None):
sor, pid, "general", "整书评分不通过但改进意见未映射到章节",
("以下评分项不达标,但 target_chapter_id 为空,系统无法自动定位待改章节:\n%s\n\n"
"请人工指定应修改的章节(或补 target_chapter_no 后重新评分)。" % summary[:3000]),
assignee_role="owner.superuser")
)
await sor.sqlExe("COMMIT", {})
return True, ("整书评分不通过:%s\n已退回 %d 个问题章节重写,系统将自动派发重写任务。"
% (summary, touched))

View File

@ -403,7 +403,6 @@ async def propose_tech_template(project_id, chapters="", template_source="",
sor, project_id, HT_TEMPLATE_CONFIRM,
"技术方案书章节骨架确认:%s" % (proj.get("name") or project_id),
"\n".join(md),
assignee_role="owner.superuser",
form_schema={"chapters": norm, "template_source": template_source or "",
"template_ref": (template_ref or "")[:2000]})
await sor.sqlExe("COMMIT", {})

View File

@ -74,7 +74,6 @@ async def create_bid_project(name, description="", org_id="", created_by="",
sor, pid, HT_TENDER_FILE,
"上传招标文件:%s" % pname,
"立项时已随附招标文件,跳过人工上传步骤。",
assignee_role="owner.superuser",
form_schema={"project_id": pid})
await sor.sqlExe(
"UPDATE pipeline_human_tasks SET status='done', qc_status='passed', "
@ -91,7 +90,6 @@ async def create_bid_project(name, description="", org_id="", created_by="",
"上传招标文件:%s" % pname,
("项目已创建,请上传招标文件(含答疑/补遗文件)。\n"
"上传后系统自动进入招标文件解析(抽取评分项、得分规则、所需资质、投标文件要求、章节骨架)。"),
assignee_role="owner.superuser",
form_schema={"project_id": pid})
return True, "project_id=%s(已发上传招标文件任务 %s" % (pid, hid)