diff --git a/config.py b/config.py index 1ca8aa8..c4f703d 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.277" +SYSTEM_VERSION = "V10.278" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index bf7037a..d119af4 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -2,7 +2,7 @@ > **最後更新**: 2026-05-19 (台北時間) > **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯,Gemini 僅備援 / 鎖定場景 -> **適用版本**: V10.277 +> **適用版本**: V10.278 --- @@ -52,7 +52,7 @@ SQL漏斗(~300筆) - 配對來源仍以 PChome crawler 真實搜尋結果為準;無競品資料時不生成挑品。 - 比對覆蓋率補強入口:`POST /api/ai/pchome-match/backfill`,優先補抓仍無有效 PChome 配對的高價 ACTIVE 商品,完成後自動重算 AI 挑品清單。 - 排程閉環:`run_pchome_match_backfill_task` 每日 10:30 執行,補抓 PChome 待比對商品、寫入歷史價格,再重算 `strategy='product_pick'` 清單。 -- PChome / MOMO 競價摘要出口 `services/competitor_intel_repository.py` 使用 5 分鐘共享快取(`COMPETITOR_INTEL_CACHE_TTL_SECONDS` 可調),避免 `/growth_analysis`、`/daily_sales`、PPT/AI 報表每次請求重跑昂貴覆蓋率與價差趨勢查詢;快取只包摘要輸出,不改 matcher 的高信心門檻與 identity_v2 準確性規則。 +- PChome / MOMO 競價摘要出口 `services/competitor_intel_repository.py` 使用 30 分鐘共享快取(`COMPETITOR_INTEL_CACHE_TTL_SECONDS` 可調),避免 `/growth_analysis`、`/daily_sales`、PPT/AI 報表每次請求重跑昂貴覆蓋率與價差趨勢查詢;`run_competitor_price_feeder_task` 與 PChome backfill 完成後會主動清除快取。快取只包摘要輸出,不改 matcher 的高信心門檻與 identity_v2 準確性規則。 - 商品看板第一屏:`/` 的 V2 看板直接以 `products`、`price_records`、`competitor_prices`、`ai_price_recommendations` 顯示比對覆蓋率、PChome 優勢、MOMO 威脅、AI 挑品與待比對優先清單;`filter=ai_picks` 可查看 50 品 AI 挑品列表,並在列表上方顯示平均信心、平均價差、最大價差與估算總價差空間,列表列內顯示 AI 排名與建議理由,且可透過 `/api/export/excel/ai-picks` 匯出 50 品 Excel 操作清單。商品看板深度快取同時寫入 `data/dashboard_full_cache.pkl`,供多個 Gunicorn worker 共用,避免部署後各 worker 重複重建 7,000+ 商品統計造成開頁變慢;所有資料異動與 AI 挑品重算都透過 `clear_dashboard_cache()` 同步清除記憶體與共享快取,手動重算 API 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。 | 角色 | 模型 | 主機 | 成本 | 每日限額 | diff --git a/routes/ai_routes.py b/routes/ai_routes.py index 7bbc55e..2060943 100644 --- a/routes/ai_routes.py +++ b/routes/ai_routes.py @@ -1719,6 +1719,7 @@ def api_pchome_match_backfill(): from config import DATABASE_PATH from sqlalchemy import create_engine from services.ai_product_pick_agent import generate_product_pick_list + from services.competitor_intel_repository import clear_competitor_intel_cache from services.competitor_price_feeder import CompetitorPriceFeeder engine = create_engine(DATABASE_PATH) @@ -1726,6 +1727,7 @@ def api_pchome_match_backfill(): pick_result = generate_product_pick_list(engine, limit=50) from services.cache_manager import clear_dashboard_cache clear_dashboard_cache() + clear_competitor_intel_cache() logger.info( "[PChomeBackfill] done total=%s matched=%s no=%s low=%s errors=%s history=%s duration=%ss pick_written=%s", result.total_skus, diff --git a/scheduler.py b/scheduler.py index 7fc5235..7ef2983 100644 --- a/scheduler.py +++ b/scheduler.py @@ -2131,6 +2131,7 @@ def run_competitor_price_feeder_task(): try: from config import DATABASE_PATH from sqlalchemy import create_engine + from services.competitor_intel_repository import clear_competitor_intel_cache from services.competitor_price_feeder import CompetitorPriceFeeder now_str = datetime.now(TAIPEI_TZ).strftime('%Y-%m-%d %H:%M') @@ -2159,6 +2160,7 @@ def run_competitor_price_feeder_task(): f"errors={result.errors} " f"耗時={result.duration_sec}s" ) + clear_competitor_intel_cache() _save_stats('competitor_price_feeder', stats) except Exception as e: @@ -2191,6 +2193,7 @@ def run_pchome_match_backfill_task(): from sqlalchemy import create_engine from services.ai_product_pick_agent import generate_product_pick_list from services.cache_manager import clear_dashboard_cache + from services.competitor_intel_repository import clear_competitor_intel_cache from services.competitor_price_feeder import CompetitorPriceFeeder now_str = datetime.now(TAIPEI_TZ).strftime('%Y-%m-%d %H:%M') @@ -2200,6 +2203,7 @@ def run_pchome_match_backfill_task(): feeder_result = CompetitorPriceFeeder(engine=engine).run_unmatched_priority(limit=120) pick_result = generate_product_pick_list(engine, limit=50) clear_dashboard_cache() + clear_competitor_intel_cache() stats = { "total_skus": feeder_result.total_skus, diff --git a/services/competitor_intel_repository.py b/services/competitor_intel_repository.py index d59e363..da4a966 100644 --- a/services/competitor_intel_repository.py +++ b/services/competitor_intel_repository.py @@ -22,7 +22,7 @@ from sqlalchemy import inspect, text PCHOME_MATCH_SCORE_FLOOR = 0.76 -COMPETITOR_INTEL_CACHE_TTL_SECONDS = int(os.getenv("COMPETITOR_INTEL_CACHE_TTL_SECONDS", "300")) +COMPETITOR_INTEL_CACHE_TTL_SECONDS = int(os.getenv("COMPETITOR_INTEL_CACHE_TTL_SECONDS", "1800")) _BASE_DIR = Path(__file__).resolve().parents[1] _CACHE_FILE = _BASE_DIR / "data" / "competitor_intel_cache.pkl" _CACHE_LOCK = Lock()