test_step3.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. """
  2. Step 3 测试:投书目录生成
  3. 读取 Step2 保存的 step2_info.pkl,再调用 generate_outline_safe 生成目录,
  4. 并把最终目录保存为 step3_outline.pkl 供 test_step4.py 复用。
  5. 用法: uv run python scripts/test_step3.py
  6. """
  7. import logging
  8. import os
  9. import pickle
  10. import sys
  11. from _bootstrap import PROJECT_ROOT
  12. # ---------- 测试数据(环境变量可覆盖) ----------
  13. TEMPLATE_PATH = os.environ.get(
  14. "PROPOSA_TEMPLATE_PATH",
  15. "src/templates/申勤投标模板.docx",
  16. )
  17. REFERENCE_BID = os.environ.get(
  18. "PROPOSA_REFERENCE_BID",
  19. "test_data/171-上海群众艺术馆/参考投标文件/物业管理费项目投标文件.docx",
  20. )
  21. OUTPUT_DIR = os.environ.get(
  22. "PROPOSA_STEP3_OUTPUT_DIR",
  23. os.environ.get("PROPOSA_WORK_DIR", "output/171-上海群众艺术馆"),
  24. )
  25. STEP2_INFO_FILE = os.environ.get(
  26. "PROPOSA_STEP2_INFO_FILE",
  27. os.path.join(OUTPUT_DIR, "step2_info.pkl"),
  28. )
  29. STEP3_OUTLINE_FILE = os.environ.get(
  30. "PROPOSA_STEP3_OUTLINE_FILE",
  31. os.path.join(OUTPUT_DIR, "step3_outline.pkl"),
  32. )
  33. os.environ["BID_OUTLINE_CACHE_DIR"] = os.path.join(OUTPUT_DIR, ".outline_cache")
  34. os.environ["BID_LLM_CACHE_DIR"] = os.path.join(OUTPUT_DIR, ".llm_cache")
  35. # ---------- 日志 ----------
  36. logging.basicConfig(
  37. level=logging.INFO,
  38. format="%(asctime)s [%(levelname)s] %(message)s",
  39. datefmt="%H:%M:%S",
  40. stream=sys.stderr,
  41. )
  42. # ---------- Step 2: 复用已落盘的分析结果 ----------
  43. print("=" * 60)
  44. print("Step 2: 复用已落盘的分析结果(Step 3 的前置)")
  45. print("=" * 60)
  46. if not os.path.isfile(STEP2_INFO_FILE):
  47. raise FileNotFoundError(
  48. f"Step2 分析结果不存在: {STEP2_INFO_FILE}\n"
  49. "请先运行 uv run python scripts/test_step2.py。"
  50. )
  51. with open(STEP2_INFO_FILE, "rb") as file:
  52. step2_payload = pickle.load(file)
  53. analysis = step2_payload["analysis"]
  54. pd = step2_payload["project_data"]
  55. print(
  56. f"复用 Step2 分析: {len(analysis.scoring_criteria)} 评分项, "
  57. f"{len(analysis.rejection_items)} 废标项"
  58. )
  59. # ---------- Step 3: 生成目录 ----------
  60. print()
  61. print("=" * 60)
  62. print("Step 3: 生成投书目录")
  63. print("=" * 60)
  64. from step3_outlining import generate_outline_safe
  65. from models import ChapterType
  66. outline = generate_outline_safe(
  67. analysis,
  68. pd,
  69. template_path=TEMPLATE_PATH,
  70. reference_bid_path=REFERENCE_BID,
  71. )
  72. # generate_outline_safe 已在返回前写出报告并执行硬门禁;失败时不会进入这里。
  73. os.makedirs(OUTPUT_DIR, exist_ok=True)
  74. with open(STEP3_OUTLINE_FILE, "wb") as file:
  75. pickle.dump(outline, file)
  76. report_md = os.path.join(OUTPUT_DIR, "step3_outline_report.md")
  77. report_json = os.path.join(OUTPUT_DIR, "step3_outline_report.json")
  78. print(f"大纲对照报告: {report_md}")
  79. print(f"机器可读映射: {report_json}")
  80. print(f"Step3 最终目录: {STEP3_OUTLINE_FILE}")
  81. # ---------- 打印结果 ----------
  82. print()
  83. print("=== 目录结构 ===")
  84. print(f"项目: {outline.project_name}")
  85. print(f"总目标字数: {outline.total_word_count_target:,}")
  86. print()
  87. biz_chs = [c for c in outline.chapters if c.chapter_type == ChapterType.BUSINESS]
  88. tech_chs = [c for c in outline.chapters if c.chapter_type == ChapterType.TECHNICAL]
  89. appendix_chs = [c for c in outline.chapters if c.chapter_type == ChapterType.APPENDIX]
  90. if biz_chs:
  91. print("【商务部分】")
  92. for ch in biz_chs:
  93. children_info = f" ({len(ch.children)} 节)" if ch.children else ""
  94. print(f" 第{ch.id}章 {ch.title} — 目标 {ch.word_count_target:,} 字{children_info}")
  95. for child in ch.children:
  96. print(f" {child.id} {child.title} — {child.word_count_target:,} 字")
  97. if tech_chs:
  98. print()
  99. print(f"【技术部分】")
  100. for ch in tech_chs:
  101. children_info = f" ({len(ch.children)} 节)" if ch.children else ""
  102. print(f" 第{ch.id}章 {ch.title} — 目标 {ch.word_count_target:,} 字{children_info}")
  103. for child in ch.children:
  104. print(f" {child.id} {child.title} — {child.word_count_target:,} 字")
  105. if appendix_chs:
  106. print()
  107. print("【附件】")
  108. for ch in appendix_chs:
  109. print(f" {ch.id} {ch.title}")
  110. total = sum(ch.word_count_target for ch in outline.chapters)
  111. print(f"\n章节总字数目标: {total:,} (配置最低: 170,000)")
  112. print()
  113. print("Step 3 测试完成 [OK]")