From 9305e9550c2d16b5997a4fd3908adff969433284 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 16 Sep 2026 18:35:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(binding):=20=E7=A9=BA=E7=BB=91=E5=AE=9A?= =?UTF-8?q?=E8=AF=AD=E4=B9=89=E8=A1=A5=E9=BD=90+=E8=A6=81=E6=B1=82?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=BA=A0=E6=AD=A3=E9=80=9A=E9=81=93(?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E5=89=8D=E9=80=BB=E8=BE=91=E8=B5=B0=E6=9F=A5?= =?UTF-8?q?=E8=87=AA=E7=BA=A0)=E2=80=94=E2=80=94=E2=91=A0=E6=98=BE?= =?UTF-8?q?=E5=BC=8F=E7=A9=BA=E7=BB=91=E5=AE=9A[]=E4=B8=8D=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E7=BC=96=E5=8F=B7=E5=8C=B9=E9=85=8D=E4=BD=86=E4=BB=8D?= =?UTF-8?q?=E5=B8=A6=E5=85=A8=E5=B1=80=E8=A7=84=E5=88=99(format/seal/copie?= =?UTF-8?q?s),=E5=90=A6=E5=88=99=E4=BF=AE=E5=A4=8D=E5=BC=95=E5=85=A5?= =?UTF-8?q?=E5=9B=9E=E5=BD=92:=E7=BB=91=E5=AE=9A=E5=88=86=E6=94=AF?= =?UTF-8?q?=E4=B8=A2=E5=85=A8=E5=B1=80=E6=A0=BC=E5=BC=8F=E8=A6=81=E6=B1=82?= =?UTF-8?q?;=E2=91=A1chapter=5Fdetail=E7=A9=BA=E7=BB=91=E5=AE=9A=E6=98=8E?= =?UTF-8?q?=E7=A4=BA'=E6=97=A0=E7=AB=A0=E8=8A=82=E7=BA=A7=E8=A6=81?= =?UTF-8?q?=E6=B1=82,=E5=8B=BF=E5=A5=97=E7=94=A8=E4=BB=96=E7=AB=A0';?= =?UTF-8?q?=E2=91=A2update=5Fdoc=5Frequirement=E5=8A=A0new=5Freq=5Ftype(?= =?UTF-8?q?=E4=B8=8E=E5=AE=9A=E4=BD=8D=E9=94=AEreq=5Ftype=E5=88=86?= =?UTF-8?q?=E5=BC=80,=E5=90=A6=E5=88=99=E6=94=B9=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=97=B6=E6=8C=89=E6=96=B0=E5=80=BC=E5=AE=9A=E4=BD=8D=E4=B8=8D?= =?UTF-8?q?=E5=88=B0=E5=8E=9F=E6=9D=A1=E7=9B=AE),=E7=BB=99=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E8=A7=84=E5=88=99=E8=AF=AF=E6=A0=87structure=E7=9A=84?= =?UTF-8?q?=E7=BA=A0=E6=AD=A3=E9=80=9A=E9=81=93;=E2=91=A3check=5Frequireme?= =?UTF-8?q?nt=5Fbinding=E5=8C=BA=E5=88=86'=E6=98=BE=E5=BC=8F=E7=A9=BA?= =?UTF-8?q?=E7=BB=91=E5=AE=9A(=E5=B7=B2=E8=A3=81=E5=86=B3=E7=BB=93?= =?UTF-8?q?=E8=AE=BA)'=E4=B8=8E'=E6=9C=AA=E7=BB=91=E5=AE=9A(=E7=BC=BA?= =?UTF-8?q?=E5=8F=A3)'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_bidding/bid_analysis_capability.py | 31 +++++++++++++++++---- pipeline_bidding/bid_write_capability.py | 26 +++++++++++++++-- pipeline_bidding/role_tool_schemas.py | 7 +++-- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/pipeline_bidding/bid_analysis_capability.py b/pipeline_bidding/bid_analysis_capability.py index 8edddf9..13b60c9 100644 --- a/pipeline_bidding/bid_analysis_capability.py +++ b/pipeline_bidding/bid_analysis_capability.py @@ -462,13 +462,17 @@ async def add_doc_requirement(project_id, requirement, req_type="structure", async def update_doc_requirement(project_id, record_id="", chapter_no="", req_type="", requirement="", source_ref="", page_limit="", - chapter_title="", delete=False, who=None, agent_id=None): + chapter_title="", delete=False, new_req_type="", + who=None, agent_id=None): """更新/删除既有投标文件要求条目(2026-09-03,根治 add 只增不改)。 定位:record_id 优先;否则 chapter_no+req_type 唯一定位。 - delete=True → 删除该条(编造/无出处项的处置); - requirement/source_ref/page_limit/chapter_title 非空 → 回写原条目 - (纠正必须回写,不得只追加新条目形成矛盾对)。 + (纠正必须回写,不得只追加新条目形成矛盾对); + - new_req_type(2026-09-16)→ 纠正条目类型:全局规则误标 structure 会占用 + 章节编号匹配位(chapter_detail 按编号回退时递错要求),应改 other/format。 + 与定位键 req_type 分开传,否则改类型时按新值定位不到原条目。 """ try: project_id = await resolve_project_id(project_id) @@ -510,8 +514,17 @@ async def update_doc_requirement(project_id, record_id="", chapter_no="", req_ty sets.append("source_ref=${sr}$"); p["sr"] = str(source_ref)[:480] if page_limit: sets.append("page_limit=${pl}$"); p["pl"] = to_int(page_limit, 0) or None + # req_type 纠正走独立参数 new_req_type(req_type 是定位键:按 chapter_no+req_type + # 定位时传新值会定位不到——2026-09-16)。全局规则条目误标 structure 会占用章节 + # 编号匹配位,导致写者按编号拿到错的要求(如"总体构成与编制顺序"应改 other)。 + if new_req_type: + rt = str(new_req_type).strip()[:30] + if rt not in ("structure", "format", "seal", "copies", "deadline", "other"): + return False, ("new_req_type 非法: %s(structure/format/seal/copies/" + "deadline/other)" % rt) + sets.append("req_type=${nrt}$"); p["nrt"] = rt if not sets: - return False, "无可更新字段(requirement/source_ref/page_limit 至少传一个)" + return False, "无可更新字段(requirement/source_ref/page_limit/req_type 至少传一个)" sets.append("updated_at=NOW()") await sor.sqlExe("UPDATE bid_doc_requirements SET " + ", ".join(sets) + " WHERE id=${r}$", p) @@ -551,9 +564,10 @@ async def check_requirement_binding(project_id): rq_by_no.setdefault(r["chapter_no"], r) bound_rq = set() lines = ["## 章节↔编写要求绑定体检(%d 章 / %d 条要求)" % (len(chapters), len(reqs)), ""] - suspicious, unbound_ch, mismatched = [], [], [] + suspicious, unbound_ch, mismatched, empty_bound = [], [], [], [] for c in chapters: - ids = json_loads(c.get("doc_requirement_ids"), []) + raw = c.get("doc_requirement_ids") + ids = json_loads(raw, []) cno = str(c.get("chapter_no") or "") ctitle = str(c.get("title") or "") if ids: @@ -562,6 +576,10 @@ async def check_requirement_binding(project_id): if bad: suspicious.append("%s %s: 绑定含失效id %s" % (cno, ctitle[:18], bad[:2])) continue + if raw is not None: + # 显式空绑定:已语义裁决"本章无章节级要求",是结论不是缺口 + empty_bound.append("%s %s" % (cno, ctitle[:18])) + continue fb = rq_by_no.get(cno) if fb: rt = str(fb.get("chapter_title") or "") @@ -582,6 +600,9 @@ async def check_requirement_binding(project_id): if unbound_ch: lines += ["### 无同号要求的章节(%d)——核对是否漏抽要求或确属无单独要求" % len(unbound_ch), *["- " + m for m in unbound_ch], ""] + if empty_bound: + lines += ["### 显式空绑定(%d)——已语义裁决本章无章节级要求(如招标书明文无需提供)" + % len(empty_bound), *[("- " + m) for m in empty_bound], ""] if orphan: lines += ["### 未被任何章节引用的 structure 要求(%d)——可能是全局规则误标 structure," "应 update_doc_requirement 改 req_type=other/format,或补绑定" % len(orphan)] diff --git a/pipeline_bidding/bid_write_capability.py b/pipeline_bidding/bid_write_capability.py index b6bb89a..209ebc9 100644 --- a/pipeline_bidding/bid_write_capability.py +++ b/pipeline_bidding/bid_write_capability.py @@ -64,16 +64,32 @@ async def chapter_detail(chapter_id, with_content="1", length=12000): # 未绑定时按编号回退,但用标题互含做词匹配触发器:不互含→输出⚠可疑警告 # (词匹配只报可疑,语义裁决权在 agent;判错用 list_doc_requirements + # patch_chapters(doc_requirements_by_no) 落绑定后再写)。 - rq_ids = json_loads(d.get("doc_requirement_ids"), []) + rq_raw = d.get("doc_requirement_ids") + rq_ids = json_loads(rq_raw, []) rq_bound = False + if rq_raw is not None: + # 显式绑定(含裁定为空绑定 "[]":语义裁决后确认本章无对应要求, + # 禁止编号回退再递错——2026-09-16 甘肃 1.9 实测:招标书明文 + # "供应商无需提供信用记录查询结果",同号要求是别的章节的) + rq_bound = True if rq_ids: inlist = ",".join("'" + str(x).replace("'", "") + "'" for x in rq_ids) rrecs = await sor.sqlExe( "SELECT req_type, chapter_no, chapter_title, requirement, page_limit, " - "source_ref FROM bid_doc_requirements WHERE id IN (" + inlist + ")", {}) + "source_ref FROM bid_doc_requirements WHERE id IN (" + inlist + ") " + "OR (project_id=${pid}$ AND req_type IN ('format','seal','copies')) " + "ORDER BY order_no", {"pid": d.get("project_id")}) + await sor.sqlExe("COMMIT", {}) + reqs = rows_to_dicts(rrecs) + elif rq_bound: + # 显式空绑定:无章节级要求,但全局规则(format/seal/copies)仍适用 + rrecs = await sor.sqlExe( + "SELECT req_type, chapter_no, chapter_title, requirement, page_limit, " + "source_ref FROM bid_doc_requirements WHERE project_id=${pid}$ " + "AND req_type IN ('format','seal','copies') ORDER BY order_no", + {"pid": d.get("project_id")}) await sor.sqlExe("COMMIT", {}) reqs = rows_to_dicts(rrecs) - rq_bound = True else: rrecs = await sor.sqlExe( "SELECT req_type, chapter_no, chapter_title, requirement, page_limit, " @@ -84,6 +100,7 @@ async def chapter_detail(chapter_id, with_content="1", length=12000): await sor.sqlExe("COMMIT", {}) reqs = rows_to_dicts(rrecs) rq_warn = "" + rq_empty = rq_bound and not rq_ids # 显式空绑定(已裁定本章无章节级要求) if not rq_bound: ctitle = (d.get("title") or "").strip() mismatched = [] @@ -100,6 +117,9 @@ async def chapter_detail(chapter_id, with_content="1", length=12000): % ";".join(mismatched[:3])) elif reqs: rq_warn = "(本章要求为编号匹配、未落显式绑定;标题核对一致,可用)" + elif rq_empty: + rq_warn = ("(本章经裁决为空绑定:无章节级编写要求,仅列全局规则——按章节 outline " + "与招标原文相关条款编写,勿套用其他章节的要求)") out = { "id": d.get("id"), "chapter_no": d.get("chapter_no"), "title": d.get("title"), "section": d.get("section"), "status": d.get("status"), "version": d.get("version"), diff --git a/pipeline_bidding/role_tool_schemas.py b/pipeline_bidding/role_tool_schemas.py index fbc3398..0b1c8da 100644 --- a/pipeline_bidding/role_tool_schemas.py +++ b/pipeline_bidding/role_tool_schemas.py @@ -134,11 +134,12 @@ BID_ROLE_TOOL_SCHEMAS = { }, "update_doc_requirement": { "module": f"{_M}.bid_analysis_capability", - "description": "更新/删除既有投标文件要求条目(纠正必须回写原条目,不得只追加新条目形成矛盾对;编造/无出处项用 delete 删除)。add_doc_requirement 只增不改", + "description": "更新/删除既有投标文件要求条目(纠正必须回写原条目,不得只追加新条目形成矛盾对;编造/无出处项用 delete 删除)。add_doc_requirement 只增不改。new_req_type 纠正条目类型:全局规则误标 structure 会占用章节编号匹配位(写者按编号拿到错的要求),应改 other/format", "params": {"project_id": "项目ID(可选,默认当前)", "record_id": "条目ID(优先)", - "chapter_no": "章节号(与req_type组合定位)", "req_type": "类型(structure/format/seal/copies/deadline/other)", + "chapter_no": "章节号(与req_type组合定位)", "req_type": "定位用类型(structure/format/seal/copies/deadline/other)", "requirement": "回写的新正文(可选)", "source_ref": "回写原文出处(可选)", - "page_limit": "页数(可选)", "delete": "true=删除该条(可选)"}, + "page_limit": "页数(可选)", "delete": "true=删除该条(可选)", + "new_req_type": "纠正条目类型(可选,与定位键req_type分开传)"}, "required": [], }, "list_scoring_items": {