V10.583 補 Paula PChome Nick 比價對齊
All checks were successful
CD Pipeline / deploy (push) Successful in 1m7s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m7s
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
【已完成】
|
【已完成】
|
||||||
|
- V10.583 補 Paula's Choice 身體乳 PChome Nick 具名 alignment:`2%水楊酸身體乳210ml二入` 可和 PChome `Nick` 補出的 `水楊酸身體乳雙入組 / 210ml x2` 對齊,進 `exact / total_price / price_alert_exact`;但 `118ml二入組(金蓋限定版)` 對上 PChome 效期品仍保留 `manual_review / identity_review`,不泛用放寬中文入數。
|
||||||
- V10.582 補 PChome 比價通知專業分級與 Nick 副標身份證據:NemoTron 價格決策信封現在保留 `momo_price`、`competitor_price`、`candidate_gap_pct` 與 `sales_7d_delta_pct`,EventRouter / Telegram 模板會把 `match_type / price_basis / alert_tier` 翻成「直接價格威脅、單位價覆核、身份覆核、壓制告警」與操作邊界;PChome crawler 會保留 `Nick` 副標為 `match_name` 給 matcher 使用,UI/DB 顯示仍維持原品名,讓容量、入數、濃度資訊可參與比對。
|
- V10.582 補 PChome 比價通知專業分級與 Nick 副標身份證據:NemoTron 價格決策信封現在保留 `momo_price`、`competitor_price`、`candidate_gap_pct` 與 `sales_7d_delta_pct`,EventRouter / Telegram 模板會把 `match_type / price_basis / alert_tier` 翻成「直接價格威脅、單位價覆核、身份覆核、壓制告警」與操作邊界;PChome crawler 會保留 `Nick` 副標為 `match_name` 給 matcher 使用,UI/DB 顯示仍維持原品名,讓容量、入數、濃度資訊可參與比對。
|
||||||
- V10.581 將 V10.580 的重複單品組安全線接進 PChome retryable revalidation 窄門:只允許 `true_low_confidence`、舊診斷為 `match_type=exact / price_basis=manual_review`、無阻擋原因且命中具名安全商品線(Bioneo 150ml x2、Cetaphil 150ml x2、Avene 300ml x4、Schick 2+1 入、KOSE 雪肌精 500ml x2 禮盒)的候選進小批次重評;仍由最新版 matcher 最終判斷是否寫入正式 `competitor_prices`。
|
- V10.581 將 V10.580 的重複單品組安全線接進 PChome retryable revalidation 窄門:只允許 `true_low_confidence`、舊診斷為 `match_type=exact / price_basis=manual_review`、無阻擋原因且命中具名安全商品線(Bioneo 150ml x2、Cetaphil 150ml x2、Avene 300ml x4、Schick 2+1 入、KOSE 雪肌精 500ml x2 禮盒)的候選進小批次重評;仍由最新版 matcher 最終判斷是否寫入正式 `competitor_prices`。
|
||||||
- V10.580 補 PChome 重複單品組 total-price 窄門:同品牌、同入數、同基礎規格且名稱高度對齊的 150ml x2、300ml x4、2+1 入等候選,可由 `exact / manual_review` 進 `exact / total_price / price_alert_exact`,正式部署前估算 213 筆高分 `true_low_confidence` 中只有 7 筆會轉自動寫入。同步新增 NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油核心油種 hard veto,避免規格一樣但油種不同的錯配污染正式價差;Paula's Choice 這類 PChome 端缺 30ml 規格的雙入組仍保留 manual review,不放寬全域門檻。
|
- V10.580 補 PChome 重複單品組 total-price 窄門:同品牌、同入數、同基礎規格且名稱高度對齊的 150ml x2、300ml x4、2+1 入等候選,可由 `exact / manual_review` 進 `exact / total_price / price_alert_exact`,正式部署前估算 213 筆高分 `true_low_confidence` 中只有 7 筆會轉自動寫入。同步新增 NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油核心油種 hard veto,避免規格一樣但油種不同的錯配污染正式價差;Paula's Choice 這類 PChome 端缺 30ml 規格的雙入組仍保留 manual review,不放寬全域門檻。
|
||||||
|
|||||||
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
|||||||
# ==========================================
|
# ==========================================
|
||||||
# 系統版本與路徑
|
# 系統版本與路徑
|
||||||
# ==========================================
|
# ==========================================
|
||||||
SYSTEM_VERSION = "V10.582"
|
SYSTEM_VERSION = "V10.583"
|
||||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||||
public_url = PUBLIC_URL # 用於模板顯示
|
public_url = PUBLIC_URL # 用於模板顯示
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@
|
|||||||
- 2026-06-04 起,`V10.580` 補 PChome 重複單品組 total-price 窄門與核心油種 veto:同品牌、同入數、同基礎規格且名稱高度對齊的重複單品組(例如 Bioneo 150ml x2、Cetaphil 150ml x2、Avene 300ml x4、Schick 2+1 入)可進 `exact / total_price / price_alert_exact`;正式部署前估算 213 筆高分 `true_low_confidence` 中僅 7 筆會被自動寫入。NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油改 hard veto,Paula's Choice 缺 30ml 規格的雙入組仍留 manual review;`MIN_MATCH_SCORE` 不變。
|
- 2026-06-04 起,`V10.580` 補 PChome 重複單品組 total-price 窄門與核心油種 veto:同品牌、同入數、同基礎規格且名稱高度對齊的重複單品組(例如 Bioneo 150ml x2、Cetaphil 150ml x2、Avene 300ml x4、Schick 2+1 入)可進 `exact / total_price / price_alert_exact`;正式部署前估算 213 筆高分 `true_low_confidence` 中僅 7 筆會被自動寫入。NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油改 hard veto,Paula's Choice 缺 30ml 規格的雙入組仍留 manual review;`MIN_MATCH_SCORE` 不變。
|
||||||
- 2026-06-04 起,`V10.581` 將重複單品組安全線接進 retryable revalidation:只收 `true_low_confidence` 中舊診斷為 `match_type=exact / price_basis=manual_review`、無 commercial / variant / count / bundle 等阻擋,且命中具名安全商品線的候選;最後仍由最新版 matcher 與 overwrite protection 決定是否寫正式比價。
|
- 2026-06-04 起,`V10.581` 將重複單品組安全線接進 retryable revalidation:只收 `true_low_confidence` 中舊診斷為 `match_type=exact / price_basis=manual_review`、無 commercial / variant / count / bundle 等阻擋,且命中具名安全商品線的候選;最後仍由最新版 matcher 與 overwrite protection 決定是否寫正式比價。
|
||||||
- 2026-06-04 起,`V10.582` 補 PChome 比價通知專業分級與 Nick 副標身份證據:NemoTron 決策信封保留 MOMO / PChome 價格、價差與 7 日業績變化;Telegram decision envelope 將 `exact / total_price / price_alert_exact` 等工程路徑翻成直接價格威脅、單位價覆核、身份覆核或壓制告警,並把「單位價/身份未確認不得用總價直接告警」寫進操作邊界。PChome `Nick` 副標會以 `match_name` 參與 matcher,比價可用到容量、入數、濃度資訊,但不改 UI/DB 正式顯示品名。
|
- 2026-06-04 起,`V10.582` 補 PChome 比價通知專業分級與 Nick 副標身份證據:NemoTron 決策信封保留 MOMO / PChome 價格、價差與 7 日業績變化;Telegram decision envelope 將 `exact / total_price / price_alert_exact` 等工程路徑翻成直接價格威脅、單位價覆核、身份覆核或壓制告警,並把「單位價/身份未確認不得用總價直接告警」寫進操作邊界。PChome `Nick` 副標會以 `match_name` 參與 matcher,比價可用到容量、入數、濃度資訊,但不改 UI/DB 正式顯示品名。
|
||||||
|
- 2026-06-04 起,`V10.583` 補 Paula's Choice 身體乳 PChome Nick 具名 alignment:`2%水楊酸身體乳210ml二入` 可和 PChome `Nick` 補出的 `水楊酸身體乳雙入組 / 210ml x2` 對齊並進 safe total-price;此版不泛用放寬中文入數,`118ml二入組(金蓋限定版)` 對上 PChome 效期品仍維持 manual review。
|
||||||
- 2026-06-04 起,`V10.578` 修正 Code Review deterministic scan 的 timeout 判定,多行 `requests.*(... timeout=...)` 不再被誤報為未設定 timeout。
|
- 2026-06-04 起,`V10.578` 修正 Code Review deterministic scan 的 timeout 判定,多行 `requests.*(... timeout=...)` 不再被誤報為未設定 timeout。
|
||||||
- 2026-06-04 起,`V10.577` Code Review OpenClaw 會在 explicit Ollama host generate 前先做短 `/api/version` preflight;GCP-A 不通時快速跳 GCP-B,避免 15 秒 timeout 後才降級,且仍不呼叫 Gemini / 111。
|
- 2026-06-04 起,`V10.577` Code Review OpenClaw 會在 explicit Ollama host generate 前先做短 `/api/version` preflight;GCP-A 不通時快速跳 GCP-B,避免 15 秒 timeout 後才降級,且仍不呼叫 Gemini / 111。
|
||||||
- 2026-06-04 起,`V10.576` 修正 GCP-only Ollama retry:caller 禁用 111 fallback 時,resolver 若回到 111 會改試 GCP-A/GCP-B allowlist,不再讓 Hermes / Code Review 類任務因 resolver 快取到 111 而 `all 0 hosts failed`。
|
- 2026-06-04 起,`V10.576` 修正 GCP-only Ollama retry:caller 禁用 111 fallback 時,resolver 若回到 111 會改試 GCP-A/GCP-B allowlist,不再讓 Hermes / Code Review 類任務因 resolver 快取到 111 而 `all 0 hosts failed`。
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
## 📅 詳細更新日誌 (考古存檔)
|
## 📅 詳細更新日誌 (考古存檔)
|
||||||
|
|
||||||
### 2026-06-01:PChome 比價新鮮度操作閉環
|
### 2026-06-01:PChome 比價新鮮度操作閉環
|
||||||
|
- **V10.583 Paula's Choice 身體乳 PChome Nick 具名 alignment**: matcher 新增 Paula's Choice `2%水楊酸身體乳210ml二入` 的窄範圍 alignment,讓 PChome `Nick` 補出的 `水楊酸身體乳雙入組 / 210ml x2` 可和 MOMO 品名對齊並進 `exact / total_price / price_alert_exact`。此版不泛用放寬中文入數;`118ml二入組(金蓋限定版)` 對上 PChome 效期品仍維持 `manual_review / identity_review`,不寫正式價差。
|
||||||
- **V10.582 PChome 比價通知專業分級 + Nick 副標身份證據**: NemoTron 價格決策信封補齊 `momo_price`、`competitor_price`、`candidate_gap_pct` 與 `sales_7d_delta_pct`,避免 EventRouter / Telegram 模板拿不到核心價格事實。價格決策模板新增「通知分級」,將 `match_type / price_basis / alert_tier` 翻成直接價格威脅、單位價覆核、身份覆核或壓制告警,並同步顯示操作邊界;單位價或 identity 未確認時明確禁止以總價直接判定價格威脅。PChome crawler 另保留 `Nick` 副標為 `match_name` 給 matcher 使用,UI/DB 顯示仍用原品名,讓容量、入數、濃度資訊可參與比對。
|
- **V10.582 PChome 比價通知專業分級 + Nick 副標身份證據**: NemoTron 價格決策信封補齊 `momo_price`、`competitor_price`、`candidate_gap_pct` 與 `sales_7d_delta_pct`,避免 EventRouter / Telegram 模板拿不到核心價格事實。價格決策模板新增「通知分級」,將 `match_type / price_basis / alert_tier` 翻成直接價格威脅、單位價覆核、身份覆核或壓制告警,並同步顯示操作邊界;單位價或 identity 未確認時明確禁止以總價直接判定價格威脅。PChome crawler 另保留 `Nick` 副標為 `match_name` 給 matcher 使用,UI/DB 顯示仍用原品名,讓容量、入數、濃度資訊可參與比對。
|
||||||
- **V10.581 重複單品組 retryable revalidation 接線**: 將 V10.580 的安全 matcher 線接到 `run_retryable_candidate_revalidation()`,但只收 `true_low_confidence` 中舊診斷已是 `match_type=exact / price_basis=manual_review`、沒有 review block,且命中具名安全商品線的候選。這讓 Bioneo / Cetaphil / Avene / Schick / KOSE 等重複單品組可以小批次重評並由最新版 matcher 決定是否寫入正式 `competitor_prices`,仍不開放泛用高分掃描。
|
- **V10.581 重複單品組 retryable revalidation 接線**: 將 V10.580 的安全 matcher 線接到 `run_retryable_candidate_revalidation()`,但只收 `true_low_confidence` 中舊診斷已是 `match_type=exact / price_basis=manual_review`、沒有 review block,且命中具名安全商品線的候選。這讓 Bioneo / Cetaphil / Avene / Schick / KOSE 等重複單品組可以小批次重評並由最新版 matcher 決定是否寫入正式 `competitor_prices`,仍不開放泛用高分掃描。
|
||||||
- **V10.580 PChome 重複單品組 total-price 窄門 + 核心油種 veto**: matcher 將同品牌、同入數、同基礎規格且名稱高度對齊的重複單品組,從 `exact / manual_review` 收斂到 `exact / total_price / price_alert_exact`;正式部署前以最新 `true_low_confidence` 重算,213 筆高分候選中僅 7 筆符合自動寫入安全條件。同步新增 NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油 hard veto,避免同容量同按壓頭但核心油種不同的候選被誤放行;PChome 端缺規格的 Paula's Choice 雙入組仍停在 manual review。
|
- **V10.580 PChome 重複單品組 total-price 窄門 + 核心油種 veto**: matcher 將同品牌、同入數、同基礎規格且名稱高度對齊的重複單品組,從 `exact / manual_review` 收斂到 `exact / total_price / price_alert_exact`;正式部署前以最新 `true_low_confidence` 重算,213 筆高分候選中僅 7 筆符合自動寫入安全條件。同步新增 NEW DIRECTIONS 甜杏仁油 vs 杏桃核仁油 hard veto,避免同容量同按壓頭但核心油種不同的候選被誤放行;PChome 端缺規格的 Paula's Choice 雙入組仍停在 manual review。
|
||||||
|
|||||||
@@ -1642,6 +1642,29 @@ def _has_cushion_refill_pack_alignment(left: ProductIdentity, right: ProductIden
|
|||||||
return aligned(left, right) or aligned(right, left)
|
return aligned(left, right) or aligned(right, left)
|
||||||
|
|
||||||
|
|
||||||
|
def _has_paulas_choice_body_lotion_210ml_2pack_alignment(
|
||||||
|
left: ProductIdentity,
|
||||||
|
right: ProductIdentity,
|
||||||
|
) -> bool:
|
||||||
|
"""Align PChome Nick `210ml x2` with MOMO `210ml二入` for the same body lotion."""
|
||||||
|
brand_tokens = left.brand_tokens | right.brand_tokens
|
||||||
|
if not ({"寶拉珍選", "paulas", "choice"} & brand_tokens):
|
||||||
|
return False
|
||||||
|
if not _has_shared_volume(left, right, 210):
|
||||||
|
return False
|
||||||
|
if not all("水楊酸" in item.searchable_name and "身體乳" in item.searchable_name for item in (left, right)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_two_pack(identity: ProductIdentity) -> bool:
|
||||||
|
text = identity.searchable_name
|
||||||
|
return bool(
|
||||||
|
re.search(r"(?:x\s*2|2\s*入|二\s*入|兩\s*入|雙\s*入|雙入組|二入組|兩入組)", text, re.I)
|
||||||
|
or (2, "入") in identity.counts
|
||||||
|
)
|
||||||
|
|
||||||
|
return has_two_pack(left) and has_two_pack(right)
|
||||||
|
|
||||||
|
|
||||||
def _has_refill_pack(identity: ProductIdentity) -> bool:
|
def _has_refill_pack(identity: ProductIdentity) -> bool:
|
||||||
text = identity.normalized_name
|
text = identity.normalized_name
|
||||||
return bool(
|
return bool(
|
||||||
@@ -2328,10 +2351,17 @@ def score_marketplace_match(
|
|||||||
if bundle_offer_conflict:
|
if bundle_offer_conflict:
|
||||||
reasons.append("bundle_offer_conflict")
|
reasons.append("bundle_offer_conflict")
|
||||||
cushion_refill_pack_alignment = _has_cushion_refill_pack_alignment(left, right)
|
cushion_refill_pack_alignment = _has_cushion_refill_pack_alignment(left, right)
|
||||||
if _has_multi_component(left) != _has_multi_component(right) and not cushion_refill_pack_alignment:
|
paulas_choice_body_lotion_2pack_alignment = _has_paulas_choice_body_lotion_210ml_2pack_alignment(left, right)
|
||||||
|
if (
|
||||||
|
_has_multi_component(left) != _has_multi_component(right)
|
||||||
|
and not cushion_refill_pack_alignment
|
||||||
|
and not paulas_choice_body_lotion_2pack_alignment
|
||||||
|
):
|
||||||
reasons.append("multi_component_conflict")
|
reasons.append("multi_component_conflict")
|
||||||
if cushion_refill_pack_alignment:
|
if cushion_refill_pack_alignment:
|
||||||
reasons.append("cushion_refill_pack_alignment")
|
reasons.append("cushion_refill_pack_alignment")
|
||||||
|
if paulas_choice_body_lotion_2pack_alignment:
|
||||||
|
reasons.append("paulas_choice_body_lotion_210ml_2pack_alignment")
|
||||||
multi_component_count_conflict = (
|
multi_component_count_conflict = (
|
||||||
_has_multi_component(left)
|
_has_multi_component(left)
|
||||||
and _has_multi_component(right)
|
and _has_multi_component(right)
|
||||||
@@ -2496,7 +2526,11 @@ def score_marketplace_match(
|
|||||||
hard_veto = brand_conflict or spec_conflict
|
hard_veto = brand_conflict or spec_conflict
|
||||||
if bundle_offer_conflict:
|
if bundle_offer_conflict:
|
||||||
hard_veto = True
|
hard_veto = True
|
||||||
if _has_multi_component(left) != _has_multi_component(right) and not cushion_refill_pack_alignment:
|
if (
|
||||||
|
_has_multi_component(left) != _has_multi_component(right)
|
||||||
|
and not cushion_refill_pack_alignment
|
||||||
|
and not paulas_choice_body_lotion_2pack_alignment
|
||||||
|
):
|
||||||
hard_veto = True
|
hard_veto = True
|
||||||
if multi_component_count_conflict:
|
if multi_component_count_conflict:
|
||||||
hard_veto = True
|
hard_veto = True
|
||||||
|
|||||||
@@ -1391,6 +1391,40 @@ def test_marketplace_matcher_promotes_safe_multi_component_exact_sets_to_total_p
|
|||||||
assert "safe_multi_component_exact_total_price" in diagnostics.reasons
|
assert "safe_multi_component_exact_total_price" in diagnostics.reasons
|
||||||
|
|
||||||
|
|
||||||
|
def test_marketplace_matcher_uses_pchome_nick_for_paulas_choice_body_lotion_pack():
|
||||||
|
from services.marketplace_product_matcher import score_marketplace_match
|
||||||
|
|
||||||
|
diagnostics = score_marketplace_match(
|
||||||
|
"【Paulas Choice 寶拉珍選】2%水楊酸身體乳210ml二入",
|
||||||
|
"【寶拉珍選】水楊酸身體乳雙入組 (2%水楊酸身體乳 210ml x2)",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert diagnostics.score >= 0.76
|
||||||
|
assert diagnostics.hard_veto is False
|
||||||
|
assert diagnostics.match_type == "exact"
|
||||||
|
assert diagnostics.price_basis == "total_price"
|
||||||
|
assert diagnostics.alert_tier == "price_alert_exact"
|
||||||
|
assert "paulas_choice_body_lotion_210ml_2pack_alignment" in diagnostics.reasons
|
||||||
|
|
||||||
|
|
||||||
|
def test_marketplace_matcher_keeps_expiry_or_variant_pack_in_manual_review():
|
||||||
|
from services.marketplace_product_matcher import score_marketplace_match
|
||||||
|
|
||||||
|
diagnostics = score_marketplace_match(
|
||||||
|
"【Paulas Choice 寶拉珍選】2%水楊酸精華液118ml二入組(金蓋限定版)",
|
||||||
|
"【寶拉珍選】水楊酸精華液雙入組 【寶拉珍選】2%水楊酸精華液 118ml雙入組 效期至2026/12",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert diagnostics.score >= 0.76
|
||||||
|
assert diagnostics.hard_veto is False
|
||||||
|
assert diagnostics.match_type == "exact"
|
||||||
|
assert diagnostics.price_basis == "manual_review"
|
||||||
|
assert diagnostics.alert_tier == "identity_review"
|
||||||
|
assert "commercial_condition_gap" in diagnostics.reasons
|
||||||
|
assert "variant_selection_review" in diagnostics.reasons
|
||||||
|
assert "safe_multi_component_exact_total_price" not in diagnostics.reasons
|
||||||
|
|
||||||
|
|
||||||
def test_marketplace_matcher_promotes_focused_lip_care_exact_lines_to_total_price():
|
def test_marketplace_matcher_promotes_focused_lip_care_exact_lines_to_total_price():
|
||||||
from services.marketplace_product_matcher import score_marketplace_match
|
from services.marketplace_product_matcher import score_marketplace_match
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user