test_rule_pack_reading.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. from copy import deepcopy
  2. from pathlib import Path
  3. from content_agent.business_modules.rule_judgment.evaluator import decide
  4. from content_agent.errors import ErrorCode
  5. from content_agent.run_service import RunService
  6. from content_agent.schemas import RunStartRequest
  7. from tests.p1_helpers import FakeQueryVariantClient, REAL_SOURCE_FIXTURE
  8. def test_rule_pack_thresholds_drive_decision(tmp_path):
  9. service = RunService(
  10. runtime_root=tmp_path / "runtime" / "v1",
  11. query_variant_client=FakeQueryVariantClient(),
  12. )
  13. state = service.start_run(
  14. RunStartRequest(platform_mode="mock", source=str(REAL_SOURCE_FIXTURE))
  15. )
  16. run_id = state["run_id"]
  17. policy_bundle = deepcopy(state["policy_bundle"])
  18. thresholds = policy_bundle["rule_pack"]["thresholds"]
  19. thresholds[0]["min_score"] = 80
  20. thresholds[1]["min_score"] = 70
  21. thresholds[1]["max_score"] = 79
  22. # 2-dim score(2026-06-12 贴题档位 45/25→40/20):relevance 0.6 -> 40, platform_heat 0.6 -> 30
  23. # => 70, lands in the reconfigured 70<=score<=79 review band.
  24. bundle = deepcopy(state["evidence_bundles"][0])
  25. bundle["pattern_match_result"]["relevance_score"] = 0.6
  26. bundle["content_engagement_metrics"]["platform_heat"] = 0.6
  27. decision = decide(run_id, state["policy_run_id"], 1, bundle, policy_bundle)
  28. assert decision["score"] == 70
  29. assert decision["decision_action"] == "KEEP_CONTENT_FOR_REVIEW"
  30. assert decision["decision_reason_code"] == "content_score_review"
  31. assert decision["decision_replay_data"]["matched_threshold"] == "70<=score<=79"
  32. assert decision["policy_run_id"] == state["policy_run_id"]
  33. assert decision["strategy_version"] == "V1"
  34. def test_rule_pack_hard_gate_reason_code_drives_decision(tmp_path):
  35. service = RunService(
  36. runtime_root=tmp_path / "runtime" / "v1",
  37. query_variant_client=FakeQueryVariantClient(),
  38. )
  39. state = service.start_run(
  40. RunStartRequest(platform_mode="mock", source=str(REAL_SOURCE_FIXTURE))
  41. )
  42. run_id = state["run_id"]
  43. bundle = deepcopy(state["evidence_bundles"][0])
  44. bundle["source_evidence"] = {}
  45. decision = decide(run_id, state["policy_run_id"], 1, bundle, state["policy_bundle"])
  46. assert decision["decision_action"] == "REJECT_CONTENT"
  47. assert decision["decision_reason_code"] == "missing_source_evidence"
  48. assert decision["search_query_effect_status"] == "rule_blocked"
  49. assert decision["triggered_blocking_rules"] == ["missing_source_evidence"]
  50. assert decision["decision_replay_data"]["primary_reason_code"] == "missing_source_evidence"
  51. def test_missing_score_uses_rule_pack_missing_policy(tmp_path):
  52. service = RunService(
  53. runtime_root=tmp_path / "runtime" / "v1",
  54. query_variant_client=FakeQueryVariantClient(),
  55. )
  56. state = service.start_run(
  57. RunStartRequest(platform_mode="mock", source=str(REAL_SOURCE_FIXTURE))
  58. )
  59. run_id = state["run_id"]
  60. bundle = deepcopy(state["evidence_bundles"][0])
  61. # No evidence for either active dim -> rule pack's score_missing_policy applies.
  62. bundle["pattern_match_result"].pop("relevance_score", None)
  63. bundle["content_engagement_metrics"].pop("platform_heat", None)
  64. decision = decide(run_id, state["policy_run_id"], 1, bundle, state["policy_bundle"])
  65. assert decision["decision_action"] == "REJECT_CONTENT"
  66. assert decision["decision_reason_code"] == "missing_score"
  67. assert decision["search_query_effect_status"] == "failed"
  68. assert decision["decision_replay_data"]["score_missing_policy"]["decision_reason_code"] == "missing_score"
  69. def test_unknown_strategy_version_fails_before_rule_decision(tmp_path):
  70. service = RunService(
  71. runtime_root=tmp_path / "runtime" / "v1",
  72. query_variant_client=FakeQueryVariantClient(),
  73. )
  74. state = service.start_run(
  75. RunStartRequest(
  76. platform_mode="mock",
  77. strategy_version="missing_strategy",
  78. source=str(REAL_SOURCE_FIXTURE),
  79. )
  80. )
  81. assert state["status"] == "failed"
  82. assert state["error_code"] == ErrorCode.POLICY_BUNDLE_NOT_FOUND.value
  83. assert state["errors"] == ["policy bundle not found"]
  84. def test_rule_judgment_does_not_call_pattern_recall_integrations():
  85. root = Path("content_agent/business_modules/rule_judgment")
  86. text = "\n".join(path.read_text(encoding="utf-8") for path in root.rglob("*.py"))
  87. assert "decode_api" not in text
  88. assert "category_match" not in text
  89. assert "match-paths" not in text
  90. def test_disabled_future_packs_not_marked_as_active_entity_packs():
  91. from content_agent.integrations.policy_json import JsonPolicyBundleStore
  92. bundle = JsonPolicyBundleStore().load_policy_bundle("V1")
  93. assert set(bundle["rule_pack_by_entity"]) == {"Content"}
  94. for entity in ("Author", "Hashtag", "Path", "Budget"):
  95. assert entity not in bundle["rule_pack_by_entity"]