""" Step 3 测试:投书目录生成 读取 Step2 保存的 step2_info.pkl,再调用 generate_outline_safe 生成目录, 并把最终目录保存为 step3_outline.pkl 供 test_step4.py 复用。 用法: uv run python scripts/test_step3.py """ import logging import os import pickle import sys from _bootstrap import PROJECT_ROOT # ---------- 测试数据(环境变量可覆盖) ---------- TEMPLATE_PATH = os.environ.get( "PROPOSA_TEMPLATE_PATH", "src/templates/申勤投标模板.docx", ) REFERENCE_BID = os.environ.get( "PROPOSA_REFERENCE_BID", "test_data/171-上海群众艺术馆/参考投标文件/物业管理费项目投标文件.docx", ) OUTPUT_DIR = os.environ.get( "PROPOSA_STEP3_OUTPUT_DIR", os.environ.get("PROPOSA_WORK_DIR", "output/171-上海群众艺术馆"), ) STEP2_INFO_FILE = os.environ.get( "PROPOSA_STEP2_INFO_FILE", os.path.join(OUTPUT_DIR, "step2_info.pkl"), ) STEP3_OUTLINE_FILE = os.environ.get( "PROPOSA_STEP3_OUTLINE_FILE", os.path.join(OUTPUT_DIR, "step3_outline.pkl"), ) os.environ["BID_OUTLINE_CACHE_DIR"] = os.path.join(OUTPUT_DIR, ".outline_cache") os.environ["BID_LLM_CACHE_DIR"] = os.path.join(OUTPUT_DIR, ".llm_cache") # ---------- 日志 ---------- logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", datefmt="%H:%M:%S", stream=sys.stderr, ) # ---------- Step 2: 复用已落盘的分析结果 ---------- print("=" * 60) print("Step 2: 复用已落盘的分析结果(Step 3 的前置)") print("=" * 60) if not os.path.isfile(STEP2_INFO_FILE): raise FileNotFoundError( f"Step2 分析结果不存在: {STEP2_INFO_FILE}\n" "请先运行 uv run python scripts/test_step2.py。" ) with open(STEP2_INFO_FILE, "rb") as file: step2_payload = pickle.load(file) analysis = step2_payload["analysis"] pd = step2_payload["project_data"] print( f"复用 Step2 分析: {len(analysis.scoring_criteria)} 评分项, " f"{len(analysis.rejection_items)} 废标项" ) # ---------- Step 3: 生成目录 ---------- print() print("=" * 60) print("Step 3: 生成投书目录") print("=" * 60) from step3_outlining import generate_outline_safe from models import ChapterType outline = generate_outline_safe( analysis, pd, template_path=TEMPLATE_PATH, reference_bid_path=REFERENCE_BID, ) # generate_outline_safe 已在返回前写出报告并执行硬门禁;失败时不会进入这里。 os.makedirs(OUTPUT_DIR, exist_ok=True) with open(STEP3_OUTLINE_FILE, "wb") as file: pickle.dump(outline, file) report_md = os.path.join(OUTPUT_DIR, "step3_outline_report.md") report_json = os.path.join(OUTPUT_DIR, "step3_outline_report.json") print(f"大纲对照报告: {report_md}") print(f"机器可读映射: {report_json}") print(f"Step3 最终目录: {STEP3_OUTLINE_FILE}") # ---------- 打印结果 ---------- print() print("=== 目录结构 ===") print(f"项目: {outline.project_name}") print(f"总目标字数: {outline.total_word_count_target:,}") print() biz_chs = [c for c in outline.chapters if c.chapter_type == ChapterType.BUSINESS] tech_chs = [c for c in outline.chapters if c.chapter_type == ChapterType.TECHNICAL] appendix_chs = [c for c in outline.chapters if c.chapter_type == ChapterType.APPENDIX] if biz_chs: print("【商务部分】") for ch in biz_chs: children_info = f" ({len(ch.children)} 节)" if ch.children else "" print(f" 第{ch.id}章 {ch.title} — 目标 {ch.word_count_target:,} 字{children_info}") for child in ch.children: print(f" {child.id} {child.title} — {child.word_count_target:,} 字") if tech_chs: print() print(f"【技术部分】") for ch in tech_chs: children_info = f" ({len(ch.children)} 节)" if ch.children else "" print(f" 第{ch.id}章 {ch.title} — 目标 {ch.word_count_target:,} 字{children_info}") for child in ch.children: print(f" {child.id} {child.title} — {child.word_count_target:,} 字") if appendix_chs: print() print("【附件】") for ch in appendix_chs: print(f" {ch.id} {ch.title}") total = sum(ch.word_count_target for ch in outline.chapters) print(f"\n章节总字数目标: {total:,} (配置最低: 170,000)") print() print("Step 3 测试完成 [OK]")