Files
ewoooc/templates/admin/ppt_audit_history.html
OoO 2e124db602
All checks were successful
CD Pipeline / deploy (push) Successful in 2m36s
feat(p47): 6 頁深挖資料庫 — 從 5 表 → 17 表,每頁加 3-5 個 widget
統帥質疑:「6 頁內容太空洞,要更貼近資料庫裡所有數據」
盤點:DB 有 22 表,6 頁原本只用 5 表(22.7% 利用率)。
本 commit 新接 12 張既有但未用的表。

K-1 host_health(接 +5 表 → 8 表)
- 加 incidents 最近 10 筆詳細(task_name/error_type/嚴重度/狀態/重試/錯誤訊息)
- 加 heal_logs 最近 10 筆(action_type/result/耗時/incident 關聯)
- 加 playbooks 庫排行 TOP 12(success_count/fail_count/成功率/啟用狀態)
- 加 backup_log 7 日歷史(type/status/size/duration/error)
- 加 embedding_retry_queue pending/failed 警示

K-2 ai_calls(接 +3 表)
- 加 24h 每小時呼叫趨勢 bucket(含成本+錯誤+流量分布條)
- 加 by model 細分(不只 provider,到 model 版本級別)TOP 15
- 加 agent_context 最近 10 筆(OpenClaw/Hermes 對話 session preview)

K-3 budget(接 +3 表)
- 加當月 Top 5 燒錢呼叫端(caller × cost ranking)
- 加過去 30 日每日成本 by provider 趨勢表
- 加 ai_price_recommendations 7 日統計(strategy 分布 + 平均信心度)

K-4 promotion_review(接 +2 表)
- 加蒸餾池 30 日 status 分布(不只 awaiting,看 8 種完整流動)
- 加 ai_insights 最近 10 筆已晉升內容預覽
- 加 agent_strategy_weights TOP 12(OpenClaw 學習權重 + 成功率)

K-5 quality_trend(接 +3 表)
- 加 RAG 整體 feedback 1-5 分分布(過去 N 日,星等視覺)
- 加 action_plans status 分布(pending/approved/executed/rejected)
- 加 action_outcomes verdict 分布(effective/neutral/backfired
  — ADR-012 閉環學習核心 KPI)

K-6 ppt_audit_history(接 +0 表,但 deeper SQL)
- 加 30 日統計卡(total/passed/failed/error/通過率/總 issue 數/平均信心度)
- 加 Top 10 反覆失敗檔案(30d 失敗次數 + total issues)
- 加 empty state 說明(PPT_VISION_ENABLED=false 時顯示啟用步驟)

DB 利用率對應:
- Phase 38 起點:5 表(22.7%)
- Phase 39-46 累計:12 表(54.5%)
- Phase 47 收官:17 表(77.3%)
  新接:playbooks / backup_log / embedding_retry_queue / agent_context /
        ai_price_recommendations / agent_strategy_weights / action_plans /
        action_outcomes(之前已接:incidents/heal_logs/ai_insights/learning_episodes/
        rag_query_log/mcp_calls/ai_calls/host_health_probes/ppt_audit_results)

每頁 widget 數對應:
- host_health: 5 卡 → 11 卡
- ai_calls: 5 卡 → 8 卡
- budget: 4 卡 → 7 卡
- promotion_review: 2 卡 → 5 卡
- quality_trend: 4 卡 → 7 卡
- ppt_audit: 3 卡 → 6 卡

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:49:52 +08:00

258 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "ewoooc_base.html" %}
{% block title %}PPT 視覺審核歷史{% endblock %}
{% block ewooo_content %}
<div class="container-fluid mt-3">
<h2 class="mb-3"><i class="fas fa-search me-2"></i>PPT 視覺審核歷史
<small class="text-muted">reports/ 目錄過去 7 日 .pptx</small>
</h2>
{% if error %}<div class="alert alert-warning"><strong><i class="fas fa-exclamation-triangle me-1"></i></strong> {{ error }}</div>{% endif %}
<div class="alert {% if vision_enabled %}alert-success{% else %}alert-secondary{% endif %} small">
<strong>PPT_VISION_ENABLED</strong>
{% if vision_enabled %}
<i class="fas fa-check me-1"></i>已啟用 — 每日 22:00 cron 自動跑 minicpm-v 視覺檢查,發現問題推送 Telegram
{% else %}
<i class="fas fa-pause me-1"></i>未啟用 — 設 PPT_VISION_ENABLED=true 並在 188 主機安裝 LibreOffice 即生效
{% endif %}
</div>
<div class="card mb-3">
<div class="card-header"><strong><i class="fas fa-folder-open me-2"></i>過去 7 日 PPT 檔案</strong></div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr>
<th>檔名</th><th class="text-end">大小 (KB)</th>
<th>修改時間</th><th>動作</th>
</tr>
</thead>
<tbody>
{% for f in files %}
<tr>
<td><code>{{ f.name }}</code></td>
<td class="text-end">{{ f.size_kb }}</td>
<td><small>{{ f.mtime }}</small></td>
<td>
<small class="text-muted">由 audit cron 22:00 自動執行</small>
</td>
</tr>
{% else %}
<tr><td colspan="4" class="text-center text-muted">過去 7 日無 PPT 生成</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card mb-3">
<div class="card-header"><strong><i class="fas fa-history me-2"></i>視覺審核歷史紀錄(最近 100 筆)</strong></div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr>
<th>審核時間</th><th>檔名</th><th>結果</th>
<th class="text-end">問題數</th><th class="text-end">信心度</th>
<th class="text-end">耗時 ms</th><th>錯誤訊息</th><th>動作</th>
</tr>
</thead>
<tbody>
{% for r in audit_records %}
<tr>
<td><small>{{ r.audited_at }}</small></td>
<td><code>{{ r.pptx_filename }}</code></td>
<td>
{% if r.audit_status == 'passed' %}
<span class="badge bg-success"><i class="fas fa-check me-1"></i>通過</span>
{% elif r.audit_status == 'failed' %}
<span class="badge bg-warning"><i class="fas fa-exclamation me-1"></i>有問題</span>
{% elif r.audit_status == 'skipped' %}
<span class="badge bg-secondary"><i class="fas fa-pause me-1"></i>跳過</span>
{% elif r.audit_status == 'error' %}
<span class="badge bg-danger"><i class="fas fa-times me-1"></i>錯誤</span>
{% else %}
<span class="badge bg-light text-dark">{{ r.audit_status }}</span>
{% endif %}
</td>
<td class="text-end">{{ r.issues_count }}</td>
<td class="text-end">{{ "%.2f"|format(r.confidence) }}</td>
<td class="text-end">{{ r.duration_ms }}</td>
<td><small class="text-muted">{{ (r.error_msg or '')[:80] }}</small></td>
<td>
{% if r.audit_status in ('failed', 'error') %}
<button class="btn btn-sm btn-outline-warning"
onclick="triggerAiderHeal({{ r.pptx_filename|tojson }}, {{ (r.error_msg or '')|tojson }})">
<i class="fas fa-wrench me-1"></i>AiderHeal
</button>
{% endif %}
</td>
</tr>
{% else %}
<tr><td colspan="8" class="text-center text-muted">尚無審核紀錄migration 030 跑過後即會累積)</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if rag_fixes %}
<div class="card mb-3" style="border-left: 4px solid #6f42c1;">
<div class="card-header bg-light">
<strong><i class="fas fa-magic me-2"></i>RAG 自動修法建議</strong>
<small class="text-muted">— 對最近 3 筆 failed audit 從 ai_insights 召回相似案例修法</small>
</div>
<div class="card-body p-2">
{% for fix in rag_fixes %}
<div class="mb-2 p-2" style="background: #fafafa; border-radius: 6px;">
<strong><code>{{ fix.pptx_filename }}</code></strong>
<small class="text-muted ms-2">{{ fix.audited_at }}</small>
<div class="small text-danger mt-1">{{ fix.error_msg }}</div>
<ul class="list-unstyled mt-2 mb-0 small">
{% for h in fix.hits %}
<li class="mb-1">
<span class="badge bg-info text-dark me-1">{{ h.insight_type }}</span>
<span class="badge bg-light text-dark me-1">相似度 {{ "%.2f"|format(h.similarity) }}</span>
{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<p class="text-muted mt-2 small">
審核結果:<strong>有問題才推 Telegram</strong>(避免靜默無問題洗版)。
手動觸發單檔審核需 SSH 到 188 主機執行:
<code>python3 -c "from services.ppt_vision_service import ppt_vision_service; print(ppt_vision_service.check_ppt_file('reports/xxx.pptx'))"</code>
</p>
<!-- Phase 47 K-6: 30d 統計 -->
{% if audit_30d_stats and audit_30d_stats.total > 0 %}
<div class="card mb-3">
<div class="card-header"><strong><i class="fas fa-chart-pie me-2"></i>過去 30 日 PPT 審核統計</strong>
<small class="text-muted">資料來源ppt_audit_results 全表聚合</small>
</div>
<div class="card-body">
<div class="row g-2">
<div class="col-md-2 col-sm-4">
<div class="border rounded p-2 text-center">
<small class="text-muted d-block">總筆數</small>
<strong style="font-size: 1.4em;">{{ audit_30d_stats.total }}</strong>
</div>
</div>
<div class="col-md-2 col-sm-4">
<div class="border rounded p-2 text-center">
<small class="text-muted d-block">通過</small>
<strong class="text-success" style="font-size: 1.4em;">{{ audit_30d_stats.passed }}</strong>
</div>
</div>
<div class="col-md-2 col-sm-4">
<div class="border rounded p-2 text-center">
<small class="text-muted d-block">失敗</small>
<strong class="{% if audit_30d_stats.failed > 0 %}text-warning{% endif %}" style="font-size: 1.4em;">{{ audit_30d_stats.failed }}</strong>
</div>
</div>
<div class="col-md-2 col-sm-4">
<div class="border rounded p-2 text-center">
<small class="text-muted d-block">錯誤</small>
<strong class="{% if audit_30d_stats.error > 0 %}text-danger{% endif %}" style="font-size: 1.4em;">{{ audit_30d_stats.error }}</strong>
</div>
</div>
<div class="col-md-2 col-sm-4">
<div class="border rounded p-2 text-center">
<small class="text-muted d-block">通過率</small>
<strong class="{% if audit_30d_stats.pass_rate >= 80 %}text-success{% elif audit_30d_stats.pass_rate >= 60 %}text-warning{% else %}text-danger{% endif %}" style="font-size: 1.4em;">{{ "%.0f"|format(audit_30d_stats.pass_rate) }}%</strong>
</div>
</div>
<div class="col-md-2 col-sm-4">
<div class="border rounded p-2 text-center">
<small class="text-muted d-block">總 issue 數</small>
<strong style="font-size: 1.4em;">{{ audit_30d_stats.total_issues }}</strong>
</div>
</div>
</div>
<div class="mt-2 small text-muted">
<i class="fas fa-info-circle me-1"></i>
通過 audit 平均信心度:<strong>{{ "%.2f"|format(audit_30d_stats.avg_confidence) }}</strong>
</div>
</div>
</div>
{% endif %}
<!-- Phase 47 K-6: Top 10 failure files -->
{% if top_failure_files %}
<div class="card mb-3" style="border-left: 4px solid #ffc107;">
<div class="card-header bg-light">
<strong><i class="fas fa-exclamation-triangle me-2"></i>Top 10 失敗檔案30d</strong>
<small class="text-muted">— 反覆失敗的 PPT 檔案,需手動處理或調整 generator</small>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0" style="font-size: 0.9em;">
<thead class="table-light">
<tr>
<th>檔名</th>
<th class="text-end">失敗次數</th>
<th class="text-end">總 issue</th>
<th>最近審核</th>
</tr>
</thead>
<tbody>
{% for f in top_failure_files %}
<tr>
<td><code>{{ f.filename }}</code></td>
<td class="text-end"><span class="badge bg-warning text-dark">{{ f.attempts }}</span></td>
<td class="text-end">{{ f.total_issues }}</td>
<td><small>{{ f.last_audit }}</small></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% if (not audit_30d_stats or audit_30d_stats.total == 0) and not vision_enabled %}
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle me-1"></i>
<strong>為什麼這頁空?</strong>
<ul class="mb-0 small mt-2">
<li>PPT_VISION_ENABLED=false在 .env 設為 true 啟用)</li>
<li>188 主機需安裝 LibreOffice<code>apt install libreoffice</code></li>
<li>需 Ollama 拉取 minicpm-v 模型(用於 PPT 視覺檢查)</li>
<li>啟用後每日 22:00 cron 自動掃當天新生 .pptx審核結果寫入 ppt_audit_results 累積歷史</li>
</ul>
</div>
{% endif %}
<p class="text-muted mt-3"><small>
<i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 / Phase 47 — PPT 視覺審核歷史
3 表深挖ppt_audit_results / reports/ 檔案系統 / ai_insights RAG
</small></p>
</div>
<script>
async function triggerAiderHeal(pptxFilename, errorMsg) {
if (!confirm(`觸發 AiderHeal 自動修復?\n\n檔案:${pptxFilename}\n錯誤:${(errorMsg || '').substring(0, 200)}\n\nAiderHeal 會嘗試修 services/ppt_generator.py 並 git push 到 main 觸發 CD。`)) return;
try {
const r = await fetch('/observability/ppt_audit/trigger_aider_heal', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({pptx_filename: pptxFilename, error_msg: errorMsg || ''}),
});
const d = await r.json();
if (d.ok) {
alert(`✅ AiderHeal 已派出\n動作:${d.action || '—'}\n訊息:${d.message || ''}`);
} else {
alert('❌ ' + (d.error || d.message || '觸發失敗'));
}
} catch (e) {
alert('Error: ' + e);
}
}
</script>
{% endblock %}