|
|
@@ -0,0 +1,216 @@
|
|
|
+"""当下供需 gap 脚本主驱每日任务入口。"""
|
|
|
+
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+from datetime import date, datetime
|
|
|
+from typing import Any
|
|
|
+
|
|
|
+from app.gap_script_demand.config import load_gap_script_demand_config
|
|
|
+from app.gap_script_demand.judging import judge_remake_suitable_demands
|
|
|
+from app.gap_script_demand.odps_demand_pool import fetch_demand_names
|
|
|
+from app.gap_script_demand.odps_writer import build_odps_row, sync_gap_script_demands_to_odps
|
|
|
+from app.gap_script_demand.repository import GapScriptDemandRepository
|
|
|
+from app.gap_script_demand.types import GapScriptDemandConfig
|
|
|
+from app.hot_content.config import load_flow_config
|
|
|
+from app.hot_content.timezone import SHANGHAI_TZ
|
|
|
+
|
|
|
+
|
|
|
+def _today_shanghai() -> date:
|
|
|
+ return datetime.now(SHANGHAI_TZ).date()
|
|
|
+
|
|
|
+
|
|
|
+def print_gap_script_demand_summary(summary: dict[str, Any]) -> None:
|
|
|
+ """将脚本主驱流程结果打印到控制台。"""
|
|
|
+ print("=" * 60)
|
|
|
+ print("当下供需gap-脚本主驱 任务结果")
|
|
|
+ print("=" * 60)
|
|
|
+ print(f"查询日期: {summary.get('query_date')}")
|
|
|
+ print(f"ODPS 分区: {summary.get('partition_dt')}")
|
|
|
+ print(f"源 strategy: {summary.get('demand_pool_strategy')}")
|
|
|
+ print(f"输出 strategy: {summary.get('output_strategy')}")
|
|
|
+ print(f"LLM 模型: {summary.get('llm_model')}")
|
|
|
+ print(f"批次大小: {summary.get('judge_batch_size')}")
|
|
|
+
|
|
|
+ if summary.get("skip_reason") in {
|
|
|
+ "no_demand_names",
|
|
|
+ "partition_data_exists",
|
|
|
+ "no_matched_demands",
|
|
|
+ }:
|
|
|
+ print(f"跳过原因: {summary.get('skip_reason')}")
|
|
|
+ if summary.get("skip_reason") == "no_matched_demands":
|
|
|
+ print(f"ODPS 需求总数: {summary.get('demand_name_total', 0)}")
|
|
|
+ print("匹配可改造数: 0")
|
|
|
+ print("=" * 60)
|
|
|
+ return
|
|
|
+
|
|
|
+ print(f"\nODPS 需求总数: {summary.get('demand_name_total', 0)}")
|
|
|
+ print(f"匹配可改造数: {summary.get('matched_count', 0)}")
|
|
|
+
|
|
|
+ matched_names = summary.get("matched_demand_names") or []
|
|
|
+ if matched_names:
|
|
|
+ print("\n保留的品类+解构词:")
|
|
|
+ for name in matched_names:
|
|
|
+ print(f" * {name}")
|
|
|
+
|
|
|
+ mysql_save = summary.get("mysql_save") or {}
|
|
|
+ if mysql_save.get("skipped"):
|
|
|
+ print(
|
|
|
+ f"\nMySQL/ODPS 跳过: reason={mysql_save.get('skip_reason')} "
|
|
|
+ f"partition_dt={mysql_save.get('partition_dt')}"
|
|
|
+ )
|
|
|
+ elif mysql_save:
|
|
|
+ print(
|
|
|
+ f"\nMySQL 写入: table={mysql_save.get('table')} "
|
|
|
+ f"saved={mysql_save.get('saved_count', 0)} "
|
|
|
+ f"strategy={mysql_save.get('strategy')}"
|
|
|
+ )
|
|
|
+ odps_sync = summary.get("odps_sync") or {}
|
|
|
+ if odps_sync:
|
|
|
+ print(
|
|
|
+ f"ODPS 写入: table={odps_sync.get('target_table')} "
|
|
|
+ f"partition_dt={odps_sync.get('partition_dt')} "
|
|
|
+ f"written={odps_sync.get('written_count', 0)}"
|
|
|
+ )
|
|
|
+
|
|
|
+ print("=" * 60)
|
|
|
+
|
|
|
+
|
|
|
+def _persist_matched_demands(
|
|
|
+ *,
|
|
|
+ demand_names: list[str],
|
|
|
+ strategy: str,
|
|
|
+ partition_dt: str,
|
|
|
+ target_table: str,
|
|
|
+ sync_odps: bool = True,
|
|
|
+) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
|
+ flow_config = load_flow_config()
|
|
|
+ repository = GapScriptDemandRepository(flow_config.mysql)
|
|
|
+ try:
|
|
|
+ mysql_save = repository.persist_matched_demands(
|
|
|
+ demand_names=demand_names,
|
|
|
+ strategy=strategy,
|
|
|
+ partition_dt=partition_dt,
|
|
|
+ )
|
|
|
+ if mysql_save.get("skipped") or not mysql_save.get("rows"):
|
|
|
+ return mysql_save, {}
|
|
|
+
|
|
|
+ if not sync_odps:
|
|
|
+ return mysql_save, {}
|
|
|
+
|
|
|
+ odps_rows = [
|
|
|
+ build_odps_row(
|
|
|
+ strategy=row["strategy"],
|
|
|
+ demand_id=row["demand_id"],
|
|
|
+ demand_name=row["demand_name"],
|
|
|
+ )
|
|
|
+ for row in mysql_save["rows"]
|
|
|
+ ]
|
|
|
+ odps_sync = sync_gap_script_demands_to_odps(
|
|
|
+ rows=odps_rows,
|
|
|
+ partition_dt=partition_dt,
|
|
|
+ target_table=target_table,
|
|
|
+ )
|
|
|
+ return mysql_save, odps_sync
|
|
|
+ finally:
|
|
|
+ repository.close()
|
|
|
+
|
|
|
+
|
|
|
+def run_gap_script_demand_daily_job(
|
|
|
+ config: GapScriptDemandConfig,
|
|
|
+ *,
|
|
|
+ query_date: date | None = None,
|
|
|
+ sync_odps: bool = True,
|
|
|
+) -> dict[str, Any]:
|
|
|
+ """执行脚本主驱流程:拉取当天供需 gap → LLM 筛选可改造项 → 写 MySQL/ODPS。"""
|
|
|
+ target_date = query_date or _today_shanghai()
|
|
|
+ partition_dt = target_date.strftime("%Y%m%d")
|
|
|
+
|
|
|
+ summary: dict[str, Any] = {
|
|
|
+ "query_date": target_date.isoformat(),
|
|
|
+ "partition_dt": partition_dt,
|
|
|
+ "demand_pool_strategy": config.demand_pool_strategy,
|
|
|
+ "output_strategy": config.output_strategy,
|
|
|
+ "demand_pool_source_table": config.demand_pool_source_table,
|
|
|
+ "llm_model": config.llm_model,
|
|
|
+ "judge_batch_size": config.judge_batch_size,
|
|
|
+ "demand_name_total": 0,
|
|
|
+ "matched_count": 0,
|
|
|
+ "matched_demand_names": [],
|
|
|
+ "mysql_save": {},
|
|
|
+ "odps_sync": {},
|
|
|
+ }
|
|
|
+
|
|
|
+ flow_config = load_flow_config()
|
|
|
+ repository = GapScriptDemandRepository(flow_config.mysql)
|
|
|
+ try:
|
|
|
+ if repository.has_partition_data(
|
|
|
+ strategy=config.output_strategy,
|
|
|
+ partition_dt=partition_dt,
|
|
|
+ ):
|
|
|
+ summary["skip_reason"] = "partition_data_exists"
|
|
|
+ summary["mysql_save"] = {
|
|
|
+ "table": "gap_script_demand_pool_di",
|
|
|
+ "strategy": config.output_strategy,
|
|
|
+ "partition_dt": partition_dt,
|
|
|
+ "skipped": True,
|
|
|
+ "skip_reason": "partition_data_exists",
|
|
|
+ }
|
|
|
+ return summary
|
|
|
+ finally:
|
|
|
+ repository.close()
|
|
|
+
|
|
|
+ demand_names = fetch_demand_names(
|
|
|
+ partition_dt=partition_dt,
|
|
|
+ strategy=config.demand_pool_strategy,
|
|
|
+ source_table=config.demand_pool_source_table,
|
|
|
+ )
|
|
|
+ summary["demand_name_total"] = len(demand_names)
|
|
|
+
|
|
|
+ if not demand_names:
|
|
|
+ summary["skip_reason"] = "no_demand_names"
|
|
|
+ return summary
|
|
|
+
|
|
|
+ print(
|
|
|
+ f"gap script demand: start judging "
|
|
|
+ f"demand_names={len(demand_names)} batch_size={config.judge_batch_size} "
|
|
|
+ f"model={config.llm_model}",
|
|
|
+ flush=True,
|
|
|
+ )
|
|
|
+ matched_demand_names = judge_remake_suitable_demands(
|
|
|
+ demand_names=demand_names,
|
|
|
+ config=config,
|
|
|
+ )
|
|
|
+ summary["matched_count"] = len(matched_demand_names)
|
|
|
+ summary["matched_demand_names"] = matched_demand_names
|
|
|
+
|
|
|
+ if not matched_demand_names:
|
|
|
+ summary["skip_reason"] = "no_matched_demands"
|
|
|
+ return summary
|
|
|
+
|
|
|
+ print("gap script demand: persisting matched demands", flush=True)
|
|
|
+ mysql_save, odps_sync = _persist_matched_demands(
|
|
|
+ demand_names=matched_demand_names,
|
|
|
+ strategy=config.output_strategy,
|
|
|
+ partition_dt=partition_dt,
|
|
|
+ target_table=config.demand_pool_source_table,
|
|
|
+ sync_odps=sync_odps,
|
|
|
+ )
|
|
|
+ summary["mysql_save"] = mysql_save
|
|
|
+ summary["odps_sync"] = odps_sync
|
|
|
+ if mysql_save.get("skipped"):
|
|
|
+ summary["skip_reason"] = mysql_save.get("skip_reason")
|
|
|
+ return summary
|
|
|
+
|
|
|
+
|
|
|
+def test_gap_script_demand_for_date(
|
|
|
+ query_date: date,
|
|
|
+ *,
|
|
|
+ sync_odps: bool = True,
|
|
|
+) -> dict[str, Any]:
|
|
|
+ """测试指定日期的脚本主驱流程。"""
|
|
|
+ config = load_gap_script_demand_config()
|
|
|
+ return run_gap_script_demand_daily_job(
|
|
|
+ config,
|
|
|
+ query_date=query_date,
|
|
|
+ sync_odps=sync_odps,
|
|
|
+ )
|