From b2dbf98c3b03351e7a980e9f94a5ff2d66ae3b0a Mon Sep 17 00:00:00 2001 From: OoO Date: Sun, 24 May 2026 12:54:00 +0800 Subject: [PATCH] V10.403 widen retryable PChome revalidation --- config.py | 2 +- docs/memory/history_logs.md | 1 + services/competitor_price_feeder.py | 21 ++++++++++++++----- ...t_competitor_match_attempts_persistence.py | 12 ++++++++++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/config.py b/config.py index 4aef3e8..89360e3 100644 --- a/config.py +++ b/config.py @@ -325,7 +325,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.402" +SYSTEM_VERSION = "V10.403" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index 32c6883..83db66f 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-05-24:PChome 近門檻身份回收第二輪 +- **V10.403 近門檻候選重驗擴池**: production 最新 72h attempt audit 顯示 7853 筆近門檻/被擋候選中,有 105 筆可被現行 matcher 回收,其中 46 筆已達 `price_alert_exact`、59 筆只進 `identity_review`。`CompetitorPriceFeeder._fetch_retryable_candidate_skus()` 擴大每日 revalidation 候選池,納入 `true_low_confidence`、`unit_comparable`、`refresh_unit_comparable` 與高分 `identity_veto`;但仍要求 PChome product_id、`best_match_score >= 0.70`,且正式寫入前必須由現行 matcher 重判、通過 hard-veto 與既有配對保護。 - **V10.402 catalog variant identity-review 回收**: marketplace matcher 新增 Johnson’s 嬰兒潤膚乳 catalog 對同香型、IM MEME 涼感定妝噴霧、SO NATURAL FIXX 定妝噴霧三條 review-only focused identity,讓同品線 catalog / variant 候選可脫離 `true_low_confidence` 但只進 `identity_review`,不直接價格告警;同版明確保留 MUJI 品牌缺漏護手霜等 brandless catalog 案例在低信心,避免品牌缺漏與多款任選同時存在時被過度救回。 - **V10.401 focused identity 邊界整理**: 將 Laundrin TOKYO 車用夾式消臭芳香劑判定抽成 `_has_laundrin_tokyo_car_freshener_alignment()`,並在 variant descriptor guard 中豁免同一條強身份線,避免 TOKYO / 車用 / 芳香劑語序差異造成誤擋。Yuskin 經典乳霜 30g 6 入組改用 `_has_exact_count_alignment()` 判斷包數,保留 6入 vs 6盒這類同數量但不同容器字詞的人工覆核路徑。 - **V10.400 氣墊粉餅補充蕊包數正規化**: marketplace matcher 針對氣墊粉餅新增保守的 `cushion_refill_pack_alignment`,只在一側明確為 `一盒兩蕊 / 2蕊`、另一側為單規格 `15g x2` 這類乘數包裝時,解除 `multi_component_conflict` 並進 `identity_review`。CLIO 羽緻無限緞光氣墊粉餅「一盒兩蕊」可被同款覆核;`2盒4蕊` 對 `15g x2` 仍維持 hard veto。同版補香氛蠟燭、TOKYO 車用消臭芳香劑、融蠟燈與有機護膚油 search identity anchors。離線 audit 759 筆 accepted 從 753 提升到 754,剩餘 5 筆 fresh veto 皆為應擋的套組/品類差異。 diff --git a/services/competitor_price_feeder.py b/services/competitor_price_feeder.py index 987f5bf..301de74 100644 --- a/services/competitor_price_feeder.py +++ b/services/competitor_price_feeder.py @@ -1006,12 +1006,12 @@ class CompetitorPriceFeeder: def _fetch_retryable_candidate_skus(self, limit: int = 80, min_score: float = 0.70) -> list: """ - 取得近門檻且非 hard veto 的候選,供 matcher 升級後重新評分。 + 取得近門檻候選,供 matcher 升級後重新評分。 這條路徑不重新搜尋,只用前次留下的 PChome product_id 批次查詢最新商品資料, 適合把舊 scorer 卡在 0.70~0.759 的真同款重新推進正式比價。 - 僅重跑明顯仍在 exact identity 軌道內、具回收價值的候選; - 真正低信心與 hard veto 不再反覆空轉。 + 僅重跑明顯有回收價值的候選;最後仍由現行 matcher 重新判斷, + 不因舊 attempt_status 自動寫入正式比價。 """ if self.engine is None: raise RuntimeError("需要注入 SQLAlchemy engine") @@ -1064,11 +1064,22 @@ class CompetitorPriceFeeder: AND COALESCE(cp.tags, '[]'::jsonb) ? 'identity_v2' WHERE lm.rn = 1 AND cp.sku IS NULL - AND la.attempt_status IN ('low_score', 'refresh_low_score', 'recoverable_low_score') + AND la.attempt_status IN ( + 'low_score', + 'refresh_low_score', + 'recoverable_low_score', + 'true_low_confidence', + 'unit_comparable', + 'refresh_unit_comparable', + 'identity_veto' + ) AND la.best_competitor_product_id IS NOT NULL AND la.best_competitor_product_id <> '' AND COALESCE(la.best_match_score, 0) >= :min_score - AND COALESCE(la.hard_veto, false) = false + AND ( + COALESCE(la.hard_veto, false) = false + OR la.attempt_status = 'identity_veto' + ) ORDER BY la.best_match_score DESC NULLS LAST, lm.momo_price DESC NULLS LAST, lm.sku LIMIT :limit """) diff --git a/tests/test_competitor_match_attempts_persistence.py b/tests/test_competitor_match_attempts_persistence.py index 9507954..7469e98 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -73,7 +73,17 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes(): retryable_source = source.split("def _fetch_retryable_candidate_skus", 1)[1].split( "def _fetch_expired_identity_skus", 1 )[0] - assert "la.attempt_status IN ('low_score', 'refresh_low_score', 'recoverable_low_score')" in retryable_source + for status in ( + "'low_score'", + "'refresh_low_score'", + "'recoverable_low_score'", + "'true_low_confidence'", + "'unit_comparable'", + "'refresh_unit_comparable'", + "'identity_veto'", + ): + assert status in retryable_source + assert "OR la.attempt_status = 'identity_veto'" in retryable_source latest_attempt_source = retryable_source.split("latest_attempt AS", 1)[1].split( "SELECT\n lm.product_id", 1 )[0]