run_hive_gap_mysql.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. """Run DemandAgent from Hive supply-gap rows and write only MySQL demand_content.
  2. This entrypoint restores the old business starting point (ODPS/Hive supply
  3. gap) without restoring prepare/run_mining or any Hive writes.
  4. """
  5. from __future__ import annotations
  6. import argparse
  7. import asyncio
  8. import json
  9. import os
  10. import sys
  11. from datetime import datetime
  12. from pathlib import Path
  13. from typing import Any
  14. sys.path.insert(0, str(Path(__file__).parent.parent.parent))
  15. def _load_project_env() -> None:
  16. try:
  17. from dotenv import load_dotenv
  18. except ImportError:
  19. return
  20. load_dotenv(Path(__file__).parent.parent.parent / ".env")
  21. def _safe_path_segment(value: str) -> str:
  22. return "".join(ch if ch.isalnum() or ch in {"-", "_"} else "_" for ch in value).strip("_") or "run"
  23. def _prepare_run_output_paths(execution_id: int, run_label: str) -> None:
  24. run_root = Path(__file__).parent / "test_output_data" / "mysql_runs" / _safe_path_segment(run_label)
  25. result_base_dir = run_root / "intermediate" / "result"
  26. os.environ["DEMAND_RESULT_BASE_DIR"] = str(result_base_dir)
  27. os.environ["DEMAND_OUTPUT_BASE_DIR"] = str(run_root / "output")
  28. os.environ["DEMAND_TRACE_STORE_PATH"] = str(run_root / ".trace")
  29. os.environ["DEMAND_WEIGHT_DATA_DIR"] = str(run_root / "intermediate" / "data")
  30. demand_items_path = result_base_dir / str(execution_id) / f"execution_id_{execution_id}_demand_items.json"
  31. if demand_items_path.exists():
  32. demand_items_path.unlink()
  33. def _configure_mysql_env(run_label: str) -> None:
  34. os.environ["DEMAND_OUTPUT_MODE"] = "mysql_demand_content"
  35. os.environ["DEMAND_MYSQL_ENTRYPOINT"] = "run_hive_gap_mysql"
  36. os.environ["DEMAND_RUN_LABEL"] = run_label
  37. os.environ.setdefault("DEMAND_ITEMSET_DETAIL_MAX_IDS", "12")
  38. os.environ.setdefault("DEMAND_ITEMSET_DETAIL_MAX_POST_IDS", "20")
  39. def _validate_execution_success(execution_id: int) -> None:
  40. from examples.demand.db_manager import query_execution_for_evidence
  41. execution = query_execution_for_evidence(execution_id)
  42. if not execution:
  43. raise ValueError(f"execution_id={execution_id} 不存在,Hive gap 入口只允许跑已有 PG execution")
  44. if str(execution.get("status") or "").strip().lower() != "success":
  45. raise ValueError(
  46. f"execution_id={execution_id} status={execution.get('status')},不是 success,拒绝生成需求"
  47. )
  48. def _build_scope(row: dict[str, Any], execution_id: int, count: int) -> dict[str, Any]:
  49. return {
  50. "scope_source": "odps_gap",
  51. "merge_leve2": row["cluster_name"],
  52. "platform": row.get("platform_type") or "piaoquan",
  53. "gap_dt": row.get("gap_dt"),
  54. "requested_count": int(count),
  55. "lack_count": row.get("lack_count"),
  56. "pattern_execution_id": int(execution_id),
  57. }
  58. def parse_args() -> argparse.Namespace:
  59. parser = argparse.ArgumentParser(
  60. description="Run DemandAgent from Hive gap rows and write only MySQL demand_content."
  61. )
  62. parser.add_argument("--execution-id", type=int, default=None, help="Existing PG pattern_mining_execution.id")
  63. parser.add_argument("--max-categories", type=int, default=None, help="Only run the first N gap categories")
  64. parser.add_argument("--max-total-count", type=int, default=None, help="Cap total requested demand count")
  65. parser.add_argument("--run-label-prefix", default=None, help="Prefix stored in ext_data.run_label")
  66. parser.add_argument("--dry-run", action="store_true", help="Print gap plan only; do not call the agent")
  67. return parser.parse_args()
  68. async def async_main() -> dict[str, Any]:
  69. _load_project_env()
  70. args = parse_args()
  71. from examples.demand.data_query_tools import get_demand_merge_level2_names
  72. from examples.demand.db_manager import query_latest_success_execution_id
  73. execution_id = args.execution_id or query_latest_success_execution_id()
  74. if not execution_id:
  75. raise ValueError("未找到 PG Pattern V2 success execution")
  76. _validate_execution_success(int(execution_id))
  77. gap_rows = get_demand_merge_level2_names()
  78. if args.max_categories is not None:
  79. gap_rows = gap_rows[: max(int(args.max_categories), 0)]
  80. remaining = int(args.max_total_count) if args.max_total_count is not None else None
  81. prefix = args.run_label_prefix or f"hive_gap_mysql_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
  82. results: list[dict[str, Any]] = []
  83. planned: list[dict[str, Any]] = []
  84. for index, row in enumerate(gap_rows, start=1):
  85. requested = int(row.get("count") or 0)
  86. if remaining is not None:
  87. if remaining <= 0:
  88. break
  89. requested = min(requested, remaining)
  90. if requested <= 0:
  91. continue
  92. cluster_name = str(row["cluster_name"])
  93. platform_type = str(row.get("platform_type") or "piaoquan")
  94. run_label = f"{prefix}_{index:02d}_{_safe_path_segment(cluster_name)}"
  95. scope = _build_scope(row, int(execution_id), requested)
  96. planned.append({"run_label": run_label, "count": requested, "demand_scope": scope})
  97. if args.dry_run:
  98. if remaining is not None:
  99. remaining -= requested
  100. continue
  101. from examples.demand.run import main as run_main
  102. _configure_mysql_env(run_label)
  103. _prepare_run_output_paths(int(execution_id), run_label)
  104. result = await run_main(
  105. cluster_name=cluster_name,
  106. platform_type=platform_type,
  107. count=requested,
  108. execution_id=int(execution_id),
  109. task_id=None,
  110. demand_scope=scope,
  111. )
  112. result["run_label"] = run_label
  113. result["demand_scope"] = scope
  114. results.append(result)
  115. if remaining is not None:
  116. remaining -= requested
  117. return {
  118. "execution_id": int(execution_id),
  119. "dry_run": bool(args.dry_run),
  120. "planned": planned,
  121. "results": results,
  122. }
  123. def main() -> None:
  124. result = asyncio.run(async_main())
  125. print(json.dumps(result, ensure_ascii=False, indent=2))
  126. if __name__ == "__main__":
  127. main()