feat(revise): revise_chapter_text定点替换已定稿章节局部文本(2026-09-16甘肃删1.9章后悬空引用实测需要)——approved章节write_chapter拒写,合规性局部修正(悬空交叉引用/违反招标书明文/跨章编号错位)无处落脚;断言式find须恰好出现1次否则整批拒绝,版本+1+审计+镜像同步,approved不触发重审(评审语义未变),扩写仍走评审退回
This commit is contained in:
parent
227471d514
commit
beca611f29
@ -225,6 +225,83 @@ async def write_chapter(chapter_id, content, summary="", mode="replace",
|
||||
% (d.get("title"), len(new_txt), ver + 1, _gap_note))
|
||||
|
||||
|
||||
async def revise_chapter_text(chapter_id, edits="", who=None, agent_id=None):
|
||||
"""定点替换已定稿章节正文的局部文本(2026-09-16 甘肃 1.9 删章后实测需要)。
|
||||
|
||||
write_chapter 对 approved 章节拒写(防误覆盖),但存在必须修正的合规性
|
||||
局部改动(悬空交叉引用/违反招标书明文),整章重写既昂贵又有丢内容风险。
|
||||
本工具做「断言式」定点替换:
|
||||
- edits = [{"find": 原文片段, "replace": 新文本}],find 必须在正文中
|
||||
**恰好出现一次**(0 次或多次都拒绝整个操作,防误伤/防歧义);
|
||||
- 全部命中才落库(先校验后写,单条不合法整批拒绝);
|
||||
- 版本 +1、审计记录、镜像文件同步;approved 状态保持不变(局部合规修正
|
||||
不触发整章重审——评审语义与分数未变)。
|
||||
|
||||
用于:删章后的悬空引用清理、与招标书明文冲突的表述纠正、跨章编号引用
|
||||
错位修正。不用于内容扩写(那走评审退回重写)。
|
||||
"""
|
||||
import json as _json
|
||||
if not chapter_id:
|
||||
return False, "缺少 chapter_id"
|
||||
if isinstance(edits, str):
|
||||
try:
|
||||
edits = _json.loads(edits)
|
||||
except Exception as e: # noqa: BLE001
|
||||
return False, "edits 解析失败(须为 JSON 数组):%s" % str(e)[:120]
|
||||
if not isinstance(edits, list) or not edits:
|
||||
return False, "edits 必须是非空数组:[{find, replace}]"
|
||||
db, dbname = get_db()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT id, project_id, chapter_no, title, content, version, status "
|
||||
"FROM " + TABLE + " WHERE id=${cid}$", {"cid": chapter_id})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
if not recs:
|
||||
return False, "章节不存在: %s" % chapter_id
|
||||
d = rec_to_dict(recs[0])
|
||||
txt = d.get("content") or ""
|
||||
norm, applied = [], []
|
||||
for i, e in enumerate(edits):
|
||||
if not isinstance(e, dict):
|
||||
return False, "edits[%d] 必须是对象 {find, replace}" % i
|
||||
find = e.get("find")
|
||||
rep = e.get("replace")
|
||||
if not isinstance(find, str) or not find.strip():
|
||||
return False, "edits[%d].find 必须是非空字符串" % i
|
||||
if rep is None or not isinstance(rep, str):
|
||||
return False, "edits[%d].replace 必须是字符串(可为空串=删除)" % i
|
||||
n = txt.count(find)
|
||||
if n != 1:
|
||||
return False, ("edits[%d] 不满足唯一性断言:find 在章节「%s」中出现 %d 次"
|
||||
"(要求恰好 1 次)。请扩大上下文片段使其唯一。find 前80字=%s"
|
||||
% (i, d.get("title"), n, find[:80]))
|
||||
norm.append((find, rep))
|
||||
# 全部断言通过才落库
|
||||
new_txt = txt
|
||||
for find, rep in norm:
|
||||
new_txt = new_txt.replace(find, rep, 1)
|
||||
applied.append(find[:40])
|
||||
ver = to_int(d.get("version"), 1)
|
||||
await sor.sqlExe(
|
||||
"UPDATE " + TABLE + " SET content=${c}$, word_count=${w}$, "
|
||||
"version=${v}$, updated_at=NOW() WHERE id=${cid}$",
|
||||
{"c": new_txt, "w": len(new_txt), "v": ver + 1, "cid": chapter_id})
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
await record(sor, d.get("project_id"), TABLE, chapter_id, "revise_text",
|
||||
from_state=d.get("status"), to_state=d.get("status"),
|
||||
who=who, agent_id=agent_id,
|
||||
detail="定点替换 %d 处:%s" % (len(norm), "|".join(applied)[:400]))
|
||||
_mir = ""
|
||||
try:
|
||||
from .bid_chapter_mirror import sync_chapter_mirror
|
||||
_mir = await sync_chapter_mirror(sor, d.get("project_id"), chapter_id)
|
||||
except Exception as _e: # noqa: BLE001
|
||||
logger.warning("chapter mirror failed: %s", str(_e)[:120])
|
||||
return True, ("章节「%s」定点替换 %d 处(v%d,%d 字符,状态 %s 不变)%s"
|
||||
% (d.get("title"), len(norm), ver + 1, len(new_txt),
|
||||
d.get("status"), ";镜像 %s" % _mir if _mir else ""))
|
||||
|
||||
|
||||
async def submit_chapter(chapter_id, note="", who=None, agent_id=None):
|
||||
"""提交章节评审:writing → written(reconciler 会自动派发评审任务)。"""
|
||||
db, dbname = get_db()
|
||||
|
||||
@ -235,6 +235,13 @@ BID_ROLE_TOOL_SCHEMAS = {
|
||||
"params": {"chapter_id": "章节ID", "note": "说明(可选)"},
|
||||
"required": ["chapter_id"],
|
||||
},
|
||||
"revise_chapter_text": {
|
||||
"module": f"{_M}.bid_write_capability",
|
||||
"description": "定点替换章节正文局部文本(含已 approved 章节):edits=[{find,replace}],find 必须在正文中恰好出现一次否则整批拒绝。仅用于合规性局部修正(悬空交叉引用/违反招标书明文/跨章编号错位),内容扩写走评审退回重写。版本+1留审计,approved 状态不触发重审",
|
||||
"params": {"chapter_id": "章节ID",
|
||||
"edits": "JSON数组 [{find:原文片段(须唯一), replace:新文本(空串=删除)}]"},
|
||||
"required": ["chapter_id", "edits"],
|
||||
},
|
||||
# ── bid_review_capability:标书评审员 ──
|
||||
"review_chapter": {
|
||||
"module": f"{_M}.bid_review_capability",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user