dim_constants.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """generate_demand_agent 分类树热度维度共享常量与工具函数。"""
  2. from __future__ import annotations
  3. from decimal import Decimal
  4. from supply_infra.db.models.category_tree_weight import CategoryTreeWeight
  5. from supply_infra.db.models.global_tree_category import GlobalTreeCategory
  6. DIM_KEYS: tuple[str, ...] = (
  7. "ext_pop",
  8. "plat_sust_pop",
  9. "plat_ly_pop",
  10. "recent_pop",
  11. )
  12. DIM_LABEL: dict[str, str] = {
  13. "ext_pop": "外部热度",
  14. "plat_sust_pop": "平台持续热度",
  15. "plat_ly_pop": "平台去年同期热度",
  16. "recent_pop": "近期热度",
  17. }
  18. # 节点下存在有数据的叶子节点时的标记
  19. HAS_DATA_LEAF_MARK = "+"
  20. def normalize_parent_id(parent_id: int | None) -> int | None:
  21. if parent_id is None or parent_id == 0:
  22. return None
  23. return parent_id
  24. def format_score(avg: Decimal | float | int | None) -> str:
  25. if avg is None:
  26. return "—"
  27. value = float(avg)
  28. if value >= 100:
  29. return f"{value:.0f}"
  30. if value >= 1:
  31. return f"{value:.1f}"
  32. return f"{value:.2f}"
  33. def dim_score(
  34. weight: CategoryTreeWeight | None,
  35. dim: str,
  36. ) -> tuple[float | None, int]:
  37. """返回 (avg, count);count>0 即有维度数据(avg 为 0 也算有)。"""
  38. if weight is None:
  39. return None, 0
  40. count = int(getattr(weight, f"{dim}_count", 0) or 0)
  41. if count <= 0:
  42. return None, 0
  43. avg = getattr(weight, f"{dim}_avg", None)
  44. return (float(avg) if avg is not None else 0.0), count
  45. def build_children_map(
  46. categories: list[GlobalTreeCategory],
  47. ) -> dict[int | None, list[GlobalTreeCategory]]:
  48. children_map: dict[int | None, list[GlobalTreeCategory]] = {}
  49. for category in categories:
  50. parent_key = normalize_parent_id(category.parent_id)
  51. children_map.setdefault(parent_key, []).append(category)
  52. for children in children_map.values():
  53. children.sort(key=lambda c: (c.level or 0, c.id))
  54. return children_map
  55. def build_score_by_id(
  56. weights: list[CategoryTreeWeight],
  57. dim: str,
  58. ) -> dict[int, tuple[float | None, int]]:
  59. return {int(row.category_id): dim_score(row, dim) for row in weights}
  60. def collect_descendant_leaves(
  61. root_id: int,
  62. children_map: dict[int | None, list[GlobalTreeCategory]],
  63. ) -> list[int]:
  64. """收集 root 子树内的叶子节点 id(若 root 本身无子节点则含 root)。"""
  65. leaves: list[int] = []
  66. stack = [root_id]
  67. seen: set[int] = set()
  68. while stack:
  69. cid = stack.pop()
  70. if cid in seen:
  71. continue
  72. seen.add(cid)
  73. kids = children_map.get(cid, [])
  74. if not kids:
  75. leaves.append(cid)
  76. else:
  77. stack.extend(int(c.id) for c in kids)
  78. leaves.sort()
  79. return leaves