import copy import unittest from models import BidOutline, Chapter, SkeletonParagraph, TenderAnalysis from step3_outlining.outline_generator import _OutlineGenerator from step3_outlining.outline_report import build_outline_report, validate_outline_gate from step3_outlining.scoring_structure import apply_scoring_structure from scripts.tests.test_scoring_structure_and_business_forms import criterion, FakeMappingLlm class TemplateHeadingLevelTests(unittest.TestCase): def setUp(self): self.parent = Chapter(id='4.13', title='十三、应急预案和紧急事件处置措施', level=2, from_template=True) self.root = Chapter(id='4', title='基本服务方案', level=1, template_chapter_id='4', children=[self.parent]) self.skeletons = [SkeletonParagraph(para_idx=0, text=self.parent.title, similarity=1.0, inferred_level=1, is_heading=True)] def test_reference_h1_cannot_override_template_h2_or_scoring_children(self): template = BidOutline(project_name='测试', chapters=[copy.deepcopy(self.root)]) _OutlineGenerator.__new__(_OutlineGenerator)._apply_heading_levels([self.root], self.skeletons) self.assertEqual(self.parent.level, 2, '参考骨架H1不得覆盖模板H2') outline = BidOutline(project_name='测试', chapters=[self.root]) scoring = criterion('SC-01', '应急预案和紧急事件处置措施', '防汛应急预案') apply_scoring_structure(outline, [scoring], llm=FakeMappingLlm()) child = self.parent.children[-1] self.assertEqual(child.level, 3) self.assertTrue(child.title.startswith('(一)')) report, _ = build_outline_report(template, outline, TenderAnalysis(project_name='测试', scoring_criteria=[scoring])) self.assertIn('H2 `4.13`', report) self.assertIn(f'H3 `{child.id}`', report) def test_gate_rejects_nested_h1_and_wrong_new_child_level(self): template = BidOutline(project_name='测试', chapters=[copy.deepcopy(self.root)]) self.parent.level = 1 self.parent.children = [Chapter(id='4.13.1', title='错误子项', level=2)] errors = validate_outline_gate(template, BidOutline(project_name='测试', chapters=[self.root]), TenderAnalysis(project_name='测试')) self.assertTrue(any('4.13 H1,应为 H2' in error for error in errors)) self.assertTrue(any('4.13.1 H2,应为 H3' in error for error in errors)) def test_non_template_reference_inference_cannot_break_parent_tree(self): self.parent.from_template = False _OutlineGenerator.__new__(_OutlineGenerator)._apply_heading_levels([self.root], self.skeletons) self.assertEqual(self.parent.level, 2)