revert(substance_gate): 回退三处正则门禁挂载(submit_chapter/review_chapter/finalize_bid_score)+material_missing守卫——同pipeline-service c0cab53回退理由:输出端堆检查打地鼠不治根,实质性判断回归LLM评审裁决(技能已改)。保留docx表格转换(write侧真bug修复)

This commit is contained in:
yumoqing 2026-09-16 16:57:54 +08:00
parent 1d8d56314e
commit 1da6be3687
3 changed files with 2 additions and 99 deletions

View File

@ -80,19 +80,6 @@ async def review_chapter(chapter_id, score, max_score="", comments="", detail=""
"删除 mermaid/ASCII 字符画后重写提交。" % "".join(sorted(set(_dg))))
comments = (dg_note + "\n" + (comments or "")).strip()
# ── 实质性响应门禁2026-09-16 用户报障根治):指南体/表单大面积待填必退 ──
# submit_chapter 已拦一道;此处兜底存量正文/绕行写入,章节评审级即给反馈
# (不必等整书评分),意见自动附带。确定性检测,不采信评分器满分。
try:
from pipeline_service.substance_gate import find_substance_problems, gate_message
_sg = find_substance_problems(c.get("content") or "")
_sg_note = gate_message(_sg, scene="本章") if _sg else ""
except ImportError:
_sg_note = ""
if _sg_note:
passed = False
comments = (_sg_note + "\n" + (comments or "")).strip()
_substance_fail = bool(_sg_note) # 实质门禁不通过 → 禁止 material_missing 豁免绕过
if not passed and not (comments or "").strip():
return False, ("评审不通过必须给出改进意见comments"
"写清缺哪些评分维度、应补什么内容、对照哪条得分规则")
@ -135,14 +122,6 @@ async def review_chapter(chapter_id, score, max_score="", comments="", detail=""
# 未回传 → 章节通过(真实分数+材料缺失披露注记,整书评分如实反映),
# 同时发 material_supply 信息流待办通知人类补料,不冻结任何流转。
_mat_missing = str(material_missing).strip().lower() in ("1", "true", "yes", "y")
# 守卫2026-09-16实质门禁不通过的章节禁止用 material_missing 豁免绕过——
# 「指南体/表单大面积待填」不是实体材料缺失,是写作本身没写实质内容;
# 若放行会把我上一轮加的占位符豁免变成质量门禁的后门(实测满分放行的帮凶)。
if _mat_missing and _substance_fail:
_mat_missing = False
comments = ("【material_missing 豁免被机制拒绝】本章未通过实质性响应门禁"
"(是指南体/表单待填,非实体材料缺失),不适用材料缺失豁免通道,"
"必须按实质门禁意见重写正文。\n" + (comments or "")).strip()
if _mat_missing:
await sor.sqlExe(
"UPDATE " + TABLE + " SET status=${st}$, review_score=${sc}$, max_score=${ms}$, "

View File

@ -200,30 +200,6 @@ async def finalize_bid_score(review_id, comments="", who=None, agent_id=None):
ratio = (total / maxsum) if maxsum > 0 else 0.0
passed = (ratio >= th["bid_pass_ratio"]) and not veto_fail
# ── 实质性响应确定性门禁2026-09-16 用户报障根治,最关键的一道)──
# 实测:指南体标书(投标函全是【待填】、实施方案写「本章覆盖评分子项①②③」)
# 被 LLM 评分器判 100/100 满分放行——评分报告自己都列了「无效标」却 passed。
# 根治:质量闸门不靠 LLM 自觉,机制层用 substance_gate 确定性扫全书章节正文,
# 命中指南体/表单大面积待填 → 强制 passed=False命中章节退回重写。
# 与 diagram_gate 同一设计哲学(花钱/交付闸门必须确定性校验兜底)。
substance_chs = [] # [(chapter_id, chapter_no, title, problems)]
try:
from pipeline_service.substance_gate import find_substance_problems
_chrecs = await sor.sqlExe(
"SELECT id, chapter_no, title, content FROM bid_chapters "
"WHERE project_id=${p}$ ORDER BY order_no, chapter_no", {"p": pid})
await sor.sqlExe("COMMIT", {})
for _c in (_chrecs or []):
_cd = rec_to_dict(_c)
_probs = find_substance_problems(_cd.get("content") or "")
if _probs:
substance_chs.append((_cd.get("id"), _cd.get("chapter_no"),
_cd.get("title"), _probs))
except ImportError:
pass # 宿主未装 pipeline-service 时跳过(逃逸阀)
if substance_chs:
passed = False
weak = [s for s in scores
if to_float(s.get("max_score"), 0.0) > 0
and to_float(s.get("score"), 0.0) / to_float(s.get("max_score"), 1.0) < th["bid_pass_ratio"]]
@ -231,13 +207,6 @@ async def finalize_bid_score(review_id, comments="", who=None, agent_id=None):
% (total, maxsum, ratio * 100, th["bid_pass_ratio"] * 100)]
if veto_fail:
summary_lines.append("⛔ 否决项未响应:" + "".join(veto_fail[:5]))
if substance_chs:
summary_lines.append("⛔ 实质性响应门禁不通过:%d 个章节是「编写指南体/表单大面积待填」"
"(不是投标文件正文本身),机制确定性拦截(不采信评分满分):"
% len(substance_chs))
for _cid, _cno, _ct, _probs in substance_chs[:12]:
summary_lines.append(" - 第%s章《%s》:%s"
% (_cno, (_ct or "")[:24], "".join(_probs)[:200]))
for s in weak[:20]:
summary_lines.append("- %s %.1f/%.1f%s"
% (s.get("item_name"), to_float(s.get("score")),
@ -291,43 +260,10 @@ async def finalize_bid_score(review_id, comments="", who=None, agent_id=None):
# 把改进意见落到问题章节approved → rejectedreconciler 会派重写任务)
touched = 0
# ① 实质性门禁命中章节强制退回(评分器可能给它们满分,但机制判定是指南体/待填)
for _cid, _cno, _ct, _probs in substance_chs:
if not _cid:
continue
_crec = await sor.sqlExe(
"SELECT id, status, revise_count FROM bid_chapters WHERE id=${c}$", {"c": _cid})
await sor.sqlExe("COMMIT", {})
if not _crec:
continue
_c = rec_to_dict(_crec[0])
if _c.get("status") == CH_REJECTED:
continue # 已退回,避免 revise_count 重复累加
_imp = ("【实质性响应门禁 · 第%s轮 · 机制确定性拦截】本章是「编写指南/作业指导书」"
"或表单大面积未填实,不是投标文件正文本身。检出:%s\n"
"必须重写为正文:① 删除全部「编制说明/编制依据/填写口径/自查表/人工填写清单」"
"等讲『怎么写』的元内容;② 投标函/承诺/报价按招标格式**直接填真实内容**"
"(投标人/报价/日期等能从项目或知识库取到的填实,实体材料缺失用规范占位符"
"【材料待补:具体材料名】,禁止【待填-xxx】自造占位③ 技术/实施方案写实质"
"措施(架构/流程/量化指标/责任人/工期),不写「覆盖评分子项①②③」式说明。"
% (review.get("round"), "".join(_probs)[:600]))
await sor.sqlExe(
"UPDATE bid_chapters SET status=${st}$, review_comment=${cm}$, "
"revise_count=${rc}$, updated_at=NOW() WHERE id=${c}$",
{"st": CH_REJECTED, "cm": _imp[:60000],
"rc": to_int(_c.get("revise_count"), 0) + 1, "c": _cid})
await sor.sqlExe("COMMIT", {})
await record(sor, pid, "bid_chapters", _cid, "substance_reject",
from_state=_c.get("status"), to_state=CH_REJECTED,
who=who, agent_id=agent_id,
detail="实质性门禁退回:%s" % ("".join(_probs))[:300])
touched += 1
# ② 评分不达标章节退回(原有逻辑)
_substance_cids = set(_cid for _cid, _, _, _ in substance_chs if _cid)
for s in weak:
cid = s.get("target_chapter_id") or ""
if not cid or cid in _substance_cids:
continue # 已被 substance 门禁退回,避免 revise_count 重复累加
if not cid:
continue
crec = await sor.sqlExe(
"SELECT id, status, revise_count, title FROM bid_chapters WHERE id=${c}$",
{"c": cid})

View File

@ -169,18 +169,6 @@ async def submit_chapter(chapter_id, note="", who=None, agent_id=None):
d = rec_to_dict(recs[0])
if len((d.get("content") or "").strip()) < MIN_CONTENT_LEN:
return False, "章节正文为空或过短,请先 write_chapter 写入正文"
# ── 实质性响应硬门禁2026-09-16 用户报障根治)──
# 拦「编写指南体/表单大面积待填」——交付的必须是投标文件正文本身,不是
# 「教人怎么写标书」的元内容(编制说明/填写口径/自查表/【待填-xxx】占位
# 确定性检测substance_gate不靠评审 LLM 自觉(实测指南体曾拿 100/100 满分)。
# 挂在 submit_chapter定稿提交唯一关口append 分批写中途不误判,提交时查全文。
try:
from pipeline_service.substance_gate import find_substance_problems, gate_message
_sg = find_substance_problems(d.get("content") or "")
if _sg:
return False, gate_message(_sg, scene="章节「%s" % (d.get("title") or chapter_id))
except ImportError:
pass # 宿主未装 pipeline-service 时放行(逃逸阀,与产线零接触分层一致)
if d.get("status") not in (CH_WRITING, CH_PENDING, CH_REJECTED):
return False, "当前状态 %s 不可提交(需 writing/pending/rejected" % d.get("status")
await sor.sqlExe(