test_step4_reporting.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import importlib.util
  2. import os
  3. import sys
  4. import tempfile
  5. import unittest
  6. from unittest.mock import patch
  7. from docx import Document
  8. from models import (
  9. BidOutline,
  10. Chapter,
  11. ChapterType,
  12. ExtractedTable,
  13. ProjectData,
  14. TenderAnalysis,
  15. )
  16. SCRIPT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
  17. if SCRIPT_DIR not in sys.path:
  18. sys.path.insert(0, SCRIPT_DIR)
  19. SPEC = importlib.util.spec_from_file_location(
  20. "manual_test_step4", os.path.join(SCRIPT_DIR, "test_step4.py")
  21. )
  22. MODULE = importlib.util.module_from_spec(SPEC)
  23. SPEC.loader.exec_module(MODULE)
  24. class Step4ReportingTests(unittest.TestCase):
  25. def test_reference_bid_loader_extracts_paragraphs_and_tables(self):
  26. table = ExtractedTable(
  27. table_id="REF-T01",
  28. table_type="bid_detail_table",
  29. source_type="reference_docx",
  30. )
  31. project_data = ProjectData(project_id="test", project_name="测试项目")
  32. with tempfile.TemporaryDirectory() as directory:
  33. reference_path = os.path.join(directory, "reference.docx")
  34. Document().save(reference_path)
  35. with (
  36. patch(
  37. "doc_reader.reader.read_docx_paragraph_texts_et",
  38. return_value=["参考投书正文"],
  39. ),
  40. patch(
  41. "step1_parsing.table_extractor.extract_reference_tables_with_llm",
  42. return_value=[table],
  43. ) as extractor,
  44. ):
  45. count = MODULE._load_reference_bid(
  46. reference_path, project_data, directory
  47. )
  48. self.assertEqual(count, 1)
  49. self.assertEqual(project_data.reference_bids[0].content, "参考投书正文")
  50. self.assertIs(project_data.reference_tables[0], table)
  51. extractor.assert_called_once_with(
  52. reference_path,
  53. output_dir=os.path.join(directory, "内容提取_参考投书"),
  54. )
  55. def test_report_exposes_step2_project_fill_fields(self):
  56. analysis = TenderAnalysis(
  57. project_name="测试",
  58. agency_name="采购中心",
  59. project_fields={
  60. "服务内容": "物业管理服务",
  61. "服务要求": "符合质量考核要求",
  62. "服务期限": "三年",
  63. },
  64. )
  65. report = MODULE._build_report(
  66. BidOutline(project_name="测试", chapters=[]),
  67. analysis,
  68. )
  69. self.assertEqual(report["project_fields"]["采购机构名称"], "采购中心")
  70. self.assertEqual(report["project_fields"]["服务期限"], "三年")
  71. def test_unchanged_template_child_is_not_reported_as_empty(self):
  72. mapped = Chapter(
  73. id="1.2",
  74. title="响应能力",
  75. level=2,
  76. direct_scoring_bindings=["SC-01#1"],
  77. supplement_content="针对本项目建立可核验的响应机制。",
  78. )
  79. unchanged = Chapter(
  80. id="1.1", title="模板原有说明", level=2, from_template=True
  81. )
  82. top = Chapter(
  83. id="1",
  84. title="需求理解",
  85. chapter_type=ChapterType.TECHNICAL,
  86. children=[unchanged, mapped],
  87. preserve_template_layout=True,
  88. template_fill_completed=True,
  89. generated_content="模板原文",
  90. content_blocks=[
  91. {"order": 0, "block_type": "template_base"},
  92. {"order": 1, "block_type": "native_table_plan"},
  93. ],
  94. )
  95. outline = BidOutline(
  96. project_name="测试",
  97. chapters=[top],
  98. evaluation_index_entries=[{
  99. "entry_id": "SC-01#1",
  100. "entry_type": "scoring",
  101. "final_heading_id": "1.2",
  102. }],
  103. )
  104. report = MODULE._build_report(outline)
  105. self.assertTrue(report["passed"], report["errors"])
  106. self.assertEqual(report["chapters"][0]["supplement_node_count"], 1)
  107. def test_missing_direct_supplement_fails_gate(self):
  108. node = Chapter(
  109. id="1.1",
  110. title="响应能力",
  111. level=2,
  112. direct_scoring_bindings=["SC-01#1"],
  113. )
  114. outline = BidOutline(
  115. project_name="测试",
  116. chapters=[Chapter(id="1", title="技术章", children=[node])],
  117. evaluation_index_entries=[{
  118. "entry_id": "SC-01#1",
  119. "entry_type": "scoring",
  120. "final_heading_id": "1.1",
  121. }],
  122. )
  123. report = MODULE._build_report(outline)
  124. self.assertFalse(report["passed"])
  125. self.assertIn("直接落点缺少补充正文", report["errors"][0])
  126. def test_unresolved_placeholder_in_chapter_artifact_fails_gate(self):
  127. with tempfile.TemporaryDirectory() as directory:
  128. artifact_path = os.path.join(directory, "chapter.docx")
  129. document = Document()
  130. document.add_paragraph("项目编号:%%项目编号%%")
  131. document.save(artifact_path)
  132. chapter = Chapter(
  133. id="1",
  134. title="投标人资格、资信证明",
  135. artifact_path=artifact_path,
  136. )
  137. report = MODULE._build_report(BidOutline(
  138. project_name="测试",
  139. chapters=[chapter],
  140. ))
  141. self.assertFalse(report["passed"])
  142. self.assertTrue(any(
  143. "章节产物仍有未解析占位符" in error
  144. for error in report["errors"]
  145. ))
  146. if __name__ == "__main__":
  147. unittest.main()