test_next_actions.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import copy
  2. import unittest
  3. from src.step3_question_answer.next_actions import build_next_actions, append_to_summary
  4. from src.step3_question_answer.recommendation import Recommender
  5. from src.step3_question_answer.report import render
  6. from test_recommendation import candidate, good_analysis, handoff, retrieval
  7. class NextActionTests(unittest.TestCase):
  8. def result(self):
  9. def model(prompt, data):
  10. if 'cards' in data:
  11. return {'text': '可了解合成支持,登记地区仍待确认。',
  12. 'source_ids': [card['source_id'] for card in data['cards']]}
  13. return good_analysis()
  14. return Recommender(model).answer(handoff(), retrieval())
  15. def test_actions_surface_pending_and_cite_exact_original(self):
  16. result = self.result()
  17. actions = result['next_actions']
  18. self.assertEqual(len(actions), 3)
  19. self.assertEqual(actions[0]['description'], '在哪个区登记?')
  20. self.assertEqual(actions[0]['citations'][0]['path'], '/data/conditions')
  21. self.assertEqual(actions[0]['citations'][0]['quote'], '在本区登记')
  22. self.assertEqual(result['summary']['status'], 'generated')
  23. html = render(result)
  24. self.assertEqual(html.count('<h3>下一步建议</h3>'), 1)
  25. self.assertIn('1. **确认相关地区**', result['summary']['text'])
  26. self.assertIn('3. ', result['summary']['text'])
  27. self.assertNotIn('相关政策要求:', result['summary']['text'])
  28. self.assertIn('相关政策要求:', actions[0]['reason'])
  29. self.assertIn('在哪个区登记?', html)
  30. self.assertIn('href="#source-' + result['sources'][0]['id'], html)
  31. self.assertLess(html.index('下一步建议'), html.index('<h2>完整参考资料'))
  32. def test_failed_policy_summary_does_not_request_application_details(self):
  33. result = Recommender(lambda *args: good_analysis()).answer(handoff(), retrieval())
  34. self.assertEqual(result['summary']['status'], 'fallback')
  35. self.assertEqual(result['next_actions'], [])
  36. self.assertEqual(result['follow_up_questions'], [])
  37. self.assertNotIn('下一步建议', result['summary']['text'])
  38. def test_duplicate_conditions_merge_sources_and_limit_three(self):
  39. result = self.result()
  40. first = result['items'][0]
  41. # Action generation uses full internal conditions before card projection.
  42. first['conditions'] = copy.deepcopy(result['candidate_diagnostics'][0]['analyses'][0]['conditions'])
  43. second = copy.deepcopy(first)
  44. second['source_id'] = 'another'
  45. source = copy.deepcopy(result['sources'][0])
  46. source['id'] = 'another'
  47. sources = result['sources'] + [source]
  48. actions = build_next_actions([first, second], sources)
  49. self.assertEqual(actions[0]['source_ids'], [first['source_id'], 'another'])
  50. self.assertEqual(sum(a['kind'] == 'confirm_information' for a in actions), 1)
  51. for index in range(5):
  52. condition = dict(first['conditions'][0], requirement=f'合成不同条件{index}', question=f'问题{index}')
  53. first['conditions'].append(condition)
  54. actions = build_next_actions([first], sources)
  55. self.assertEqual(len(actions), 3)
  56. self.assertTrue(all(a['kind'] == 'confirm_information' for a in actions))
  57. def test_no_evidence_not_confirmed_or_overview_has_no_forced_questions(self):
  58. result = self.result()
  59. for status, quote in [('满足', '在本区登记'), ('待确认', ''), ('待确认', '不存在的条件')]:
  60. result['items'][0]['conditions'] = [dict(good_analysis()['conditions'][0], status=status, quote=quote)]
  61. actions = build_next_actions(result['items'], result['sources'])
  62. self.assertEqual(len(actions), 3)
  63. self.assertFalse(any(a['kind'] == 'confirm_information' for a in actions))
  64. self.assertEqual(build_next_actions([], result['sources']), [])
  65. self.assertEqual(build_next_actions(result['items'], result['sources'], False), [])
  66. for field in ('support_methods_standards', 'application_conditions'):
  67. items = copy.deepcopy(result['items'])
  68. items[0]['card'][field] = None
  69. self.assertEqual(len(build_next_actions(items, result['sources'])), 3)
  70. def test_excluded_or_no_results_and_overview_do_not_emit_actions(self):
  71. for mode in ('overview', 'empty', 'failure', 'ineligible'):
  72. context, records = handoff(), retrieval()
  73. analysis = good_analysis()
  74. if mode == 'overview':
  75. context['conversation_kind'] = 'policy_overview'
  76. elif mode in ('empty', 'failure'):
  77. records = retrieval([], complete=mode != 'failure')
  78. else:
  79. analysis['conditions'][0].update(status='不满足', user_evidence=context['original_question'])
  80. result = Recommender(lambda *args: copy.deepcopy(analysis)).answer(context, records)
  81. self.assertEqual(result['next_actions'], [], mode)
  82. self.assertNotIn('### 下一步建议', result['summary']['text'])
  83. def test_action_text_is_html_escaped(self):
  84. result = self.result()
  85. result['next_actions'][0]['description'] = '<script>bad()</script>'
  86. result['summary'] = append_to_summary({'text': '原总结', 'source_ids': []}, result['next_actions'])
  87. self.assertIn('&lt;script&gt;bad()', render(result))
  88. self.assertNotIn('<script>bad()', render(result))
  89. def test_existing_summary_content_metadata_and_sources_preserved(self):
  90. actions = self.result()['next_actions']
  91. original = {'text': '原结论', 'source_ids': ['qa'], 'status': 'generated', 'notices': ['原提示']}
  92. updated = append_to_summary(original, actions)
  93. self.assertTrue(updated['text'].startswith('原结论\n\n### 下一步建议'))
  94. self.assertEqual(updated['source_ids'], ['qa', actions[0]['source_ids'][0]])
  95. self.assertEqual(updated['status'], 'generated')
  96. self.assertEqual(updated['notices'], ['原提示'])
  97. self.assertEqual(original['text'], '原结论')
  98. self.assertIs(append_to_summary(original, []), original)