diff --git a/TODO_NEXT_STEPS.txt b/TODO_NEXT_STEPS.txt index 20543cf..d43fe09 100644 --- a/TODO_NEXT_STEPS.txt +++ b/TODO_NEXT_STEPS.txt @@ -4,6 +4,7 @@ ================================================================================ 【已完成】 + - V10.215 強化 `/observability/ppt_audit_history` 視覺問題追蹤:將 `issues_found` 拆成投影片、問題類型、問題文字與回放入口,新增「視覺問題追蹤」面板,讓問題簡報能直接定位與預覽。 - 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 fallback;PPT 截圖送模型前轉輕量 JPEG、縮小輸出 token,降低單檔審核耗時。 - V10.211 補 `/observability/ppt_audit_history` 全類型視覺 QA:審核歷史不再限 daily,頁面新增「立即視覺 QA」非阻塞補跑,結果寫入 `ppt_audit_results`;模型失敗時也保留 slide error,避免產線狀態只剩空白。 diff --git a/config.py b/config.py index 9509544..7a998cb 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.214" +SYSTEM_VERSION = "V10.215" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/routes/admin_observability_routes.py b/routes/admin_observability_routes.py index 87b0193..c33e1d4 100644 --- a/routes/admin_observability_routes.py +++ b/routes/admin_observability_routes.py @@ -2818,6 +2818,48 @@ def ppt_audit_history(): return ';'.join(snippets) return ';'.join(snippets) + def _load_ppt_issues(raw_issues): + 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 [] + return issues_payload if isinstance(issues_payload, list) else [] + + def _classify_ppt_issue(issue_text: str): + text = issue_text or '' + if any(k in text for k in ['圖表', '切掉', '截斷', '超出', '溢出']): + return '版面越界', 'error' + if any(k in text for k in ['空白', '未填', '缺少', '無資料']): + return '內容缺漏', 'warn' + if any(k in text for k in ['低對比', '顏色', '字體', '字型', '閱讀']): + return '可讀性', 'warn' + return '視覺問題', 'warn' + + def _extract_ppt_issue_items(raw_issues, *, pptx_filename: str, audited_at: str): + issue_items = [] + for slide_item in _load_ppt_issues(raw_issues): + if not isinstance(slide_item, dict): + continue + slide = slide_item.get('slide') + slide_label = f"S{slide}" if slide else 'S?' + for raw_issue in slide_item.get('issues') or []: + issue_text = str(raw_issue).strip() + if not issue_text: + continue + category, status = _classify_ppt_issue(issue_text) + issue_items.append({ + 'pptx_filename': pptx_filename, + 'audited_at': audited_at, + 'slide_label': slide_label, + 'category': category, + 'status': status, + 'text': issue_text, + }) + return issue_items + # Phase 38+:讀指定月份 / 指定簡報類型 audit 歷史 try: session = get_session() @@ -2836,24 +2878,45 @@ def ppt_audit_history(): """), audit_params, ).fetchall() - audit_records = [ - { - 'audited_at': r[0].strftime('%Y-%m-%d %H:%M'), - 'pptx_filename': r[1], + audit_records = [] + for r in audit_rows: + audited_at = r[0].strftime('%Y-%m-%d %H:%M') + pptx_filename = r[1] + raw_issues = r[7] + audit_records.append({ + 'audited_at': audited_at, + 'pptx_filename': pptx_filename, 'audit_status': r[2], 'issues_count': int(r[3] or 0), '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 - ] + 'issue_summary': _summarize_ppt_issues(raw_issues), + 'issue_items': _extract_ppt_issue_items( + raw_issues, + pptx_filename=pptx_filename, + audited_at=audited_at, + ), + }) finally: session.close() except Exception: logger.debug("PPT audit history table unavailable; rendering empty audit history", exc_info=True) + issue_items = [ + issue + for record in audit_records + for issue in record.get('issue_items', []) + ] + issue_files = {issue.get('pptx_filename') for issue in issue_items if issue.get('pptx_filename')} + issue_digest = { + 'total': len(issue_items), + 'files': len(issue_files), + 'error_count': sum(1 for issue in issue_items if issue.get('status') == 'error'), + 'warn_count': sum(1 for issue in issue_items if issue.get('status') == 'warn'), + 'latest_audit': issue_items[0].get('audited_at') if issue_items else '', + } + # PPT vision 啟用狀態 vision_status = {'enabled': False, 'ready': False, 'blockers': ['視覺狀態讀取失敗']} try: @@ -3040,6 +3103,8 @@ def ppt_audit_history(): generation_runs=generation_runs, pipeline_view=pipeline_view, vision_audit_filenames=vision_audit_filenames, + issue_items=issue_items, + issue_digest=issue_digest, error=error, ) diff --git a/templates/admin/ppt_audit_history.html b/templates/admin/ppt_audit_history.html index 9f45fb5..b86885b 100644 --- a/templates/admin/ppt_audit_history.html +++ b/templates/admin/ppt_audit_history.html @@ -235,6 +235,49 @@ {% endfor %} + {% if issue_items %} +
+
+
+
Vision Findings
+

視覺問題追蹤

+
+
+ {{ issue_digest.total }} 問題 + {{ issue_digest.files }} 檔案 + {{ issue_digest.error_count }} 高優先 + {{ issue_digest.latest_audit or '—' }} 最近審核 +
+
+
+ {% for issue in issue_items[:8] %} +
+
+ {{ issue.slide_label }} + {{ issue.category }} +
+ {{ issue.pptx_filename }} +

{{ issue.text }}

+
+ {{ issue.audited_at }} + + 回放 + +
+
+ {% endfor %} +
+
+ {% endif %}