policy_json.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. from __future__ import annotations
  2. from pathlib import Path
  3. from typing import Any
  4. from content_agent.constants import DEFAULT_POLICY_BUNDLE_ID, EVIDENCE_BUNDLE_SCHEMA_VERSION, RUNTIME_SCHEMA_VERSION
  5. from content_agent.errors import ContentAgentError, ErrorCode
  6. from content_agent.integrations import config_store
  7. from content_agent.integrations.walk_strategy_json import WalkStrategyStore
  8. class JsonPolicyBundleStore:
  9. def __init__(self, root_dir: Path | str = Path(".")) -> None:
  10. self.root_dir = Path(root_dir)
  11. def load_policy_bundle(self, strategy_version: str) -> dict[str, Any]:
  12. rule_pack_path = self.root_dir / "product_documents/规则包/douyin_rule_packs.v1.json"
  13. if not rule_pack_path.exists():
  14. raise FileNotFoundError(rule_pack_path)
  15. rule_package, rule_pack_text = config_store.load_json(rule_pack_path)
  16. rule_pack_hash = config_store.sha256_text(rule_pack_text)
  17. actual_strategy_version = _strategy_version(rule_package)
  18. if strategy_version != actual_strategy_version:
  19. raise ValueError(f"unknown strategy_version: {strategy_version}")
  20. dispatch = _select_dispatch(rule_package, actual_strategy_version)
  21. rule_pack = _find_rule_pack_by_dispatch(rule_package, dispatch)
  22. rule_pack_by_entity = _build_rule_pack_by_entity(rule_package, actual_strategy_version)
  23. walk_strategy = WalkStrategyStore(self.root_dir).load_walk_strategy()
  24. bundle = {
  25. "policy_bundle_id": DEFAULT_POLICY_BUNDLE_ID,
  26. "strategy_version": actual_strategy_version,
  27. "rule_package_id": rule_package.get("package_id"),
  28. "rule_package_name": rule_package.get("package_name"),
  29. "rule_pack": rule_pack,
  30. "rule_pack_id": rule_pack["rule_pack_id"],
  31. "rule_pack_version": rule_pack["version"],
  32. "dispatch": dispatch,
  33. "dispatch_id": dispatch["dispatch_id"],
  34. "rule_pack_by_entity": rule_pack_by_entity,
  35. "runtime_stage": dispatch["runtime_stage"],
  36. "target_entity": dispatch["target_entity"],
  37. "content_format": dispatch["content_format"],
  38. "evidence_bundle_schema_version": EVIDENCE_BUNDLE_SCHEMA_VERSION,
  39. "runtime_record_schema_version": RUNTIME_SCHEMA_VERSION,
  40. "shared_contracts": rule_package.get("shared_contracts", {}),
  41. "source_evidence_policy": rule_package.get("source_evidence_policy", {}),
  42. "effect_status_mapping": rule_package.get("effect_status_mapping", []),
  43. "query_effect_aggregation": rule_package.get("query_effect_aggregation", []),
  44. "runtime_status_contract": rule_package.get("runtime_status_contract", {}),
  45. "decision_reason_codes": rule_package.get("decision_reason_codes", []),
  46. "strategy_id": (rule_package.get("strategy_binding") or {}).get(
  47. "strategy_id", "douyin_content_find_v1"
  48. ),
  49. "walk_strategy_id": walk_strategy.get("strategy_id"),
  50. "walk_strategy_version": walk_strategy.get("walk_strategy_version"),
  51. "walk_strategy_source_ref": walk_strategy.get("walk_strategy_source_ref"),
  52. "strategy_source_ref": {
  53. "file": str(rule_pack_path),
  54. "updated_at": rule_package.get("updated_at"),
  55. "content_sha256": rule_pack_hash,
  56. "generated_from": "p5_rule_pack_only",
  57. },
  58. "rule_pack_source_ref": {
  59. "file": str(rule_pack_path),
  60. "updated_at": rule_package.get("updated_at"),
  61. "content_sha256": rule_pack_hash,
  62. "generated_from": "local_product_json",
  63. },
  64. "policy_bundle_hash": rule_pack_hash,
  65. }
  66. return bundle
  67. def _strategy_version(rule_package: dict[str, Any]) -> str:
  68. binding_version = (rule_package.get("strategy_binding") or {}).get("strategy_version")
  69. return binding_version or "V1"
  70. def _select_dispatch(
  71. rule_package: dict[str, Any],
  72. strategy_version: str,
  73. *,
  74. target_entity: str = "Content",
  75. content_format: str = "video",
  76. runtime_stage: str = "V1.0",
  77. platform: str = "douyin",
  78. ) -> dict[str, Any]:
  79. matches = _enabled_dispatches(
  80. rule_package,
  81. strategy_version,
  82. target_entity=target_entity,
  83. content_format=content_format,
  84. runtime_stage=runtime_stage,
  85. platform=platform,
  86. )
  87. return _assert_single_enabled_dispatch(matches, target_entity=target_entity, content_format=content_format)
  88. def _enabled_dispatches(
  89. rule_package: dict[str, Any],
  90. strategy_version: str,
  91. *,
  92. target_entity: str,
  93. content_format: str,
  94. runtime_stage: str,
  95. platform: str,
  96. ) -> list[dict[str, Any]]:
  97. return [
  98. dispatch
  99. for dispatch in rule_package.get("rule_pack_dispatch", [])
  100. if dispatch.get("dispatch_enabled")
  101. and dispatch.get("platform") == platform
  102. and dispatch.get("runtime_stage") == runtime_stage
  103. and dispatch.get("strategy_version") == strategy_version
  104. and dispatch.get("target_entity") == target_entity
  105. and dispatch.get("content_format") == content_format
  106. ]
  107. def _assert_single_enabled_dispatch(
  108. matches: list[dict[str, Any]],
  109. *,
  110. target_entity: str,
  111. content_format: str,
  112. ) -> dict[str, Any]:
  113. if len(matches) == 1:
  114. return matches[0]
  115. if not matches:
  116. raise ValueError(f"dispatch not found for {target_entity}/{content_format}")
  117. conflict_rule_pack_ids = [dispatch.get("rule_pack_id") for dispatch in matches]
  118. raise ContentAgentError(
  119. ErrorCode.CONFIG_RULE_PACK_DISPATCH_CONFLICT,
  120. f"CONFIG_RULE_PACK_DISPATCH_CONFLICT: multiple enabled dispatches for "
  121. f"{target_entity}/{content_format}: {conflict_rule_pack_ids}",
  122. {"target_entity": target_entity, "content_format": content_format,
  123. "conflict_rule_pack_ids": conflict_rule_pack_ids},
  124. )
  125. def _build_rule_pack_by_entity(rule_package: dict[str, Any], strategy_version: str) -> dict[str, Any]:
  126. by_entity: dict[str, Any] = {}
  127. for dispatch in rule_package.get("rule_pack_dispatch", []):
  128. if not (
  129. dispatch.get("dispatch_enabled")
  130. and dispatch.get("platform") == "douyin"
  131. and dispatch.get("runtime_stage") == "V1.0"
  132. and dispatch.get("strategy_version") == strategy_version
  133. ):
  134. continue
  135. by_entity[dispatch["target_entity"]] = {
  136. "dispatch": dispatch,
  137. "rule_pack": _find_rule_pack_by_dispatch(rule_package, dispatch),
  138. }
  139. return by_entity
  140. def _find_rule_pack_by_dispatch(rule_package: dict[str, Any], dispatch: dict[str, Any]) -> dict[str, Any]:
  141. matches = [
  142. rule_pack
  143. for rule_pack in rule_package.get("rule_packs", [])
  144. if rule_pack.get("enabled")
  145. and rule_pack.get("rule_pack_id") == dispatch.get("rule_pack_id")
  146. and rule_pack.get("version") == dispatch.get("rule_pack_version")
  147. ]
  148. if len(matches) != 1:
  149. raise ValueError(
  150. f"dispatch {dispatch.get('dispatch_id')} matched {len(matches)} enabled rule packs"
  151. )
  152. return matches[0]