From 03d60c202fb26dc6ad74541fc9731618153d940c Mon Sep 17 00:00:00 2001 From: OoO Date: Thu, 4 Jun 2026 11:39:33 +0800 Subject: [PATCH] =?UTF-8?q?V10.579=20=E5=BC=B7=E5=8C=96=E9=AB=98=E4=BF=A1?= =?UTF-8?q?=E5=BF=83=E6=AF=94=E5=83=B9=E5=AE=89=E5=85=A8=E8=A6=86=E8=93=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO_NEXT_STEPS.txt | 1 + config.py | 2 +- .../current_execution_queue_20260524.md | 1 + docs/memory/history_logs.md | 1 + services/code_review_pipeline_service.py | 2 +- services/marketplace_product_matcher.py | 20 +++++++++- tests/test_code_review_claude_routing.py | 6 +-- tests/test_marketplace_product_matcher.py | 39 +++++++++++++++++++ 8 files changed, 65 insertions(+), 7 deletions(-) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 9eb822f..234cbac 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.579 補 PChome 高信心 total-price safe family:SAB 私密防護舒緩噴霧 30ml 與 Herbacin 小甘菊 20ml 護手霜在同款式、同規格、無 variant/commercial gap 時可由 focused matcher 進 `exact / total_price / price_alert_exact`,讓近門檻重評能真正寫入正式比價;Herbacin 柔皙 vs 野生玫瑰等跨 variant 仍保留在 review,不放寬全域門檻。同版將 Code Review GCP-B secondary timeout 預設由 60 秒收斂到 25 秒,GCP-A preflight 不通且 GCP-B 生成卡住時更快回 deterministic local degraded,不呼叫 Gemini/111。 - V10.578 修正 Code Review 靜態掃描 timeout 誤報:Hermes deterministic scan 對 `requests.get/post/...` 會檢查同一呼叫 block 的後續行,多行呼叫已帶 `timeout=` 時不再報「HTTP request 未設定 timeout」。避免 V10.577 的 preflight helper 因多行格式被自己誤判為 MEDIUM。 - V10.577 補 Code Review Ollama host preflight:OpenClaw 架構評估在 explicit GCP host generate 前先以短 `/api/version` 探測健康度,GCP-A 不通時會快速跳 GCP-B,不再等 15 秒 generate timeout;仍維持 GCP-A/GCP-B 優先、111 預設禁用、Gemini hard-disabled 預設不呼叫。 - V10.576 補 PChome backfill backlog 的型錄 lane counts,讓 `/api/ai/pchome-match/backfill/status` 也能回傳 `catalog_variant_review`、`catalog_unit_review`、`catalog_identity_review` 三條操作隊列;同版修正 `OllamaService.generate(allow_111_fallback=False)`,當 lazy resolver 快取到 111 時會強制改試 GCP-A/GCP-B allowlist,不再直接 `all 0 hosts failed`,且仍不把長分析推給 111。 diff --git a/config.py b/config.py index 11f75cf..37996c3 100644 --- a/config.py +++ b/config.py @@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.578" +SYSTEM_VERSION = "V10.579" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/memory/current_execution_queue_20260524.md b/docs/memory/current_execution_queue_20260524.md index f7823d5..651365d 100644 --- a/docs/memory/current_execution_queue_20260524.md +++ b/docs/memory/current_execution_queue_20260524.md @@ -104,6 +104,7 @@ - 2026-05-31 起,`V10.506` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval gate:在 review decision 通過後只審核 operator human approval 摘要,要求 decision linkage、approval identity、target table、row count、dedupe keys、`approved_for_writer_preflight` approval result、decision/approval evidence refs、artifact paths、matched row exact-identity/variant/overwrite guard 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 approval record、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler,只放行到後續 writer preflight 設計。 - 2026-05-31 起,`V10.509` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval Writer Preflight gate:在 human approval 通過後只審核 operator writer preflight 摘要,要求 approval linkage、writer_preflight_id、target operation、row count、dedupe keys、approved decision 到 target review_state 的逐列映射、decision/approval/preflight evidence refs、matched row exact-identity/variant/overwrite guard 與 operator boundary;仍不讀 token、不執行 CLI、不開 DB、不寫 preflight/approval/decision/match、不更新 review_state、不補 queue、不掛 scheduler,只放行到後續 CLI review / run package 設計。 - 2026-06-01 起,`V10.566` 新增市場情報 Professional Source Governance gate:將 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 納入 source contract,並接上 `/api/market_intel/mcp_professional_source_governance`、UI preview panel、deployment readiness check 與 production smoke target;仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler。 +- 2026-06-04 起,`V10.579` 補 PChome 高信心 total-price safe family:SAB 私密防護舒緩噴霧 30ml、Herbacin 小甘菊 20ml 護手霜在同款式同規格且無 variant/commercial gap 時可進 `exact / total_price / price_alert_exact`;跨款式反測仍擋在 review,`MIN_MATCH_SCORE` 不變。同版將 Code Review GCP-B secondary timeout 收斂到 25 秒,GCP-A/GCP-B 都慢時更快回 local degraded。 - 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.576` 修正 GCP-only Ollama retry:caller 禁用 111 fallback 時,resolver 若回到 111 會改試 GCP-A/GCP-B allowlist,不再讓 Hermes / Code Review 類任務因 resolver 快取到 111 而 `all 0 hosts failed`。 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index c14fd2e..b84c58b 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-06-01:PChome 比價新鮮度操作閉環 +- **V10.579 PChome 高信心 total-price safe family + Code Review timeout 收斂**: matcher 新增 SAB 私密防護舒緩噴霧 30ml 與 Herbacin 小甘菊 20ml 護手霜的窄範圍 total-price safe 路徑。這些候選仍必須通過既有 score、hard veto、variant/commercial gap 與 overwrite protection;Herbacin 柔皙 vs 野生玫瑰跨 variant 反測維持不進正式價差。目的在不放寬 `MIN_MATCH_SCORE` 的前提下,把可證明同款的高信心 `true_low_confidence` 轉進正式比價覆蓋。同版將 Code Review GCP-B secondary timeout 預設由 60 秒收斂到 25 秒,GCP-A preflight 不通且 GCP-B 生成卡住時更快回 deterministic local degraded,不呼叫 Gemini/111。 - **V10.578 Code Review 靜態掃描 timeout 誤報修正**: Hermes deterministic scan 對 `requests.get/post/put/delete/patch` 改檢查同一呼叫 block 的後續行,已在多行呼叫中帶 `timeout=` 時不再報「HTTP request 未設定 timeout」。這修掉 V10.577 preflight helper 被 Code Review 自己誤判為 MEDIUM 的噪音。 - **V10.577 Code Review Ollama host preflight**: OpenClaw 架構評估在 explicit GCP host generate 前先以短 `/api/version` 探測健康度;若 GCP-A 從 188 連線 timeout,會快速跳到 GCP-B `gemma3:4b`,避免每次等 primary generate timeout。此 preflight 只作用於 Code Review Ollama-first 路徑,仍維持 111 預設禁用、Gemini hard-disabled 預設不呼叫。 - **V10.576 PChome backlog lane 與 GCP-only Ollama retry 修補**: `/api/ai/pchome-match/backfill/status` 的 coverage 與 operation backlog 同步輸出 `catalog_variant_review`、`catalog_unit_review`、`catalog_identity_review`,讓 Dashboard 操作建議可直接跳到三條人工隊列。同版修正 `OllamaService.generate(allow_111_fallback=False)`:若 resolver 已快取到 111,會改試尚未嘗試的 GCP-A/GCP-B allowlist,不再直接 `all 0 hosts failed`,同時仍避免長分析落到 111。 diff --git a/services/code_review_pipeline_service.py b/services/code_review_pipeline_service.py index 64c89c0..27cd726 100644 --- a/services/code_review_pipeline_service.py +++ b/services/code_review_pipeline_service.py @@ -66,7 +66,7 @@ CODE_REVIEW_OLLAMA_SECONDARY_MODEL = os.getenv( "gemma3:4b", ) CODE_REVIEW_OLLAMA_SECONDARY_TIMEOUT = int( - os.getenv("CODE_REVIEW_OLLAMA_SECONDARY_TIMEOUT", "60") + os.getenv("CODE_REVIEW_OLLAMA_SECONDARY_TIMEOUT", "25") ) CODE_REVIEW_OLLAMA_FALLBACK_MODEL = os.getenv( "CODE_REVIEW_OLLAMA_FALLBACK_MODEL", diff --git a/services/marketplace_product_matcher.py b/services/marketplace_product_matcher.py index 17832e3..5f9848e 100644 --- a/services/marketplace_product_matcher.py +++ b/services/marketplace_product_matcher.py @@ -535,12 +535,18 @@ FOCUSED_IDENTITY_BRANDLESS_REVIEW_REASONS = { "the_forest_maple_diffuser_flower_brandless", } +FOCUSED_IDENTITY_BRANDLESS_TOTAL_PRICE_REASONS = { + "herbacin_classic_hand_cream_20ml_brandless", +} + FOCUSED_IDENTITY_TOTAL_PRICE_REASONS = { "3w_clinic_collagen_foundation_50ml_2pack", "hanamisui_moisture_original_gel_1_7g_3pack", "hanamisui_inclear_private_gel_1_7g_3pack", "hanamisui_relax_lavender_gel_1_7g_3pack", "the_ordinary_caffeine_egcg_30ml", + "herbacin_classic_hand_cream_20ml_brandless", + "sab_private_spray", "st_clare_private_mousse_150ml_2pack", "st_clare_private_mousse_spray_set", "biopeutic_plus_aha_lotion_20_150ml", @@ -2239,7 +2245,6 @@ def _classify_match_quality( ) or ( focused_total_price_safe - and brand_score >= 0.95 and type_score >= 0.55 and score >= 0.86 ) @@ -2582,9 +2587,20 @@ def score_marketplace_match( ) ) ) + focused_total_price_brand_safe = ( + brand_score >= 0.95 + or ( + focused_exact_line_reason in FOCUSED_IDENTITY_BRANDLESS_TOTAL_PRICE_REASONS + and brand_score == 0.55 + and bool(left.brand_tokens) != bool(right.brand_tokens) + and spec_score >= 0.85 + and token_score >= 0.70 + and sequence_score >= 0.55 + ) + ) focused_exact_total_price_safe = ( focused_exact_line_reason in FOCUSED_IDENTITY_TOTAL_PRICE_REASONS - and brand_score >= 0.95 + and focused_total_price_brand_safe and not hard_veto and spec_score >= 0.45 and token_score >= 0.30 diff --git a/tests/test_code_review_claude_routing.py b/tests/test_code_review_claude_routing.py index ce05a29..7381cb3 100644 --- a/tests/test_code_review_claude_routing.py +++ b/tests/test_code_review_claude_routing.py @@ -226,7 +226,7 @@ def test_code_review_ollama_defaults_use_fast_local_model(monkeypatch): assert svc_mod.CODE_REVIEW_OLLAMA_MODEL == "qwen2.5-coder:7b" assert svc_mod.CODE_REVIEW_OLLAMA_TIMEOUT == 15 assert svc_mod.CODE_REVIEW_OLLAMA_SECONDARY_MODEL == "gemma3:4b" - assert svc_mod.CODE_REVIEW_OLLAMA_SECONDARY_TIMEOUT == 60 + assert svc_mod.CODE_REVIEW_OLLAMA_SECONDARY_TIMEOUT == 25 assert svc_mod.CODE_REVIEW_OLLAMA_FALLBACK_MODEL == "hermes3:latest" assert svc_mod.CODE_REVIEW_OLLAMA_FALLBACK_TIMEOUT == 20 assert svc_mod.CODE_REVIEW_OLLAMA_NUM_PREDICT == 384 @@ -305,7 +305,7 @@ def test_openclaw_uses_secondary_local_model_before_gemini(monkeypatch): assert result == "SECONDARY-LOCAL" assert [call["model"] for call in calls] == ["qwen2.5-coder:7b", "gemma3:4b"] assert calls[0]["timeout"] == 15 - assert calls[1]["timeout"] == 60 + assert calls[1]["timeout"] == 25 fake_claude.generate.assert_not_called() fake_genai.GenerativeModel.assert_not_called() fake_elephant.generate.assert_not_called() @@ -366,7 +366,7 @@ def test_openclaw_preflight_skips_dead_primary_before_generate(monkeypatch): assert calls == [{ "host": ollama_mod.OLLAMA_HOST_SECONDARY, "model": "gemma3:4b", - "timeout": 60, + "timeout": 25, }] fake_claude.generate.assert_not_called() fake_genai.GenerativeModel.assert_not_called() diff --git a/tests/test_marketplace_product_matcher.py b/tests/test_marketplace_product_matcher.py index 0546694..4d1906c 100644 --- a/tests/test_marketplace_product_matcher.py +++ b/tests/test_marketplace_product_matcher.py @@ -1946,6 +1946,7 @@ def test_marketplace_matcher_promotes_focused_low_score_exact_identity_lines(): assert diagnostics.hard_veto is False assert expected_reason in diagnostics.reasons if expected_reason in { + "focused_exact_identity_sab_private_spray", "focused_exact_identity_herb24_mist_diffuser_black", "focused_exact_identity_pavaruni_40_scent_oil", "focused_exact_identity_pavaruni_20_scent_candle", @@ -1956,6 +1957,44 @@ def test_marketplace_matcher_promotes_focused_low_score_exact_identity_lines(): assert diagnostics.alert_tier == "price_alert_exact" +def test_marketplace_matcher_promotes_precise_brandless_herbacin_hand_cream_to_total_price(): + from services.marketplace_product_matcher import score_marketplace_match + + cases = [ + ( + "【Herbacin 德國小甘菊】小甘菊柔皙護手霜20ml", + "小甘菊柔皙護手霜 20ml", + ), + ( + "【Herbacin 德國小甘菊】小甘菊野生玫瑰護手霜20ml 條狀", + "小甘菊野生玫瑰護手霜20ml", + ), + ] + + for momo_name, competitor_name in cases: + diagnostics = score_marketplace_match(momo_name, competitor_name) + 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 "focused_exact_total_price_safe" in diagnostics.reasons + assert "focused_exact_identity_herbacin_classic_hand_cream_20ml_brandless" in diagnostics.reasons + + +def test_marketplace_matcher_keeps_herbacin_cross_variant_out_of_total_price(): + from services.marketplace_product_matcher import score_marketplace_match + + diagnostics = score_marketplace_match( + "【Herbacin 德國小甘菊】小甘菊柔皙護手霜20ml", + "小甘菊野生玫瑰護手霜20ml", + ) + + assert diagnostics.price_basis != "total_price" + assert diagnostics.alert_tier != "price_alert_exact" + assert "focused_exact_total_price_safe" not in diagnostics.reasons + + def test_marketplace_matcher_keeps_high_variant_low_score_lines_outside_focused_promotion(): from services.marketplace_product_matcher import score_marketplace_match