Surface review decision envelopes in dashboard export

This commit is contained in:
OoO
2026-05-24 23:14:46 +08:00
parent 98cd401f88
commit b0a267823a
9 changed files with 99 additions and 2 deletions

View File

@@ -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 guardrailsDashboard、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` 正式價差。

View File

@@ -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 # 用於模板顯示

View File

@@ -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 路由架構

View File

@@ -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、人工下一步、預期價差與不可自動寫正式價差的 guardrailsDashboard、Agent、Telegram、PPT 後續共用此 contract。
- 2026-05-24 23:15 CST 起Dashboard 覆核卡與 PChome 覆核 Excel 匯出也顯示/輸出信封摘要、資料品質、HITL、trace、自動執行阻擋原因與證據摘要下載檔不得丟失 guardrails。
- 告警不得再輸出空泛「預期效益」必須帶資料品質、證據來源、HITL 邊界與 trace id。
- Agent 建議只能輔助排序與分析,不得繞過 matcher / feeder / review service 寫正式價格。

View File

@@ -13,6 +13,7 @@
## 📅 詳細更新日誌 (考古存檔)
### 2026-05-24PChome 近門檻身份回收第二輪
- **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、建議人工動作、預期價差、資料品質與「不可自動寫正式價差」guardrailsreview 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不刪資料、不寫正式價格表。

View File

@@ -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',

View File

@@ -190,6 +190,17 @@
{% endif %}
</div>
<div class="dashboard-focus-sub">{{ item.action_label }}</div>
{% if item.decision_envelope %}
{% set envelope = item.decision_envelope %}
{% set guardrails = envelope.guardrails or {} %}
<div class="dashboard-review-envelope" aria-label="決策信封摘要">
<span>{{ envelope.severity or 'P4' }}</span>
<span>{{ guardrails.data_quality or 'partial' }}</span>
{% if guardrails.can_auto_execute == false %}
<span>HITL</span>
{% endif %}
</div>
{% endif %}
{% if item.diagnostic_reasons %}
<div class="dashboard-review-reasons" aria-label="比對診斷原因">
{% for reason in item.diagnostic_reasons[:4] %}
@@ -556,6 +567,8 @@
{% elif current_filter == 'pchome_review' %}
<td>
{% set review = item.pchome_review %}
{% set envelope = review.decision_envelope if review else {} %}
{% set guardrails = envelope.guardrails if envelope else {} %}
<div class="dashboard-review-card">
<div class="dashboard-ai-pick-head">
<span class="dashboard-ai-pick-rank">{{ review.status_label if review else match_status.label }}</span>
@@ -564,6 +577,18 @@
{% endif %}
</div>
<div class="dashboard-ai-pick-reason">{{ review.action_label if review else decision.summary }}</div>
{% if envelope %}
<div class="dashboard-review-envelope" aria-label="決策信封摘要">
<span>{{ envelope.severity or 'P4' }}</span>
<span>{{ guardrails.data_quality or 'partial' }}</span>
{% if guardrails.can_auto_execute == false %}
<span>HITL</span>
{% endif %}
{% if envelope.decision_id %}
<span title="{{ envelope.decision_id }}">trace</span>
{% endif %}
</div>
{% endif %}
{% if review %}
{% if review.candidate_pc_name or review.candidate_pc_price %}
<div class="dashboard-review-note">

View File

@@ -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

View File

@@ -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;