| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- import unittest
- from unittest.mock import Mock
- from src.step3_question_answer.finance_display import add_detail_links, answer_link_position
- from src.step3_question_answer.knowledge_answers import synthesize_answers, LINK_RULE, synthesis_prompts
- def entry(index, kind='service_guide'):
- return {'source_id': str(index), 'title': f'合成事项{index}', 'knowledge_type': kind,
- 'detail_url': f'https://example.invalid/product/{index}',
- 'service_link': {'url': f'https://example.invalid/service/{index}',
- 'title': f'合成事项{index}', 'label': '查看办事指南'}}
- class LinkPlacementTests(unittest.TestCase):
- def test_original_and_other_question_bind_model_anchors_without_extra_calls(self):
- for question in ('我想开奶茶店,有什么惠企政策', '经营许可延续要多久'):
- records = [entry(0), entry(1, 'finance')]
- for record in records:
- record.update(answer='合成支持说明。', citations=[{'path': '/data/body', 'quote': '合成支持说明。'}])
- model = Mock(return_value={'text': '### 支持\n\n事项办理说明。\n\n产品条件说明。',
- 'source_ids': ['0', '1'], 'link_sections': [
- {'source_id': '0', 'heading': '支持', 'after': '事项办理说明。'},
- {'source_id': '1', 'heading': '支持', 'after': '产品条件说明。'}]})
- result = synthesize_answers(model, question, records, [], 'policy_overview')
- model.assert_called_once()
- self.assertIn(LINK_RULE, model.call_args.args[0])
- self.assertEqual(model.call_args.args[1]['question'], question)
- text = result['text']
- self.assertLess(text.index('事项办理说明。'), text.index('/service/0'))
- self.assertLess(text.index('/service/0'), text.index('产品条件说明。'))
- self.assertLess(text.index('产品条件说明。'), text.index('/product/1'))
- prompt, _ = synthesis_prompts({'question': '普通咨询', 'evidence': [{}]})
- self.assertNotIn(LINK_RULE, prompt)
- def test_channel_link_follows_explanation_and_precedes_materials(self):
- for channel, materials in (('**办理渠道**', '**申请材料**'),
- ('#### 办理渠道', '#### 申请材料')):
- body = f'### 合成事项0\n\n{channel}\n\n支持在线提交。\n\n{materials}\n\n提交申请表。'
- text, links = add_detail_links(body, [entry(0)], ['0'])
- pos = text.index('https://')
- self.assertGreater(pos, text.index('支持在线提交。'))
- self.assertLess(pos, text.index(materials))
- self.assertEqual(len(links), 1)
- def test_finance_and_service_anchors_follow_each_answer_in_shared_chapter(self):
- for count in (1, 3, 7):
- entries = [entry(i, 'finance' if i % 2 else 'service_guide') for i in range(count)]
- paragraphs = [f'第{i}项支持说明,限合成地区的对应主体。' for i in range(count)]
- body = '### 支持与办理\n\n' + '\n\n'.join(paragraphs) + '\n\n### 其他说明\n\n其他。'
- mapping = [{'source_id': str(i), 'heading': '支持与办理', 'after': p}
- for i, p in enumerate(paragraphs)]
- text, links = add_detail_links(body, entries, [str(i) for i in range(count)], mapping)
- self.assertEqual(len(links), count)
- for i, link in enumerate(links):
- pos = text.index(link['url'])
- self.assertGreater(pos, text.index(paragraphs[i]))
- following = paragraphs[i + 1] if i + 1 < count else '### 其他说明'
- self.assertLess(pos, text.index(following))
- again, again_links = add_detail_links(text, entries, [str(i) for i in range(count)], mapping)
- self.assertEqual((again, again_links), (text, links))
- def test_anchor_without_headings_keeps_each_list_answer_and_qualifier_intact(self):
- body = '- 第一项支持。限合成主体。\n- 第二项说明。\n\n补充说明。'
- text, _ = add_detail_links(body, [entry(0)], ['0'], [
- {'source_id': '0', 'heading': '', 'after': '第一项支持。'}])
- self.assertGreater(text.index('https://'), text.index('限合成主体。'))
- self.assertLess(text.index('https://'), text.index('第二项说明。'))
- def test_short_answer_early_inline_and_duplicate_markers_move_after_answer(self):
- for kind, prefix in (('finance', 'finance'), ('service_guide', 'service')):
- marker = f'[[{prefix}_detail:0]]'
- for body in (marker + '\n\n合成事项需要三个工作日。',
- '合成事项' + marker + '需要三个工作日。',
- '合成事项需要三个工作日。' + marker + marker):
- text, links = add_detail_links(body, [entry(0, kind)], ['0'], [])
- self.assertEqual(len(links), 1)
- self.assertGreater(text.index('https://'), text.index('需要三个工作日。'))
- self.assertNotIn('[[', text)
- self.assertEqual(add_detail_links(text, [entry(0, kind)], ['0'], []), (text, links))
- def test_empty_answer_and_ambiguous_multi_source_footer_do_not_gain_links(self):
- for body in ('### 合成事项0\n\n[[finance_detail:0]]', '[[finance_detail:0]]'):
- text, links = add_detail_links(body, [entry(0, 'finance')], ['0'])
- self.assertEqual(links, [])
- self.assertNotIn('https://', text)
- body = '甲段说明内容。\n\n乙段说明内容。\n\n[[finance_detail:0]]\n\n[[finance_detail:1]]'
- text, links = add_detail_links(body, [entry(0, 'finance'), entry(1, 'finance')], ['0', '1'])
- self.assertEqual(links, [])
- self.assertNotIn('https://', text)
- def test_wrong_footer_marker_cannot_override_a_known_chapter(self):
- body = ('### 合成事项0\n\n本项支持说明。\n\n### 无关事项\n\n其他说明。'
- '\n\n[[finance_detail:0]]')
- text, _ = add_detail_links(body, [entry(0, 'finance')], ['0'])
- self.assertGreater(text.index('https://'), text.index('本项支持说明。'))
- self.assertLess(text.index('https://'), text.index('### 无关事项'))
- def test_invalid_or_ambiguous_anchors_do_not_attach_to_unrelated_footer(self):
- body = '重复说明内容。\n\n重复说明内容。'
- for anchor in ('重复说明内容。', '不存在的说明内容。'):
- text, links = add_detail_links(body, [entry(0, 'finance')], ['0'], [
- {'source_id': '0', 'heading': '', 'after': anchor}])
- self.assertEqual(text, body)
- self.assertEqual(links, [])
- self.assertIsNone(answer_link_position('### 办理渠道\n\n实际办理说明。', '办理渠道'))
- def test_finance_nested_sections_are_complete_before_link(self):
- body = '### 合成事项0\n\n#### 额度\n\n上限说明。\n\n#### 条件\n\n限制说明。\n\n### 其他\n\n其他说明。'
- text, _ = add_detail_links(body, [entry(0, 'finance')], ['0'])
- self.assertGreater(text.index('https://'), text.index('限制说明。'))
- self.assertLess(text.index('https://'), text.index('### 其他'))
|