feat(governance): automate identity and comparison coverage
This commit is contained in:
111
run_scheduler.py
111
run_scheduler.py
@@ -3,7 +3,7 @@
|
||||
run_scheduler.py — momo-scheduler 容器入口點
|
||||
|
||||
排程任務清單(對齊 app.py init_scheduler + scheduler.py 全任務):
|
||||
每 30 分鐘:auto_import、whitepage_check
|
||||
每 30 分鐘:revenue_automation_cycle(auto_import → comparison backfill)、whitepage_check
|
||||
每 1 小時:momo、edm、festival
|
||||
每 4 小時:competitor_price_feeder、external_offer_sync、icaim_analysis
|
||||
每 6 小時:quality_rescore、action_plan_hygiene
|
||||
@@ -22,7 +22,7 @@ import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Optional, Tuple
|
||||
from typing import Callable, Optional, Tuple
|
||||
|
||||
import schedule
|
||||
|
||||
@@ -60,6 +60,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
_AI_CALLS_ERROR_SPIKE_LAST_PUSH_TS = 0.0
|
||||
_OLLAMA_111_USAGE_LAST_PUSH_TS = 0.0
|
||||
_REVENUE_AUTOMATION_LOCK = threading.Lock()
|
||||
|
||||
|
||||
def _scheduler_stats_path() -> str:
|
||||
@@ -122,6 +123,96 @@ def run_pchome_growth_momo_backfill_catchup_task():
|
||||
return {"status": "failed", "error": str(error)}
|
||||
|
||||
|
||||
def _launch_revenue_automation_background(
|
||||
job_name: str,
|
||||
target: Callable[[], object],
|
||||
) -> dict[str, str]:
|
||||
"""Launch one revenue job without blocking or overlapping the scheduler loop."""
|
||||
if not _REVENUE_AUTOMATION_LOCK.acquire(blocking=False):
|
||||
logger.info("[RevenueAutomation] skipped overlapping job=%s", job_name)
|
||||
return {"status": "skipped", "reason": "revenue_automation_already_running"}
|
||||
|
||||
def _runner():
|
||||
try:
|
||||
target()
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
"[RevenueAutomation] background job failed job=%s error=%s",
|
||||
job_name,
|
||||
error,
|
||||
exc_info=True,
|
||||
)
|
||||
_notify_scheduler_failure(
|
||||
job_name,
|
||||
error,
|
||||
source="Scheduler.RevenueAutomation",
|
||||
event_type="revenue_automation_background_failure",
|
||||
title="PChome 收益自動化背景任務失敗",
|
||||
)
|
||||
finally:
|
||||
_REVENUE_AUTOMATION_LOCK.release()
|
||||
|
||||
thread = threading.Thread(
|
||||
target=_runner,
|
||||
daemon=True,
|
||||
name=f"revenue-automation-{job_name}",
|
||||
)
|
||||
try:
|
||||
thread.start()
|
||||
except Exception:
|
||||
_REVENUE_AUTOMATION_LOCK.release()
|
||||
raise
|
||||
return {"status": "queued", "job": job_name}
|
||||
|
||||
|
||||
def run_revenue_automation_cycle_task() -> dict[str, object]:
|
||||
"""Refresh sales first, then catch up the bounded same-item mapping lane."""
|
||||
logger.info("[RevenueAutomation] cycle started: sales acquisition -> comparison backfill")
|
||||
run_auto_import_task()
|
||||
backfill = run_pchome_growth_momo_backfill_catchup_task()
|
||||
logger.info(
|
||||
"[RevenueAutomation] cycle completed backfill_status=%s",
|
||||
backfill.get("status"),
|
||||
)
|
||||
return {"status": "completed", "backfill": backfill}
|
||||
|
||||
|
||||
def run_revenue_automation_cycle_background_task() -> dict[str, str]:
|
||||
return _launch_revenue_automation_background(
|
||||
"revenue-cycle",
|
||||
run_revenue_automation_cycle_task,
|
||||
)
|
||||
|
||||
|
||||
def run_pchome_growth_momo_backfill_background_task() -> dict[str, str]:
|
||||
return _launch_revenue_automation_background(
|
||||
"pchome-growth-backfill",
|
||||
run_pchome_growth_momo_backfill_task,
|
||||
)
|
||||
|
||||
|
||||
def start_revenue_automation_watchdog() -> threading.Thread:
|
||||
"""Keep revenue refresh alive even when a legacy schedule job blocks run_pending."""
|
||||
try:
|
||||
interval_seconds = int(os.getenv("REVENUE_AUTOMATION_WATCHDOG_SECONDS", "1800"))
|
||||
except ValueError:
|
||||
interval_seconds = 1800
|
||||
interval_seconds = max(60, interval_seconds)
|
||||
|
||||
def _watchdog():
|
||||
while True:
|
||||
run_revenue_automation_cycle_background_task()
|
||||
time.sleep(interval_seconds)
|
||||
|
||||
thread = threading.Thread(
|
||||
target=_watchdog,
|
||||
daemon=True,
|
||||
name="revenue-automation-watchdog",
|
||||
)
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
|
||||
def _env_flag(name: str, default: bool = False) -> bool:
|
||||
raw = os.getenv(name)
|
||||
if raw is None:
|
||||
@@ -185,8 +276,8 @@ def _notify_scheduler_failure(
|
||||
|
||||
|
||||
def _register_schedules():
|
||||
schedule.every(30).minutes.do(run_auto_import_task)
|
||||
logger.info("📅 每 30 分鐘:auto_import")
|
||||
schedule.every(30).minutes.do(run_revenue_automation_cycle_background_task)
|
||||
logger.info("📅 每 30 分鐘:revenue_automation_cycle(業績匯入 → 比價回填,非阻塞)")
|
||||
|
||||
schedule.every(30).minutes.do(run_whitepage_check)
|
||||
logger.info("📅 每 30 分鐘:whitepage_check")
|
||||
@@ -374,12 +465,9 @@ def _register_schedules():
|
||||
schedule.every().day.at("10:30").do(run_pchome_match_backfill_task)
|
||||
logger.info("📅 每日 10:30:pchome_match_backfill")
|
||||
|
||||
schedule.every().day.at("10:45").do(run_pchome_growth_momo_backfill_task)
|
||||
schedule.every().day.at("10:45").do(run_pchome_growth_momo_backfill_background_task)
|
||||
logger.info("📅 每日 10:45:pchome_growth_momo_backfill(高業績商品補 MOMO 對應)")
|
||||
|
||||
schedule.every(30).minutes.do(run_pchome_growth_momo_backfill_catchup_task)
|
||||
logger.info("📅 每 30 分鐘:pchome_growth_momo_backfill_catchup(錯過每日任務時自動補跑)")
|
||||
|
||||
# Operation Ollama-First v5.0 — Phase 1 收尾:每日 23:55 LLM Token 日報
|
||||
schedule.every().day.at("23:55").do(run_daily_token_report_task)
|
||||
logger.info("📅 每日 23:55:daily_token_report")
|
||||
@@ -1368,6 +1456,13 @@ if __name__ == "__main__":
|
||||
_register_schedules()
|
||||
logger.info("✅ 全部排程任務已註冊")
|
||||
|
||||
_revenue_watchdog_thread = start_revenue_automation_watchdog()
|
||||
logger.info(
|
||||
"[RevenueAutomation] watchdog=%s alive=%s",
|
||||
_revenue_watchdog_thread.name,
|
||||
_revenue_watchdog_thread.is_alive(),
|
||||
)
|
||||
|
||||
try:
|
||||
from services.openclaw_bot.embedding_worker_runtime import start_embedding_worker
|
||||
|
||||
|
||||
Reference in New Issue
Block a user