補 PPT 視覺 QA 診斷摘要
All checks were successful
CD Pipeline / deploy (push) Successful in 1m8s

This commit is contained in:
OoO
2026-05-19 00:04:27 +08:00
parent 1cf1fd01b1
commit f51dc173f7
5 changed files with 47 additions and 8 deletions

View File

@@ -4,6 +4,7 @@
================================================================================
【已完成】
- V10.213 補 `/observability/ppt_audit_history` 視覺 QA 診斷摘要:審核歷史與 Action Queue 直接顯示 `ppt_audit_results.issues_found` 的投影片問題,讓「有問題」可追查,不再只剩問題數或空白錯誤欄。
- V10.212 修正 PPT 視覺 QA 的 Ollama 三主機 fallback當 Primary/Secondary request timeout 超過 unhealthy TTL 時,第三輪仍強制打 111 final fallbackPPT 截圖送模型前轉輕量 JPEG、縮小輸出 token降低單檔審核耗時。
- V10.211 補 `/observability/ppt_audit_history` 全類型視覺 QA審核歷史不再限 daily頁面新增「立即視覺 QA」非阻塞補跑結果寫入 `ppt_audit_results`;模型失敗時也保留 slide error避免產線狀態只剩空白。
- V10.210 補 `/observability/ppt_audit_history` 審核歷史同頁回放:每筆 daily 視覺審核紀錄的動作欄新增「回放」按鈕,沿用 PDF 預覽抽屜並保留下載/開新頁,讓問題追查不必再回檔案表找簡報。

View File

@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.212"
SYSTEM_VERSION = "V10.213"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -2473,7 +2473,7 @@ def _build_ppt_pipeline_view(files, auto_generation, audit_stats, generation_run
triage_entries.append({
'title': item.get('pptx_filename') or '未命名檔案',
'meta': item.get('audited_at') or '時間未知',
'detail': item.get('error_msg') or f"問題 {item.get('issues_count', 0)}",
'detail': item.get('issue_summary') or item.get('error_msg') or f"問題 {item.get('issues_count', 0)}",
'status_label': '視覺 QA',
'filename': item.get('pptx_filename') or '',
'report_type': 'daily',
@@ -2538,7 +2538,7 @@ def _build_ppt_pipeline_view(files, auto_generation, audit_stats, generation_run
{
'title': item.get('pptx_filename') or '未命名檔案',
'meta': item.get('audited_at') or '時間未知',
'detail': item.get('error_msg') or f"問題 {item.get('issues_count', 0)} 個,信心 {item.get('confidence', 0):.2f}",
'detail': item.get('issue_summary') or item.get('error_msg') or f"問題 {item.get('issues_count', 0)} 個,信心 {item.get('confidence', 0):.2f}",
'status_label': '需修復' if item.get('audit_status') == 'failed' else '需排查',
'filename': item.get('pptx_filename'),
}
@@ -2792,6 +2792,32 @@ def ppt_audit_history():
audit_filter_sql = " AND pptx_filename LIKE :audit_prefix"
audit_params['audit_prefix'] = f"{report_prefix}%"
def _summarize_ppt_issues(raw_issues) -> str:
"""把 ppt_audit_results.issues_found 壓成表格可讀的診斷摘要。"""
if not raw_issues:
return ''
try:
import json as _json
issues_payload = _json.loads(raw_issues) if isinstance(raw_issues, str) else raw_issues
except Exception:
return ''
if not isinstance(issues_payload, list):
return ''
snippets = []
for item in issues_payload:
if not isinstance(item, dict):
continue
slide = item.get('slide')
for issue in item.get('issues') or []:
text = str(issue).strip()
if not text:
continue
prefix = f"S{slide}: " if slide else ""
snippets.append(f"{prefix}{text}")
if len(snippets) >= 3:
return ''.join(snippets)
return ''.join(snippets)
# Phase 38+:讀指定月份 / 指定簡報類型 audit 歷史
try:
session = get_session()
@@ -2799,7 +2825,8 @@ def ppt_audit_history():
audit_rows = session.execute(
sa_text(f"""
SELECT audited_at, pptx_filename, audit_status,
issues_count, confidence, duration_ms, error_msg
issues_count, confidence, duration_ms, error_msg,
issues_found
FROM ppt_audit_results
WHERE audited_at >= :month_start
AND audited_at < :month_end
@@ -2818,6 +2845,7 @@ def ppt_audit_history():
'confidence': float(r[4] or 0),
'duration_ms': int(r[5] or 0),
'error_msg': r[6],
'issue_summary': _summarize_ppt_issues(r[7]),
}
for r in audit_rows
]

View File

@@ -373,7 +373,7 @@
<div class="table-responsive">
<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">耗時</th><th>錯誤</th><th>動作</th></tr>
<tr><th>時間</th><th>檔名</th><th>結果</th><th class="text-end">問題</th><th class="text-end">信心</th><th class="text-end">耗時</th><th>診斷摘要</th><th>動作</th></tr>
</thead>
<tbody>
{% for r in audit_records %}
@@ -390,7 +390,7 @@
<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><small class="text-muted">{{ (r.issue_summary or r.error_msg or '')[:180] }}</small></td>
<td>
{% if r.pptx_filename %}
<a class="btn btn-sm btn-outline-primary"

View File

@@ -343,7 +343,16 @@ def test_ppt_audit_history_audit_rows_include_inline_replay(client, monkeypatch,
result.fetchall.return_value = rows
elif 'SELECT audited_at, pptx_filename' in sql:
rows = [
(datetime(2026, 5, 17, 22, 5), 'ocbot_daily_20260517.pptx', 'passed', 0, 0.93, 1200, '')
(
datetime(2026, 5, 17, 22, 5),
'ocbot_daily_20260517.pptx',
'failed',
1,
0.93,
1200,
'',
[{'slide': 1, 'issues': ['⚠️ 圖表被切掉:右側圖例超出邊界']}],
)
]
result.fetchall.return_value = rows
elif 'COALESCE(AVG(confidence)' in sql:
@@ -370,6 +379,7 @@ def test_ppt_audit_history_audit_rows_include_inline_replay(client, monkeypatch,
assert '審核回放 · ocbot_daily_20260517.pptx' in html
assert 'data-ppt-open-preview' in html
assert 'ocbot_daily_20260517.pptx?action=pdf' in html
assert '圖表被切掉' in html
assert '回放' in html
@@ -396,7 +406,7 @@ def test_ppt_audit_history_weekly_rows_include_visual_audit(client, monkeypatch,
result.fetchall.return_value = []
elif 'SELECT audited_at, pptx_filename' in sql:
result.fetchall.return_value = [
(datetime(2026, 5, 18, 22, 7), 'ocbot_weekly_20260518.pptx', 'passed', 0, 0.91, 1800, '')
(datetime(2026, 5, 18, 22, 7), 'ocbot_weekly_20260518.pptx', 'passed', 0, 0.91, 1800, '', [])
]
elif 'COALESCE(AVG(confidence)' in sql:
result.fetchone.return_value = (1, 1, 0, 0, 0, 0.91, 0)