From f232639c996580a32ee243537e3a5f2e1223d6ec Mon Sep 17 00:00:00 2001 From: OoO Date: Mon, 1 Jun 2026 20:58:04 +0800 Subject: [PATCH] =?UTF-8?q?V10.558=20=E8=A3=9C=20legacy=20focused=20reason?= =?UTF-8?q?=20=E5=9B=9E=E5=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO_NEXT_STEPS.txt | 1 + config.py | 2 +- docs/memory/history_logs.md | 1 + services/competitor_price_feeder.py | 12 ++++++++++-- ...st_competitor_match_attempts_persistence.py | 18 ++++++++++++++++++ 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 97ab021..306e1ba 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - 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。 - V10.555 補 focused total-price reason-based 回刷:`_fetch_retryable_candidate_skus()` 新增一條結構化 reason 窄門,只要舊 attempt 已帶 `focused_exact_total_price_safe` 且命中 matcher 的 `FOCUSED_IDENTITY_TOTAL_PRICE_REASONS`,即可進近門檻重評;仍要求無 hard veto、`exact_identity`、分數下限,並排除 commercial / variant / count / bundle 等阻擋理由。這讓已經被 matcher 明確判為 total-price exact 的舊候選不再依賴手寫商品名 SQL 才能回刷,同時 rom&nd / Solone / Summer’s Eve 等 review-only 品線仍不會進自動價差線。 diff --git a/config.py b/config.py index 1b413b4..86f183c 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.557" +SYSTEM_VERSION = "V10.558" 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 b8b3fe9..711408d 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **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。 - **V10.555 focused total-price reason-based 回刷窄門**: `_fetch_retryable_candidate_skus()` 新增結構化 diagnostics reason 回刷入口,舊 attempt 若已帶 `focused_exact_total_price_safe` 且命中 matcher 的 `FOCUSED_IDENTITY_TOTAL_PRICE_REASONS`,即可進近門檻重評,不再完全依賴手寫商品名 SQL。此路徑仍要求分數下限、無 hard veto、`exact_identity`,並套用 commercial / variant / count / bundle 等阻擋理由;rom&nd、Solone、Summer’s Eve 等 review-only focused line 不在 total-price reason set,仍不會被推入自動價差。 diff --git a/services/competitor_price_feeder.py b/services/competitor_price_feeder.py index f4722b0..3b8ae89 100644 --- a/services/competitor_price_feeder.py +++ b/services/competitor_price_feeder.py @@ -1405,8 +1405,16 @@ class CompetitorPriceFeeder: AND COALESCE(la.best_match_score, 0) >= :min_score AND COALESCE(la.hard_veto, false) = false AND COALESCE(la.match_diagnostic_json->>'comparison_mode', 'exact_identity') = 'exact_identity' - AND COALESCE(la.match_diagnostic_json->'reasons', '[]'::jsonb) ? 'focused_exact_total_price_safe' - AND COALESCE(la.match_diagnostic_json->'reasons', '[]'::jsonb) ?| array[{FOCUSED_TOTAL_PRICE_IDENTITY_SQL_REASON_LIST}] + AND ( + ( + COALESCE(la.match_diagnostic_json->'reasons', '[]'::jsonb) ? 'focused_exact_total_price_safe' + AND COALESCE(la.match_diagnostic_json->'reasons', '[]'::jsonb) ?| array[{FOCUSED_TOTAL_PRICE_IDENTITY_SQL_REASON_LIST}] + ) + OR ( + COALESCE(la.best_match_score, 0) >= :match_score_floor + AND COALESCE(la.match_diagnostic_json->'reasons', '[]'::jsonb) ?| array[{FOCUSED_TOTAL_PRICE_IDENTITY_SQL_REASON_LIST}] + ) + ) AND NOT ( COALESCE(la.match_diagnostic_json->'reasons', '[]'::jsonb) ?| array[{REVALIDATABLE_REVIEW_BLOCK_SQL_REASON_LIST}] diff --git a/tests/test_competitor_match_attempts_persistence.py b/tests/test_competitor_match_attempts_persistence.py index 9194d3f..af4fe77 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -171,6 +171,10 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes(): assert "strong_exact_spec_match" in retryable_source assert "focused_exact_total_price_safe" in retryable_source assert "FOCUSED_TOTAL_PRICE_IDENTITY_SQL_REASON_LIST" in retryable_source + assert "COALESCE(la.best_match_score, 0) >= :match_score_floor" in retryable_source + assert "focused_exact_total_price_safe" in retryable_source.split( + "FOCUSED_TOTAL_PRICE_IDENTITY_SQL_REASON_LIST", 1 + )[0] assert "focused_exact_identity_romand_juicy_lip_tint_2_catalog" not in retryable_source assert "focused_exact_identity_solone_longlasting_eyeliner" not in retryable_source assert "focused_exact_identity_summer_eve_full_skin_wash_2pack" not in retryable_source @@ -290,6 +294,20 @@ def test_competitor_feeder_focused_total_price_reason_gate_requires_named_identi ) +def test_competitor_feeder_legacy_focused_identity_reason_uses_global_score_floor(): + source = (ROOT / "services/competitor_price_feeder.py").read_text(encoding="utf-8") + retryable_source = source.split("def _fetch_retryable_candidate_skus", 1)[1].split( + "def preview_retryable_candidate_revalidation", 1 + )[0] + focused_branch = retryable_source.split("focused_exact_total_price_safe", 1)[1].split( + "REVALIDATABLE_REVIEW_BLOCK_SQL_REASON_LIST", 1 + )[0] + + assert "FOCUSED_TOTAL_PRICE_IDENTITY_SQL_REASON_LIST" in focused_branch + assert "COALESCE(la.best_match_score, 0) >= :match_score_floor" in focused_branch + assert "FOCUSED_TOTAL_PRICE_SAFE_SQL_REASON_LIST" not in focused_branch + + def test_competitor_feeder_blocks_identity_review_from_auto_price_write(): from types import SimpleNamespace