test_pg_evidence_builder.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. import unittest
  2. from unittest.mock import patch
  3. from examples.demand.evidence_pack_builder import build_evidence_pack
  4. def _execution():
  5. return {"id": 581, "status": "success"}
  6. def _itemset(**overrides):
  7. data = {
  8. "id": 1607313,
  9. "execution_id": 581,
  10. "mining_config_id": 2081,
  11. "mining_config_execution_id": 581,
  12. "mining_config_scope": "topic",
  13. "scope": "topic",
  14. "item_count": 1,
  15. "support": 0.1,
  16. "absolute_support": 2,
  17. "matched_post_ids": ["p1", "p2"],
  18. }
  19. data.update(overrides)
  20. return data
  21. def _item(**overrides):
  22. data = {
  23. "itemset_item_id": 1,
  24. "itemset_id": 1607313,
  25. "category_id": 10,
  26. "bound_category_id": 10,
  27. "category_found": True,
  28. "category_execution_id": 581,
  29. "category_name": "综合性腐败",
  30. "category_path": "/事件行为/违纪违法/综合性腐败",
  31. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  32. "dimension": "实质",
  33. "point_type": "关键点",
  34. "element_name": None,
  35. }
  36. data.update(overrides)
  37. return data
  38. def _binding(**overrides):
  39. data = {
  40. "itemset_id": 1607313,
  41. "itemset_item_id": 1,
  42. "category_id": 10,
  43. "dimension": "实质",
  44. "point_type": "关键点",
  45. "element_name": None,
  46. "matched_element_count": 1,
  47. "matched_post_count": 1,
  48. "matched_post_ids": ["p1"],
  49. "sample_elements": [{"name": "综合性腐败", "category_path": "/事件行为/违纪违法/综合性腐败"}],
  50. }
  51. data.update(overrides)
  52. return data
  53. def _empty_binding(post_ids=None):
  54. binding = _binding()
  55. binding["matched_element_count"] = 0
  56. binding["matched_post_count"] = 0
  57. binding["matched_post_ids"] = post_ids or []
  58. binding["sample_elements"] = []
  59. return binding
  60. class PgEvidenceBuilderTest(unittest.TestCase):
  61. def _build(self, evidence_refs):
  62. return build_evidence_pack(
  63. 581,
  64. {"element_names": ["综合性腐败"], "evidence_refs": evidence_refs},
  65. trace_id="trace-test",
  66. demand_task_id=1,
  67. demand_content_id=1,
  68. )
  69. def _patch_success(self, itemsets=None):
  70. patches = [
  71. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value=_execution()),
  72. patch("examples.demand.evidence_pack_builder.query_itemset_evidence", return_value=itemsets or [_itemset()]),
  73. patch("examples.demand.evidence_pack_builder.query_itemset_items_with_categories", return_value=[_item()]),
  74. patch("examples.demand.evidence_pack_builder.query_element_bindings_for_items", return_value=[_binding()]),
  75. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  76. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  77. ]
  78. for patcher in patches:
  79. patcher.start()
  80. self.addCleanup(patcher.stop)
  81. def test_topic_itemset_builds_pg_evidence_pack(self):
  82. self._patch_success()
  83. result = self._build(
  84. {
  85. "source_kind": "pattern_itemset",
  86. "source_tool": "get_itemset_detail",
  87. "itemset_ids": [1607313],
  88. "source_post_id": "p1",
  89. "seed_terms": ["LLM伪造词"],
  90. }
  91. )
  92. self.assertTrue(result["success"])
  93. evidence_pack = result["evidence_pack"]
  94. self.assertEqual(evidence_pack["pattern_source_system"], "pg_pattern_v2")
  95. self.assertEqual(evidence_pack["validation_status"], "passed")
  96. self.assertEqual(evidence_pack["source_certainty"], "db_validated")
  97. self.assertEqual(evidence_pack["matched_post_ids"], ["p1", "p2"])
  98. self.assertEqual(evidence_pack["seed_terms"], ["综合性腐败"])
  99. self.assertIn("query_seed_points", evidence_pack)
  100. self.assertIn("demand_scope", evidence_pack)
  101. def test_non_topic_scope_is_rejected(self):
  102. self._patch_success(itemsets=[_itemset(scope="topic_element")])
  103. result = self._build(
  104. {
  105. "source_kind": "pattern_itemset",
  106. "itemset_ids": [1607313],
  107. "source_post_id": "p1",
  108. }
  109. )
  110. self.assertFalse(result["success"])
  111. self.assertIn("scope=topic_element", result["reject_reason"])
  112. def test_source_post_outside_matched_posts_is_rejected(self):
  113. self._patch_success()
  114. result = self._build(
  115. {
  116. "source_kind": "pattern_itemset",
  117. "itemset_ids": [1607313],
  118. "source_post_id": "not-a-source-post",
  119. }
  120. )
  121. self.assertFalse(result["success"])
  122. self.assertIn("not in matched_post_ids", result["reject_reason"])
  123. def test_falls_back_to_db_validated_source_post_with_element_binding(self):
  124. bindings = [
  125. [_empty_binding()],
  126. [{**_binding(), "matched_post_ids": ["p2"]}],
  127. [{**_binding(), "matched_post_ids": ["p2"]}],
  128. ]
  129. with (
  130. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value=_execution()),
  131. patch("examples.demand.evidence_pack_builder.query_itemset_evidence", return_value=[_itemset()]),
  132. patch("examples.demand.evidence_pack_builder.query_itemset_items_with_categories", return_value=[_item()]),
  133. patch("examples.demand.evidence_pack_builder.query_element_bindings_for_items", side_effect=bindings),
  134. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  135. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  136. ):
  137. result = self._build(
  138. {
  139. "source_kind": "pattern_itemset",
  140. "itemset_ids": [1607313],
  141. "source_post_id": "p1",
  142. }
  143. )
  144. self.assertTrue(result["success"])
  145. self.assertEqual(result["evidence_pack"]["source_post_id"], "p2")
  146. def test_multiple_itemsets_are_supported(self):
  147. patches = [
  148. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value=_execution()),
  149. patch(
  150. "examples.demand.evidence_pack_builder.query_itemset_evidence",
  151. return_value=[
  152. _itemset(),
  153. _itemset(id=1607314, mining_config_id=2082, absolute_support=2),
  154. ],
  155. ),
  156. patch(
  157. "examples.demand.evidence_pack_builder.query_itemset_items_with_categories",
  158. return_value=[
  159. _item(),
  160. _item(itemset_item_id=2, itemset_id=1607314, category_id=11, bound_category_id=11),
  161. ],
  162. ),
  163. patch(
  164. "examples.demand.evidence_pack_builder.query_element_bindings_for_items",
  165. return_value=[
  166. _binding(),
  167. _binding(itemset_id=1607314, itemset_item_id=2, category_id=11),
  168. ],
  169. ),
  170. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  171. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  172. ]
  173. for patcher in patches:
  174. patcher.start()
  175. self.addCleanup(patcher.stop)
  176. result = self._build(
  177. {
  178. "source_kind": "pattern_itemset",
  179. "itemset_ids": [1607313, 1607314],
  180. "source_post_id": "p1",
  181. }
  182. )
  183. self.assertTrue(result["success"])
  184. self.assertEqual(result["evidence_pack"]["itemset_ids"], [1607313, 1607314])
  185. self.assertEqual(result["evidence_pack"]["source_kind"], "pattern_itemset")
  186. def test_high_weight_element_source_builds_pg_evidence_pack(self):
  187. rows = [
  188. {
  189. "id": 1,
  190. "post_id": "p1",
  191. "source_table": "post_decode_topic_point_element",
  192. "source_element_id": 11,
  193. "point_type": "灵感点",
  194. "point_text": "反腐故事",
  195. "element_type": "实质",
  196. "name": "综合性腐败",
  197. "category_id": 10,
  198. "category_name": "综合性腐败",
  199. "category_path": "/事件行为/违纪违法/综合性腐败",
  200. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  201. "topic_point_id": 101,
  202. }
  203. ]
  204. with (
  205. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value={**_execution(), "post_count": 10}),
  206. patch("examples.demand.evidence_pack_builder.query_source_elements", return_value=rows),
  207. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  208. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  209. ):
  210. result = self._build(
  211. {
  212. "sources": [
  213. {
  214. "source_kind": "high_weight_element",
  215. "source_tool": "get_weight_score_topn",
  216. "element_names": ["综合性腐败"],
  217. "element_type": "实质",
  218. }
  219. ]
  220. }
  221. )
  222. self.assertTrue(result["success"])
  223. pack = result["evidence_pack"]
  224. self.assertEqual(pack["source_kind"], "high_weight_element")
  225. self.assertEqual(pack["itemset_ids"], [])
  226. self.assertEqual(pack["matched_post_ids"], ["p1"])
  227. self.assertEqual(pack["seed_terms"], ["综合性腐败"])
  228. self.assertEqual(pack["evidence_sources"][0]["source_kind"], "high_weight_element")
  229. def test_co_occurrence_alias_is_normalized(self):
  230. rows = [
  231. {
  232. "id": 1,
  233. "post_id": "p1",
  234. "source_table": "post_decode_topic_point_element",
  235. "source_element_id": 11,
  236. "point_type": "灵感点",
  237. "point_text": "反腐故事",
  238. "element_type": "实质",
  239. "name": "综合性腐败",
  240. "category_id": 10,
  241. "category_name": "综合性腐败",
  242. "category_path": "/事件行为/违纪违法/综合性腐败",
  243. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  244. "topic_point_id": 101,
  245. }
  246. ]
  247. with (
  248. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value={**_execution(), "post_count": 10}),
  249. patch("examples.demand.evidence_pack_builder.query_source_elements", return_value=rows),
  250. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  251. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  252. ):
  253. result = self._build(
  254. {
  255. "sources": [
  256. {
  257. "source_kind": "co-occurrence",
  258. "source_tool": "get_element_co_occurrences",
  259. "element_names": ["综合性腐败"],
  260. }
  261. ]
  262. }
  263. )
  264. self.assertTrue(result["success"])
  265. self.assertEqual(result["evidence_pack"]["source_kind"], "element_co_occurrence")
  266. def test_high_weight_category_source_builds_pg_evidence_pack(self):
  267. rows = [
  268. {
  269. "id": 1,
  270. "post_id": "p1",
  271. "source_table": "post_decode_topic_point_element",
  272. "source_element_id": 11,
  273. "point_type": "灵感点",
  274. "point_text": "反腐故事",
  275. "element_type": "实质",
  276. "name": "综合性腐败",
  277. "category_id": 10,
  278. "category_name": "综合性腐败",
  279. "category_path": "/事件行为/违纪违法/综合性腐败",
  280. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  281. "topic_point_id": 101,
  282. }
  283. ]
  284. with (
  285. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value={**_execution(), "post_count": 10}),
  286. patch("examples.demand.evidence_pack_builder.query_source_elements", return_value=rows),
  287. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  288. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  289. ):
  290. result = self._build(
  291. {
  292. "sources": [
  293. {
  294. "source_kind": "high_weight_category",
  295. "source_tool": "get_weight_score_topn",
  296. "category_ids": [10],
  297. }
  298. ]
  299. }
  300. )
  301. self.assertTrue(result["success"])
  302. pack = result["evidence_pack"]
  303. self.assertEqual(pack["source_kind"], "high_weight_category")
  304. self.assertEqual(pack["matched_post_ids"], ["p1"])
  305. self.assertEqual(pack["seed_terms"], ["综合性腐败"])
  306. self.assertEqual(pack["evidence_sources"][0]["source_kind"], "high_weight_category")
  307. def test_category_co_occurrence_source_builds_pg_evidence_pack(self):
  308. rows = [
  309. {
  310. "id": 1,
  311. "post_id": "p1",
  312. "source_table": "post_decode_topic_point_element",
  313. "source_element_id": 11,
  314. "point_type": "灵感点",
  315. "point_text": "反腐故事",
  316. "element_type": "实质",
  317. "name": "综合性腐败",
  318. "category_id": 10,
  319. "category_name": "综合性腐败",
  320. "category_path": "/事件行为/违纪违法/综合性腐败",
  321. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  322. "topic_point_id": 101,
  323. }
  324. ]
  325. with (
  326. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value={**_execution(), "post_count": 10}),
  327. patch("examples.demand.evidence_pack_builder.query_source_elements", return_value=rows),
  328. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  329. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  330. ):
  331. result = self._build(
  332. {
  333. "sources": [
  334. {
  335. "source_kind": "category_co_occurrence",
  336. "source_tool": "get_category_co_occurrences",
  337. "category_names": ["综合性腐败"],
  338. }
  339. ]
  340. }
  341. )
  342. self.assertTrue(result["success"])
  343. self.assertEqual(result["evidence_pack"]["source_kind"], "category_co_occurrence")
  344. if __name__ == "__main__":
  345. unittest.main()