diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index e1e9237..eff7260 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.457 將 PChome 覆核 `decision_envelope` 連到人工操作面:Dashboard 覆核卡新增決策等級、資料品質、HITL/trace 信封摘要;`/api/export/excel/pchome-review` 匯出同步增加決策信封 ID、決策類型、建議代碼、責任人、資料品質、自動執行允許與證據摘要,讓線上操作與下載檔都保留同一份 guardrails。 - V10.456 將 PChome 覆核隊列接上 `decision_envelope` contract:`fetch_competitor_review_queue()` 與 `/api/pchome-review/queue` 每筆候選都輸出同一份 SKU、PChome 候選、match evidence、recommended_action、expected_impact 與 HITL guardrails,Dashboard、Agent、Telegram、PPT 後續不得再各自重建比價判讀格式;同版將 review queue cache key 升到 v3,避免正式環境沿用舊 payload。 - V10.455 讓 EventRouter 對 `decision_envelope` 事件走直送證據模板:NemoTron / 價格比對已產生 SKU、PChome 候選、match evidence 與 HITL guardrails 時,不再進 L1/L2 AI 重新摘要,避免額外模型呼叫與告警文字二次發散;Telegram 決策信封同步補「標的」區塊,顯示 SKU、商品與 PChome 候選。同版補 `audit_competitor_match_attempt_rescore.py --retract-variant-accepted`,可把最新仍帶 `variant_selection_review` 的 `rescore_accepted_current` 批次追加退回 `true_low_confidence`,且不寫正式價差表。 - V10.454 補 feeder / rescore 正式寫入安全閘門:matcher 若只到 `manual_review` / `identity_review` / `variant_selection_review`,例如 MOMO 多款任選唇膏對 PChome 單一款式,只能進 `true_low_confidence` 覆核,不得由 retryable replay、known identity refresh 或 rescore accepted 語意自動寫入 `competitor_prices` 正式價差。 diff --git a/config.py b/config.py index 0e260b0..69b5512 100644 --- a/config.py +++ b/config.py @@ -325,7 +325,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.456" +SYSTEM_VERSION = "V10.457" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/docs/AI_INTELLIGENCE_MODULE_SOT.md b/docs/AI_INTELLIGENCE_MODULE_SOT.md index 38ed3fb..a5f4a53 100644 --- a/docs/AI_INTELLIGENCE_MODULE_SOT.md +++ b/docs/AI_INTELLIGENCE_MODULE_SOT.md @@ -2,7 +2,7 @@ > **最後更新**: 2026-05-24 (台北時間) > **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯,Gemini 備援預設關閉 -> **適用版本**: V10.456 +> **適用版本**: V10.457 --- @@ -48,6 +48,7 @@ - 競品比價相關的 Agent 建議只能讀 `competitor_match_attempts` / review queue / `competitor_prices` 的既有證據;不得直接寫 `competitor_prices` 或覆蓋 `_should_upsert_competitor_price()` 的保護規則。 - 已帶 `decision_envelope` 的價格/覆核事件必須由 EventRouter 直接渲染證據模板,不再進 L1/L2 AI 重新摘要;Telegram 決策信封需顯示標的 SKU、商品名稱、PChome 候選、evidence、guardrails 與 HITL 動作,避免已有實證的比價告警被二次生成文字稀釋或造成額外模型成本。 - PChome 覆核隊列本身也必須輸出 `decision_envelope`:`fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 的每筆候選需帶相同的 `subject`、`evidence`、`recommended_action`、`expected_impact` 與 `guardrails`,供 Dashboard、Agent、Telegram 與 PPT 共用;任何下游不得另寫一套比價狀態翻譯或繞過 HITL guardrails。 +- Dashboard 覆核卡與 `/api/export/excel/pchome-review` 也必須顯示/匯出 `decision_envelope` 的等級、資料品質、建議代碼、HITL、trace 與 `can_auto_execute=false` 邊界;操作員離開系統畫面或下載 Excel 後,仍要看得到「不可自動寫正式價差」的 guardrails。 ## 一、四 AI Agent 路由架構 diff --git a/docs/memory/current_execution_queue_20260524.md b/docs/memory/current_execution_queue_20260524.md index 65d7f96..47518ab 100644 --- a/docs/memory/current_execution_queue_20260524.md +++ b/docs/memory/current_execution_queue_20260524.md @@ -12,6 +12,7 @@ - 2026-05-24 22:17 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.453`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111、`/api/pchome-review/queue` 三個 status 查詢成功、rescore audit read-only `selection_mode=latest_sku_only`。 - 2026-05-24 22:55 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.455`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、EventRouter `decision_envelope` 直送不進 L1/L2 AI handler、Telegram 信封顯示標的 SKU 與 PChome 候選、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111、`/api/pchome-review/queue?review_status=rescore_accepted` 查詢成功、10 分鐘錯誤 log 未見 Traceback / ERROR / CRITICAL。已執行 `--retract-variant-accepted`,最新 `rescore_accepted_current` 中 `variant_selection_review` 殘留為 0。 - 2026-05-24 23:05 CST 狀態:`main` 已推 Gitea 並部署到 188,正式 `/health` 為 `V10.456`。本輪 recreate `momo-app`、`scheduler`、`telegram-bot`;未使用 `--remove-orphans`,未碰 `momo-db`。Smoke 通過:三個 app 容器 healthy、`/api/pchome-review/queue?review_status=rescore_accepted` 每筆帶 `decision_envelope`、guardrail `can_auto_execute=false`、Gemini hard disabled 且 24 小時 `ai_calls` 無 Gemini provider、Ollama 順序維持 GCP-A → GCP-B → 111、5 分鐘三容器錯誤 log 未見 Traceback / ERROR / CRITICAL。 +- 2026-05-24 23:15 CST 狀態:V10.457 補 Dashboard / Excel 的 PChome review `decision_envelope` 顯示與匯出;待部署後回填正式 `/health` 與 smoke 結果。 ## 1. MOMO / PChome 核心比價準確率 @@ -42,6 +43,7 @@ - `decision_envelope` 已接到 NemoTron 價格告警與人工覆核,下一步要讓 OpenClaw、ElephantAlpha、PPT QA 與 review queue 共用同一份 evidence contract。 - 2026-05-24 22:44 CST 起,EventRouter 對已附 `decision_envelope` 的事件直接渲染證據模板,不呼叫 L1/L2 AI handler;這讓 NemoTron 價格告警、人工覆核與後續 Agent 共用同一份 SKU / PChome / evidence / guardrails,不再二次生成摘要。 - 2026-05-24 23:00 CST 起,`fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 每筆候選也帶 `decision_envelope`,包含 SKU/PChome 標的、match evidence、人工下一步、預期價差與不可自動寫正式價差的 guardrails;Dashboard、Agent、Telegram、PPT 後續共用此 contract。 +- 2026-05-24 23:15 CST 起,Dashboard 覆核卡與 PChome 覆核 Excel 匯出也顯示/輸出信封摘要、資料品質、HITL、trace、自動執行阻擋原因與證據摘要;下載檔不得丟失 guardrails。 - 告警不得再輸出空泛「預期效益」;必須帶資料品質、證據來源、HITL 邊界與 trace id。 - Agent 建議只能輔助排序與分析,不得繞過 matcher / feeder / review service 寫正式價格。 diff --git a/docs/memory/history_logs.md b/docs/memory/history_logs.md index 61e940e..1d10cfe 100644 --- a/docs/memory/history_logs.md +++ b/docs/memory/history_logs.md @@ -13,6 +13,7 @@ ## 📅 詳細更新日誌 (考古存檔) ### 2026-05-24:PChome 近門檻身份回收第二輪 +- **V10.457 Dashboard / Excel 決策信封連動**: 商品看板 PChome 覆核卡顯示 `decision_envelope` 的決策等級、資料品質、HITL 與 trace;`/api/export/excel/pchome-review` 匯出新增決策信封 ID、建議代碼、責任人、資料品質、自動執行允許、阻擋原因與證據摘要,讓下載檔仍保留不可自動寫正式價差的 guardrails。 - **V10.456 review queue 決策信封**: `fetch_competitor_review_queue()`、`fetch_competitor_review_queue_page()` 與 `/api/pchome-review/queue` 每筆 PChome 覆核候選都輸出 `decision_envelope`,包含標的 SKU/PChome 候選、match evidence、建議人工動作、預期價差、資料品質與「不可自動寫正式價差」guardrails;review queue cache key 升到 v3,避免正式環境沿用舊 payload。 - **V10.455 EventRouter 決策信封直送**: 已帶 `decision_envelope` 的價格/覆核事件會略過 L1/L2 AI 重新摘要,直接用 Telegram 證據模板通知;決策信封新增標的區塊,顯示 SKU、商品名稱、PChome 候選 ID/名稱,避免 NemoTron 已有實證的價格告警被二次生成文字稀釋或產生額外模型呼叫。 - **V10.455 rescore variant retraction CLI**: `audit_competitor_match_attempt_rescore.py --retract-variant-accepted` 可找出最新仍為 `rescore_accepted_current` 且帶 `variant_selection_review` 的 SKU,追加 `true_low_confidence` 退回列;保留歷史 audit trail,不刪資料、不寫正式價格表。 diff --git a/routes/export_routes.py b/routes/export_routes.py index 7706ea4..e68e1f3 100644 --- a/routes/export_routes.py +++ b/routes/export_routes.py @@ -48,6 +48,39 @@ def _get_sales_cache(): return _SALES_PROCESSED_CACHE +def _flatten_review_decision_envelope(item): + """Flatten the shared review decision envelope into operator-friendly columns.""" + envelope = item.get('decision_envelope') or {} + guardrails = envelope.get('guardrails') or {} + recommended_action = envelope.get('recommended_action') or {} + evidence = envelope.get('evidence') or [] + evidence_parts = [] + if isinstance(evidence, list): + for row in evidence[:6]: + if not isinstance(row, dict): + continue + metric = row.get('metric') or row.get('type') or 'evidence' + value = row.get('value') + basis = row.get('basis') or '' + text = f"{metric}={value}" if value not in (None, '') else str(metric) + if basis: + text = f"{text} ({basis})" + evidence_parts.append(text) + + return { + '決策信封ID': envelope.get('decision_id') or '', + '決策類型': envelope.get('decision_type') or '', + '決策等級': envelope.get('severity') or '', + '決策建議代碼': recommended_action.get('action') or '', + '決策責任人': recommended_action.get('owner') or '', + '需人工覆核': '是' if recommended_action.get('requires_hitl') else '否', + '資料品質': guardrails.get('data_quality') or '', + '自動執行允許': '是' if guardrails.get('can_auto_execute') else '否', + '自動執行阻擋原因': guardrails.get('blocked_reason') or '', + '決策證據摘要': ';'.join(evidence_parts), + } + + # ========================================== # 全分類匯出 # ========================================== @@ -308,6 +341,7 @@ def export_excel_pchome_review(): '狀態': item.get('status_label') or '', '建議處置': item.get('action_label') or '', '診斷原因': item.get('diagnostic_reason_text') or '', + **_flatten_review_decision_envelope(item), 'MOMO商品ID': sku, 'MOMO商品名稱': item.get('name') or '', '分類': item.get('category') or '', @@ -336,7 +370,11 @@ def export_excel_pchome_review(): 'MOMO商品名稱', '候選PChome商品名稱', '建議處置', + '決策信封ID', + '決策建議代碼', '診斷原因', + '自動執行阻擋原因', + '決策證據摘要', '單位價比較', '原始診斷', 'MOMO商品URL', diff --git a/templates/dashboard_v2.html b/templates/dashboard_v2.html index 05e1996..d618732 100644 --- a/templates/dashboard_v2.html +++ b/templates/dashboard_v2.html @@ -190,6 +190,17 @@ {% endif %}
{{ item.action_label }}
+ {% if item.decision_envelope %} + {% set envelope = item.decision_envelope %} + {% set guardrails = envelope.guardrails or {} %} +
+ {{ envelope.severity or 'P4' }} + {{ guardrails.data_quality or 'partial' }} + {% if guardrails.can_auto_execute == false %} + HITL + {% endif %} +
+ {% endif %} {% if item.diagnostic_reasons %}
{% for reason in item.diagnostic_reasons[:4] %} @@ -556,6 +567,8 @@ {% elif current_filter == 'pchome_review' %} {% set review = item.pchome_review %} + {% set envelope = review.decision_envelope if review else {} %} + {% set guardrails = envelope.guardrails if envelope else {} %}
{{ review.status_label if review else match_status.label }} @@ -564,6 +577,18 @@ {% endif %}
{{ review.action_label if review else decision.summary }}
+ {% if envelope %} +
+ {{ envelope.severity or 'P4' }} + {{ guardrails.data_quality or 'partial' }} + {% if guardrails.can_auto_execute == false %} + HITL + {% endif %} + {% if envelope.decision_id %} + trace + {% endif %} +
+ {% endif %} {% if review %} {% if review.candidate_pc_name or review.candidate_pc_price %}
diff --git a/tests/test_frontend_v2_assets.py b/tests/test_frontend_v2_assets.py index aae8bfe..7d40736 100644 --- a/tests/test_frontend_v2_assets.py +++ b/tests/test_frontend_v2_assets.py @@ -239,6 +239,10 @@ def test_pchome_review_export_and_diagnostics_use_real_queue_data(): assert "@export_bp.route('/api/export/excel/pchome-review')" in export_source assert "fetch_competitor_review_queue_page" in export_source assert "診斷原因" in export_source + assert "_flatten_review_decision_envelope" in export_source + assert "決策信封ID" in export_source + assert "自動執行允許" in export_source + assert "決策證據摘要" in export_source assert "原始診斷" in export_source assert "PChome比價覆核_" in export_source assert "MATCH_DIAGNOSTIC_REASON_LABELS" in repository_source @@ -251,11 +255,14 @@ def test_pchome_review_export_and_diagnostics_use_real_queue_data(): assert "妝效質地不同" in route_source assert "_extract_match_diagnostic_reasons" in route_source assert "匯出覆核" in dashboard + assert "review.decision_envelope" in dashboard + assert "dashboard-review-envelope" in dashboard assert "review.diagnostic_reasons" in dashboard assert "item.pchome_match_attempt.diagnostic_reasons" in dashboard assert "dashboard-review-reasons" in dashboard assert "dashboard-review-actions" in dashboard assert ".dashboard-review-reasons" in dashboard_css + assert ".dashboard-review-envelope" in dashboard_css assert ".dashboard-review-actions" in dashboard_css assert ".dashboard-review-action.is-research" in dashboard_css diff --git a/web/static/css/page-dashboard-v2.css b/web/static/css/page-dashboard-v2.css index b7f5c9f..ea33dc9 100644 --- a/web/static/css/page-dashboard-v2.css +++ b/web/static/css/page-dashboard-v2.css @@ -968,6 +968,28 @@ line-height: 1.45; } + .dashboard-review-envelope { + display: flex; + flex-wrap: wrap; + gap: 4px; + min-width: 0; + } + + .dashboard-review-envelope span { + display: inline-flex; + align-items: center; + min-height: 20px; + padding: 2px 7px; + color: var(--momo-text-primary); + background: rgba(54, 73, 93, 0.07); + border: 1px solid rgba(54, 73, 93, 0.18); + border-radius: var(--momo-radius-pill); + font-size: 10px; + font-weight: 800; + line-height: 1.2; + text-transform: uppercase; + } + .dashboard-review-actions { display: flex; flex-wrap: wrap;