test_v4_rule_pack_contract.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. from pathlib import Path
  2. from copy import deepcopy
  3. import json
  4. from scripts.validate_v4_config_contract import (
  5. _check_v4_rule_pack_contract,
  6. assert_no_v4_legacy_fields,
  7. validate_v4_config_contract,
  8. )
  9. ROOT = Path(__file__).resolve().parents[1]
  10. def test_v4_config_contract_passes_with_m3_rule_pack_switch():
  11. assert validate_v4_config_contract(ROOT) == []
  12. def test_v4_contract_fixture_contains_no_legacy_rule_fields():
  13. fixture = {
  14. "schema_version": "v4_scorecard.v1",
  15. "scorecard": {
  16. "query_relevance_score": 80,
  17. "platform_performance_score": 70,
  18. "missing_observable_fields": [],
  19. },
  20. "decision_replay_data": {"allow_walk": True},
  21. }
  22. assert assert_no_v4_legacy_fields(fixture) == []
  23. def test_v4_legacy_blocklist_reports_precise_paths():
  24. fixture = {
  25. "schema_version": "v4_scorecard.v1",
  26. "scorecard": {
  27. "query_relevance_score": 80,
  28. "platform_heat": 70,
  29. },
  30. }
  31. assert assert_no_v4_legacy_fields(fixture) == ["v4_contract.scorecard.platform_heat"]
  32. def test_production_v4_rule_pack_contains_no_legacy_scoring_fields():
  33. pkg = json.loads(
  34. (ROOT / "product_documents/规则包/douyin_rule_packs.v1.json").read_text(encoding="utf-8")
  35. )
  36. pack = pkg["rule_packs"][0]
  37. assert pkg["strategy_binding"]["strategy_version"] == "V4"
  38. assert pack["scorecard"]["schema_version"] == "v4_scorecard.v1"
  39. assert assert_no_v4_legacy_fields(pack, "rule_pack") == []
  40. assert [row["key"] for row in pack["scorecard"]["dimensions"]] == [
  41. "query_relevance",
  42. "platform_performance",
  43. "fifty_plus",
  44. ]
  45. assert [row["max_score"] for row in pack["scorecard"]["dimensions"]] == [50, 50, 100]
  46. assert [row["weight_percent"] for row in pack["scorecard"]["dimensions"]] == [50, 50, 30]
  47. profiles = pack["scorecard"]["score_weight_profiles"]["profiles"]
  48. by_id = {profile["profile_id"]: profile for profile in profiles}
  49. assert by_id["base_video_two_dimension_v1"]["weights"] == {
  50. "query_relevance": 50,
  51. "platform_performance": 50,
  52. }
  53. assert by_id["audience_50plus_available_v1"]["weights"] == {
  54. "query_relevance": 35,
  55. "platform_performance": 35,
  56. "fifty_plus": 30,
  57. }
  58. assert by_id["audience_50plus_skipped_by_query_gate_v1"]["weights"] == {
  59. "query_relevance": 35,
  60. "platform_performance": 35,
  61. }
  62. assert {
  63. alias
  64. for profile in profiles
  65. for alias in profile.get("historical_alias_ids", [])
  66. } == {
  67. "default_two_dimension",
  68. "douyin_fifty_plus_ok",
  69. "douyin_fifty_plus_not_attempted",
  70. }
  71. def test_v4_contract_allows_extra_active_dimension_but_requires_base_dimensions():
  72. pkg = json.loads(
  73. (ROOT / "product_documents/规则包/douyin_rule_packs.v1.json").read_text(encoding="utf-8")
  74. )
  75. with_extra = deepcopy(pkg)
  76. scorecard = with_extra["rule_packs"][0]["scorecard"]
  77. scorecard["dimensions"].append(
  78. {
  79. "key": "fifty_plus_fit",
  80. "label": "50+ 匹配",
  81. "max_score": 20,
  82. "weight_percent": 20,
  83. "runtime_status": "active",
  84. }
  85. )
  86. scorecard["scoring_rules"].append(
  87. {
  88. "scoring_rule_id": "score_fifty_plus_precomputed",
  89. "dimension_key": "fifty_plus_fit",
  90. }
  91. )
  92. findings: list[dict[str, str]] = []
  93. _check_v4_rule_pack_contract(findings, with_extra)
  94. assert findings == []
  95. missing_base = deepcopy(pkg)
  96. missing_base["rule_packs"][0]["scorecard"]["dimensions"] = [
  97. row
  98. for row in missing_base["rule_packs"][0]["scorecard"]["dimensions"]
  99. if row["key"] != "platform_performance"
  100. ]
  101. findings = []
  102. _check_v4_rule_pack_contract(findings, missing_base)
  103. assert any(finding["check_id"] == "v4_scorecard_dimensions_invalid" for finding in findings)
  104. def test_v4_contract_rejects_invalid_m2_score_weight_profiles():
  105. pkg = json.loads(
  106. (ROOT / "product_documents/规则包/douyin_rule_packs.v1.json").read_text(encoding="utf-8")
  107. )
  108. missing_profile = deepcopy(pkg)
  109. missing_profile["rule_packs"][0]["scorecard"]["score_weight_profiles"]["profiles"][2]["historical_alias_ids"] = []
  110. findings: list[dict[str, str]] = []
  111. _check_v4_rule_pack_contract(findings, missing_profile)
  112. assert any(finding["check_id"] == "v4_score_weight_profiles_invalid" for finding in findings)
  113. unknown_dimension = deepcopy(pkg)
  114. unknown_dimension["rule_packs"][0]["scorecard"]["score_weight_profiles"]["profiles"][0]["weights"]["made_up_dimension"] = 10
  115. findings = []
  116. _check_v4_rule_pack_contract(findings, unknown_dimension)
  117. assert any(finding["check_id"] == "v4_score_weight_profile_dimension_unknown" for finding in findings)