service.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. """当下供需 gap 脚本主驱每日任务入口。"""
  2. from __future__ import annotations
  3. from datetime import date, datetime
  4. from typing import Any
  5. from app.gap_script_demand.config import load_gap_script_demand_config
  6. from app.gap_script_demand.judging import judge_remake_suitable_demands
  7. from app.gap_script_demand.odps_demand_pool import fetch_demand_names
  8. from app.gap_script_demand.odps_writer import build_odps_row, sync_gap_script_demands_to_odps
  9. from app.gap_script_demand.repository import GapScriptDemandRepository
  10. from app.gap_script_demand.types import GapScriptDemandConfig
  11. from app.hot_content.config import load_flow_config
  12. from app.hot_content.timezone import SHANGHAI_TZ
  13. def _today_shanghai() -> date:
  14. return datetime.now(SHANGHAI_TZ).date()
  15. def print_gap_script_demand_summary(summary: dict[str, Any]) -> None:
  16. """将脚本主驱流程结果打印到控制台。"""
  17. print("=" * 60)
  18. print("当下供需gap-脚本主驱 任务结果")
  19. print("=" * 60)
  20. print(f"查询日期: {summary.get('query_date')}")
  21. print(f"ODPS 分区: {summary.get('partition_dt')}")
  22. print(f"源 strategy: {summary.get('demand_pool_strategy')}")
  23. print(f"输出 strategy: {summary.get('output_strategy')}")
  24. print(f"LLM 模型: {summary.get('llm_model')}")
  25. print(f"批次大小: {summary.get('judge_batch_size')}")
  26. if summary.get("skip_reason") in {
  27. "no_demand_names",
  28. "partition_data_exists",
  29. "no_matched_demands",
  30. }:
  31. print(f"跳过原因: {summary.get('skip_reason')}")
  32. if summary.get("skip_reason") == "no_matched_demands":
  33. print(f"ODPS 需求总数: {summary.get('demand_name_total', 0)}")
  34. print("匹配可改造数: 0")
  35. print("=" * 60)
  36. return
  37. print(f"\nODPS 需求总数: {summary.get('demand_name_total', 0)}")
  38. print(f"匹配可改造数: {summary.get('matched_count', 0)}")
  39. matched_names = summary.get("matched_demand_names") or []
  40. if matched_names:
  41. print("\n保留的品类+解构词:")
  42. for name in matched_names:
  43. print(f" * {name}")
  44. mysql_save = summary.get("mysql_save") or {}
  45. if mysql_save.get("skipped"):
  46. print(
  47. f"\nMySQL/ODPS 跳过: reason={mysql_save.get('skip_reason')} "
  48. f"partition_dt={mysql_save.get('partition_dt')}"
  49. )
  50. elif mysql_save:
  51. print(
  52. f"\nMySQL 写入: table={mysql_save.get('table')} "
  53. f"saved={mysql_save.get('saved_count', 0)} "
  54. f"strategy={mysql_save.get('strategy')}"
  55. )
  56. odps_sync = summary.get("odps_sync") or {}
  57. if odps_sync:
  58. print(
  59. f"ODPS 写入: table={odps_sync.get('target_table')} "
  60. f"partition_dt={odps_sync.get('partition_dt')} "
  61. f"written={odps_sync.get('written_count', 0)}"
  62. )
  63. print("=" * 60)
  64. def _persist_matched_demands(
  65. *,
  66. demand_names: list[str],
  67. strategy: str,
  68. partition_dt: str,
  69. target_table: str,
  70. sync_odps: bool = True,
  71. ) -> tuple[dict[str, Any], dict[str, Any]]:
  72. flow_config = load_flow_config()
  73. repository = GapScriptDemandRepository(flow_config.mysql)
  74. try:
  75. mysql_save = repository.persist_matched_demands(
  76. demand_names=demand_names,
  77. strategy=strategy,
  78. partition_dt=partition_dt,
  79. )
  80. if mysql_save.get("skipped") or not mysql_save.get("rows"):
  81. return mysql_save, {}
  82. if not sync_odps:
  83. return mysql_save, {}
  84. odps_rows = [
  85. build_odps_row(
  86. strategy=row["strategy"],
  87. demand_id=row["demand_id"],
  88. demand_name=row["demand_name"],
  89. )
  90. for row in mysql_save["rows"]
  91. ]
  92. odps_sync = sync_gap_script_demands_to_odps(
  93. rows=odps_rows,
  94. partition_dt=partition_dt,
  95. target_table=target_table,
  96. )
  97. return mysql_save, odps_sync
  98. finally:
  99. repository.close()
  100. def run_gap_script_demand_daily_job(
  101. config: GapScriptDemandConfig,
  102. *,
  103. query_date: date | None = None,
  104. sync_odps: bool = True,
  105. ) -> dict[str, Any]:
  106. """执行脚本主驱流程:拉取当天供需 gap → LLM 筛选可改造项 → 写 MySQL/ODPS。"""
  107. target_date = query_date or _today_shanghai()
  108. partition_dt = target_date.strftime("%Y%m%d")
  109. summary: dict[str, Any] = {
  110. "query_date": target_date.isoformat(),
  111. "partition_dt": partition_dt,
  112. "demand_pool_strategy": config.demand_pool_strategy,
  113. "output_strategy": config.output_strategy,
  114. "demand_pool_source_table": config.demand_pool_source_table,
  115. "llm_model": config.llm_model,
  116. "judge_batch_size": config.judge_batch_size,
  117. "demand_name_total": 0,
  118. "matched_count": 0,
  119. "matched_demand_names": [],
  120. "mysql_save": {},
  121. "odps_sync": {},
  122. }
  123. flow_config = load_flow_config()
  124. repository = GapScriptDemandRepository(flow_config.mysql)
  125. try:
  126. if repository.has_partition_data(
  127. strategy=config.output_strategy,
  128. partition_dt=partition_dt,
  129. ):
  130. summary["skip_reason"] = "partition_data_exists"
  131. summary["mysql_save"] = {
  132. "table": "gap_script_demand_pool_di",
  133. "strategy": config.output_strategy,
  134. "partition_dt": partition_dt,
  135. "skipped": True,
  136. "skip_reason": "partition_data_exists",
  137. }
  138. return summary
  139. finally:
  140. repository.close()
  141. demand_names = fetch_demand_names(
  142. partition_dt=partition_dt,
  143. strategy=config.demand_pool_strategy,
  144. source_table=config.demand_pool_source_table,
  145. )
  146. summary["demand_name_total"] = len(demand_names)
  147. if not demand_names:
  148. summary["skip_reason"] = "no_demand_names"
  149. return summary
  150. print(
  151. f"gap script demand: start judging "
  152. f"demand_names={len(demand_names)} batch_size={config.judge_batch_size} "
  153. f"model={config.llm_model}",
  154. flush=True,
  155. )
  156. matched_demand_names = judge_remake_suitable_demands(
  157. demand_names=demand_names,
  158. config=config,
  159. )
  160. summary["matched_count"] = len(matched_demand_names)
  161. summary["matched_demand_names"] = matched_demand_names
  162. if not matched_demand_names:
  163. summary["skip_reason"] = "no_matched_demands"
  164. return summary
  165. print("gap script demand: persisting matched demands", flush=True)
  166. mysql_save, odps_sync = _persist_matched_demands(
  167. demand_names=matched_demand_names,
  168. strategy=config.output_strategy,
  169. partition_dt=partition_dt,
  170. target_table=config.demand_pool_source_table,
  171. sync_odps=sync_odps,
  172. )
  173. summary["mysql_save"] = mysql_save
  174. summary["odps_sync"] = odps_sync
  175. if mysql_save.get("skipped"):
  176. summary["skip_reason"] = mysql_save.get("skip_reason")
  177. return summary
  178. def test_gap_script_demand_for_date(
  179. query_date: date,
  180. *,
  181. sync_odps: bool = True,
  182. ) -> dict[str, Any]:
  183. """测试指定日期的脚本主驱流程。"""
  184. config = load_gap_script_demand_config()
  185. return run_gap_script_demand_daily_job(
  186. config,
  187. query_date=query_date,
  188. sync_odps=sync_odps,
  189. )