feat: schedule growth momo backfill
All checks were successful
CD Pipeline / deploy (push) Successful in 1m11s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m11s
This commit is contained in:
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.634"
|
||||
SYSTEM_VERSION = "V10.635"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -1662,63 +1662,10 @@ def api_pchome_growth_opportunities():
|
||||
}), 500
|
||||
|
||||
|
||||
def _growth_candidate_auto_compare_type(candidate):
|
||||
auto_type = str(candidate.get("auto_compare_type") or "").strip()
|
||||
if auto_type in {"total_price", "unit_price"}:
|
||||
return auto_type
|
||||
if candidate.get("can_auto_compare") is True:
|
||||
return "total_price"
|
||||
return "manual_review"
|
||||
def _run_pchome_growth_momo_backfill(engine, limit):
|
||||
from services.pchome_growth_momo_backfill_service import run_pchome_growth_momo_backfill
|
||||
|
||||
|
||||
def _growth_momo_backfill_targets_from_payload(payload, limit):
|
||||
opportunities = list((payload or {}).get("opportunities") or [])
|
||||
targets = []
|
||||
for item in opportunities:
|
||||
action = item.get("recommended_action") or {}
|
||||
if item.get("external_price"):
|
||||
continue
|
||||
if action.get("code") != "map_external_product":
|
||||
continue
|
||||
product_id = str(item.get("pchome_product_id") or "").strip()
|
||||
product_name = str(item.get("product_name") or "").strip()
|
||||
if not product_id or not product_name:
|
||||
continue
|
||||
target = {
|
||||
"product_id": product_id,
|
||||
"name": product_name,
|
||||
"price": item.get("pchome_price"),
|
||||
"sales_7d": item.get("sales_7d"),
|
||||
"priority_score": item.get("priority_score"),
|
||||
}
|
||||
targets.append(target)
|
||||
if len(targets) >= limit:
|
||||
break
|
||||
return targets
|
||||
|
||||
|
||||
def _build_pchome_growth_payload(engine, limit):
|
||||
from services.pchome_revenue_growth_service import build_pchome_growth_opportunities
|
||||
|
||||
return build_pchome_growth_opportunities(engine, limit=limit)
|
||||
|
||||
|
||||
def _search_growth_momo_candidates(targets, limit):
|
||||
from services.momo_crawler import search_momo_products_for_pchome_products
|
||||
|
||||
return search_momo_products_for_pchome_products(
|
||||
targets,
|
||||
max_products=limit,
|
||||
limit_per_product=6,
|
||||
max_terms_per_product=4,
|
||||
min_score=0.45,
|
||||
)
|
||||
|
||||
|
||||
def _sync_growth_momo_candidates(engine, candidates):
|
||||
from services.external_market_offer_service import sync_targeted_momo_candidates_to_external_offers
|
||||
|
||||
return sync_targeted_momo_candidates_to_external_offers(engine, candidates, dry_run=False)
|
||||
return run_pchome_growth_momo_backfill(engine, limit=limit)
|
||||
|
||||
|
||||
@ai_bp.route('/api/ai/pchome-growth/backfill-momo-candidates', methods=['POST'])
|
||||
@@ -1736,89 +1683,13 @@ def api_pchome_growth_backfill_momo_candidates():
|
||||
from config import DATABASE_PATH
|
||||
|
||||
engine = _create_icaim_dashboard_engine(DATABASE_PATH)
|
||||
before_payload = _build_pchome_growth_payload(engine, limit=max(limit, 16))
|
||||
targets = _growth_momo_backfill_targets_from_payload(before_payload, limit)
|
||||
if not targets:
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "目前高業績清單沒有需要補 MOMO 對應的商品。",
|
||||
"data": {
|
||||
"scanned_products": 0,
|
||||
"target_count": 0,
|
||||
"candidate_count": 0,
|
||||
"auto_compare_count": 0,
|
||||
"review_count": 0,
|
||||
"external_offer_sync": {
|
||||
"success": True,
|
||||
"status": "not_needed",
|
||||
"written_count": 0,
|
||||
"message": "沒有需要同步的自動候選。",
|
||||
},
|
||||
"before_stats": before_payload.get("stats") or {},
|
||||
"after_stats": before_payload.get("stats") or {},
|
||||
"targets": [],
|
||||
},
|
||||
})
|
||||
|
||||
search_success, search_message, candidates = _search_growth_momo_candidates(targets, limit)
|
||||
candidates = list(candidates or [])
|
||||
exact_candidates = [
|
||||
item for item in candidates
|
||||
if _growth_candidate_auto_compare_type(item) == "total_price"
|
||||
]
|
||||
unit_candidates = [
|
||||
item for item in candidates
|
||||
if _growth_candidate_auto_compare_type(item) == "unit_price"
|
||||
]
|
||||
review_candidates = [
|
||||
item for item in candidates
|
||||
if _growth_candidate_auto_compare_type(item) not in {"total_price", "unit_price"}
|
||||
]
|
||||
auto_candidates = [*exact_candidates, *unit_candidates]
|
||||
external_offer_sync = {
|
||||
"success": True,
|
||||
"status": "not_found",
|
||||
"written_count": 0,
|
||||
"message": "已搜尋 MOMO,但尚未找到可自動寫入的同款或單位價候選。",
|
||||
}
|
||||
if auto_candidates:
|
||||
external_offer_sync = _sync_growth_momo_candidates(engine, auto_candidates)
|
||||
|
||||
after_payload = _build_pchome_growth_payload(engine, limit=max(limit, 16))
|
||||
result = _run_pchome_growth_momo_backfill(engine, limit)
|
||||
_PCHOME_GROWTH_CACHE.update({
|
||||
"expires_at": 0.0,
|
||||
"epoch": 0.0,
|
||||
"payload": None,
|
||||
})
|
||||
|
||||
written_count = int(external_offer_sync.get("written_count") or 0)
|
||||
message = (
|
||||
f"已掃描 {len(targets)} 個高業績商品,找到 {len(candidates)} 筆 MOMO 候選,"
|
||||
f"自動寫入 {written_count} 筆。"
|
||||
)
|
||||
if not search_success and not candidates:
|
||||
message = search_message or "已搜尋 MOMO,但沒有找到可用候選。"
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": message,
|
||||
"data": {
|
||||
"search_success": bool(search_success),
|
||||
"search_message": search_message,
|
||||
"scanned_products": len(targets),
|
||||
"target_count": len(targets),
|
||||
"candidate_count": len(candidates),
|
||||
"exact_compare_count": len(exact_candidates),
|
||||
"unit_compare_count": len(unit_candidates),
|
||||
"auto_compare_count": len(auto_candidates),
|
||||
"review_count": len(review_candidates),
|
||||
"external_offer_sync": external_offer_sync,
|
||||
"before_stats": before_payload.get("stats") or {},
|
||||
"after_stats": after_payload.get("stats") or {},
|
||||
"targets": targets[:8],
|
||||
"review_candidates": review_candidates[:8],
|
||||
},
|
||||
})
|
||||
return jsonify(result)
|
||||
except Exception as exc:
|
||||
logger.error("[PChomeGrowth] MOMO 對應補抓失敗: %s", exc, exc_info=True)
|
||||
return jsonify({
|
||||
|
||||
@@ -9,7 +9,7 @@ run_scheduler.py — momo-scheduler 容器入口點
|
||||
每 6 小時:quality_rescore、action_plan_hygiene
|
||||
每 12 小時:dedup_batch
|
||||
每 10 分鐘:ppt_auto_generation_catchup(補跑被長任務卡過的定期簡報)
|
||||
每 1 天 :db_backup(03:00)、cleanup_agent_context(03:30)、backup_monitor(04:00)、daily_report(09:00)、roi_monthly_report gate(09:05)、ai_smoke_summary(09:10)、observability_daily_summary(09:30)、pchome_match_backfill(10:30)、openclaw_meta_analysis(12:00, Phase 4 降頻)、ppt_auto_generation_daily(20:30)、ppt_vision_audit(22:00)、daily_token_report(23:55)
|
||||
每 1 天 :db_backup(03:00)、cleanup_agent_context(03:30)、backup_monitor(04:00)、daily_report(09:00)、roi_monthly_report gate(09:05)、ai_smoke_summary(09:10)、observability_daily_summary(09:30)、pchome_match_backfill(10:30)、pchome_growth_momo_backfill(10:45)、openclaw_meta_analysis(12:00, Phase 4 降頻)、ppt_auto_generation_daily(20:30)、ppt_vision_audit(22:00)、daily_token_report(23:55)
|
||||
每 1 週 :weekly_strategy(週一 06:00)、ppt_auto_generation_weekly(週一 20:40)
|
||||
每 1 月 :monthly_report(每月1日 07:00)、ppt_auto_generation_monthly(每月1日 20:50)
|
||||
每 1 季 :ppt_auto_generation_quarterly(1/4/7/10 月 1 日 21:00)
|
||||
@@ -36,6 +36,7 @@ from scheduler import (
|
||||
run_competitor_price_feeder_task,
|
||||
run_external_offer_sync_task,
|
||||
run_pchome_match_backfill_task,
|
||||
run_pchome_growth_momo_backfill_task,
|
||||
run_icaim_analysis_task,
|
||||
run_weekly_strategy_task,
|
||||
run_db_backup_task,
|
||||
@@ -308,6 +309,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)
|
||||
logger.info("📅 每日 10:45:pchome_growth_momo_backfill(高業績商品補 MOMO 對應)")
|
||||
|
||||
# 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")
|
||||
|
||||
74
scheduler.py
74
scheduler.py
@@ -2408,6 +2408,80 @@ def run_pchome_match_backfill_task():
|
||||
logging.error(f"[Scheduler] [PChomeBackfill] event_router 失敗: {_router_e}")
|
||||
|
||||
|
||||
def run_pchome_growth_momo_backfill_task():
|
||||
"""
|
||||
PChome 高業績商品 MOMO 對應補齊任務(每日執行)
|
||||
優先處理成長作戰台中尚未有外部比價資料的高業績商品,寫入 external_offers。
|
||||
"""
|
||||
try:
|
||||
from config import DATABASE_PATH
|
||||
from sqlalchemy import create_engine
|
||||
from services.pchome_growth_momo_backfill_service import run_pchome_growth_momo_backfill
|
||||
|
||||
now_str = datetime.now(TAIPEI_TZ).strftime('%Y-%m-%d %H:%M')
|
||||
limit = int(os.getenv("PCHOME_GROWTH_MOMO_BACKFILL_LIMIT", "8"))
|
||||
logging.info(
|
||||
"[Scheduler] [PChomeGrowthMomoBackfill] 🚀 啟動高業績商品 MOMO 對應補齊 | %s | limit=%s",
|
||||
now_str,
|
||||
limit,
|
||||
)
|
||||
|
||||
engine = create_engine(DATABASE_PATH)
|
||||
try:
|
||||
result = run_pchome_growth_momo_backfill(engine, limit=limit)
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
data = result.get("data") or {}
|
||||
before_stats = data.get("before_stats") or {}
|
||||
after_stats = data.get("after_stats") or {}
|
||||
sync_result = data.get("external_offer_sync") or {}
|
||||
stats = {
|
||||
"status": "Success" if result.get("success") else "Skipped",
|
||||
"scanned_products": data.get("scanned_products", 0),
|
||||
"candidate_count": data.get("candidate_count", 0),
|
||||
"auto_compare_count": data.get("auto_compare_count", 0),
|
||||
"review_count": data.get("review_count", 0),
|
||||
"written_count": sync_result.get("written_count", 0),
|
||||
"sync_status": sync_result.get("status"),
|
||||
"mapping_rate_before": before_stats.get("mapping_rate"),
|
||||
"mapping_rate_after": after_stats.get("mapping_rate"),
|
||||
"mapped_count_before": before_stats.get("mapped_count"),
|
||||
"mapped_count_after": after_stats.get("mapped_count"),
|
||||
"needs_mapping_count_after": after_stats.get("needs_mapping_count"),
|
||||
"message": result.get("message"),
|
||||
}
|
||||
logging.info(
|
||||
"[Scheduler] [PChomeGrowthMomoBackfill] ✅ 完成 | scanned=%s candidates=%s auto=%s written=%s review=%s mapping=%s%%→%s%%",
|
||||
stats["scanned_products"],
|
||||
stats["candidate_count"],
|
||||
stats["auto_compare_count"],
|
||||
stats["written_count"],
|
||||
stats["review_count"],
|
||||
stats["mapping_rate_before"],
|
||||
stats["mapping_rate_after"],
|
||||
)
|
||||
_save_stats('pchome_growth_momo_backfill', stats)
|
||||
|
||||
except Exception as e:
|
||||
import traceback as _tb
|
||||
logging.error(f"[Scheduler] [PChomeGrowthMomoBackfill] 🚨 任務異常 | Error: {e}")
|
||||
_save_stats('pchome_growth_momo_backfill', {"status": "Failed", "error": str(e)})
|
||||
try:
|
||||
from services.event_router import notify_failure
|
||||
notify_failure(
|
||||
task_name="run_pchome_growth_momo_backfill_task",
|
||||
error=e,
|
||||
source="Scheduler.PChomeGrowthMomoBackfill",
|
||||
event_type="pchome_growth_momo_backfill_failure",
|
||||
priority="P2",
|
||||
title="PChome 高業績商品 MOMO 對應補齊異常",
|
||||
trace=_tb.format_exc(),
|
||||
)
|
||||
except Exception as _router_e:
|
||||
logging.error(f"[Scheduler] [PChomeGrowthMomoBackfill] event_router 失敗: {_router_e}")
|
||||
|
||||
|
||||
def run_icaim_analysis_task():
|
||||
"""
|
||||
ICAIM 競價情報分析排程任務(每 6 小時執行一次)
|
||||
|
||||
@@ -77,7 +77,7 @@ def _store_action_memory(
|
||||
ALLOWED_RETRY_TASKS = {
|
||||
"run_auto_import_task", "run_momo_task", "run_edm_task",
|
||||
"run_competitor_price_feeder_task", "run_backup_monitor_task",
|
||||
"run_pchome_match_backfill_task",
|
||||
"run_pchome_match_backfill_task", "run_pchome_growth_momo_backfill_task",
|
||||
"run_icaim_analysis_task", "run_festival_task", "run_whitepage_check",
|
||||
"run_icaim_analysis_task", "run_db_backup_task", "run_promo_event_task",
|
||||
}
|
||||
|
||||
165
services/pchome_growth_momo_backfill_service.py
Normal file
165
services/pchome_growth_momo_backfill_service.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""PChome 高業績商品主動反查 MOMO 候選,寫入外部報價層。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
def candidate_auto_compare_type(candidate: dict[str, Any]) -> str:
|
||||
auto_type = str(candidate.get("auto_compare_type") or "").strip()
|
||||
if auto_type in {"total_price", "unit_price"}:
|
||||
return auto_type
|
||||
if candidate.get("can_auto_compare") is True:
|
||||
return "total_price"
|
||||
return "manual_review"
|
||||
|
||||
|
||||
def build_momo_backfill_targets(payload: dict[str, Any], limit: int) -> list[dict[str, Any]]:
|
||||
opportunities = list((payload or {}).get("opportunities") or [])
|
||||
targets: list[dict[str, Any]] = []
|
||||
for item in opportunities:
|
||||
action = item.get("recommended_action") or {}
|
||||
if item.get("external_price"):
|
||||
continue
|
||||
if action.get("code") != "map_external_product":
|
||||
continue
|
||||
product_id = str(item.get("pchome_product_id") or "").strip()
|
||||
product_name = str(item.get("product_name") or "").strip()
|
||||
if not product_id or not product_name:
|
||||
continue
|
||||
targets.append({
|
||||
"product_id": product_id,
|
||||
"name": product_name,
|
||||
"price": item.get("pchome_price"),
|
||||
"sales_7d": item.get("sales_7d"),
|
||||
"priority_score": item.get("priority_score"),
|
||||
})
|
||||
if len(targets) >= limit:
|
||||
break
|
||||
return targets
|
||||
|
||||
|
||||
def _default_build_payload(engine, limit: int) -> dict[str, Any]:
|
||||
from services.pchome_revenue_growth_service import build_pchome_growth_opportunities
|
||||
|
||||
return build_pchome_growth_opportunities(engine, limit=limit)
|
||||
|
||||
|
||||
def _default_search_candidates(targets: list[dict[str, Any]], limit: int):
|
||||
from services.momo_crawler import search_momo_products_for_pchome_products
|
||||
|
||||
return search_momo_products_for_pchome_products(
|
||||
targets,
|
||||
max_products=limit,
|
||||
limit_per_product=6,
|
||||
max_terms_per_product=4,
|
||||
min_score=0.45,
|
||||
)
|
||||
|
||||
|
||||
def _default_sync_candidates(engine, candidates: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
from services.external_market_offer_service import sync_targeted_momo_candidates_to_external_offers
|
||||
|
||||
return sync_targeted_momo_candidates_to_external_offers(engine, candidates, dry_run=False)
|
||||
|
||||
|
||||
def run_pchome_growth_momo_backfill(
|
||||
engine,
|
||||
*,
|
||||
limit: int = 12,
|
||||
build_payload_func: Callable[[Any, int], dict[str, Any]] | None = None,
|
||||
search_func: Callable[[list[dict[str, Any]], int], tuple[bool, str, list[dict[str, Any]]]] | None = None,
|
||||
sync_func: Callable[[Any, list[dict[str, Any]]], dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""補高業績 PChome 商品的 MOMO 對應。
|
||||
|
||||
不呼叫 LLM,只搜尋 MOMO 候選,並只把可自動判斷的 total_price / unit_price
|
||||
寫入 external_offers;需人工確認的候選只回報、不寫入。
|
||||
"""
|
||||
limit = max(1, min(int(limit or 12), 20))
|
||||
build_payload = build_payload_func or _default_build_payload
|
||||
search_candidates = search_func or _default_search_candidates
|
||||
sync_candidates = sync_func or _default_sync_candidates
|
||||
|
||||
before_payload = build_payload(engine, max(limit, 16))
|
||||
targets = build_momo_backfill_targets(before_payload, limit)
|
||||
if not targets:
|
||||
return {
|
||||
"success": True,
|
||||
"message": "目前高業績清單沒有需要補 MOMO 對應的商品。",
|
||||
"data": {
|
||||
"scanned_products": 0,
|
||||
"target_count": 0,
|
||||
"candidate_count": 0,
|
||||
"exact_compare_count": 0,
|
||||
"unit_compare_count": 0,
|
||||
"auto_compare_count": 0,
|
||||
"review_count": 0,
|
||||
"external_offer_sync": {
|
||||
"success": True,
|
||||
"status": "not_needed",
|
||||
"written_count": 0,
|
||||
"message": "沒有需要同步的自動候選。",
|
||||
},
|
||||
"before_stats": before_payload.get("stats") or {},
|
||||
"after_stats": before_payload.get("stats") or {},
|
||||
"targets": [],
|
||||
"review_candidates": [],
|
||||
},
|
||||
}
|
||||
|
||||
search_success, search_message, candidates = search_candidates(targets, limit)
|
||||
candidates = list(candidates or [])
|
||||
exact_candidates = [
|
||||
item for item in candidates
|
||||
if candidate_auto_compare_type(item) == "total_price"
|
||||
]
|
||||
unit_candidates = [
|
||||
item for item in candidates
|
||||
if candidate_auto_compare_type(item) == "unit_price"
|
||||
]
|
||||
review_candidates = [
|
||||
item for item in candidates
|
||||
if candidate_auto_compare_type(item) not in {"total_price", "unit_price"}
|
||||
]
|
||||
auto_candidates = [*exact_candidates, *unit_candidates]
|
||||
external_offer_sync = {
|
||||
"success": True,
|
||||
"status": "not_found",
|
||||
"written_count": 0,
|
||||
"message": "已搜尋 MOMO,但尚未找到可自動寫入的同款或單位價候選。",
|
||||
}
|
||||
if auto_candidates:
|
||||
external_offer_sync = sync_candidates(engine, auto_candidates)
|
||||
|
||||
after_payload = build_payload(engine, max(limit, 16))
|
||||
written_count = int(external_offer_sync.get("written_count") or 0)
|
||||
message = (
|
||||
f"已掃描 {len(targets)} 個高業績商品,找到 {len(candidates)} 筆 MOMO 候選,"
|
||||
f"自動寫入 {written_count} 筆。"
|
||||
)
|
||||
if not search_success and not candidates:
|
||||
message = search_message or "已搜尋 MOMO,但沒有找到可用候選。"
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": message,
|
||||
"data": {
|
||||
"search_success": bool(search_success),
|
||||
"search_message": search_message,
|
||||
"scanned_products": len(targets),
|
||||
"target_count": len(targets),
|
||||
"candidate_count": len(candidates),
|
||||
"exact_compare_count": len(exact_candidates),
|
||||
"unit_compare_count": len(unit_candidates),
|
||||
"auto_compare_count": len(auto_candidates),
|
||||
"review_count": len(review_candidates),
|
||||
"external_offer_sync": external_offer_sync,
|
||||
"before_stats": before_payload.get("stats") or {},
|
||||
"after_stats": after_payload.get("stats") or {},
|
||||
"targets": targets[:8],
|
||||
"review_candidates": review_candidates[:8],
|
||||
},
|
||||
}
|
||||
@@ -99,7 +99,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="growth-backfill-status momo-mono" data-pchome-growth-backfill-status>
|
||||
按下後會優先補高業績商品的 MOMO 對應
|
||||
每日 10:45 自動補對應;按下可立即補高業績商品的 MOMO 對應
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -385,5 +385,9 @@ def test_external_offer_sync_is_registered_in_scheduler():
|
||||
|
||||
assert "def run_external_offer_sync_task" in scheduler_source
|
||||
assert "sync_legacy_momo_reference_offers" in scheduler_source
|
||||
assert "def run_pchome_growth_momo_backfill_task" in scheduler_source
|
||||
assert "run_pchome_growth_momo_backfill" in scheduler_source
|
||||
assert "run_external_offer_sync_task" in run_scheduler_source
|
||||
assert "run_pchome_growth_momo_backfill_task" in run_scheduler_source
|
||||
assert "external_offer_sync" in run_scheduler_source
|
||||
assert "pchome_growth_momo_backfill" in run_scheduler_source
|
||||
|
||||
@@ -899,11 +899,16 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
|
||||
run_scheduler_source = (ROOT / "run_scheduler.py").read_text(encoding="utf-8")
|
||||
agent_actions_source = (ROOT / "services/agent_actions.py").read_text(encoding="utf-8")
|
||||
assert "def run_pchome_match_backfill_task" in scheduler_source
|
||||
assert "def run_pchome_growth_momo_backfill_task" in scheduler_source
|
||||
assert "_save_stats('pchome_match_backfill'" in scheduler_source
|
||||
assert "_save_stats('pchome_growth_momo_backfill'" in scheduler_source
|
||||
assert "retryable_candidate_revalidation_total" in scheduler_source
|
||||
assert "run_pchome_match_backfill_task" in run_scheduler_source
|
||||
assert "run_pchome_growth_momo_backfill_task" in run_scheduler_source
|
||||
assert "每日 10:30:pchome_match_backfill" in run_scheduler_source
|
||||
assert "每日 10:45:pchome_growth_momo_backfill" in run_scheduler_source
|
||||
assert '"run_pchome_match_backfill_task"' in agent_actions_source
|
||||
assert '"run_pchome_growth_momo_backfill_task"' in agent_actions_source
|
||||
|
||||
assert "產生今日清單" in template
|
||||
assert "補齊比價資料" in template
|
||||
@@ -917,6 +922,7 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
|
||||
assert "PChome 比價補強" in dashboard_template
|
||||
assert "data-pchome-growth-backfill-trigger" in dashboard_template
|
||||
assert "data-pchome-growth-backfill-status" in dashboard_template
|
||||
assert "每日 10:45 自動補對應" in dashboard_template
|
||||
assert "PCHOME MATCH BACKFILL" not in dashboard_template
|
||||
assert ">ACTIVE<" not in dashboard_template
|
||||
assert "目前 ACTIVE 商品" not in dashboard_template
|
||||
|
||||
@@ -225,15 +225,13 @@ def test_pchome_growth_route_cache_respects_shared_invalidation_epoch(monkeypatc
|
||||
assert routes._get_cached_pchome_growth_payload() is None
|
||||
|
||||
|
||||
def test_pchome_growth_momo_backfill_route_targets_unmapped_high_sales_items(monkeypatch):
|
||||
from flask import Flask
|
||||
from routes import ai_routes as routes
|
||||
def test_pchome_growth_momo_backfill_service_targets_unmapped_high_sales_items():
|
||||
from services.pchome_growth_momo_backfill_service import run_pchome_growth_momo_backfill
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeEngine:
|
||||
def dispose(self):
|
||||
captured["disposed"] = True
|
||||
pass
|
||||
|
||||
before_payload = {
|
||||
"success": True,
|
||||
@@ -309,20 +307,14 @@ def test_pchome_growth_momo_backfill_route_targets_unmapped_high_sales_items(mon
|
||||
"unit_price_count": 1,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", lambda database_path: FakeEngine())
|
||||
monkeypatch.setattr(routes, "_build_pchome_growth_payload", fake_build_payload)
|
||||
monkeypatch.setattr(routes, "_search_growth_momo_candidates", fake_search)
|
||||
monkeypatch.setattr(routes, "_sync_growth_momo_candidates", fake_sync)
|
||||
payload = run_pchome_growth_momo_backfill(
|
||||
FakeEngine(),
|
||||
limit=2,
|
||||
build_payload_func=fake_build_payload,
|
||||
search_func=fake_search,
|
||||
sync_func=fake_sync,
|
||||
)
|
||||
|
||||
app = Flask(__name__)
|
||||
with app.test_request_context(
|
||||
"/api/ai/pchome-growth/backfill-momo-candidates",
|
||||
method="POST",
|
||||
json={"limit": 2},
|
||||
):
|
||||
response = routes.api_pchome_growth_backfill_momo_candidates.__wrapped__()
|
||||
|
||||
payload = response.get_json()
|
||||
assert payload["success"] is True
|
||||
assert payload["data"]["scanned_products"] == 2
|
||||
assert payload["data"]["candidate_count"] == 3
|
||||
@@ -334,6 +326,47 @@ def test_pchome_growth_momo_backfill_route_targets_unmapped_high_sales_items(mon
|
||||
assert [item["price"] for item in captured["targets"]] == [920, 760]
|
||||
assert [item["product_id"] for item in captured["sync_candidates"]] == ["MOMO-AUTO", "MOMO-UNIT"]
|
||||
assert captured["search_limit"] == 2
|
||||
|
||||
|
||||
def test_pchome_growth_momo_backfill_route_calls_shared_service(monkeypatch):
|
||||
from flask import Flask
|
||||
from routes import ai_routes as routes
|
||||
|
||||
captured = {}
|
||||
|
||||
class FakeEngine:
|
||||
def dispose(self):
|
||||
captured["disposed"] = True
|
||||
|
||||
def fake_run(engine, limit):
|
||||
captured["limit"] = limit
|
||||
return {
|
||||
"success": True,
|
||||
"message": "已完成",
|
||||
"data": {
|
||||
"scanned_products": 2,
|
||||
"candidate_count": 3,
|
||||
"auto_compare_count": 2,
|
||||
"review_count": 1,
|
||||
"external_offer_sync": {"written_count": 2},
|
||||
},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(routes, "_create_icaim_dashboard_engine", lambda database_path: FakeEngine())
|
||||
monkeypatch.setattr(routes, "_run_pchome_growth_momo_backfill", fake_run)
|
||||
|
||||
app = Flask(__name__)
|
||||
with app.test_request_context(
|
||||
"/api/ai/pchome-growth/backfill-momo-candidates",
|
||||
method="POST",
|
||||
json={"limit": 2},
|
||||
):
|
||||
response = routes.api_pchome_growth_backfill_momo_candidates.__wrapped__()
|
||||
|
||||
payload = response.get_json()
|
||||
assert payload["success"] is True
|
||||
assert payload["data"]["external_offer_sync"]["written_count"] == 2
|
||||
assert captured["limit"] == 2
|
||||
assert captured["disposed"] is True
|
||||
|
||||
|
||||
|
||||
@@ -226,6 +226,8 @@ def test_roi_ai_smoke_and_daily_report_schedules_stay_staggered():
|
||||
assert 'schedule.every().day.at("09:00").do(run_daily_report_task)' in source
|
||||
assert 'schedule.every().day.at("09:05").do(run_roi_monthly_report_if_new_month)' in source
|
||||
assert 'schedule.every().day.at("09:10").do(run_ai_smoke_daily_summary_task)' in source
|
||||
assert 'schedule.every().day.at("10:30").do(run_pchome_match_backfill_task)' in source
|
||||
assert 'schedule.every().day.at("10:45").do(run_pchome_growth_momo_backfill_task)' in source
|
||||
assert "schedule.every(6).hours.do(run_action_plan_hygiene_task)" in source
|
||||
assert "schedule.every(15).minutes.do(run_ollama_111_usage_guard_check)" in source
|
||||
|
||||
|
||||
Reference in New Issue
Block a user