auto_assign_grade_plan.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. """按需求数量自动划分分级计划组(替代统筹 Agent 的默认定时任务路径)。"""
  2. from __future__ import annotations
  3. import logging
  4. from typing import Any
  5. from agents.demand_grade_orchestrator_agent.common.assignment import (
  6. resolve_planning_state,
  7. )
  8. from agents.demand_grade_orchestrator_agent.common.plan_persist import persist_groups_one_by_one
  9. from agents.demand_grade_orchestrator_agent.common.plan_record import prepare_grade_groups
  10. from agents.demand_grade_orchestrator_agent.common.tree_state import load_tree_state
  11. from supply_infra.scheduler.plan_group_batch import (
  12. MAX_DEMANDS_PER_BATCH,
  13. pack_category_units,
  14. resolve_demands_for_category_ids,
  15. )
  16. logger = logging.getLogger(__name__)
  17. _DEFAULT_GROUPING_STRATEGY = "同分类与同父分类优先,每组约30个需求"
  18. _DEFAULT_REASON = "代码自动分配"
  19. _DEFAULT_TRAITS = _DEFAULT_GROUPING_STRATEGY
  20. def _parent_id(category_id: int, by_id: dict[int, Any]) -> int | None:
  21. row = by_id.get(category_id)
  22. if row is None:
  23. return None
  24. parent = getattr(row, "parent_id", None)
  25. if parent in (None, 0):
  26. return None
  27. return int(parent)
  28. def build_auto_assign_groups(
  29. biz_dt: str,
  30. *,
  31. unassigned_category_ids: list[int] | None = None,
  32. max_demands_per_group: int = MAX_DEMANDS_PER_BATCH,
  33. ) -> list[dict[str, Any]]:
  34. """为未分批分类节点生成自动分配批次(仅 category_ids,供 prepare_grade_groups 使用)。"""
  35. planning_state = resolve_planning_state(biz_dt)
  36. category_ids = list(unassigned_category_ids or planning_state["unassigned_category_ids"])
  37. if not category_ids:
  38. return []
  39. by_id, _children, _weights = load_tree_state(biz_dt)
  40. units: list[tuple[int, int | None, list[dict[str, Any]]]] = []
  41. for category_id in category_ids:
  42. demands = resolve_demands_for_category_ids(biz_dt, [category_id])
  43. if not demands:
  44. continue
  45. units.append((category_id, _parent_id(category_id, by_id), demands))
  46. packed = pack_category_units(units, max_demands_per_group=max_demands_per_group)
  47. return [
  48. {
  49. "category_ids": group_category_ids,
  50. "planning_reason": _DEFAULT_REASON,
  51. "shared_traits": _DEFAULT_TRAITS,
  52. }
  53. for group_category_ids in packed
  54. ]
  55. def auto_assign_daily_grade_plan(
  56. *,
  57. biz_dt: str,
  58. max_demands_per_group: int = MAX_DEMANDS_PER_BATCH,
  59. ) -> dict[str, Any]:
  60. """自动划分并落库当天分级计划组(每组约 30 个需求,同分类/同父分类优先)。"""
  61. planning_state = resolve_planning_state(biz_dt)
  62. if not planning_state["can_plan_more"]:
  63. logger.info(
  64. "跳过自动分配: biz_dt=%s reason=%s",
  65. biz_dt,
  66. planning_state["skip_reason"],
  67. )
  68. return {
  69. "skipped": True,
  70. "reason": planning_state["skip_reason"],
  71. "planning_state": planning_state,
  72. }
  73. groups = build_auto_assign_groups(
  74. biz_dt,
  75. unassigned_category_ids=planning_state["unassigned_category_ids"],
  76. max_demands_per_group=max_demands_per_group,
  77. )
  78. if not groups:
  79. logger.info(
  80. "跳过自动分配:无待分配需求 biz_dt=%s unassigned_categories=%s",
  81. biz_dt,
  82. len(planning_state["unassigned_category_ids"]),
  83. )
  84. return {
  85. "skipped": True,
  86. "reason": "无待分配需求",
  87. "planning_state": planning_state,
  88. }
  89. logger.info(
  90. "执行自动分配: biz_dt=%s unassigned_categories=%s planned_groups=%s max_demands_per_group=%s",
  91. biz_dt,
  92. len(planning_state["unassigned_category_ids"]),
  93. len(groups),
  94. max_demands_per_group,
  95. )
  96. prepared = prepare_grade_groups(
  97. biz_dt,
  98. _DEFAULT_GROUPING_STRATEGY,
  99. groups,
  100. assigned_category_ids=set(planning_state["assigned_category_ids"]),
  101. )
  102. persist_result = persist_groups_one_by_one(biz_dt, prepared)
  103. return {
  104. "skipped": False,
  105. "planning_state": planning_state,
  106. "prepared_group_count": len(prepared.get("groups") or []),
  107. "persist_result": persist_result,
  108. }