feat: patch_chapters工具(根治create_chapter_outline只增不改)+list_*项目上下文缺失改报错不静默空
This commit is contained in:
parent
6c87cd12b6
commit
fdd98266f8
@ -806,7 +806,9 @@ async def list_scoring_items(project_id, section=""):
|
|||||||
try:
|
try:
|
||||||
project_id = await resolve_project_id(project_id)
|
project_id = await resolve_project_id(project_id)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return []
|
# 2026-09-03:静默返回 [] 会让 LLM 误判「无数据」(实测 C-12 绑定阻塞根因),
|
||||||
|
# 改为明确报错,提示补 project_id。
|
||||||
|
return "ERROR: 项目上下文缺失(%s)。请显式传 project_id 后重试。" % e
|
||||||
db, dbname = get_db()
|
db, dbname = get_db()
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
sql = ("SELECT id, section, item_no, item_name, max_score, is_veto, "
|
sql = ("SELECT id, section, item_no, item_name, max_score, is_veto, "
|
||||||
@ -828,7 +830,7 @@ async def list_doc_requirements(project_id, req_type=""):
|
|||||||
try:
|
try:
|
||||||
project_id = await resolve_project_id(project_id)
|
project_id = await resolve_project_id(project_id)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return []
|
return "ERROR: 项目上下文缺失(%s)。请显式传 project_id 后重试。" % e
|
||||||
db, dbname = get_db()
|
db, dbname = get_db()
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
sql = ("SELECT id, req_type, chapter_no, chapter_title, requirement, page_limit "
|
sql = ("SELECT id, req_type, chapter_no, chapter_title, requirement, page_limit "
|
||||||
@ -938,3 +940,98 @@ async def create_chapter_outline(project_id, chapters="", who=None, agent_id=Non
|
|||||||
who=who, agent_id=agent_id, detail="新增 %d 章" % created)
|
who=who, agent_id=agent_id, detail="新增 %d 章" % created)
|
||||||
return True, ("章节骨架已生成:新增 %d 章,已存在跳过 %d 章。"
|
return True, ("章节骨架已生成:新增 %d 章,已存在跳过 %d 章。"
|
||||||
"系统将自动为每个章节派发编写任务。" % (created, skipped))
|
"系统将自动为每个章节派发编写任务。" % (created, skipped))
|
||||||
|
|
||||||
|
|
||||||
|
async def patch_chapters(project_id, patches="", who=None, agent_id=None):
|
||||||
|
"""按 chapter_no 增量更新已有章节(2026-09-03,根治 create_chapter_outline 只增不改)。
|
||||||
|
|
||||||
|
patches: JSON 数组,每项 {chapter_no, title?, outline?, section?,
|
||||||
|
scoring_item_ids?, scoring_items_by_name?, max_score?}。
|
||||||
|
- scoring_item_ids 直接覆盖(传真实 id 数组);
|
||||||
|
- scoring_items_by_name: [{item_name, max_score?}] 按名称在 bid_scoring_items
|
||||||
|
反查 id 绑定(item_name 为「商务N|名称|出处」格式时按包含匹配);
|
||||||
|
- 白名单外的章节不动;不存在的 chapter_no 报错列出。
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
project_id = await resolve_project_id(project_id)
|
||||||
|
except ValueError as e:
|
||||||
|
return False, str(e)
|
||||||
|
items = json_loads(patches, []) if isinstance(patches, str) else (patches or [])
|
||||||
|
if not items:
|
||||||
|
return False, "patches 为空:传 JSON 数组 [{chapter_no, ...}]"
|
||||||
|
db, dbname = get_db()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
sitems = await sor.sqlExe(
|
||||||
|
"SELECT id, item_name, max_score FROM bid_scoring_items WHERE project_id=${pid}$",
|
||||||
|
{"pid": project_id})
|
||||||
|
await sor.sqlExe("COMMIT", {})
|
||||||
|
sc = rows_to_dicts(sitems)
|
||||||
|
|
||||||
|
def _bind_by_name(names):
|
||||||
|
ids, miss = [], []
|
||||||
|
for n in (names or []):
|
||||||
|
nm = (n.get("item_name") or "").strip() if isinstance(n, dict) else str(n or "").strip()
|
||||||
|
hit = None
|
||||||
|
for s in sc:
|
||||||
|
sn = s.get("item_name") or ""
|
||||||
|
if nm and (nm == sn or nm in sn or sn in nm):
|
||||||
|
hit = s
|
||||||
|
break
|
||||||
|
if hit:
|
||||||
|
ids.append(hit["id"])
|
||||||
|
else:
|
||||||
|
miss.append(nm)
|
||||||
|
return ids, miss
|
||||||
|
|
||||||
|
updated, missing, bound, unbound = [], [], 0, []
|
||||||
|
for it in items:
|
||||||
|
if not isinstance(it, dict):
|
||||||
|
continue
|
||||||
|
cno = str(it.get("chapter_no") or "").strip()
|
||||||
|
if not cno:
|
||||||
|
continue
|
||||||
|
recs = await sor.sqlExe(
|
||||||
|
"SELECT id, scoring_item_ids FROM bid_chapters "
|
||||||
|
"WHERE project_id=${pid}$ AND chapter_no=${cno}$ LIMIT 1",
|
||||||
|
{"pid": project_id, "cno": cno})
|
||||||
|
await sor.sqlExe("COMMIT", {})
|
||||||
|
if not recs:
|
||||||
|
missing.append(cno)
|
||||||
|
continue
|
||||||
|
sets, p = [], {"pid": project_id, "cno": cno}
|
||||||
|
if it.get("title"):
|
||||||
|
sets.append("title=${t}$"); p["t"] = str(it["title"])[:250]
|
||||||
|
if it.get("outline") is not None and it.get("outline") != "":
|
||||||
|
sets.append("outline=${o}$"); p["o"] = str(it["outline"])[:20000]
|
||||||
|
if it.get("section"):
|
||||||
|
sets.append("section=${s}$"); p["s"] = str(it["section"])[:30]
|
||||||
|
ids = list(it.get("scoring_item_ids") or [])
|
||||||
|
if it.get("scoring_items_by_name"):
|
||||||
|
b, m = _bind_by_name(it["scoring_items_by_name"])
|
||||||
|
ids = b
|
||||||
|
bound += len(b)
|
||||||
|
unbound += m
|
||||||
|
if it.get("scoring_item_ids") is not None or it.get("scoring_items_by_name"):
|
||||||
|
sets.append("scoring_item_ids=${si}$")
|
||||||
|
p["si"] = json.dumps(ids, ensure_ascii=False)
|
||||||
|
if it.get("max_score") is not None:
|
||||||
|
sets.append("max_score=${ms}$"); p["ms"] = to_float(it["max_score"], 0)
|
||||||
|
if not sets:
|
||||||
|
continue
|
||||||
|
sets.append("updated_at=NOW()")
|
||||||
|
sets.append("version=version+1")
|
||||||
|
await sor.sqlExe(
|
||||||
|
"UPDATE bid_chapters SET " + ", ".join(sets) +
|
||||||
|
" WHERE project_id=${pid}$ AND chapter_no=${cno}$", p)
|
||||||
|
updated.append(cno)
|
||||||
|
await sor.sqlExe("COMMIT", {})
|
||||||
|
await record(sor, project_id, "bid_chapters", project_id, "patch_outline",
|
||||||
|
who=who, agent_id=agent_id,
|
||||||
|
detail="更新 %d 章:%s" % (len(updated), ",".join(updated)[:200]))
|
||||||
|
msg = "章节增量更新:成功 %d 章(%s),评分项按名绑定 %d 条" % (
|
||||||
|
len(updated), ",".join(updated)[:200], bound)
|
||||||
|
if missing:
|
||||||
|
msg += ";不存在的章节:%s" % ",".join(missing)
|
||||||
|
if unbound:
|
||||||
|
msg += ";按名未绑定的评分项:%s" % ",".join(unbound)[:200]
|
||||||
|
return True, msg
|
||||||
|
|||||||
@ -96,7 +96,7 @@ async def list_qualifications(project_id, match_status=""):
|
|||||||
try:
|
try:
|
||||||
project_id = await resolve_project_id(project_id)
|
project_id = await resolve_project_id(project_id)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return []
|
return "ERROR: 项目上下文缺失(%s)。请显式传 project_id 后重试。" % e
|
||||||
db, dbname = get_db()
|
db, dbname = get_db()
|
||||||
async with db.sqlorContext(dbname) as sor:
|
async with db.sqlorContext(dbname) as sor:
|
||||||
sql = ("SELECT id, qual_name, is_mandatory, owner, match_status, kb_doc_id, "
|
sql = ("SELECT id, qual_name, is_mandatory, owner, match_status, kb_doc_id, "
|
||||||
|
|||||||
@ -103,6 +103,13 @@ BID_ROLE_TOOL_SCHEMAS = {
|
|||||||
"params": {"project_id": "项目ID(可选,默认当前)", "chapters": "章节JSON数组(可选,默认自动生成)"},
|
"params": {"project_id": "项目ID(可选,默认当前)", "chapters": "章节JSON数组(可选,默认自动生成)"},
|
||||||
"required": [],
|
"required": [],
|
||||||
},
|
},
|
||||||
|
"patch_chapters": {
|
||||||
|
"module": f"{_M}.bid_analysis_capability",
|
||||||
|
"description": "按 chapter_no 增量更新已有章节(改标题/outline/挂载评分项/满分)。create_chapter_outline 只增不改,修正既有骨架必须用它。scoring_items_by_name 按名称反查绑定真实 id",
|
||||||
|
"params": {"project_id": "项目ID(可选,默认当前)",
|
||||||
|
"patches": "JSON数组[{chapter_no,title?,outline?,section?,scoring_item_ids?,scoring_items_by_name?,max_score?}]"},
|
||||||
|
"required": ["patches"],
|
||||||
|
},
|
||||||
"list_scoring_items": {
|
"list_scoring_items": {
|
||||||
"module": f"{_M}.bid_analysis_capability",
|
"module": f"{_M}.bid_analysis_capability",
|
||||||
"description": "列出评分项与得分规则",
|
"description": "列出评分项与得分规则",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user