fix(dashboard): warm cache after AI pick refresh
All checks were successful
CD Pipeline / deploy (push) Successful in 3m37s
All checks were successful
CD Pipeline / deploy (push) Successful in 3m37s
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本文件定義專案開發的核心準則與不可違反的規範
|
||||
> **建立日期**: 2026-01-12
|
||||
> **當前版本**: V10.62 (Product pick regeneration clears dashboard cache)
|
||||
> **當前版本**: V10.63 (Warm dashboard cache after AI pick regeneration)
|
||||
> **最後更新**: 2026-05-01
|
||||
|
||||
---
|
||||
|
||||
4
app.py
4
app.py
@@ -95,8 +95,8 @@ except Exception as e:
|
||||
sys_log.error(f"無法檢測磁碟空間: {e}")
|
||||
|
||||
# 🚩 系統版本定義 (備份與顯示用)
|
||||
# 🚩 2026-05-01 V10.62: Product pick regeneration clears dashboard cache
|
||||
SYSTEM_VERSION = "V10.62"
|
||||
# 🚩 2026-05-01 V10.63: Warm dashboard cache after AI pick regeneration
|
||||
SYSTEM_VERSION = "V10.63"
|
||||
|
||||
# ==========================================
|
||||
# 🔒 SQL Injection 防護函數
|
||||
|
||||
@@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.62"
|
||||
SYSTEM_VERSION = "V10.63"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -37,7 +37,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'` 清單。
|
||||
- 商品看板第一屏:`/` 的 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()` 同步清除記憶體與共享快取。
|
||||
- 商品看板第一屏:`/` 的 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 會立即預熱商品看板快取,避免第一位使用者承擔重建成本。
|
||||
|
||||
| 角色 | 模型 | 主機 | 成本 | 每日限額 |
|
||||
|------|------|------|------|---------|
|
||||
|
||||
@@ -1625,6 +1625,7 @@ def api_generate_product_picks():
|
||||
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 routes.dashboard_routes import get_full_dashboard_data
|
||||
|
||||
payload = request.get_json(silent=True) or {}
|
||||
limit = int(payload.get('limit', 50))
|
||||
@@ -1633,6 +1634,11 @@ def api_generate_product_picks():
|
||||
engine = create_engine(DATABASE_PATH)
|
||||
result = generate_product_pick_list(engine, limit=limit)
|
||||
clear_dashboard_cache()
|
||||
dashboard_cache_warmed = False
|
||||
try:
|
||||
dashboard_cache_warmed = bool(get_full_dashboard_data())
|
||||
except Exception as warm_err:
|
||||
logger.warning(f"[ProductPickAgent] 商品看板快取預熱失敗: {warm_err}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
@@ -1641,6 +1647,7 @@ def api_generate_product_picks():
|
||||
'candidates': result.candidates,
|
||||
'written': result.written,
|
||||
'generated_at': result.generated_at,
|
||||
'dashboard_cache_warmed': dashboard_cache_warmed,
|
||||
'picks': result.picks[:50],
|
||||
}
|
||||
})
|
||||
@@ -1668,6 +1675,8 @@ def api_pchome_match_backfill():
|
||||
engine = create_engine(DATABASE_PATH)
|
||||
result = CompetitorPriceFeeder(engine=engine).run_unmatched_priority(limit=limit)
|
||||
pick_result = generate_product_pick_list(engine, limit=50)
|
||||
from services.cache_manager import clear_dashboard_cache
|
||||
clear_dashboard_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,
|
||||
|
||||
@@ -2071,6 +2071,7 @@ def run_pchome_match_backfill_task():
|
||||
from config import DATABASE_PATH
|
||||
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_price_feeder import CompetitorPriceFeeder
|
||||
|
||||
now_str = datetime.now(TAIPEI_TZ).strftime('%Y-%m-%d %H:%M')
|
||||
@@ -2079,6 +2080,7 @@ def run_pchome_match_backfill_task():
|
||||
engine = create_engine(DATABASE_PATH)
|
||||
feeder_result = CompetitorPriceFeeder(engine=engine).run_unmatched_priority(limit=120)
|
||||
pick_result = generate_product_pick_list(engine, limit=50)
|
||||
clear_dashboard_cache()
|
||||
|
||||
stats = {
|
||||
"total_skus": feeder_result.total_skus,
|
||||
|
||||
@@ -181,6 +181,8 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
|
||||
|
||||
assert "generate_product_pick_list" in agent_source
|
||||
assert "clear_dashboard_cache()" in route_source
|
||||
assert "get_full_dashboard_data()" in route_source
|
||||
assert "dashboard_cache_warmed" in route_source
|
||||
assert "competitor_prices" in agent_source
|
||||
assert "competitor_price_history" in agent_source
|
||||
assert "daily_sales_snapshot" in agent_source
|
||||
|
||||
Reference in New Issue
Block a user