fix(binding): 空绑定语义补齐+要求类型纠正通道(部署前逻辑走查自纠)——①显式空绑定[]不回退编号匹配但仍带全局规则(format/seal/copies),否则修复引入回归:绑定分支丢全局格式要求;②chapter_detail空绑定明示'无章节级要求,勿套用他章';③update_doc_requirement加new_req_type(与定位键req_type分开,否则改类型时按新值定位不到原条目),给全局规则误标structure的纠正通道;④check_requirement_binding区分'显式空绑定(已裁决结论)'与'未绑定(缺口)'

This commit is contained in:
yumoqing 2026-09-16 18:35:47 +08:00
parent f9cbfdc625
commit 9305e9550c
3 changed files with 53 additions and 11 deletions

View File

@ -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_type2026-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_typereq_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 非法: %sstructure/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)]

View File

@ -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"),

View File

@ -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": {