diff --git a/pipeline_bidding/bid_common.py b/pipeline_bidding/bid_common.py index 4cb3b79..f26f621 100644 --- a/pipeline_bidding/bid_common.py +++ b/pipeline_bidding/bid_common.py @@ -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 diff --git a/pipeline_bidding/bid_flow.py b/pipeline_bidding/bid_flow.py index 58c4bd8..4b37ffa 100644 --- a/pipeline_bidding/bid_flow.py +++ b/pipeline_bidding/bid_flow.py @@ -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 diff --git a/pipeline_bidding/bid_kb_capability.py b/pipeline_bidding/bid_kb_capability.py index 2be4117..c2be4ba 100644 --- a/pipeline_bidding/bid_kb_capability.py +++ b/pipeline_bidding/bid_kb_capability.py @@ -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}$ " diff --git a/pipeline_bidding/bid_qc_capability.py b/pipeline_bidding/bid_qc_capability.py index 031f86e..deb6201 100644 --- a/pipeline_bidding/bid_qc_capability.py +++ b/pipeline_bidding/bid_qc_capability.py @@ -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"]) diff --git a/pipeline_bidding/bid_review_capability.py b/pipeline_bidding/bid_review_capability.py index 2c596e7..5d30125 100644 --- a/pipeline_bidding/bid_review_capability.py +++ b/pipeline_bidding/bid_review_capability.py @@ -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"])) diff --git a/pipeline_bidding/bid_score_capability.py b/pipeline_bidding/bid_score_capability.py index f143b7d..100bfe4 100644 --- a/pipeline_bidding/bid_score_capability.py +++ b/pipeline_bidding/bid_score_capability.py @@ -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)) diff --git a/pipeline_bidding/bid_tech_capability.py b/pipeline_bidding/bid_tech_capability.py index ccbabe0..7821621 100644 --- a/pipeline_bidding/bid_tech_capability.py +++ b/pipeline_bidding/bid_tech_capability.py @@ -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", {}) diff --git a/pipeline_bidding/bid_tender_capability.py b/pipeline_bidding/bid_tender_capability.py index 886e285..5637588 100644 --- a/pipeline_bidding/bid_tender_capability.py +++ b/pipeline_bidding/bid_tender_capability.py @@ -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)