"""阶段 4:汇总报告 + 结构化问题抽取。""" import json from datetime import datetime from pathlib import Path from .. import config def summarize(ctx, findings, log=print): """用 LLM 把各图框结论汇总为总报告。""" if not findings: return "未识别到可审查的图框,或所有图框均无有效包围盒。" report_input = "\n\n".join( f"【图框 {f['frame']}(图幅 {f.get('sheet')})】\n{f['finding']}" for f in findings ) system = ( "你是图纸审查负责人。请把下面各图框的审查意见汇总为一份结构化的审查报告:" "先给整体结论,再按图框列出问题,最后给出整改建议。语言简洁、条目化。" ) try: return ctx.llm.chat_text(system, report_input) except Exception as exc: # noqa: BLE001 log(f"[汇总失败] {exc}") return report_input _EXTRACT_SYSTEM = ( "你是结构化信息抽取器。请把下面的图纸审查意见抽取为 JSON 数组," "每个元素形如:" '{"frame": <图框序号>, "handle": "<实体句柄,没有则省略>", ' '"category": "<图层/文字/标注/几何/图面整体>", ' '"severity": "", "message": "<问题描述>"}。' "只输出 JSON 数组,不要输出任何解释、不要用 markdown 代码块包裹。" "若某条意见无法定位到具体实体,省略 handle 字段。" ) def extract_issues(ctx, findings, log=print): """把自由文本的审查意见抽取为结构化问题列表(用于批注回写)。""" if not findings: return [] source = "\n\n".join( f"图框 {f['frame']}:\n{f['finding']}" for f in findings ) try: text = ctx.llm.chat_text(_EXTRACT_SYSTEM, source) text = text.strip() if text.startswith("```"): text = text.strip("`") text = text.split("\n", 1)[1] if "\n" in text else text data = json.loads(text) if isinstance(data, list): return data except Exception as exc: # noqa: BLE001 log(f"[结构化抽取失败] {exc}") return [] def save_report(markdown, findings, plan, issues=None, log=print): """落盘 Markdown + JSON 报告,返回两个路径。""" out_dir = Path(config.REPORT_DIR) out_dir.mkdir(parents=True, exist_ok=True) stamp = datetime.now().strftime("%Y%m%d_%H%M%S") md_path = out_dir / f"review_{stamp}.md" json_path = out_dir / f"review_{stamp}.json" md_path.write_text(markdown, encoding="utf-8") json_path.write_text( json.dumps( { "generated_at": stamp, "findings": findings, "issues": issues or [], "frames": plan.get("frames"), }, ensure_ascii=False, indent=2, ), encoding="utf-8", ) log(f"[报告] 已写入 {md_path} 与 {json_path}") return md_path, json_path