diff --git a/config.py b/config.py
index f5df2f5..4db4ffb 100644
--- a/config.py
+++ b/config.py
@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
-SYSTEM_VERSION = "V10.122"
+SYSTEM_VERSION = "V10.123"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示
diff --git a/templates/admin/_observability_labels.html b/templates/admin/_observability_labels.html
new file mode 100644
index 0000000..aea0a18
--- /dev/null
+++ b/templates/admin/_observability_labels.html
@@ -0,0 +1,144 @@
+{% macro strategy(value, fallback='未分類策略') -%}
+ {%- set labels = {
+ 'product_pick': '選品攻擊',
+ 'price_recommendation': '價格建議',
+ 'price_adjustment': '價格調整',
+ 'price_reduction': '降價攻擊',
+ 'price_drop': '降價攻擊',
+ 'discount_attack': '降價攻擊',
+ 'price_increase': '漲價防守',
+ 'margin_repair': '毛利修復',
+ 'inventory_clearance': '庫存去化',
+ 'competitor_response': '競品回應',
+ 'competitor_check': '競品複核',
+ 'promotion': '活動促銷',
+ 'stockout': '缺貨處理',
+ 'repricing': '價格重定',
+ 'watch': '觀察',
+ 'maintain': '維持價格',
+ 'unknown': '未分類策略'
+ } -%}
+ {%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
+{%- endmacro %}
+
+{% macro status(value, fallback='未分類') -%}
+ {%- set labels = {
+ 'pending': '待處理',
+ 'awaiting_review': '待審核',
+ 'approved': '已核准',
+ 'rejected': '已拒絕',
+ 'rejected_quality': '品質拒絕',
+ 'rejected_hallucination': '幻覺拒絕',
+ 'rejected_duplicate': '重複拒絕',
+ 'rejected_human': '人工拒絕',
+ 'expired': '已過期',
+ 'done': '已完成',
+ 'completed': '已完成',
+ 'running': '執行中',
+ 'success': '成功',
+ 'failed': '失敗',
+ 'matched': '已比對',
+ 'unmatched': '未比對',
+ 'ok': '正常',
+ 'cache_only': '只用快取',
+ 'passed': '已通過',
+ 'error': '錯誤',
+ 'skipped': '已跳過',
+ 'unknown': '未分類'
+ } -%}
+ {%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
+{%- endmacro %}
+
+{% macro verdict(value, fallback='未分類') -%}
+ {%- set labels = {
+ 'effective': '有效',
+ 'success': '成功',
+ 'positive': '正向',
+ 'backfired': '反效果',
+ 'negative': '負向',
+ 'failed': '失敗',
+ 'neutral': '中性',
+ 'pending': '待回收',
+ 'inconclusive': '尚未定論',
+ 'no_data': '無資料',
+ 'unknown': '未分類'
+ } -%}
+ {%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
+{%- endmacro %}
+
+{% macro plan_type(value, fallback='未分類計畫') -%}
+ {%- set labels = {
+ 'action_plan': '行動計畫',
+ 'price_adjustment': '價格調整',
+ 'product_pick': '選品攻擊',
+ 'promotion_review': '活動複核',
+ 'stockout_followup': '缺貨跟進',
+ 'competitor_check': '競品複核',
+ 'margin_repair': '毛利修復',
+ 'strategy_review': '策略複核',
+ 'quality_review': '品質複核'
+ } -%}
+ {%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
+{%- endmacro %}
+
+{% macro metric(value, fallback='未分類指標') -%}
+ {%- set labels = {
+ 'sales': '銷售',
+ 'revenue': '業績',
+ 'margin': '毛利',
+ 'profit': '毛利',
+ 'conversion': '轉換',
+ 'price': '價格',
+ 'stock': '庫存',
+ 'orders': '訂單',
+ 'unknown': '未分類指標'
+ } -%}
+ {%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
+{%- endmacro %}
+
+{% macro insight(value, fallback='未分類洞察') -%}
+ {%- set labels = {
+ 'product_pick': '選品攻擊',
+ 'price_recommendation': '價格建議',
+ 'competitor_price': '競品價格',
+ 'sales_anomaly': '業績異常',
+ 'budget_strategy': '預算策略',
+ 'rag_feedback': 'RAG 反饋',
+ 'ppt_audit': 'PPT 審核',
+ 'quality_issue': '品質問題',
+ 'promotion': '活動促銷',
+ 'market_signal': '市場訊號',
+ 'strategy': '策略洞察',
+ 'unknown': '未分類洞察'
+ } -%}
+ {%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
+{%- endmacro %}
+
+{% macro provider(value, fallback='未分類供應商') -%}
+ {%- set labels = {
+ 'gcp_ollama': '主力 Ollama',
+ 'ollama_secondary': '備援 Ollama',
+ 'ollama_111': '111 Ollama',
+ 'nim_via_elephant': 'NIM Elephant',
+ 'gemini': 'Gemini',
+ 'claude': 'Claude',
+ 'nim': 'NIM',
+ 'openrouter': 'OpenRouter',
+ 'unknown': '未分類供應商'
+ } -%}
+ {%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
+{%- endmacro %}
+
+{% macro source(value, fallback='未分類來源') -%}
+ {%- set labels = {
+ 'ai_insights': 'AI 知識庫',
+ 'ai_learning_episodes': '學習片段',
+ 'ai_price_recommendations': 'AI 價格建議',
+ 'action_plans': '行動計畫',
+ 'action_outcomes': '實際結果',
+ 'competitor_match_attempts': '競品比對',
+ 'competitor_price_history': '競品價格歷史',
+ 'ppt_audit_results': 'PPT 審核結果'
+ } -%}
+ {%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
+{%- endmacro %}
diff --git a/templates/admin/ai_calls_dashboard.html b/templates/admin/ai_calls_dashboard.html
index 9e2cf10..b07bb6b 100644
--- a/templates/admin/ai_calls_dashboard.html
+++ b/templates/admin/ai_calls_dashboard.html
@@ -42,6 +42,7 @@
@media (max-width: 720px) { .calls-command, .calls-mini-grid { grid-template-columns:1fr; } }
+{% import "admin/_observability_labels.html" as obs_label %}
{% set total = summary.total_calls or 0 %}
{% set errors = summary.error_calls or 0 %}
{% set error_rate = (errors / total * 100) if total > 0 else 0 %}
@@ -58,7 +59,7 @@
@@ -98,7 +99,7 @@
{% for row in by_provider[:4] %}
-
{{ row.provider }}{{ "{:,}".format(row.calls) }}${{ "%.2f"|format(row.cost) }} · {{ "{:,}".format(row.tokens) }} tk
+
{{ obs_label.provider(row.provider) }}{{ "{:,}".format(row.calls) }}${{ "%.2f"|format(row.cost) }} · {{ "{:,}".format(row.tokens) }} 權杖
{% else %}
尚無供應商資料
{% endfor %}
@@ -118,13 +119,13 @@
{% if by_model %}
- | 模型 | 供應商 | 呼叫 | 權杖 | 成本 | 耗時 | 錯誤 |
{% for m in by_model %}{{ m.model[:35] }} | {{ m.provider }} | {{ "{:,}".format(m.calls) }} | {{ "{:,}".format(m.tokens) }} | ${{ "%.4f"|format(m.cost) }} | {{ m.avg_ms }} ms | {% if m.errors > 0 %}{{ m.errors }}{% else %}0{% endif %} |
{% endfor %}
+ | 模型 | 供應商 | 呼叫 | 權杖 | 成本 | 耗時 | 錯誤 |
{% for m in by_model %}{{ m.model[:35] }} | {{ obs_label.provider(m.provider) }} | {{ "{:,}".format(m.calls) }} | {{ "{:,}".format(m.tokens) }} | ${{ "%.4f"|format(m.cost) }} | {{ m.avg_ms }} ms | {% if m.errors > 0 %}{{ m.errors }}{% else %}0{% endif %} |
{% endfor %}
{% endif %}
- | 編號 | 時間 | 呼叫端 | 供應商 | 模型 | 輸入 | 輸出 | 耗時 | 狀態 | 成本 | 標記 |
{% for r in recent %}| {{ r.id }} | {{ r.called_at }} | {{ r.caller }} | {{ r.provider }} | {{ r.model[:25] }} | {{ r.in_tokens }} | {{ r.out_tokens }} | {{ r.duration_ms }} | {{ r.status }} | ${{ "%.4f"|format(r.cost) }} | {% if r.cache_hit %}快取{% endif %}{% if r.rag_hit %}RAG{% endif %} |
{% endfor %}
+ | 編號 | 時間 | 呼叫端 | 供應商 | 模型 | 輸入 | 輸出 | 耗時 | 狀態 | 成本 | 標記 |
{% for r in recent %}| {{ r.id }} | {{ r.called_at }} | {{ r.caller }} | {{ obs_label.provider(r.provider) }} | {{ r.model[:25] }} | {{ r.in_tokens }} | {{ r.out_tokens }} | {{ r.duration_ms }} | {{ obs_label.status(r.status, '-') }} | ${{ "%.4f"|format(r.cost) }} | {% if r.cache_hit %}快取{% endif %}{% if r.rag_hit %}RAG{% endif %} |
{% endfor %}
Ollama 優先策略 v5.0 — AI 流量控制塔
diff --git a/templates/admin/budget.html b/templates/admin/budget.html
index 08c014a..bb53b68 100644
--- a/templates/admin/budget.html
+++ b/templates/admin/budget.html
@@ -31,6 +31,7 @@
@media (max-width:720px){ .gov-command,.gov-mini-grid{grid-template-columns:1fr;} }
+{% import "admin/_observability_labels.html" as obs_label %}
{% set total_budget = namespace(value=0) %}{% set total_spent = namespace(value=0) %}{% set warn_count = namespace(value=0) %}{% set throttled_count = namespace(value=0) %}
{% for r in rows %}{% set total_budget.value = total_budget.value + (r.budget_usd or 0) %}{% set total_spent.value = total_spent.value + (r.spent or 0) %}{% if r.ratio >= 0.8 %}{% set warn_count.value = warn_count.value + 1 %}{% endif %}{% if r.throttled %}{% set throttled_count.value = throttled_count.value + 1 %}{% endif %}{% endfor %}
{% set total_ratio = (total_spent.value / total_budget.value * 100) if total_budget.value > 0 else 0 %}
@@ -40,7 +41,7 @@
AI 成本治理 · 預算 / 節流 / RAG 策略
AI 成本治理艙
這頁回答一個問題:AI 中樞花錢是否仍在治理邊界內?預算、實際支出、月底推估、節流狀態與 RAG 策略建議集中在同一個工作台。
- 超過 110% 時不用等 排程,直接重算供應商節流。
+ 超過 110% 時不用等排程,直接重算供應商節流。
當月花費
${{ "%.2f"|format(total_spent.value) }}預算 ${{ "%.2f"|format(total_budget.value) }}
預算使用率
{{ "%.0f"|format(total_ratio) }}%全供應商加總
@@ -55,7 +56,7 @@
-
+
{% if cost_trend_30d %}
@@ -75,11 +76,11 @@
{% if budget_strategies %}
-
{% for s in budget_strategies %}
{{ s.insight_type }}相似度 {{ "%.2f"|format(s.similarity) }}{{ s.content }}{% if s.content|length >= 240 %}…{% endif %}
{% endfor %}
+
{% for s in budget_strategies %}
{{ obs_label.insight(s.insight_type) }}相似度 {{ "%.2f"|format(s.similarity) }}{{ s.content }}{% if s.content|length >= 240 %}…{% endif %}
{% endfor %}
{% endif %}
{% if price_rec_7d %}
-
{% for p in price_rec_7d %}
{{ p.strategy }}{{ p.count }}信心 {{ "%.2f"|format(p.avg_confidence) }}
{% endfor %}
+
{% for p in price_rec_7d %}
{{ obs_label.strategy(p.strategy) }}{{ p.count }}信心 {{ "%.2f"|format(p.avg_confidence) }}
{% endfor %}
{% endif %}
Ollama 優先策略 v5.0 — AI 成本治理艙
@@ -87,8 +88,9 @@
diff --git a/templates/admin/business_intel.html b/templates/admin/business_intel.html
index d4104a2..178c5c8 100644
--- a/templates/admin/business_intel.html
+++ b/templates/admin/business_intel.html
@@ -417,6 +417,7 @@
{% endblock %}
{% block ewooo_content %}
+{% import "admin/_observability_labels.html" as obs_label %}
{% set rec_total = rec_by_strategy|sum(attribute='count') if rec_by_strategy else 0 %}
{% set ns = namespace(conf_total=0, effective=0, backfired=0, neutral=0, verdict_total=0) %}
{% for r in rec_by_strategy %}
@@ -497,7 +498,7 @@
{% if unfollowed_count > 0 %}
- {{ unfollowed_count }} 筆高信心 AI 價格建議尚未跟進,建議優先轉為 action_plan 或標記原因。
+ {{ unfollowed_count }} 筆高信心 AI 價格建議尚未跟進,建議優先轉為行動計畫或標記原因。
需人工決策
{% endif %}
@@ -508,7 +509,7 @@
策略族群雷達
-
把 AI 價格建議依 strategy 聚合,快速判斷目前主攻降價、防守或毛利修復。
+
把 AI 價格建議依策略類型聚合,快速判斷目前主攻降價、防守或毛利修復。
{{ rec_by_strategy|length }} 類策略
@@ -517,7 +518,7 @@
{% for r in rec_by_strategy %}
- {{ r.strategy or '未分類策略' }}
+ {{ obs_label.strategy(r.strategy) }}
{{ r.count }}建議數
{{ '%.0f'|format((r.avg_confidence or 0) * 100) }}%信心
@@ -553,7 +554,7 @@
{{ r.name or '-' }}
- {{ r.strategy or '-' }}
+ {{ obs_label.strategy(r.strategy, '-') }}
{{ '%.0f'|format((r.confidence or 0) * 100) }}%
@@ -596,12 +597,12 @@
{% for r in loop_records %}
- #{{ r.plan_id }} {{ r.plan_type or '-' }} |
+ #{{ r.plan_id }} {{ obs_label.plan_type(r.plan_type, '-') }} |
{{ r.sku }} |
- {{ r.status or '-' }} |
+ {{ obs_label.status(r.status, '-') }} |
{{ r.created_at or '-' }} / {{ r.executed_at or '-' }} |
- {{ r.verdict or '-' }} |
- {{ r.metric_type or '-' }} {{ r.before or '-' }} → {{ r.after or '-' }} |
+ {{ obs_label.verdict(r.verdict, '-') }} |
+ {{ obs_label.metric(r.metric_type, '-') }} {{ r.before or '-' }} → {{ r.after or '-' }} |
{{ '%.1f'|format(r.change_pct or 0) }}% |
{% endfor %}
@@ -629,7 +630,7 @@
| 結論 | 數量 | 平均變化 |
{% for v in verdict_stats %}
- | {{ v.verdict or '未分類' }} | {{ v.count }} | {{ '%.1f'|format(v.avg_delta or 0) }}% |
+ | {{ obs_label.verdict(v.verdict) }} | {{ v.count }} | {{ '%.1f'|format(v.avg_delta or 0) }}% |
{% endfor %}
@@ -653,7 +654,7 @@
{% for m in match_stats %}
- | {{ m.status or '-' }} |
+ {{ obs_label.status(m.status, '-') }} |
{{ m.count }} |
{{ '%.1f'|format(m.avg_candidates or 0) }} |
{{ '%.2f'|format(m.avg_score or 0) }} |
@@ -708,11 +709,23 @@
const verdictRows = {{ verdict_stats|tojson }};
const canvas = document.getElementById('verdictPieChart');
if (!canvas || !verdictRows || verdictRows.length === 0) return;
+ const verdictLabelMap = {
+ effective: '有效',
+ success: '成功',
+ positive: '正向',
+ backfired: '反效果',
+ negative: '負向',
+ failed: '失敗',
+ neutral: '中性',
+ pending: '待回收',
+ inconclusive: '尚未定論',
+ no_data: '無資料'
+ };
new Chart(canvas, {
type: 'doughnut',
data: {
- labels: verdictRows.map(row => row.verdict || '未分類'),
+ labels: verdictRows.map(row => verdictLabelMap[row.verdict] || row.verdict || '未分類'),
datasets: [{
data: verdictRows.map(row => row.count || 0),
backgroundColor: ['#2f8f6b', '#c96442', '#f1b45a', '#6d4b3f', '#d9a06f'],
diff --git a/templates/admin/observability_overview.html b/templates/admin/observability_overview.html
index 7c337f0..5bd6748 100644
--- a/templates/admin/observability_overview.html
+++ b/templates/admin/observability_overview.html
@@ -406,6 +406,7 @@
}
+{% import "admin/_observability_labels.html" as obs_label %}
{% set ai = summary.ai_calls if summary.ai_calls else none %}
{% set host_count = summary.hosts|length if summary.hosts else 0 %}
{% set host_bad = namespace(value=0) %}
@@ -507,7 +508,7 @@
{% for b in summary.budget_alerts %}
| {{ b.period }} |
- {{ b.provider }} |
+ {{ obs_label.provider(b.provider) }} |
${{ "%.2f"|format(b.spent) }} |
${{ "%.2f"|format(b.budget) }} |
{{ "%.0f"|format(b.ratio * 100) }}% |
@@ -609,9 +610,9 @@
RAG 與品質
diff --git a/templates/admin/ppt_audit_history.html b/templates/admin/ppt_audit_history.html
index a587e03..f592263 100644
--- a/templates/admin/ppt_audit_history.html
+++ b/templates/admin/ppt_audit_history.html
@@ -8,14 +8,15 @@
.ppt-hero{padding:clamp(1.2rem,2.4vw,2rem);background:radial-gradient(circle at 12% 14%,rgba(201,100,66,.18),transparent 24rem),radial-gradient(circle at 88% 8%,rgba(79,111,143,.14),transparent 22rem),linear-gradient(135deg,rgba(255,248,239,.98),rgba(255,255,255,.74))}.ppt-kicker{color:var(--obs-accent);font-size:.76rem;letter-spacing:.13em;text-transform:uppercase;font-weight:850}.ppt-title{margin:.45rem 0 .25rem;font-family:'Noto Sans TC','Inter',sans-serif;font-size:var(--obs-title-size);letter-spacing:-.055em;line-height:.98}.ppt-subtitle{color:var(--obs-muted);max-width:860px;line-height:1.7}.ppt-command{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;margin-top:1rem}.ppt-signal{padding:.95rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.62)}.ppt-label{color:var(--obs-muted);font-size:.72rem;letter-spacing:.1em;text-transform:uppercase}.ppt-value{display:block;margin-top:.28rem;font-size:var(--obs-value-size);font-weight:880;letter-spacing:-.045em}.ppt-grid{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(330px,.8fr);gap:1rem;margin-top:1rem}.ppt-stack{display:grid;gap:1rem}.ppt-panel-head,.ppt-table-title{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;padding:1.05rem 1.1rem .25rem}.ppt-panel-title,.ppt-table-title h3{margin:.15rem 0 0;font-size:1.1rem;font-weight:850;letter-spacing:-.025em}.ppt-panel-body{padding:1rem 1.1rem 1.1rem}.ppt-table-shell{overflow:hidden;margin-top:1rem}.ppt-mini-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.7rem}.ppt-mini{padding:.85rem;border:1px solid var(--obs-line);border-radius:18px;background:rgba(255,255,255,.58)}.ppt-mini strong{display:block;margin-top:.24rem;font-size:1.35rem;letter-spacing:-.04em}.fix-card{padding:.85rem;border:1px solid var(--obs-line);border-radius:18px;background:rgba(255,255,255,.58);margin-bottom:.7rem}.status-good{color:var(--obs-green)}.status-warn{color:var(--obs-amber)}.status-bad{color:var(--obs-red)}.status-blue{color:var(--obs-blue)}@media(max-width:1100px){.ppt-command{grid-template-columns:repeat(2,minmax(0,1fr))}.ppt-grid{grid-template-columns:1fr}}@media(max-width:720px){.ppt-command,.ppt-mini-grid{grid-template-columns:1fr}}
+{% import "admin/_observability_labels.html" as obs_label %}
-
PPT 視覺 QA 產線 · minicpm-v / AiderHeal / RAG 修法
PPT 視覺 QA 產線
這頁追蹤每份自動簡報是否通過視覺審核:檔案產出、minicpm-v 審核、Telegram 推送、RAG 修法建議與 AiderHeal 自動修 generator。
視覺模型
{{ '啟用' if vision_enabled else '停用' }}PPT_VISION_ENABLED30 日總量
{{ audit_30d_stats.total if audit_30d_stats else 0 }}審核紀錄通過率
{{ "%.0f"|format(audit_30d_stats.pass_rate) if audit_30d_stats else '—' }}{% if audit_30d_stats %}%{% endif %}過去 30 日問題數
{{ audit_30d_stats.total_issues if audit_30d_stats else 0 }}視覺問題數
+
PPT 視覺 QA 產線 · minicpm-v / AiderHeal / RAG 修法
PPT 視覺 QA 產線
這頁追蹤每份自動簡報是否通過視覺審核:檔案產出、minicpm-v 審核、Telegram 推送、RAG 修法建議與 AiderHeal 自動修產生器。
視覺模型
{{ '啟用' if vision_enabled else '停用' }}PPT_VISION_ENABLED30 日總量
{{ audit_30d_stats.total if audit_30d_stats else 0 }}審核紀錄通過率
{{ "%.0f"|format(audit_30d_stats.pass_rate) if audit_30d_stats else '—' }}{% if audit_30d_stats %}%{% endif %}過去 30 日問題數
{{ audit_30d_stats.total_issues if audit_30d_stats else 0 }}視覺問題數
{% if error %}
{{ error }}
{% endif %}
| 時間 | 檔名 | 結果 | 問題 | 信心 | 耗時 | 錯誤 | 動作 |
{% for r in audit_records %}| {{ r.audited_at }} | {{ r.pptx_filename }} | {% if r.audit_status == 'passed' %}通過{% elif r.audit_status == 'failed' %}有問題{% elif r.audit_status == 'error' %}錯誤{% elif r.audit_status == 'skipped' %}跳過{% else %}{{ r.audit_status }}{% endif %} | {{ r.issues_count }} | {{ "%.2f"|format(r.confidence) }} | {{ r.duration_ms }} | {{ (r.error_msg or '')[:80] }} | {% if r.audit_status in ('failed','error') %}{% endif %} |
{% else %}| 尚無審核紀錄 |
{% endfor %}
-
| 檔名 | KB | 修改時間 | 狀態 |
{% for f in files %}{{ f.name }} | {{ f.size_kb }} | {{ f.mtime }} | 22:00 cron 自動審核 |
{% else %}| 過去 7 日無 PPT 生成 |
{% endfor %}
+
| 檔名 | KB | 修改時間 | 狀態 |
{% for f in files %}{{ f.name }} | {{ f.size_kb }} | {{ f.mtime }} | 22:00 排程自動審核 |
{% else %}| 過去 7 日無 PPT 生成 |
{% endfor %}
- {% if rag_fixes %}
{% for fix in rag_fixes %}
{{ fix.pptx_filename }}{{ fix.audited_at }}{{ fix.error_msg }}
{% for h in fix.hits %}- {{ h.insight_type }}相似度 {{ "%.2f"|format(h.similarity) }}{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
- {% if (not audit_30d_stats or audit_30d_stats.total == 0) and not vision_enabled %}
為什麼這頁空?- PPT_VISION_ENABLED=false
- 188 主機需安裝 LibreOffice
- 需 Ollama 拉取 minicpm-v 模型
- 啟用後每日 22:00 cron 寫入 ppt_audit_results
{% endif %}
+ {% if rag_fixes %}
{% for fix in rag_fixes %}
{{ fix.pptx_filename }}{{ fix.audited_at }}{{ fix.error_msg }}
{% for h in fix.hits %}- {{ obs_label.insight(h.insight_type) }}相似度 {{ "%.2f"|format(h.similarity) }}{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
+ {% if (not audit_30d_stats or audit_30d_stats.total == 0) and not vision_enabled %}
為什麼這頁空?- PPT_VISION_ENABLED=false
- 188 主機需安裝 LibreOffice
- 需 Ollama 拉取 minicpm-v 模型
- 啟用後每日 22:00 排程寫入 ppt_audit_results
{% endif %}
Ollama 優先策略 v5.0 — PPT 視覺 QA 產線
diff --git a/templates/admin/promotion_review.html b/templates/admin/promotion_review.html
index 70c35a9..ebb0467 100644
--- a/templates/admin/promotion_review.html
+++ b/templates/admin/promotion_review.html
@@ -32,6 +32,7 @@
@media (max-width:720px){ .gate-command,.gate-mini-grid{grid-template-columns:1fr;} .episode-head{display:block;} }
+{% import "admin/_observability_labels.html" as obs_label %}
{% set total_dist = (episode_distribution_30d.values() | sum) if episode_distribution_30d else 0 %}
{% set approved_30d = episode_distribution_30d.get('approved', 0) if episode_distribution_30d else 0 %}
{% set rejected_30d = namespace(value=0) %}
@@ -66,12 +67,12 @@
{% for ep in episodes %}
-
學習片段 #{{ ep.id }} {{ ep.episode_type }}{% if ep.source_table %}{{ ep.source_table }}#{{ ep.source_id }}{% endif %}權重 {{ "%.2f"|format(ep.weight) }}品質 {{ "%.2f"|format(ep.quality_score) }}
+
學習片段 #{{ ep.id }} {{ obs_label.insight(ep.episode_type) }}{% if ep.source_table %}{{ obs_label.source(ep.source_table) }} #{{ ep.source_id }}{% endif %}權重 {{ "%.2f"|format(ep.weight) }}品質 {{ "%.2f"|format(ep.quality_score) }}
{{ ep.created_at }}
{{ ep.distilled_text }}
- {% if ep.similar_insights %}
Top 3 相似已晉升知識(用來判斷是否重複){% for sim in ep.similar_insights %}- #{{ sim.id }}{{ sim.insight_type }}相似度 {{ "%.2f"|format(sim.similarity) }}{{ sim.content }}{% if sim.content|length >= 180 %}…{% endif %}
{% endfor %}
{% else %}
知識庫無 cosine ≥ 0.7 相似內容,可能是新領域知識。
{% endif %}
+ {% if ep.similar_insights %}
Top 3 相似已晉升知識(用來判斷是否重複){% for sim in ep.similar_insights %}- #{{ sim.id }}{{ obs_label.insight(sim.insight_type) }}相似度 {{ "%.2f"|format(sim.similarity) }}{{ sim.content }}{% if sim.content|length >= 180 %}…{% endif %}
{% endfor %}
{% else %}
知識庫無相似度 ≥ 0.7 的相似內容,可能是新領域知識。
{% endif %}
@@ -86,7 +87,7 @@
{% endif %}
{% if strategy_weights %}
- {% for s in strategy_weights[:6] %}
{{ s.strategy_key[:22] }}{{ "%.2f"|format(s.weight) }}成功 {{ s.success }} · 失敗 {{ s.fail }}
{% endfor %}
+ {% for s in strategy_weights[:6] %}
{{ obs_label.strategy(s.strategy_key) }}{{ "%.2f"|format(s.weight) }}成功 {{ s.success }} · 失敗 {{ s.fail }}
{% endfor %}
{% endif %}
@@ -102,6 +103,6 @@
{% endblock %}
diff --git a/templates/admin/quality_trend.html b/templates/admin/quality_trend.html
index 07186cc..9a7a790 100644
--- a/templates/admin/quality_trend.html
+++ b/templates/admin/quality_trend.html
@@ -12,6 +12,7 @@
@media(max-width:1100px){.quality-command{grid-template-columns:repeat(2,minmax(0,1fr))}.quality-grid{grid-template-columns:1fr}}@media(max-width:720px){.quality-command,.quality-mini-grid{grid-template-columns:1fr}}
+{% import "admin/_observability_labels.html" as obs_label %}
{% set total_feedback = namespace(value=0) %}{% set worst_avg = namespace(value=5) %}{% for caller, info in trends %}{% set total_feedback.value = total_feedback.value + (info.total_feedback or 0) %}{% if info.avg_score < worst_avg.value %}{% set worst_avg.value = info.avg_score %}{% endif %}{% endfor %}
{% set episode_total = (episode_distribution.values() | sum) if episode_distribution else 0 %}
{% set rag_total = (rag_overall_dist | sum(attribute='count')) if rag_overall_dist else 0 %}
@@ -23,17 +24,17 @@
| 呼叫端 | 平均 | 讚 | 倒讚 | 總數 | 趨勢 | 分布 |
{% for caller, info in trends %}{{ caller }} | {{ "%.2f"|format(info.avg_score) }}/5 | {{ info.thumbs_up }} | {{ info.thumbs_down }} | {{ info.total_feedback }} | {% if info.trend == 'positive' %}正向{% elif info.trend == 'negative' %}負向{% elif info.trend == 'neutral' %}中性{% else %}無資料{% endif %} | |
{% else %}| 無反饋資料 |
{% endfor %}
- {% if action_plans_status %}
| 狀態 | 計畫類型 | 數量 |
{% for a in action_plans_status %}| {{ a.status }} | {{ a.plan_type }} | {{ a.count }} |
{% endfor %}
{% endif %}
+ {% if action_plans_status %}
| 狀態 | 計畫類型 | 數量 |
{% for a in action_plans_status %}| {{ obs_label.status(a.status) }} | {{ obs_label.plan_type(a.plan_type) }} | {{ a.count }} |
{% endfor %}
{% endif %}
- {% if rag_root_causes %}{% for rc in rag_root_causes %}
{{ rc.caller }}{{ "%.2f"|format(rc.avg_score) }}/5{{ rc.feedback_n }} 筆{% for h in rc.hits %}- {{ h.insight_type }}相似度 {{ "%.2f"|format(h.similarity) }}{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
+ {% if rag_root_causes %}{% for rc in rag_root_causes %}
{{ rc.caller }}{{ "%.2f"|format(rc.avg_score) }}/5{{ rc.feedback_n }} 筆{% for h in rc.hits %}- {{ obs_label.insight(h.insight_type) }}相似度 {{ "%.2f"|format(h.similarity) }}{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
{% if recommendations %}{% for rec in recommendations %}- {% if rec.action == 'review' %}{% else %}{% endif %}
{{ rec.caller }}:{{ rec.reason }} {% endfor %}
{% endif %}
- {% if action_outcomes_stats %}{% set total_ao = (action_outcomes_stats | sum(attribute='count')) or 1 %}{% for r in action_outcomes_stats %}
{{ r.verdict }}{{ r.count }}{{ "%.1f"|format(r.count / total_ao * 100) }}%
{% endfor %}
{% endif %}
+ {% if action_outcomes_stats %}{% set total_ao = (action_outcomes_stats | sum(attribute='count')) or 1 %}{% for r in action_outcomes_stats %}
{{ obs_label.verdict(r.verdict) }}{{ r.count }}{{ "%.1f"|format(r.count / total_ao * 100) }}%
{% endfor %}
{% endif %}
Ollama 優先策略 v5.0 — AI 品質診斷台
diff --git a/templates/admin/rag_queries.html b/templates/admin/rag_queries.html
index fe25f29..63c1ca9 100644
--- a/templates/admin/rag_queries.html
+++ b/templates/admin/rag_queries.html
@@ -40,7 +40,9 @@
{% endblock %}