From c0cab536df903452c0e98e279afb2285a54f5b42 Mon Sep 17 00:00:00 2001 From: ymq Date: Wed, 16 Sep 2026 16:07:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(substance=5Fgate):=20=E6=A0=87=E4=B9=A6?= =?UTF-8?q?=E5=AE=9E=E8=B4=A8=E6=80=A7=E5=93=8D=E5=BA=94=E7=A1=AE=E5=AE=9A?= =?UTF-8?q?=E6=80=A7=E9=97=A8=E7=A6=81(2026-09-16=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=8A=A5=E9=9A=9C=E6=A0=B9=E6=B2=BB'=E6=A3=80=E6=A0=B8?= =?UTF-8?q?=E5=BE=97=E6=BB=A1=E5=88=86=E4=BD=86=E8=B4=A8=E9=87=8F=E5=A4=AA?= =?UTF-8?q?=E4=BD=8E')=E2=80=94=E2=80=94=E6=96=B0=E5=BB=BAsubstance=5Fgate?= =?UTF-8?q?=E6=A3=80=E6=B5=8B'=E7=BC=96=E5=86=99=E6=8C=87=E5=8D=97?= =?UTF-8?q?=E4=BD=93/=E8=A1=A8=E5=8D=95=E5=A4=A7=E9=9D=A2=E7=A7=AF?= =?UTF-8?q?=E5=BE=85=E5=A1=AB'(=E9=9D=9E=E6=8A=95=E6=A0=87=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=AD=A3=E6=96=87=E6=9C=AC=E8=BA=AB):=E5=85=83?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E6=A0=87=E9=A2=98(=E7=BC=96=E5=88=B6?= =?UTF-8?q?=E8=AF=B4=E6=98=8E/=E7=BC=96=E5=88=B6=E4=BE=9D=E6=8D=AE/?= =?UTF-8?q?=E5=A1=AB=E5=86=99=E5=8F=A3=E5=BE=84/=E8=87=AA=E6=9F=A5?= =?UTF-8?q?=E8=A1=A8/=E4=BA=BA=E5=B7=A5=E5=A1=AB=E5=86=99=E6=B8=85?= =?UTF-8?q?=E5=8D=95/=E5=BA=94=E7=AD=94=E5=8F=A3=E5=BE=84)+=E5=85=83?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=8F=A5(=E6=8C=89X=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E7=BC=96=E5=88=B6/=E5=AE=8C=E6=95=B4=E8=A6=86=E7=9B=96?= =?UTF-8?q?=E8=AF=84=E5=88=86=E5=AD=90=E9=A1=B9/=E8=A7=81=E7=AC=ACX?= =?UTF-8?q?=E8=8A=82=E6=B8=85=E5=8D=95)+=E9=9D=9E=E8=A7=84=E8=8C=83?= =?UTF-8?q?=E5=BE=85=E5=A1=AB=E5=8D=A0=E4=BD=8D(=E3=80=90=E5=BE=85?= =?UTF-8?q?=E5=A1=AB-xxx=E3=80=91,=E5=8C=BA=E5=88=AB=E4=BA=8E=E5=90=88?= =?UTF-8?q?=E6=B3=95=E3=80=90=E6=9D=90=E6=96=99=E5=BE=85=E8=A1=A5:?= =?UTF-8?q?=E3=80=91)=E3=80=82=E5=88=86=E5=B1=82=E5=88=A4=E5=AE=9A(?= =?UTF-8?q?=E8=B5=B0=E6=9F=A5=E6=A0=A1=E6=AD=A3:=E9=98=88=E5=80=BC4?= =?UTF-8?q?=E6=9B=BE=E8=AF=AF=E4=BC=A42.2.1/2.2.2=E6=9C=89=E5=AE=9E?= =?UTF-8?q?=E8=B4=A8=E5=86=85=E5=AE=B9=E4=BB=85=E5=B8=A6=E5=89=8D=E8=A8=80?= =?UTF-8?q?=E7=AB=A0,=E6=94=B9=E4=B8=BA=E9=AB=98=E7=B2=BE=E5=BA=A6?= =?UTF-8?q?=E7=BB=84=E5=90=88:=E5=BE=85=E5=A1=AB>=3D2=E6=88=96=E7=BB=BC?= =?UTF-8?q?=E5=90=88=E5=88=86>=3D15=E6=88=96=E5=85=83=E6=A0=87=E9=A2=98>?= =?UTF-8?q?=3D3=E4=B8=94=E5=BE=85=E5=A1=AB>=3D1)=E3=80=82=E7=9C=9F?= =?UTF-8?q?=E5=AE=9E34=E7=AB=A0=E5=AE=9E=E6=B5=8B:=E7=B2=BE=E5=87=86?= =?UTF-8?q?=E5=88=A43=E7=AB=A0=E6=8C=87=E5=8D=97=E4=BD=93(1.1=E6=8A=95?= =?UTF-8?q?=E6=A0=87=E5=87=BD17=E5=A4=84=E5=BE=85=E5=A1=AB/2.1=E5=95=86?= =?UTF-8?q?=E5=8A=A1/2.1.4=E4=B8=9A=E7=BB=A9=E8=A1=A831=E5=A4=84=E5=BE=85?= =?UTF-8?q?=E5=A1=AB),2.2.1(320=E8=A1=8C=E7=9C=9F=E5=AE=9E=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E8=A1=A8)/2.2.2(=E7=9C=9F=E5=AE=9E=E5=AE=9E=E6=96=BD?= =?UTF-8?q?=E6=96=B9=E6=A1=88)=E6=AD=A3=E7=A1=AE=E6=94=BE=E8=A1=8C?= =?UTF-8?q?=E3=80=82=E5=AF=B9=E9=BD=90diagram=5Fgate=E7=A1=AE=E5=AE=9A?= =?UTF-8?q?=E6=80=A7=E9=97=A8=E7=A6=81=E5=93=B2=E5=AD=A6:=E8=B4=A8?= =?UTF-8?q?=E9=87=8F=E9=97=B8=E9=97=A8=E4=B8=8D=E9=9D=A0LLM=E8=87=AA?= =?UTF-8?q?=E8=A7=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_service/substance_gate.py | 150 +++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 pipeline_service/substance_gate.py diff --git a/pipeline_service/substance_gate.py b/pipeline_service/substance_gate.py new file mode 100644 index 0000000..47d7a5a --- /dev/null +++ b/pipeline_service/substance_gate.py @@ -0,0 +1,150 @@ +"""pipeline_service.substance_gate — 标书「实质性响应」硬门禁(2026-09-16 用户报障根治)。 + +用户原话级问题:「目前产出的投标书像是编写指南,而不是标书本身」「标书质量太低, +检核还得了满分,这本身就是问题」。 + +实测根因(甘肃交通项目 bid_v1):写者交付的正文是**编写指南体**—— + - 1.1 投标函开头「一、编制依据与响应说明」「按封面原样抄录,见第六节《人工填写清单》」 + +17 处「【待填-投标总价】」「【待填-投标人名称】」+《自查表》《人工填写清单》; + - 2.2.2 实施方案开头「按第三章格式编制,完整覆盖评分标准四个子项①②③④」。 +这些是「教人怎么写标书」的元内容(meta-content),不是投标文件正文本身。 +而 review_chapter / finalize_bid_score 只查篇幅+伪图+得分率,**没有任何「这是不是 +标书正文」的门禁**,评分器(LLM)按「写作层是否提到评分子维度」打分,指南体表面 +把每个子维度都「提到」了 → 满分放行 → 评分报告自己列了「无效标」却给 100/100。 + +判定是**确定性的**(质量闸门不靠 LLM 自觉,对齐 diagram_gate 的分层原则): + - 元内容标题(## 编制说明/编制依据/填写口径/自查表/人工填写清单/…)→ 强信号 + - 元内容句(「按…格式编制」「完整覆盖…子项」「见第X节《…清单》」「提示性括注」)→ 中信号 + - 非规范待填占位(【待填-xxx】/[待填],区别于合法的【材料待补:xxx】)→ 强信号 +综合打分超阈值 → 判为「指南体/未实质填写」,机制层拒绝。 + +豁免边界(避免误伤真实标书): + - 【材料待补:xxx】是合法占位符规范(实体材料缺失),不计入待填违规; + - 签字栏下划线(___)、日期「年 月 日」是真实标书正常留白,不计入; + - 阈值适中:主体合格、仅开头一段啰嗦的章节(如 2.2.2)不触发整章拒绝, + 通篇待填+编制说明的空壳(如 1.1)必触发。 + +消费方: + - pipeline-bidding bid_write_capability.write_chapter(写入时拦指南体) + - pipeline-bidding bid_review_capability.review_chapter(评审兜底) + - pipeline-bidding bid_score_capability.finalize_bid_score(整书放行前硬门禁) +""" + +import re + +# 合法占位符(实体材料缺失,2026-09-16 占位符规范)——不计入待填违规 +_LEGIT_PLACEHOLDER_RE = re.compile(r"【材料待补[::]") + +# 元内容标题:## 编制说明 / 编制依据 / 填写口径 / 自查表 / 人工填写清单 / … +_META_HEADING_RE = re.compile( + r"^#{1,6}\s*[^#\n]*?" + r"(编制说明|编制依据|编制要求|编制口径|编制环节|编写说明|编写依据|编写要求|" + r"填写口径|填写说明|填报口径|填报说明|填报要求|" + r"自查表|自检表|核对表|人工填写清单|待人工签章清单|待签章清单|" + r"应答口径|认定口径|落稿|回填规则|页码回填|响应说明|编制与.{0,6}说明)", + re.M) + +# 元内容句:正文里「讲怎么写」而非「写内容」的句子 +_META_SENTENCE_PATTERNS = ( + r"按[^。\n]{0,30}格式编制", + r"按[^。\n]{0,30}(规定|要求)填报", + r"本(?:章|节|部分|表)[^。\n]{0,15}按[^。\n]{0,25}编制", + r"完整覆盖[^。\n]{0,20}(?:子项|维度|评分|要求)", + r"逐(?:项|条|字)(?:照录|抄录|填报)[^。\n]{0,15}(?:原文|格式|栏目)", + r"见[^。\n]{0,12}《[^》\n]{0,20}(?:清单|说明|表)》", + r"(?:提示性|操作性)[^。\n]{0,8}括注", + r"不占用[^。\n]{0,10}栏目", + r"一律置于[^。\n]{0,12}第[^。\n]{0,4}节", + r"(?:未|不得|禁止)(?:删改|增设|改变)[^。\n]{0,10}栏目", + r"原样(?:抄录|填报|照录)", + r"本(?:清单|表|节)[^。\n]{0,10}(?:集中|统一)(?:标注|管理|列明)", +) +_META_SENTENCE_RE = re.compile("|".join("(?:%s)" % p for p in _META_SENTENCE_PATTERNS)) + +# 非规范待填占位:【待填-xxx】/[待填]/(待填)/ 待填写。区别于合法的【材料待补:】 +_FILL_PLACEHOLDER_RE = re.compile( + r"[【\[((]\s*待填[^\]】))\n]{0,40}[\]】))]" # 【待填-投标总价】 + r"|待填[写补]?\s*[-—::]" # 待填-xxx / 待填写: + r"|[【\[((]\s*待(?:补(?![::])|定|确认)[^\]】))\n]{0,30}[\]】))]" # 【待定】【待确认】(非材料待补) +) + +# 阈值:综合分 >= 此值判为指南体/未实质填写 +# 元内容标题 +2 / 元内容句 +1 / 待填占位 +1 +GUIDE_SCORE_THRESHOLD = 4 + + +def analyze_substance(text): + """分析正文实质性。返回结构化结果(不抛异常)。 + + 分层判定(2026-09-16 走查校正:阈值4曾误伤2.2.1/2.2.2等有实质内容仅带前言的章): + - 待填占位(【待填-xxx】)= 无歧义「表单没填」铁证 → 强信号,权重高; + - 元内容标题/句 = 「讲怎么写」→ 弱信号,真实标书也常带「编制依据」前言, + 单独不足以判指南体,需与待填或高密度叠加。 + is_guide(硬门禁拒绝)只在高精度组合下成立,避免误伤有实质内容的章节。 + + { + 'is_guide': bool, # 硬门禁:判为指南体/未实质填写(机制拒绝) + 'score': int, # 综合违规分(软信号,喂评分器) + 'meta_density': float, # 元内容密度(每千字违规命中数) + 'meta_headings': [str], + 'meta_sentences': [str], + 'fill_placeholders': [str], + } + """ + text = text or "" + headings = [m.group(0).strip()[:60] for m in _META_HEADING_RE.finditer(text)] + sentences = [m.group(0).strip()[:50] for m in _META_SENTENCE_RE.finditer(text)] + # 待填占位:排除合法【材料待补:】——先挖掉合法占位再统计 + scrubbed = _LEGIT_PLACEHOLDER_RE.sub("", text) + fills = [m.group(0).strip()[:40] for m in _FILL_PLACEHOLDER_RE.finditer(scrubbed)] + + n_h, n_s, n_f = len(headings), len(sentences), len(fills) + score = n_h * 2 + n_s + n_f + kchars = max(1.0, len(text) / 1000.0) + meta_density = round((n_h + n_s) / kchars, 2) + + # 硬门禁(高精度,宁缺毋滥——误伤真实内容会逼无谓重写): + # ① 待填占位 >= 2:表单大面积没填(1.1=17、2.1.4=31 铁证);或 + # ② 综合分 >= 15:元内容压倒性(通篇讲怎么写);或 + # ③ 元内容标题 >= 3 且 待填 >= 1:既是指南框架又没填实。 + is_guide = (n_f >= 2) or (score >= 15) or (n_h >= 3 and n_f >= 1) + return { + "is_guide": is_guide, + "score": score, + "meta_density": meta_density, + "meta_headings": headings, + "meta_sentences": sentences, + "fill_placeholders": fills, + } + + +def find_substance_problems(text): + """返回问题描述列表(空 = 通过)。供门禁调用方判定 + 拼提示。""" + a = analyze_substance(text) + if not a["is_guide"]: + return [] + problems = [] + if a["meta_headings"]: + problems.append("元内容标题 %d 处:%s" + % (len(a["meta_headings"]), "、".join(a["meta_headings"][:5]))) + if a["meta_sentences"]: + problems.append("编写说明句 %d 处:%s" + % (len(a["meta_sentences"]), "、".join(a["meta_sentences"][:5]))) + if a["fill_placeholders"]: + problems.append("非规范待填占位 %d 处:%s" + % (len(a["fill_placeholders"]), "、".join(a["fill_placeholders"][:8]))) + return problems + + +def gate_message(problems, scene="标书章节"): + """门禁拒绝提示(带改法,对齐 diagram_gate.gate_message 风格)。""" + return ("FAIL: %s 是「编写指南/作业指导书」,不是投标文件正文本身(2026-09-16 硬门禁)。\n" + "检出问题:%s\n" + "改法:① 删除所有「编制说明/编制依据/填写口径/自查表/人工填写清单/应答口径」" + "等讲『怎么写』的元内容段落——正文只写『写什么』;" + "② 投标函/承诺函/报价表等按招标格式**直接填真实内容**(投标人名称、报价、" + "日期等能从项目/知识库取到的就填实,取不到的实体材料用规范占位符" + "【材料待补:具体材料名】,禁止用【待填-xxx】这类自造占位);" + "③ 技术方案/实施方案写实质措施(架构/流程/指标/责任人/工期)," + "不写「本章覆盖评分子项①②③」这类编写说明。" + % (scene, ";".join(problems)))