新增 PPT 審核歷史同頁回放
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s

This commit is contained in:
OoO
2026-05-18 20:40:25 +08:00
parent e880c91028
commit be0dd41472
4 changed files with 70 additions and 1 deletions

View File

@@ -4,6 +4,7 @@
================================================================================
【已完成】
- V10.210 補 `/observability/ppt_audit_history` 審核歷史同頁回放:每筆 daily 視覺審核紀錄的動作欄新增「回放」按鈕,沿用 PDF 預覽抽屜並保留下載/開新頁,讓問題追查不必再回檔案表找簡報。
- V10.208 修正 `/observability/ppt_audit_history` 同頁預覽抽屜 selectorModal 標題改用獨立 `data-ppt-preview-modal-title`,避免與多個預覽連結的資料屬性衝突。
- V10.207 強化 `/observability/ppt_audit_history` 同頁線上預覽:所有可預覽簡報按鈕改為開啟頁內 PDF 預覽抽屜,保留開新頁與下載,降低產線頁來回跳轉成本並改善手機操作。
- V10.205 補 `/observability/ppt_audit_history` 本頁批次 PDF 預熱Preview Workbench 可一鍵預熱頁面上尚未快取的 PPTX沿用單檔 JSON 端點逐一建立 PDF 快取並即時更新狀態。

View File

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

View File

@@ -386,6 +386,20 @@
<td class="text-end">{{ r.duration_ms }}</td>
<td><small class="text-muted">{{ (r.error_msg or '')[:80] }}</small></td>
<td>
{% if r.pptx_filename %}
<a class="btn btn-sm btn-outline-primary"
href="{{ url_for('admin_observability.ppt_audit_file', filename=r.pptx_filename) }}"
target="_blank"
rel="noopener"
data-ppt-open-preview
data-ppt-filename="{{ r.pptx_filename }}"
data-ppt-preview-title="審核回放 · {{ r.pptx_filename }}"
data-ppt-preview-pdf="{{ url_for('admin_observability.ppt_audit_file', filename=r.pptx_filename, action='pdf') }}"
data-ppt-preview-page="{{ url_for('admin_observability.ppt_audit_file', filename=r.pptx_filename) }}"
data-ppt-download-url="{{ url_for('admin_observability.ppt_audit_file', filename=r.pptx_filename, action='download') }}">
<i class="fas fa-eye me-1"></i>回放
</a>
{% endif %}
{% if r.audit_status in ('failed','error') %}
<button class="btn btn-sm btn-outline-warning"
type="button"

View File

@@ -319,6 +319,60 @@ def test_ppt_audit_file_view_renders_online_preview(client, monkeypatch, tmp_pat
assert '下載 PPTX' in html
def test_ppt_audit_history_audit_rows_include_inline_replay(client, monkeypatch, tmp_path):
"""審核歷史每筆紀錄都應能直接開同頁 PDF 回放。"""
import zipfile
from datetime import datetime
from unittest.mock import MagicMock
from routes import admin_observability_routes as mod
reports_dir = tmp_path / 'reports'
reports_dir.mkdir()
pptx = reports_dir / 'ocbot_daily_20260517.pptx'
with zipfile.ZipFile(pptx, 'w') as zf:
zf.writestr('[Content_Types].xml', '<Types></Types>')
monkeypatch.setenv('REPORTS_DIR', str(reports_dir))
class FakeSession:
def execute(self, statement, _params=None):
sql = str(statement)
result = MagicMock()
if 'FROM ppt_reports' in sql:
rows = []
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, '')
]
result.fetchall.return_value = rows
elif 'COALESCE(AVG(confidence)' in sql:
row = (1, 1, 0, 0, 0, 0.93, 0)
result.fetchone.return_value = row
elif 'GROUP BY pptx_filename' in sql:
rows = []
result.fetchall.return_value = rows
else:
rows = []
result.fetchall.return_value = rows
result.fetchone.return_value = (0,)
return result
def close(self):
return None
monkeypatch.setattr(mod, 'get_session', lambda: FakeSession())
r = client.get('/observability/ppt_audit_history?month=2026-05')
html = r.data.decode('utf-8')
assert r.status_code == 200
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
def test_ppt_audit_history_shows_preview_prewarm_action(client, monkeypatch, tmp_path):
"""未快取 PDF 的 PPTX 要能在產線清單直接預熱預覽。"""
import zipfile