From 06e196a2821400b9cdb5c5dfc23e85e104058ba8 Mon Sep 17 00:00:00 2001 From: OoO Date: Sun, 31 May 2026 12:47:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E7=B7=8A=20PChome=20=E8=BF=91?= =?UTF-8?q?=E9=96=80=E6=AA=BB=E8=87=AA=E5=8B=95=E5=9B=9E=E5=88=B7=E9=9A=8A?= =?UTF-8?q?=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO_NEXT_STEPS.txt | 2 ++ config.py | 2 +- .../current_execution_queue_20260524.md | 2 ++ services/competitor_price_feeder.py | 26 ++++++++++--------- ...t_competitor_match_attempts_persistence.py | 10 +++++-- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 9157019..dea7ad6 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,8 @@ ================================================================================ 【已完成】 + - V10.493 新增市場情報 MCP Fetch Candidate Handoff Review 安全預覽 gate:只審核 parser review 後的候選交接包,確認 source/candidate key 對齊、queue policy 仍是 manual preview、候選數維持小批次、無 raw/secret/side-effect;API 不建立 queue、不寫 DB、不讀 artifact、不連外、不掛 scheduler。 + - V10.492 收緊 PChome 近門檻自動回刷隊列:`retryable_candidate_revalidation` 不再把 `identity_veto`、`unit_comparable`、`true_low_confidence` 納入每日自動回刷;只處理 `recoverable_low_score` 與 legacy `low_score / refresh_low_score`,並要求無 hard veto、仍在 `exact_identity`、且具備同品線/identity anchor 證據。這讓「可救回」與「正確阻擋」在操作層面真正分流,避免為了壓低 low_score 而重跑不該自動推進的候選。 - V10.491 新增市場情報 MCP Fetch Result Parser Review 安全預覽 gate:只審核操作員貼回的 parser 結構化摘要,對齊 receipt source/path、候選必要欄位、公開 URL、小批次上限與 raw HTML/secret/side-effect 風險;API 不讀 artifact、不執行 parser CLI、不抓外站、不寫 DB、不掛 scheduler。 - V10.489 補 PChome 低分同款人工覆核回收與 gate-pass 風險邊界:TS6 超美白香氛誘霜 120g/ml、W 修護保養蝸牛特潤修護面膜 6 片、Derma 大地 Eco 植萃護膚油 2 入,從低信心升成 `identity_review` 人工覆核候選;Clarins 輕盈美體護理油 vs 身體調和護理油、台塑生醫嬰兒沐浴/洗髮組合數量反轉、isLeaf 私密慕絲香型數量不一致改 hard veto;HOOOME 大理石暖燈 vs 泛稱經典款改留 `variant_selection_review`。正式價差表仍需人工採用才會寫入。Production 已部署 `/health=V10.489`;500 筆 read-only audit 由 V10.486 基線 `gate_pass=129 / identity_veto=1 / still_low=370` 收斂為 `gate_pass=124 / identity_veto=4 / still_low=372`。測試:完整 `pytest` 1289 passed / 9 skipped。 - V10.488 新增市場情報 MCP Fetch Run Receipt 安全預覽 gate,只審核操作員 dry-run receipt,不執行 CLI、不抓外站、不寫 DB。 diff --git a/config.py b/config.py index 22d56e8..915b0cf 100644 --- a/config.py +++ b/config.py @@ -350,7 +350,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.491" +SYSTEM_VERSION = "V10.493" 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 9480ada..0843e3a 100644 --- a/docs/memory/current_execution_queue_20260524.md +++ b/docs/memory/current_execution_queue_20260524.md @@ -87,6 +87,8 @@ - 2026-05-29 起,`V10.489` 補 PChome 低分同款人工覆核回收與 gate-pass 風險邊界:TS6 超美白香氛誘霜 120g/ml、W 修護保養蝸牛特潤修護面膜 6 片、Derma 大地 Eco 植萃護膚油 2 入,從低信心升成 `identity_review` 候選;Clarins 輕盈美體護理油 vs 身體調和護理油、台塑生醫嬰兒沐浴/洗髮組合數量反轉、isLeaf 私密慕絲香型數量不一致改 hard veto;HOOOME 大理石暖燈 vs 泛稱經典款只留 `variant_selection_review`,不進 total-price accepted。Production 已部署 `/health=V10.489`;500 筆 read-only audit 由 V10.486 基線 `gate_pass=129 / identity_veto=1 / still_low=370` 收斂為 `gate_pass=124 / identity_veto=4 / still_low=372`,三應用容器 healthy、`momo-db` 未 recreate。 - 2026-05-31 起,`V10.490` 補 ElephantAlpha / OpenClaw 舊策略 action 相容:正式日誌觀察到 `agent=openclaw action=generate_market_strategy` 被當未知步驟丟錯;此類 OpenClaw strategy 產生步驟已定義為 advisory skipped,只記 warning,不觸發 circuit breaker、不執行外部策略、不影響價格行動。測試覆蓋 `generate_market_strategy` 與 `generate_resource_optimization_strategy`,未知 action 仍維持 fail-fast。 - 2026-05-31 起,`V10.491` 新增市場情報 MCP Fetch Result Parser Review gate:在 receipt gate 後只審核操作員 shell parser 貼回的結構化摘要,對齊 source/path、公開 URL、候選必要欄位、小批次上限、raw HTML/secret/side-effect 風險;仍不讀 artifact、不執行 CLI、不連外、不寫 DB、不掛 scheduler。 +- 2026-05-31 起,`V10.492` 收緊 PChome 近門檻自動回刷:`run_retryable_candidate_revalidation()` 只回刷 `recoverable_low_score` 與 legacy `low_score / refresh_low_score`,且 SQL 端要求 `hard_veto=false`、`comparison_mode=exact_identity`、diagnostic reasons 命中同品線/identity anchor;`identity_veto`、`unit_comparable`、`true_low_confidence` 不再進每日自動回刷隊列,需等待新證據或人工處理。 +- 2026-05-31 起,`V10.493` 新增市場情報 MCP Fetch Candidate Handoff Review gate:在 parser review 通過後只審核候選交接包,要求 source/candidate key 完全對齊、queue policy 維持 manual preview、小批次上限與操作員無寫入/無連外/無排程確認;仍不建立 queue、不寫 DB、不讀 artifact、不連外、不掛 scheduler。 ## 3. 12 Agent 決策信封整合 diff --git a/services/competitor_price_feeder.py b/services/competitor_price_feeder.py index 4487997..317fc6b 100644 --- a/services/competitor_price_feeder.py +++ b/services/competitor_price_feeder.py @@ -65,6 +65,8 @@ RECOVERABLE_DIAGNOSTIC_REASONS = { "spec_name_alignment", } +RECOVERABLE_SQL_REASON_LIST = ", ".join(f"'{reason}'" for reason in sorted(RECOVERABLE_DIAGNOSTIC_REASONS)) + # ── Feeder 結果 ─────────────────────────────────────── @dataclass class FeederResult: @@ -1037,16 +1039,17 @@ class CompetitorPriceFeeder: """ 取得近門檻候選,供 matcher 升級後重新評分。 - 這條路徑不重新搜尋,只用前次留下的 PChome product_id 批次查詢最新商品資料, - 適合把舊 scorer 卡在 0.70~0.759 的真同款重新推進正式比價。 - 僅重跑明顯有回收價值的候選;最後仍由現行 matcher 重新判斷, - 不因舊 attempt_status 自動寫入正式比價。 + 這條路徑優先用前次留下的 PChome product_id 批次查詢最新商品資料; + 若 product_id 過期或重評仍低分,才走受控 fresh search recovery。 + 自動隊列只收「近門檻、無 hard veto、仍在 exact_identity 軌道、已有同品線證據」 + 的候選;identity_veto、unit_comparable、true_low_confidence 不在自動回刷主戰場。 + 最後仍由現行 matcher 重新判斷,不因舊 attempt_status 自動寫入正式比價。 """ if self.engine is None: raise RuntimeError("需要注入 SQLAlchemy engine") from sqlalchemy import text - sql = text(""" + sql = text(f""" WITH latest_momo AS ( SELECT p.id AS product_id, @@ -1067,6 +1070,7 @@ class CompetitorPriceFeeder: cma.best_match_score, cma.attempt_status, cma.hard_veto, + cma.match_diagnostic_json, cma.attempted_at FROM competitor_match_attempts cma WHERE cma.source = 'pchome' @@ -1096,18 +1100,16 @@ class CompetitorPriceFeeder: AND la.attempt_status IN ( 'low_score', 'refresh_low_score', - 'recoverable_low_score', - 'true_low_confidence', - 'unit_comparable', - 'refresh_unit_comparable', - 'identity_veto' + 'recoverable_low_score' ) 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.match_diagnostic_json->>'comparison_mode', 'exact_identity') = 'exact_identity' AND ( - COALESCE(la.hard_veto, false) = false - OR la.attempt_status = 'identity_veto' + la.attempt_status = 'recoverable_low_score' + OR COALESCE(la.match_diagnostic_json->'reasons', '[]'::jsonb) ?| array[{RECOVERABLE_SQL_REASON_LIST}] ) 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 5cbb2de..99b5263 100644 --- a/tests/test_competitor_match_attempts_persistence.py +++ b/tests/test_competitor_match_attempts_persistence.py @@ -111,13 +111,19 @@ def test_competitor_feeder_persists_all_match_attempt_outcomes(): "'low_score'", "'refresh_low_score'", "'recoverable_low_score'", + ): + assert status in retryable_source + retryable_status_list = retryable_source.split("la.attempt_status IN (", 1)[1].split(")", 1)[0] + for status in ( "'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 + assert status not in retryable_status_list + assert "COALESCE(la.hard_veto, false) = false" in retryable_source + assert "match_diagnostic_json->>'comparison_mode'" in retryable_source + assert "?| array[" in retryable_source latest_attempt_source = retryable_source.split("latest_attempt AS", 1)[1].split( "SELECT\n lm.product_id", 1 )[0]