diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 306e1ba..ed56b81 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.559 收斂 retryable 有效身份新鮮度:`_fetch_retryable_candidate_skus()` 不再把 `expires_at IS NULL` 的舊 PChome `identity_v2` 當成有效阻擋條件,只有明確 `expires_at > CURRENT_TIMESTAMP` 的新鮮 identity 才會阻止 near-threshold revalidation。未知新鮮度仍走 V10.551 的 expired / recovery 刷新入口,重評後仍必須通過最新版 matcher、hard-veto、auto write safety 與既有正式候選覆寫保護,避免為了拉覆蓋率犧牲準確率。 - V10.558 補 legacy focused identity reason 回刷窄門:舊 attempt 若沒有新版 `focused_exact_total_price_safe` marker,但已帶具名 `focused_exact_identity_*` 且該 identity 屬於 matcher total-price safe set,並且舊分數已達全域 `MIN_MATCH_SCORE`,可進近門檻重評。這補上歷史資料缺 marker 的漏接情境;仍要求無 hard veto、`exact_identity`、無 commercial / variant / count / bundle 阻擋,最後由最新版 matcher 決定是否能寫正式價差。 - V10.557 收緊 focused reason-based 回刷 guard:上一版 reason-based 回刷現在不只要求 `focused_exact_total_price_safe`,還必須同時命中一條具名 `focused_exact_identity_*` 且該 identity 屬於 matcher 的 total-price safe set。這避免未來只有總開關、缺少具名身份證據的舊 attempt 被納入回刷;rom&nd / Solone / Summer’s Eve 等 review-only focused line 仍被測試鎖在自動價差線外。 - V10.556 修 Ollama GCP-B model fallback:GCP-B 若缺 coder/large 指定模型,先用 host-compatible fallback `gemma3:4b` 留在 GCP-B,不直接把流量推到 111;`model not found` 404 視為模型缺失,不再把整台 GCP-B 標 unhealthy。主機順序仍維持 GCP-A → GCP-B → 111。 diff --git a/config.py b/config.py index 86f183c..d255178 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.558" +SYSTEM_VERSION = "V10.559" 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 711408d..287ad68 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **V10.559 retryable 有效身份新鮮度收斂**: `_fetch_retryable_candidate_skus()` 的既有 identity 阻擋條件改成只接受 `cp.expires_at > CURRENT_TIMESTAMP`,不再讓 `expires_at IS NULL` 的未知新鮮度舊配對壓住近門檻候選回刷。未知新鮮度仍由 expired identity refresh / recovery 路徑處理,最後寫入仍必須通過現行 matcher、hard-veto、auto write safety 與 stronger existing production match 保護。 - **V10.558 legacy focused identity reason 回刷補漏**: `_fetch_retryable_candidate_skus()` 在 V10.557 的具名 identity guard 之外,補上歷史資料缺 marker 的情境:舊 attempt 若沒有新版 `focused_exact_total_price_safe`,但已有具名 `focused_exact_identity_*` 且該 identity 屬於 matcher total-price safe set,並且舊分數已達全域 `MIN_MATCH_SCORE`,可進近門檻重評。仍要求無 hard veto、`exact_identity`、無 commercial / variant / count / bundle 阻擋,最後由最新版 matcher 決定是否能寫正式價差。 - **V10.557 focused reason-based 回刷具名 identity guard**: V10.555 的結構化 reason 回刷再收緊,`_fetch_retryable_candidate_skus()` 不只要求 `focused_exact_total_price_safe`,還必須同時命中一條具名 `focused_exact_identity_*` 且該 identity 來自 matcher 的 total-price safe set。這避免只有總開關、缺少身份線索的舊 attempt 被納入回刷;rom&nd、Solone、Summer’s Eve 等 review-only focused line 仍被測試鎖在自動價差線外。 - **V10.556 GCP-B model fallback 防 111 過載**: `OllamaService.generate()` 現在會在 GCP-B 對 coder/large 模型使用 host-compatible fallback(預設 `gemma3:4b`),避免 GCP-B 缺 `qwen2.5-coder:7b` 時直接被標成 unhealthy 並把流量推到 111。HTTP 404 且訊息為 model not found 時視為模型缺失,不再 mark 整台 host unhealthy;其他 HTTP / timeout 仍照舊標 unhealthy。主機順序仍是 GCP-A → GCP-B → 111。 diff --git a/services/competitor_price_feeder.py b/services/competitor_price_feeder.py index 3b8ae89..21a31b6 100644 --- a/services/competitor_price_feeder.py +++ b/services/competitor_price_feeder.py @@ -1379,7 +1379,7 @@ class CompetitorPriceFeeder: LEFT JOIN competitor_prices cp ON cp.sku = p.i_code AND cp.source = 'pchome' - AND (cp.expires_at IS NULL OR cp.expires_at > CURRENT_TIMESTAMP) + AND cp.expires_at > CURRENT_TIMESTAMP AND COALESCE(cp.match_score, 0) >= :match_score_floor AND COALESCE(cp.tags, '[]'::jsonb) ? 'identity_v2' WHERE cp.sku IS NULL diff --git a/tests/test_competitor_match_attempts_persistence.py b/tests/test_competitor_match_attempts_persistence.py index af4fe77..727b9dd 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -241,6 +241,8 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes(): assert "FROM candidate_attempt la" in retryable_source assert "JOIN LATERAL" in retryable_source assert "ORDER BY pr.timestamp DESC, pr.id DESC" in retryable_source + assert "AND cp.expires_at > CURRENT_TIMESTAMP" in retryable_source + assert "(cp.expires_at IS NULL OR cp.expires_at > CURRENT_TIMESTAMP)" not in retryable_source assert "latest_price.price AS momo_price" in retryable_source assert "ROW_NUMBER() OVER (PARTITION BY p.id" not in retryable_source assert "lm.rn = 1" not in retryable_source