This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.291 補核心 MOMO/PChome 比價第三層語意:同核心商品但買送、套組、件數不同時標記 `unit_comparable`,只寫入 `competitor_match_attempts`,商品看板顯示「需單位價比較」,不再把不同販售組合直接寫進正式總價差。
|
||||
- V10.292 補核心 MOMO/PChome 比價第三層語意:同核心商品但買送、套組、件數不同且只有單一基礎規格時標記 `unit_comparable`,只寫入 `competitor_match_attempts`,商品看板顯示「需單位價比較」;多容量/多品項套組仍保持不可比較,避免把不同販售組合直接寫進正式總價差。
|
||||
- V10.289 重排 Elephant Alpha L3 HITL `ea_escalation` Telegram 告警:改成專業 incident brief 格式,分成決策狀態、背景摘要、風險摘要、TOP 待審 SKU 與建議處置;價格行動會拆出 MOMO/PChome 價格、價差、人工處置與 PChome ID,避免長 bullet 難讀。
|
||||
- V10.284 關閉 Code Review Hermes LLM scan 預設路徑:Step 2 改 deterministic fast static scan,不再讓部署後先卡三段 Ollama timeout;若需要 LLM scan 可用 `CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=true` 顯式開啟,仍只走本地矩陣、不走 Gemini。
|
||||
- V10.283 將 Code Review Hermes scan 收斂為 fast compact prompt:預設 2 檔 × 900 字、輸出 384 tokens,仍走 GCP-A → GCP-B → 111 本地矩陣,避免部署後 code_review_hermes 先卡三段 timeout。
|
||||
|
||||
@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.291"
|
||||
SYSTEM_VERSION = "V10.292"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> **最後更新**: 2026-05-19 (台北時間)
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯,Gemini 僅備援 / 鎖定場景
|
||||
> **適用版本**: V10.291
|
||||
> **適用版本**: V10.292
|
||||
|
||||
---
|
||||
|
||||
@@ -345,7 +345,7 @@ LEFT JOIN competitor_prices cp
|
||||
- `services/competitor_identity_revalidator.py` 可對既有 `competitor_prices` legacy row 離線重跑 `identity_v2`:只有新版 matcher 分數 `>= 0.76` 且無 hard veto 才補 `identity_v2` / `legacy_revalidated` tags;預設不刷新 `expires_at`,避免過期價格進入決策。
|
||||
- `CompetitorPriceFeeder.run_expired_identity_refresh()` 會優先刷新已通過 `identity_v2` 但 TTL 過期的 PChome row:直接用既有 `competitor_product_id` 批次呼叫 PChome 商品 API,再用新版 matcher 重新驗證名稱/規格/價格 sanity,通過後寫回 `competitor_prices` 與 `competitor_price_history`。這條路徑提升新鮮價格覆蓋率,但不降低 match threshold,也不讓過期價格直接進入決策。
|
||||
- `marketplace_product_matcher.py` 的擴充只能走「正向證據 + 反向 veto」:品牌一致、商品線/型號訊號強、價格合理且無 hard veto 時才允許 `strong_product_line_match` 加分;補充瓶/補充包/refill 與一般正裝不互相配對,分享組/加量組/明星組等組合包不得誤配單品。
|
||||
- 套組/買送/件數不同但品牌、核心商品線與基礎規格一致時,matcher 必須回傳 `comparison_mode='unit_comparable'` 與 `unit_comparable` reason;Feeder 只能寫入 `competitor_match_attempts.attempt_status='unit_comparable'` 或 `refresh_unit_comparable`,不得寫入 `competitor_prices`,直到下游支援單位價換算或人工覆核。
|
||||
- 套組/買送/件數不同但品牌、核心商品線與單一基礎規格一致時,matcher 必須回傳 `comparison_mode='unit_comparable'` 與 `unit_comparable` reason;Feeder 只能寫入 `competitor_match_attempts.attempt_status='unit_comparable'` 或 `refresh_unit_comparable`,不得寫入 `competitor_prices`,直到下游支援單位價換算或人工覆核。若任一側含多個不同容量/重量規格,視為多品項套組,不可進 `unit_comparable`。
|
||||
- PChome feeder 的外部 request timeout 由 `PCHOME_FEEDER_TIMEOUT` 控制,預設 12 秒;排程不得因單一 PChome 搜尋 API timeout 被拖到數分鐘。
|
||||
- 商品看板的 PChome 狀態必須把 matcher 診斷原因翻成可行動語意:品牌衝突、規格衝突、補充包差異、組合差異、商品線不符等,不可只顯示籠統「待比對」或「身份否決」。
|
||||
- Dashboard 必須把「待比對」拆成可診斷狀態:`價格過期待刷新`、`舊版配對待重驗`、`低分配對待審`、`身份否決`、`需單位價比較`、`找不到同款`、`抓取異常`、`尚未搜尋`。不可再用單一「待比對」掩蓋資料品質原因。
|
||||
|
||||
@@ -483,12 +483,24 @@ def _spec_mention_count(identity: ProductIdentity) -> int:
|
||||
|
||||
|
||||
def _has_overlapping_base_spec(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
for left_value in left.volumes_ml:
|
||||
if any(_close_number(left_value, right_value) for right_value in right.volumes_ml):
|
||||
return True
|
||||
for left_value in left.weights_g:
|
||||
if any(_close_number(left_value, right_value) for right_value in right.weights_g):
|
||||
return True
|
||||
left_volumes = tuple(sorted(set(left.volumes_ml)))
|
||||
right_volumes = tuple(sorted(set(right.volumes_ml)))
|
||||
if left_volumes or right_volumes:
|
||||
if not left_volumes or not right_volumes:
|
||||
return False
|
||||
if len(left_volumes) > 1 or len(right_volumes) > 1:
|
||||
return False
|
||||
return _close_number(left_volumes[0], right_volumes[0])
|
||||
|
||||
left_weights = tuple(sorted(set(left.weights_g)))
|
||||
right_weights = tuple(sorted(set(right.weights_g)))
|
||||
if left_weights or right_weights:
|
||||
if not left_weights or not right_weights:
|
||||
return False
|
||||
if len(left_weights) > 1 or len(right_weights) > 1:
|
||||
return False
|
||||
return _close_number(left_weights[0], right_weights[0])
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -109,6 +109,21 @@ def test_marketplace_matcher_marks_bundle_single_as_unit_comparable_not_exact():
|
||||
assert "comparison_unit_comparable" in diagnostics.tags
|
||||
|
||||
|
||||
def test_marketplace_matcher_does_not_unit_compare_multi_component_set():
|
||||
from services.marketplace_product_matcher import score_marketplace_match
|
||||
|
||||
diagnostics = score_marketplace_match(
|
||||
"【蘭蔻】官方直營 玫瑰霜60ml+玫瑰精露150ml",
|
||||
"【蘭蔻】絕對完美玫瑰霜 60ml",
|
||||
momo_price=18765,
|
||||
competitor_price=5349,
|
||||
)
|
||||
|
||||
assert diagnostics.hard_veto is True
|
||||
assert diagnostics.comparison_mode == "not_comparable"
|
||||
assert "unit_comparable" not in diagnostics.reasons
|
||||
|
||||
|
||||
def test_marketplace_matcher_does_not_promote_wide_price_refill_candidate():
|
||||
from services.marketplace_product_matcher import score_marketplace_match
|
||||
|
||||
|
||||
Reference in New Issue
Block a user