fix(binding): 空绑定语义补齐+要求类型纠正通道(部署前逻辑走查自纠)——①显式空绑定[]不回退编号匹配但仍带全局规则(format/seal/copies),否则修复引入回归:绑定分支丢全局格式要求;②chapter_detail空绑定明示'无章节级要求,勿套用他章';③update_doc_requirement加new_req_type(与定位键req_type分开,否则改类型时按新值定位不到原条目),给全局规则误标structure的纠正通道;④check_requirement_binding区分'显式空绑定(已裁决结论)'与'未绑定(缺口)'
This commit is contained in:
parent
f9cbfdc625
commit
9305e9550c
@ -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="",
|
async def update_doc_requirement(project_id, record_id="", chapter_no="", req_type="",
|
||||||
requirement="", source_ref="", page_limit="",
|
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 只增不改)。
|
"""更新/删除既有投标文件要求条目(2026-09-03,根治 add 只增不改)。
|
||||||
|
|
||||||
定位:record_id 优先;否则 chapter_no+req_type 唯一定位。
|
定位:record_id 优先;否则 chapter_no+req_type 唯一定位。
|
||||||
- delete=True → 删除该条(编造/无出处项的处置);
|
- delete=True → 删除该条(编造/无出处项的处置);
|
||||||
- requirement/source_ref/page_limit/chapter_title 非空 → 回写原条目
|
- requirement/source_ref/page_limit/chapter_title 非空 → 回写原条目
|
||||||
(纠正必须回写,不得只追加新条目形成矛盾对)。
|
(纠正必须回写,不得只追加新条目形成矛盾对);
|
||||||
|
- new_req_type(2026-09-16)→ 纠正条目类型:全局规则误标 structure 会占用
|
||||||
|
章节编号匹配位(chapter_detail 按编号回退时递错要求),应改 other/format。
|
||||||
|
与定位键 req_type 分开传,否则改类型时按新值定位不到原条目。
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
project_id = await resolve_project_id(project_id)
|
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]
|
sets.append("source_ref=${sr}$"); p["sr"] = str(source_ref)[:480]
|
||||||
if page_limit:
|
if page_limit:
|
||||||
sets.append("page_limit=${pl}$"); p["pl"] = to_int(page_limit, 0) or None
|
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:
|
if not sets:
|
||||||
return False, "无可更新字段(requirement/source_ref/page_limit 至少传一个)"
|
return False, "无可更新字段(requirement/source_ref/page_limit/req_type 至少传一个)"
|
||||||
sets.append("updated_at=NOW()")
|
sets.append("updated_at=NOW()")
|
||||||
await sor.sqlExe("UPDATE bid_doc_requirements SET " + ", ".join(sets) +
|
await sor.sqlExe("UPDATE bid_doc_requirements SET " + ", ".join(sets) +
|
||||||
" WHERE id=${r}$", p)
|
" WHERE id=${r}$", p)
|
||||||
@ -551,9 +564,10 @@ async def check_requirement_binding(project_id):
|
|||||||
rq_by_no.setdefault(r["chapter_no"], r)
|
rq_by_no.setdefault(r["chapter_no"], r)
|
||||||
bound_rq = set()
|
bound_rq = set()
|
||||||
lines = ["## 章节↔编写要求绑定体检(%d 章 / %d 条要求)" % (len(chapters), len(reqs)), ""]
|
lines = ["## 章节↔编写要求绑定体检(%d 章 / %d 条要求)" % (len(chapters), len(reqs)), ""]
|
||||||
suspicious, unbound_ch, mismatched = [], [], []
|
suspicious, unbound_ch, mismatched, empty_bound = [], [], [], []
|
||||||
for c in chapters:
|
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 "")
|
cno = str(c.get("chapter_no") or "")
|
||||||
ctitle = str(c.get("title") or "")
|
ctitle = str(c.get("title") or "")
|
||||||
if ids:
|
if ids:
|
||||||
@ -562,6 +576,10 @@ async def check_requirement_binding(project_id):
|
|||||||
if bad:
|
if bad:
|
||||||
suspicious.append("%s %s: 绑定含失效id %s" % (cno, ctitle[:18], bad[:2]))
|
suspicious.append("%s %s: 绑定含失效id %s" % (cno, ctitle[:18], bad[:2]))
|
||||||
continue
|
continue
|
||||||
|
if raw is not None:
|
||||||
|
# 显式空绑定:已语义裁决"本章无章节级要求",是结论不是缺口
|
||||||
|
empty_bound.append("%s %s" % (cno, ctitle[:18]))
|
||||||
|
continue
|
||||||
fb = rq_by_no.get(cno)
|
fb = rq_by_no.get(cno)
|
||||||
if fb:
|
if fb:
|
||||||
rt = str(fb.get("chapter_title") or "")
|
rt = str(fb.get("chapter_title") or "")
|
||||||
@ -582,6 +600,9 @@ async def check_requirement_binding(project_id):
|
|||||||
if unbound_ch:
|
if unbound_ch:
|
||||||
lines += ["### 无同号要求的章节(%d)——核对是否漏抽要求或确属无单独要求"
|
lines += ["### 无同号要求的章节(%d)——核对是否漏抽要求或确属无单独要求"
|
||||||
% len(unbound_ch), *["- " + m for m in unbound_ch], ""]
|
% 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:
|
if orphan:
|
||||||
lines += ["### 未被任何章节引用的 structure 要求(%d)——可能是全局规则误标 structure,"
|
lines += ["### 未被任何章节引用的 structure 要求(%d)——可能是全局规则误标 structure,"
|
||||||
"应 update_doc_requirement 改 req_type=other/format,或补绑定" % len(orphan)]
|
"应 update_doc_requirement 改 req_type=other/format,或补绑定" % len(orphan)]
|
||||||
|
|||||||
@ -64,16 +64,32 @@ async def chapter_detail(chapter_id, with_content="1", length=12000):
|
|||||||
# 未绑定时按编号回退,但用标题互含做词匹配触发器:不互含→输出⚠可疑警告
|
# 未绑定时按编号回退,但用标题互含做词匹配触发器:不互含→输出⚠可疑警告
|
||||||
# (词匹配只报可疑,语义裁决权在 agent;判错用 list_doc_requirements +
|
# (词匹配只报可疑,语义裁决权在 agent;判错用 list_doc_requirements +
|
||||||
# patch_chapters(doc_requirements_by_no) 落绑定后再写)。
|
# 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
|
rq_bound = False
|
||||||
|
if rq_raw is not None:
|
||||||
|
# 显式绑定(含裁定为空绑定 "[]":语义裁决后确认本章无对应要求,
|
||||||
|
# 禁止编号回退再递错——2026-09-16 甘肃 1.9 实测:招标书明文
|
||||||
|
# "供应商无需提供信用记录查询结果",同号要求是别的章节的)
|
||||||
|
rq_bound = True
|
||||||
if rq_ids:
|
if rq_ids:
|
||||||
inlist = ",".join("'" + str(x).replace("'", "") + "'" for x in rq_ids)
|
inlist = ",".join("'" + str(x).replace("'", "") + "'" for x in rq_ids)
|
||||||
rrecs = await sor.sqlExe(
|
rrecs = await sor.sqlExe(
|
||||||
"SELECT req_type, chapter_no, chapter_title, requirement, page_limit, "
|
"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", {})
|
await sor.sqlExe("COMMIT", {})
|
||||||
reqs = rows_to_dicts(rrecs)
|
reqs = rows_to_dicts(rrecs)
|
||||||
rq_bound = True
|
|
||||||
else:
|
else:
|
||||||
rrecs = await sor.sqlExe(
|
rrecs = await sor.sqlExe(
|
||||||
"SELECT req_type, chapter_no, chapter_title, requirement, page_limit, "
|
"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", {})
|
await sor.sqlExe("COMMIT", {})
|
||||||
reqs = rows_to_dicts(rrecs)
|
reqs = rows_to_dicts(rrecs)
|
||||||
rq_warn = ""
|
rq_warn = ""
|
||||||
|
rq_empty = rq_bound and not rq_ids # 显式空绑定(已裁定本章无章节级要求)
|
||||||
if not rq_bound:
|
if not rq_bound:
|
||||||
ctitle = (d.get("title") or "").strip()
|
ctitle = (d.get("title") or "").strip()
|
||||||
mismatched = []
|
mismatched = []
|
||||||
@ -100,6 +117,9 @@ async def chapter_detail(chapter_id, with_content="1", length=12000):
|
|||||||
% ";".join(mismatched[:3]))
|
% ";".join(mismatched[:3]))
|
||||||
elif reqs:
|
elif reqs:
|
||||||
rq_warn = "(本章要求为编号匹配、未落显式绑定;标题核对一致,可用)"
|
rq_warn = "(本章要求为编号匹配、未落显式绑定;标题核对一致,可用)"
|
||||||
|
elif rq_empty:
|
||||||
|
rq_warn = ("(本章经裁决为空绑定:无章节级编写要求,仅列全局规则——按章节 outline "
|
||||||
|
"与招标原文相关条款编写,勿套用其他章节的要求)")
|
||||||
out = {
|
out = {
|
||||||
"id": d.get("id"), "chapter_no": d.get("chapter_no"), "title": d.get("title"),
|
"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"),
|
"section": d.get("section"), "status": d.get("status"), "version": d.get("version"),
|
||||||
|
|||||||
@ -134,11 +134,12 @@ BID_ROLE_TOOL_SCHEMAS = {
|
|||||||
},
|
},
|
||||||
"update_doc_requirement": {
|
"update_doc_requirement": {
|
||||||
"module": f"{_M}.bid_analysis_capability",
|
"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(优先)",
|
"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": "回写原文出处(可选)",
|
"requirement": "回写的新正文(可选)", "source_ref": "回写原文出处(可选)",
|
||||||
"page_limit": "页数(可选)", "delete": "true=删除该条(可选)"},
|
"page_limit": "页数(可选)", "delete": "true=删除该条(可选)",
|
||||||
|
"new_req_type": "纠正条目类型(可选,与定位键req_type分开传)"},
|
||||||
"required": [],
|
"required": [],
|
||||||
},
|
},
|
||||||
"list_scoring_items": {
|
"list_scoring_items": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user