V10.565 補比價覆蓋率操作建議
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.565 補 PChome 覆蓋率操作建議:`/api/ai/pchome-match/backfill/status` 會把低覆蓋率拆成 `operation_backlog`,分別列出刷新舊 identity、重評近門檻、補抓未配對、人工覆核、單位價覆核與過期搜尋救援預覽;同時回傳 `recommended_next_action`,Dashboard 狀態摘要會顯示「建議執行比價補強 / 刷新過期 identity / 處理覆核」等下一步,讓覆蓋率 KPI 直接連到可執行行動。
|
||||
- V10.563 收斂正式 preview 假可救候選:M.A.C 超持妝輕透濾鏡蜜粉若只有 PChome 端出現明確色號(例如 `#絕絕紫`),會標成 `variant_selection_review` 並維持 `true_low_confidence`,不再佔 recoverable 池;SAUGELLA 賽吉兒菁萃潔浴凝露新增潤澤 / 日用型 / 加強 / 黃金女郎型變體互斥,避免同品線不同私密清潔款式被誤救成 matched。
|
||||
- V10.561 補 PChome 比價補強前端分段回饋:Dashboard 的 PChome 卡片從「補抓產線」改為「比價補強產線」,按鈕與確認文案同步說明會先刷新舊 identity、再重評近門檻與補抓未配對;結果區新增刷新 / 重評 / 補抓三段 matched/total 摘要,避免後端已完成分段統計但操作員仍只看到一個籠統成功數。
|
||||
- V10.560 串起手動 PChome 比價補強三段式流程:`/api/ai/pchome-match/backfill` 現在不只跑近門檻重評與未配對補抓,也會先用小批次 `run_expired_identity_refresh()` 刷新已知 `identity_v2` 舊價格,讓操作員按一次補強就能同時處理「舊 identity 新鮮度」、「near-threshold low_score」與「pending identity」三條主線。結果 payload 新增 `stale_identity_refresh` 分段統計,方便後續 Dashboard / 簡報 / AI 決策知道覆蓋率改善是來自刷新、重評或補抓。
|
||||
|
||||
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.563"
|
||||
SYSTEM_VERSION = "V10.565"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
## 📅 詳細更新日誌 (考古存檔)
|
||||
|
||||
### 2026-06-01:PChome 比價新鮮度操作閉環
|
||||
- **V10.565 PChome 覆蓋率操作建議**: 補強 `/api/ai/pchome-match/backfill/status`,將低覆蓋率拆成 `operation_backlog`:刷新舊 identity、重評近門檻、補抓未配對、人工覆核、單位價覆核與過期搜尋救援預覽;並新增 `recommended_next_action`,Dashboard 狀態摘要會直接顯示建議下一步,避免使用者只看到低覆蓋率卻不知道該按哪條產線。
|
||||
- **V10.563 正式 preview 假可救候選收斂**: 針對正式 `retryable_candidate_preview` 露出的 M.A.C 蜜粉與 SAUGELLA 菁萃潔浴凝露案例補 guard。M.A.C 單邊明確色號(如 `#絕絕紫`)會進 `variant_selection_review`,維持 `true_low_confidence`;SAUGELLA 潤澤 / 日用型 / 加強 / 黃金女郎型互斥,直接 hard veto,避免同品線不同私密清潔款式被當成 recoverable low_score。
|
||||
- **V10.561 PChome 比價補強前端分段回饋**: Dashboard 的 PChome 操作卡改名為「比價補強產線」,手動按鈕與確認文案同步說明三段流程;結果摘要會顯示刷新、重評、補抓各自的 matched/total,讓操作員能判斷覆蓋率改善來自舊 identity 新鮮度回補、近門檻 matcher 回刷,或 pending 商品 fresh search 補抓。
|
||||
- **V10.560 手動 PChome 比價補強三段式串接**: `/api/ai/pchome-match/backfill` 與每日 scheduler 口徑對齊,手動執行時先小批次刷新過期 `identity_v2`,再跑近門檻候選重評,最後補抓高優先未配對商品。回傳結果新增 `stale_identity_refresh` 分段統計,讓後續 Dashboard、簡報與 AI 決策能區分覆蓋率改善來自舊 identity 新鮮度回補、matcher 回刷,還是 fresh search 補抓。
|
||||
|
||||
@@ -2189,6 +2189,11 @@ def _build_pchome_backfill_coverage_payload():
|
||||
coverage = fetch_competitor_coverage(engine) or {}
|
||||
revalidation_preview = _build_pchome_revalidation_preview_payload(engine)
|
||||
stale_recovery_preview = _build_pchome_stale_recovery_preview_payload(engine)
|
||||
operation_backlog = _build_pchome_operation_backlog(
|
||||
coverage,
|
||||
revalidation_preview,
|
||||
stale_recovery_preview,
|
||||
)
|
||||
return {
|
||||
'available': True,
|
||||
'active_with_price': int(coverage.get('active_with_price') or 0),
|
||||
@@ -2215,6 +2220,8 @@ def _build_pchome_backfill_coverage_payload():
|
||||
'stale_recovery_preview': stale_recovery_preview,
|
||||
'stale_recovery_preview_count': int(stale_recovery_preview.get('candidate_count') or 0),
|
||||
'stale_recovery_preview_has_more': bool(stale_recovery_preview.get('has_more')),
|
||||
'operation_backlog': operation_backlog,
|
||||
'recommended_next_action': _pick_pchome_recommended_next_action(operation_backlog),
|
||||
}
|
||||
except Exception as exc:
|
||||
logger.warning(f"[PChomeBackfill] coverage snapshot unavailable: {exc}")
|
||||
@@ -2227,6 +2234,88 @@ def _build_pchome_backfill_coverage_payload():
|
||||
engine.dispose()
|
||||
|
||||
|
||||
def _build_pchome_operation_backlog(coverage, revalidation_preview, stale_recovery_preview):
|
||||
"""把低覆蓋率拆成可操作 backlog,避免只回傳一個模糊百分比。"""
|
||||
retryable_count = int((revalidation_preview or {}).get('candidate_count') or 0)
|
||||
return {
|
||||
'refresh_stale': {
|
||||
'label': '刷新舊 identity',
|
||||
'count': int((coverage or {}).get('stale_matches') or 0),
|
||||
'endpoint': '/api/ai/pchome-match/refresh-stale',
|
||||
},
|
||||
'retryable_revalidation': {
|
||||
'label': '重評近門檻',
|
||||
'count': retryable_count,
|
||||
'endpoint': '/api/ai/pchome-match/backfill',
|
||||
},
|
||||
'unmatched_priority': {
|
||||
'label': '補抓未配對',
|
||||
'count': int((coverage or {}).get('pending') or 0),
|
||||
'endpoint': '/api/ai/pchome-match/backfill',
|
||||
},
|
||||
'manual_review': {
|
||||
'label': '人工覆核',
|
||||
'count': int((coverage or {}).get('actionable_review_count') or 0),
|
||||
'endpoint': '/vendor-stockout/list',
|
||||
},
|
||||
'unit_price_review': {
|
||||
'label': '單位價覆核',
|
||||
'count': int((coverage or {}).get('unit_comparable_count') or 0),
|
||||
'endpoint': '/vendor-stockout/list?review_status=unit_comparable',
|
||||
},
|
||||
'stale_search_recovery_preview': {
|
||||
'label': '過期 identity 搜尋救援預覽',
|
||||
'count': int((stale_recovery_preview or {}).get('candidate_count') or 0),
|
||||
'endpoint': '/api/ai/pchome-match/backfill/status',
|
||||
'read_only': True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _pick_pchome_recommended_next_action(operation_backlog):
|
||||
"""根據 backlog 選一個清楚的下一步;不在這裡自動執行任何寫入。"""
|
||||
retryable = int(operation_backlog.get('retryable_revalidation', {}).get('count') or 0)
|
||||
pending = int(operation_backlog.get('unmatched_priority', {}).get('count') or 0)
|
||||
stale = int(operation_backlog.get('refresh_stale', {}).get('count') or 0)
|
||||
manual = int(operation_backlog.get('manual_review', {}).get('count') or 0)
|
||||
unit_price = int(operation_backlog.get('unit_price_review', {}).get('count') or 0)
|
||||
|
||||
if retryable > 0 or pending > 0:
|
||||
return {
|
||||
'key': 'run_backfill',
|
||||
'label': '執行比價補強',
|
||||
'reason': '同時刷新少量舊 identity、重評近門檻候選,並補抓高優先未配對商品。',
|
||||
'endpoint': '/api/ai/pchome-match/backfill',
|
||||
}
|
||||
if stale > 0:
|
||||
return {
|
||||
'key': 'refresh_stale',
|
||||
'label': '刷新過期 identity',
|
||||
'reason': '已有身份配對但價格過期,先補回可決策的新鮮價格。',
|
||||
'endpoint': '/api/ai/pchome-match/refresh-stale',
|
||||
}
|
||||
if unit_price > 0:
|
||||
return {
|
||||
'key': 'review_unit_price',
|
||||
'label': '處理單位價覆核',
|
||||
'reason': '商品可比較但需要人工確認單位價換算,避免錯誤總價決策。',
|
||||
'endpoint': '/vendor-stockout/list?review_status=unit_comparable',
|
||||
}
|
||||
if manual > 0:
|
||||
return {
|
||||
'key': 'manual_review',
|
||||
'label': '處理人工覆核',
|
||||
'reason': '剩餘項目需要人工判斷款式、色號、件數或既有候選衝突。',
|
||||
'endpoint': '/vendor-stockout/list',
|
||||
}
|
||||
return {
|
||||
'key': 'observe',
|
||||
'label': '維持觀測',
|
||||
'reason': '目前沒有明確的自動補強 backlog。',
|
||||
'endpoint': '/api/ai/pchome-match/backfill/status',
|
||||
}
|
||||
|
||||
|
||||
def _build_pchome_revalidation_preview_payload(engine):
|
||||
"""回傳近門檻重評候選的只讀預覽,供操作員判讀下一步。"""
|
||||
now_ts = datetime.now().timestamp()
|
||||
|
||||
@@ -528,6 +528,10 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
|
||||
assert "preview_expired_identity_recovery" in route_source
|
||||
assert "revalidation_preview" in route_source
|
||||
assert "stale_recovery_preview" in route_source
|
||||
assert "_build_pchome_operation_backlog" in route_source
|
||||
assert "_pick_pchome_recommended_next_action" in route_source
|
||||
assert "'operation_backlog': operation_backlog" in route_source
|
||||
assert "'recommended_next_action': _pick_pchome_recommended_next_action(operation_backlog)" in route_source
|
||||
assert "status['coverage'] = _build_pchome_backfill_coverage_payload()" in route_source
|
||||
assert "run_unmatched_priority(limit=unmatched_limit)" in route_source
|
||||
assert "stale_refresh_limit = max(5, min(40, max(5, limit // 3)))" in route_source
|
||||
@@ -594,6 +598,8 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
|
||||
assert "刷新', result.stale_identity_refresh" in dashboard_js
|
||||
assert "formatBackfillLimitedCount" in dashboard_js
|
||||
assert "status.coverage" in dashboard_js
|
||||
assert "coverage.recommended_next_action" in dashboard_js
|
||||
assert "建議 ${recommended.label}" in dashboard_js
|
||||
assert "可用比價 ${formatBackfillRate(coverage.decision_ready_rate)}" in dashboard_js
|
||||
assert "待刷新 ${formatBackfillCount(coverage.stale_matches)}" in dashboard_js
|
||||
assert "待補抓 ${formatBackfillCount(coverage.pending)}" in dashboard_js
|
||||
|
||||
@@ -327,6 +327,8 @@ let priceChartInstance = null;
|
||||
const staleRecoveryText = staleRecoveryAvailable
|
||||
? ` · 可救援 ${formatBackfillLimitedCount(staleRecovery.candidate_count, staleRecovery.has_more)}`
|
||||
: '';
|
||||
const recommended = coverage.recommended_next_action || {};
|
||||
const recommendedText = recommended.label ? ` · 建議 ${recommended.label}` : '';
|
||||
return (
|
||||
`可用比價 ${formatBackfillRate(coverage.decision_ready_rate)}`
|
||||
+ ` · 身份 ${formatBackfillRate(coverage.match_rate)}`
|
||||
@@ -336,6 +338,7 @@ let priceChartInstance = null;
|
||||
+ previewText
|
||||
+ reviewGatedText
|
||||
+ staleRecoveryText
|
||||
+ recommendedText
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user