test_v4_walk_contract.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import json
  2. from pathlib import Path
  3. ROOT = Path(__file__).resolve().parents[1]
  4. DATA_DIR = ROOT / "tech_documents/数据接口与来源"
  5. def test_v4_query_source_refs_support_three_sources():
  6. query = {
  7. "pattern_seed_ref": {"query_source_type": "seed"},
  8. "raw_payload": {
  9. "query_source_refs": [
  10. {"query_source_type": "seed", "source_rank": 1},
  11. {"query_source_type": "topic_point", "source_rank": 2},
  12. {"query_source_type": "terminal_element", "source_rank": 3},
  13. ]
  14. },
  15. }
  16. source_types = {ref["query_source_type"] for ref in query["raw_payload"]["query_source_refs"]}
  17. assert source_types == {"seed", "topic_point", "terminal_element"}
  18. def test_v4_tag_walk_contract_uses_80_threshold_and_raw_tag_query():
  19. tag_action = {
  20. "edge_id": "hashtag_to_query",
  21. "raw_payload": {
  22. "tag_text": "父爱感悟",
  23. "next_query": "父爱感悟",
  24. "tag_query_relevance_score": 80,
  25. "tag_walk_status": "allow",
  26. },
  27. }
  28. payload = tag_action["raw_payload"]
  29. assert payload["tag_query_relevance_score"] >= 80
  30. assert payload["next_query"] == payload["tag_text"]
  31. assert payload["tag_walk_status"] == "allow"
  32. def test_v4_douyin_author_gate_contract_uses_elderly_ratio_and_tgi():
  33. author_asset = {
  34. "platform": "douyin",
  35. "elderly_ratio": 0.61,
  36. "elderly_tgi": 121,
  37. "eligible_as_source": 1,
  38. "profile_snapshot": {"source": "hotspotbao_account_profile"},
  39. "evidence_refs": {"decision_ids": ["decision_001"]},
  40. }
  41. assert author_asset["elderly_ratio"] > 0.6
  42. assert author_asset["elderly_tgi"] > 120
  43. assert author_asset["eligible_as_source"] == 1
  44. assert author_asset["profile_snapshot"]
  45. assert author_asset["evidence_refs"]
  46. def test_v4_three_short_video_platform_author_work_edges_match_m3_contract():
  47. douyin = _json(DATA_DIR / "platform_profiles/douyin.json")
  48. kuaishou = _json(DATA_DIR / "platform_profiles/kuaishou.json")
  49. shipinhao = _json(DATA_DIR / "platform_profiles/shipinhao.json")
  50. assert douyin["edges"]["author_to_works"]["status"] == "supported"
  51. assert douyin["edges"]["author_work_to_content"]["status"] == "supported"
  52. assert kuaishou["edges"]["author_to_works"]["status"] == "supported"
  53. assert kuaishou["edges"]["author_work_to_content"]["status"] == "supported"
  54. assert kuaishou["endpoints"]["blogger"]["path"] == "/crawler/kuai_shou/blogger"
  55. assert shipinhao["edges"]["author_to_works"]["status"] == "blocked"
  56. assert shipinhao["edges"]["author_work_to_content"]["status"] == "blocked"
  57. assert shipinhao["endpoints"]["account_info"]["status"] == "blocked"
  58. def test_v4_m2_platform_profiles_define_observable_fields():
  59. for platform in ["douyin", "kuaishou", "shipinhao"]:
  60. profile = _json(DATA_DIR / f"platform_profiles/{platform}.json")
  61. covered = {
  62. item["field"]
  63. for item in profile["observable_fields"] + profile["missing_observable_fields"]
  64. }
  65. assert covered == {
  66. "statistics.digg_count",
  67. "statistics.comment_count",
  68. "statistics.share_count",
  69. "statistics.collect_count",
  70. "statistics.play_count",
  71. }
  72. for item in profile["missing_observable_fields"]:
  73. assert item["missing_type"] in {"natural_platform_missing", "runtime_missing"}
  74. def test_v4_video_and_author_dedup_keys_are_separate():
  75. dedup = _json(DATA_DIR / "walk_policy.json")["dedup"]
  76. assert "platform_content_id" in dedup["content_key"]
  77. assert "platform_author_id" in dedup["author_key"]
  78. assert dedup["content_key"] != dedup["author_key"]
  79. def test_v4_walk_policy_defines_allow_walk_gate():
  80. gate = _json(DATA_DIR / "walk_policy.json")["v4_walk_gate"]
  81. assert gate["requires_allow_walk"] is True
  82. assert gate["deny_reason_code"] == "v4_allow_walk_denied"
  83. assert set(gate["applies_to_edges"]) == {
  84. "hashtag_to_query",
  85. "author_to_works",
  86. }
  87. assert {
  88. "decision_id",
  89. "allow_walk",
  90. "allow_walk_reason",
  91. "walk_gate_snapshot",
  92. } <= set(gate["raw_payload_fields"])
  93. def test_v4_walk_strategy_json_and_excel_define_allow_walk_gate():
  94. import pytest
  95. openpyxl = pytest.importorskip("openpyxl")
  96. strategy = _json(ROOT / "product_documents/抖音游走策略/douyin_walk_strategy.v1.json")
  97. gate = strategy["v4_walk_gate"][0]
  98. assert gate["gate_id"] == "allow_walk_required"
  99. assert gate["deny_reason_code"] == "v4_allow_walk_denied"
  100. workbook = openpyxl.load_workbook(
  101. ROOT / "tech_documents/游走策略/游走策略配置表.xlsx",
  102. read_only=True,
  103. data_only=True,
  104. )
  105. assert "v4_walk_gate" in workbook.sheetnames
  106. rows = list(workbook["v4_walk_gate"].iter_rows(values_only=True))
  107. assert rows[0][:4] == (
  108. "gate_id",
  109. "requires_allow_walk",
  110. "source_field",
  111. "deny_reason_code",
  112. )
  113. assert rows[2][0] == "allow_walk_required"
  114. assert rows[2][3] == "v4_allow_walk_denied"
  115. def _json(path: Path):
  116. return json.loads(path.read_text(encoding="utf-8"))