fix(ai): supersede old product picks
All checks were successful
CD Pipeline / deploy (push) Successful in 2m48s

This commit is contained in:
OoO
2026-05-01 16:24:15 +08:00
parent b3d00a011c
commit 9e2337764b
6 changed files with 32 additions and 5 deletions

View File

@@ -2,7 +2,7 @@
> 本文件定義專案開發的核心準則與不可違反的規範
> **建立日期**: 2026-01-12
> **當前版本**: V10.63 (Warm dashboard cache after AI pick regeneration)
> **當前版本**: V10.64 (Keep only latest 50 AI product picks pending)
> **最後更新**: 2026-05-01
---

4
app.py
View File

@@ -95,8 +95,8 @@ except Exception as e:
sys_log.error(f"無法檢測磁碟空間: {e}")
# 🚩 系統版本定義 (備份與顯示用)
# 🚩 2026-05-01 V10.63: Warm dashboard cache after AI pick regeneration
SYSTEM_VERSION = "V10.63"
# 🚩 2026-05-01 V10.64: Keep only latest 50 AI product picks pending
SYSTEM_VERSION = "V10.64"
# ==========================================
# 🔒 SQL Injection 防護函數

View File

@@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.63"
SYSTEM_VERSION = "V10.64"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -31,7 +31,7 @@ SQL漏斗(~300筆)
`services/ai_product_pick_agent.py` 新增 PChome 銷售用挑品 Agent
- 只讀真實資料表:`products``price_records``competitor_prices``competitor_price_history`,若 `daily_sales_snapshot` 可用則納入近 7 天銷售額、數量、毛利或成本推算毛利率。
- 將 PChome 比 MOMO 有價格優勢、比對信心足夠、且有歷史快照或銷售動能的品項寫入 `ai_price_recommendations`。信心度不以固定倍率灌高,而是由商機分數與證據完整度共同決定,證據包含 PChome match score、歷史快照、銷售/毛利、PChome 商品 ID/名稱、抓取時間與促銷/評價/庫存標籤。
- 將 PChome 比 MOMO 有價格優勢、比對信心足夠、且有歷史快照或銷售動能的品項寫入 `ai_price_recommendations`。信心度不以固定倍率灌高,而是由商機分數與證據完整度共同決定,證據包含 PChome match score、歷史快照、銷售/毛利、PChome 商品 ID/名稱、抓取時間與促銷/評價/庫存標籤。每次重算只保留最新 50 品為 `status='pending'`,未進榜舊品標為 `superseded`,避免統計與清單超量。
- 寫入策略使用 `strategy='product_pick'`,保留在既有 AI 決策表,不新增假頁面或暫存 JSON。
- 後台入口:`POST /api/ai/product-picks/generate``/ai_intelligence` 可手動產生清單。
- 配對來源仍以 PChome crawler 真實搜尋結果為準;無競品資料時不生成挑品。

View File

@@ -423,6 +423,30 @@ def _write_pick(conn, pick: Dict[str, Any]) -> None:
})
def _supersede_old_picks(conn, current_skus: List[str]) -> None:
from sqlalchemy import bindparam, text
if not current_skus:
conn.execute(text("""
UPDATE ai_price_recommendations
SET status = 'superseded',
updated_at = CURRENT_TIMESTAMP
WHERE strategy = 'product_pick'
AND status = 'pending'
"""))
return
stmt = text("""
UPDATE ai_price_recommendations
SET status = 'superseded',
updated_at = CURRENT_TIMESTAMP
WHERE strategy = 'product_pick'
AND status = 'pending'
AND sku NOT IN :current_skus
""").bindparams(bindparam("current_skus", expanding=True))
conn.execute(stmt, {"current_skus": [str(sku) for sku in current_skus]})
def generate_product_pick_list(engine, limit: int = 50) -> ProductPickResult:
"""產生並保存 AI 建議挑品清單。"""
generated_at = datetime.now().isoformat(timespec="seconds")
@@ -439,6 +463,7 @@ def generate_product_pick_list(engine, limit: int = 50) -> ProductPickResult:
picks = picks[:limit]
for pick in picks:
_write_pick(conn, pick)
_supersede_old_picks(conn, [pick["sku"] for pick in picks])
return ProductPickResult(
candidates=len(rows),

View File

@@ -197,6 +197,8 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
assert "evidence_quality" in agent_source
assert "opportunity_score" in agent_source
assert "margin_rate" in agent_source
assert "_supersede_old_picks" in agent_source
assert "status = 'superseded'" in agent_source
assert "{date_col}::date" in agent_source
assert "conn.rollback()" in agent_source