diff --git a/config.py b/config.py index 5774365..587fbc5 100644 --- a/config.py +++ b/config.py @@ -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 # 用於模板顯示 diff --git a/routes/ai_routes.py b/routes/ai_routes.py index 643e449..94176b9 100644 --- a/routes/ai_routes.py +++ b/routes/ai_routes.py @@ -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({ diff --git a/run_scheduler.py b/run_scheduler.py index 71a3ef5..bf4c499 100644 --- a/run_scheduler.py +++ b/run_scheduler.py @@ -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") diff --git a/scheduler.py b/scheduler.py index 121a91d..59ac206 100644 --- a/scheduler.py +++ b/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 小時執行一次) diff --git a/services/agent_actions.py b/services/agent_actions.py index 4ebe19b..8532498 100644 --- a/services/agent_actions.py +++ b/services/agent_actions.py @@ -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", } diff --git a/services/pchome_growth_momo_backfill_service.py b/services/pchome_growth_momo_backfill_service.py new file mode 100644 index 0000000..5206fe6 --- /dev/null +++ b/services/pchome_growth_momo_backfill_service.py @@ -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], + }, + } diff --git a/templates/dashboard_v2.html b/templates/dashboard_v2.html index a5ef37d..5fa32b4 100644 --- a/templates/dashboard_v2.html +++ b/templates/dashboard_v2.html @@ -99,7 +99,7 @@ {% endfor %}
- 按下後會優先補高業績商品的 MOMO 對應 + 每日 10:45 自動補對應;按下可立即補高業績商品的 MOMO 對應
diff --git a/tests/test_external_market_offer_service.py b/tests/test_external_market_offer_service.py index e67f79e..7e4c7c0 100644 --- a/tests/test_external_market_offer_service.py +++ b/tests/test_external_market_offer_service.py @@ -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 diff --git a/tests/test_frontend_v2_assets.py b/tests/test_frontend_v2_assets.py index 8bbe8ea..03ed72b 100644 --- a/tests/test_frontend_v2_assets.py +++ b/tests/test_frontend_v2_assets.py @@ -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 diff --git a/tests/test_pchome_revenue_growth_service.py b/tests/test_pchome_revenue_growth_service.py index 7198770..8c9d236 100644 --- a/tests/test_pchome_revenue_growth_service.py +++ b/tests/test_pchome_revenue_growth_service.py @@ -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 diff --git a/tests/test_run_scheduler_embed_consistency.py b/tests/test_run_scheduler_embed_consistency.py index 7966bb6..29a4b7b 100644 --- a/tests/test_run_scheduler_embed_consistency.py +++ b/tests/test_run_scheduler_embed_consistency.py @@ -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