test_pg_evidence_builder.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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_demand_scope_platform_is_canonicalized(self):
  102. self._patch_success()
  103. result = build_evidence_pack(
  104. 581,
  105. {
  106. "element_names": ["综合性腐败"],
  107. "evidence_refs": {
  108. "source_kind": "pattern_itemset",
  109. "source_tool": "get_itemset_detail",
  110. "itemset_ids": [1607313],
  111. "source_post_id": "p1",
  112. },
  113. },
  114. trace_id="trace-test",
  115. demand_task_id=1,
  116. demand_content_id=1,
  117. demand_scope={"platform": "票圈"},
  118. )
  119. self.assertTrue(result["success"])
  120. self.assertEqual(result["evidence_pack"]["demand_scope"]["platform"], "piaoquan")
  121. def test_defaults_to_piaoquan_scope_and_caps_query_seed_points(self):
  122. with (
  123. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value=_execution()),
  124. patch("examples.demand.evidence_pack_builder.query_itemset_evidence", return_value=[_itemset(matched_post_ids=["pq1", "pq2"])]) as itemset_evidence,
  125. patch("examples.demand.evidence_pack_builder.query_itemset_items_with_categories", return_value=[_item()]),
  126. patch("examples.demand.evidence_pack_builder.query_element_bindings_for_items", return_value=[_binding(matched_post_ids=["pq1"])]),
  127. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  128. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]) as seed_points,
  129. patch.dict("os.environ", {"DEMAND_QUERY_SEED_POINTS_TOP_K": "100"}),
  130. ):
  131. result = self._build(
  132. {
  133. "source_kind": "pattern_itemset",
  134. "source_tool": "get_itemset_detail",
  135. "itemset_ids": [1607313],
  136. "source_post_id": "pq1",
  137. }
  138. )
  139. self.assertTrue(result["success"])
  140. self.assertEqual(result["evidence_pack"]["demand_scope"]["platform"], "piaoquan")
  141. self.assertEqual(itemset_evidence.call_args.kwargs["platform"], "piaoquan")
  142. seed_points.assert_called_once()
  143. self.assertEqual(seed_points.call_args.kwargs["matched_post_ids"], ["pq1", "pq2"])
  144. self.assertEqual(seed_points.call_args.kwargs["top_k"], 30)
  145. def test_non_topic_scope_is_rejected(self):
  146. self._patch_success(itemsets=[_itemset(scope="topic_element")])
  147. result = self._build(
  148. {
  149. "source_kind": "pattern_itemset",
  150. "itemset_ids": [1607313],
  151. "source_post_id": "p1",
  152. }
  153. )
  154. self.assertFalse(result["success"])
  155. self.assertIn("scope=topic_element", result["reject_reason"])
  156. def test_source_post_outside_matched_posts_is_rejected(self):
  157. self._patch_success()
  158. result = self._build(
  159. {
  160. "source_kind": "pattern_itemset",
  161. "itemset_ids": [1607313],
  162. "source_post_id": "not-a-source-post",
  163. }
  164. )
  165. self.assertFalse(result["success"])
  166. self.assertIn("not in matched_post_ids", result["reject_reason"])
  167. def test_falls_back_to_db_validated_source_post_with_element_binding(self):
  168. bindings = [
  169. [_empty_binding()],
  170. [{**_binding(), "matched_post_ids": ["p2"]}],
  171. [{**_binding(), "matched_post_ids": ["p2"]}],
  172. ]
  173. with (
  174. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value=_execution()),
  175. patch("examples.demand.evidence_pack_builder.query_itemset_evidence", return_value=[_itemset()]),
  176. patch("examples.demand.evidence_pack_builder.query_itemset_items_with_categories", return_value=[_item()]),
  177. patch("examples.demand.evidence_pack_builder.query_element_bindings_for_items", side_effect=bindings),
  178. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  179. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  180. ):
  181. result = self._build(
  182. {
  183. "source_kind": "pattern_itemset",
  184. "itemset_ids": [1607313],
  185. "source_post_id": "p1",
  186. }
  187. )
  188. self.assertTrue(result["success"])
  189. self.assertEqual(result["evidence_pack"]["source_post_id"], "p2")
  190. def test_multiple_itemsets_are_supported(self):
  191. patches = [
  192. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value=_execution()),
  193. patch(
  194. "examples.demand.evidence_pack_builder.query_itemset_evidence",
  195. return_value=[
  196. _itemset(),
  197. _itemset(id=1607314, mining_config_id=2082, absolute_support=2),
  198. ],
  199. ),
  200. patch(
  201. "examples.demand.evidence_pack_builder.query_itemset_items_with_categories",
  202. return_value=[
  203. _item(),
  204. _item(itemset_item_id=2, itemset_id=1607314, category_id=11, bound_category_id=11),
  205. ],
  206. ),
  207. patch(
  208. "examples.demand.evidence_pack_builder.query_element_bindings_for_items",
  209. return_value=[
  210. _binding(),
  211. _binding(itemset_id=1607314, itemset_item_id=2, category_id=11),
  212. ],
  213. ),
  214. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  215. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  216. ]
  217. for patcher in patches:
  218. patcher.start()
  219. self.addCleanup(patcher.stop)
  220. result = self._build(
  221. {
  222. "source_kind": "pattern_itemset",
  223. "itemset_ids": [1607313, 1607314],
  224. "source_post_id": "p1",
  225. }
  226. )
  227. self.assertTrue(result["success"])
  228. self.assertEqual(result["evidence_pack"]["itemset_ids"], [1607313, 1607314])
  229. self.assertEqual(result["evidence_pack"]["source_kind"], "pattern_itemset")
  230. def test_high_weight_element_source_builds_pg_evidence_pack(self):
  231. rows = [
  232. {
  233. "id": 1,
  234. "post_id": "p1",
  235. "source_table": "post_decode_topic_point_element",
  236. "source_element_id": 11,
  237. "point_type": "灵感点",
  238. "point_text": "反腐故事",
  239. "element_type": "实质",
  240. "name": "综合性腐败",
  241. "category_id": 10,
  242. "category_name": "综合性腐败",
  243. "category_path": "/事件行为/违纪违法/综合性腐败",
  244. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  245. "topic_point_id": 101,
  246. }
  247. ]
  248. with (
  249. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value={**_execution(), "post_count": 10}),
  250. patch("examples.demand.evidence_pack_builder.query_source_elements", return_value=rows),
  251. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  252. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  253. ):
  254. result = self._build(
  255. {
  256. "sources": [
  257. {
  258. "source_kind": "high_weight_element",
  259. "source_tool": "get_weight_score_topn",
  260. "element_names": ["综合性腐败"],
  261. "element_type": "实质",
  262. }
  263. ]
  264. }
  265. )
  266. self.assertTrue(result["success"])
  267. pack = result["evidence_pack"]
  268. self.assertEqual(pack["source_kind"], "high_weight_element")
  269. self.assertEqual(pack["itemset_ids"], [])
  270. self.assertEqual(pack["matched_post_ids"], ["p1"])
  271. self.assertEqual(pack["seed_terms"], ["综合性腐败"])
  272. self.assertEqual(pack["evidence_sources"][0]["source_kind"], "high_weight_element")
  273. def test_co_occurrence_alias_is_normalized(self):
  274. rows = [
  275. {
  276. "id": 1,
  277. "post_id": "p1",
  278. "source_table": "post_decode_topic_point_element",
  279. "source_element_id": 11,
  280. "point_type": "灵感点",
  281. "point_text": "反腐故事",
  282. "element_type": "实质",
  283. "name": "综合性腐败",
  284. "category_id": 10,
  285. "category_name": "综合性腐败",
  286. "category_path": "/事件行为/违纪违法/综合性腐败",
  287. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  288. "topic_point_id": 101,
  289. }
  290. ]
  291. with (
  292. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value={**_execution(), "post_count": 10}),
  293. patch("examples.demand.evidence_pack_builder.query_source_elements", return_value=rows),
  294. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  295. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  296. ):
  297. result = self._build(
  298. {
  299. "sources": [
  300. {
  301. "source_kind": "co-occurrence",
  302. "source_tool": "get_element_co_occurrences",
  303. "element_names": ["综合性腐败"],
  304. }
  305. ]
  306. }
  307. )
  308. self.assertTrue(result["success"])
  309. self.assertEqual(result["evidence_pack"]["source_kind"], "element_co_occurrence")
  310. def test_high_weight_category_source_builds_pg_evidence_pack(self):
  311. rows = [
  312. {
  313. "id": 1,
  314. "post_id": "p1",
  315. "source_table": "post_decode_topic_point_element",
  316. "source_element_id": 11,
  317. "point_type": "灵感点",
  318. "point_text": "反腐故事",
  319. "element_type": "实质",
  320. "name": "综合性腐败",
  321. "category_id": 10,
  322. "category_name": "综合性腐败",
  323. "category_path": "/事件行为/违纪违法/综合性腐败",
  324. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  325. "topic_point_id": 101,
  326. }
  327. ]
  328. with (
  329. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value={**_execution(), "post_count": 10}),
  330. patch("examples.demand.evidence_pack_builder.query_source_elements", return_value=rows),
  331. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  332. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  333. ):
  334. result = self._build(
  335. {
  336. "sources": [
  337. {
  338. "source_kind": "high_weight_category",
  339. "source_tool": "get_weight_score_topn",
  340. "category_ids": [10],
  341. }
  342. ]
  343. }
  344. )
  345. self.assertTrue(result["success"])
  346. pack = result["evidence_pack"]
  347. self.assertEqual(pack["source_kind"], "high_weight_category")
  348. self.assertEqual(pack["matched_post_ids"], ["p1"])
  349. self.assertEqual(pack["seed_terms"], ["综合性腐败"])
  350. self.assertEqual(pack["evidence_sources"][0]["source_kind"], "high_weight_category")
  351. def test_category_co_occurrence_source_builds_pg_evidence_pack(self):
  352. rows = [
  353. {
  354. "id": 1,
  355. "post_id": "p1",
  356. "source_table": "post_decode_topic_point_element",
  357. "source_element_id": 11,
  358. "point_type": "灵感点",
  359. "point_text": "反腐故事",
  360. "element_type": "实质",
  361. "name": "综合性腐败",
  362. "category_id": 10,
  363. "category_name": "综合性腐败",
  364. "category_path": "/事件行为/违纪违法/综合性腐败",
  365. "category_full_path": "/事件行为/违纪违法/综合性腐败",
  366. "topic_point_id": 101,
  367. }
  368. ]
  369. with (
  370. patch("examples.demand.evidence_pack_builder.query_execution_for_evidence", return_value={**_execution(), "post_count": 10}),
  371. patch("examples.demand.evidence_pack_builder.query_source_elements", return_value=rows),
  372. patch("examples.demand.evidence_pack_builder.query_case_ids_by_post_ids", return_value=[]),
  373. patch("examples.demand.evidence_pack_builder.query_seed_points_for_sources", return_value=[]),
  374. ):
  375. result = self._build(
  376. {
  377. "sources": [
  378. {
  379. "source_kind": "category_co_occurrence",
  380. "source_tool": "get_category_co_occurrences",
  381. "category_names": ["综合性腐败"],
  382. }
  383. ]
  384. }
  385. )
  386. self.assertTrue(result["success"])
  387. self.assertEqual(result["evidence_pack"]["source_kind"], "category_co_occurrence")
  388. if __name__ == "__main__":
  389. unittest.main()