538 lines
26 KiB
HTML
538 lines
26 KiB
HTML
{% extends "ewoooc_base.html" %}
|
||
|
||
{% block title %}PPT 視覺 QA 產線{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-ppt-audit-history.css') }}">
|
||
{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
{% import "admin/_observability_labels.html" as obs_label %}
|
||
{% set report_is_daily = report_type == 'daily' %}
|
||
|
||
<div class="container-fluid mt-3">
|
||
<section class="ppt-hero">
|
||
<div class="ppt-kicker"><i class="fas fa-search me-1"></i> PPT 視覺 QA 產線 · minicpm-v / AiderHeal / RAG 修法</div>
|
||
<h1 class="ppt-title">PPT 視覺 QA 產線</h1>
|
||
<p class="ppt-subtitle">這頁追蹤每份自動簡報是否通過視覺審核:檔案產出、minicpm-v 審核、Telegram 推送、RAG 修法建議與 AiderHeal 自動修產生器。</p>
|
||
<div class="ppt-command">
|
||
<div class="ppt-signal">
|
||
<div class="ppt-label">視覺模型</div>
|
||
<span class="ppt-value {% if vision_enabled %}status-good{% else %}status-warn{% endif %}">{{ '啟用' if vision_enabled else '停用' }}</span>
|
||
<small class="text-muted">minicpm-v + LibreOffice</small>
|
||
</div>
|
||
<div class="ppt-signal">
|
||
<div class="ppt-label">{{ report_month }} {{ selected_report_type.label }}</div>
|
||
<span class="ppt-value">{{ files|length }}</span>
|
||
<small class="text-muted">檔案數</small>
|
||
</div>
|
||
<div class="ppt-signal">
|
||
<div class="ppt-label">審核紀錄</div>
|
||
<span class="ppt-value {% if report_is_daily and audit_30d_stats and audit_30d_stats.pass_rate >= 80 %}status-good{% elif report_is_daily and audit_30d_stats and audit_30d_stats.pass_rate >= 60 %}status-warn{% elif report_is_daily and audit_30d_stats %}status-bad{% else %}status-blue{% endif %}">
|
||
{% if report_is_daily %}{{ audit_30d_stats.total if audit_30d_stats else '—' }}{% else %}—{% endif %}
|
||
</span>
|
||
<small class="text-muted">{{ report_is_daily and '僅 daily' or '切到 daily 可查看' }}</small>
|
||
</div>
|
||
<div class="ppt-signal">
|
||
<div class="ppt-label">問題數</div>
|
||
<span class="ppt-value {% if report_is_daily and audit_30d_stats and audit_30d_stats.total_issues > 0 %}status-warn{% elif report_is_daily and audit_30d_stats %}status-good{% else %}status-blue{% endif %}">
|
||
{% if report_is_daily %}{{ audit_30d_stats.total_issues if audit_30d_stats else '—' }}{% else %}—{% endif %}
|
||
</span>
|
||
<small class="text-muted">視覺問題數</small>
|
||
</div>
|
||
<div class="ppt-signal">
|
||
<div class="ppt-label">定義覆蓋</div>
|
||
<span class="ppt-value {% if auto_generation.missing_count == 0 and auto_generation.total > 0 %}status-good{% elif auto_generation.missing_count > 0 %}status-warn{% else %}status-blue{% endif %}">
|
||
{{ auto_generation.ready_count }}/{{ auto_generation.total }}
|
||
</span>
|
||
<small class="text-muted">自動簡報產線</small>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
{% if not vision_status.ready %}
|
||
<section class="ppt-diagnostic-strip">
|
||
<div>
|
||
<div class="ppt-label">視覺 QA 尚未就緒</div>
|
||
<strong>目前不是模型能力問題,而是執行環境尚未完整開啟。</strong>
|
||
</div>
|
||
<div class="ppt-diagnostic-items">
|
||
<span>PPT_VISION_ENABLED={{ vision_status.env_value }}</span>
|
||
<span>模型 {{ vision_status.model }}</span>
|
||
<span>{{ 'LibreOffice OK' if vision_status.converter_ready else '缺 LibreOffice' }}</span>
|
||
</div>
|
||
{% if vision_status.blockers %}
|
||
<small class="text-muted">{{ vision_status.blockers|join(';') }}</small>
|
||
{% endif %}
|
||
</section>
|
||
{% endif %}
|
||
{% if error %}<div class="alert alert-warning mt-3"><strong><i class="fas fa-triangle-exclamation me-1"></i></strong>{{ error }}</div>{% endif %}
|
||
|
||
<section class="ppt-toolbar">
|
||
<div class="d-flex align-items-center gap-2">
|
||
<a class="btn btn-sm btn-outline-secondary" href="{{ url_for('admin_observability.ppt_audit_history', month=prev_month_label, report_type=report_type) }}">
|
||
<i class="fas fa-angle-left me-1"></i>上個月
|
||
</a>
|
||
<span class="badge bg-light text-dark border py-2 px-3">{{ report_month }}</span>
|
||
{% if show_next_month %}
|
||
<a class="btn btn-sm btn-outline-secondary" href="{{ url_for('admin_observability.ppt_audit_history', month=next_month_label, report_type=report_type) }}">
|
||
下個月<i class="fas fa-angle-right ms-1"></i>
|
||
</a>
|
||
{% else %}
|
||
<button class="btn btn-sm btn-outline-secondary" type="button" disabled>下個月<i class="fas fa-angle-right ms-1"></i></button>
|
||
{% endif %}
|
||
</div>
|
||
<div class="ppt-type-tabs">
|
||
{% for opt in report_type_options %}
|
||
<a class="btn btn-sm {% if report_type == opt.key %}btn-primary{% else %}btn-outline-primary{% endif %} ppt-type-chip" href="{{ url_for('admin_observability.ppt_audit_history', month=report_month, report_type=opt.key) }}">
|
||
{{ opt.label }}
|
||
</a>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
{% if files %}
|
||
<section class="ppt-deck-workbench" aria-label="最近可預覽簡報">
|
||
<div class="ppt-workbench-head">
|
||
<div>
|
||
<div class="ppt-label">Preview Workbench</div>
|
||
<h2 class="ppt-panel-title">最近可預覽簡報</h2>
|
||
</div>
|
||
<small class="text-muted">最新 {{ files[:4]|length }} 份,直接線上預覽或下載原始 PPTX</small>
|
||
</div>
|
||
<div class="ppt-deck-rail">
|
||
{% for f in files[:4] %}
|
||
<article class="ppt-deck-card {% if not f.file_exists or not f.is_valid_ppt %}is-disabled{% endif %}">
|
||
<div class="ppt-deck-meta">
|
||
<span class="ppt-label">{{ selected_report_type.label }}</span>
|
||
<span>{{ f.mtime }}</span>
|
||
</div>
|
||
<h3>{{ f.name }}</h3>
|
||
<div class="ppt-deck-facts">
|
||
<span>{{ f.size_kb if f.size_kb is not none else '—' }} KB</span>
|
||
<span>{{ f.source }}</span>
|
||
{% if f.file_exists and f.is_valid_ppt %}<span class="status-good">可預覽</span>{% else %}<span class="status-bad">需回補</span>{% endif %}
|
||
</div>
|
||
<div class="ppt-file-actions">
|
||
{% if f.file_exists and f.is_valid_ppt %}
|
||
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('admin_observability.ppt_audit_file', filename=f.name) }}" target="_blank" rel="noopener">
|
||
<i class="fas fa-eye me-1"></i>線上預覽
|
||
</a>
|
||
{% endif %}
|
||
{% if f.file_exists %}
|
||
<a class="btn btn-outline-secondary btn-sm" href="{{ url_for('admin_observability.ppt_audit_file', filename=f.name, action='download') }}">
|
||
<i class="fas fa-download me-1"></i>下載
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
{% endif %}
|
||
<section class="ppt-health-board" aria-label="PPT 產線健康總覽">
|
||
<div class="ppt-health-main is-{{ pipeline_view.status }}">
|
||
<div class="ppt-label">Pipeline Health</div>
|
||
<h2>{{ pipeline_view.title }}</h2>
|
||
<p>{{ pipeline_view.message }}</p>
|
||
<div class="ppt-health-facts">
|
||
<span><strong>{{ pipeline_view.ready_count }}/{{ pipeline_view.total_count }}</strong> 定義覆蓋</span>
|
||
<span><strong>{{ pipeline_view.valid_preview_count }}</strong> 份可預覽</span>
|
||
<span><strong>{{ pipeline_view.audit_total }}</strong> 筆視覺 QA</span>
|
||
</div>
|
||
</div>
|
||
<div class="ppt-stage-grid">
|
||
{% for stage in pipeline_view.stages %}
|
||
<article class="ppt-stage-card is-{{ stage.status }}">
|
||
<div class="ppt-stage-icon"><i class="fas fa-{{ stage.icon }}" aria-hidden="true"></i></div>
|
||
<div>
|
||
<div class="ppt-label">{{ stage.label }}</div>
|
||
<strong>{{ stage.value }}</strong>
|
||
<small>{{ stage.meta }}</small>
|
||
<p>{{ stage.detail }}</p>
|
||
</div>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
<section class="ppt-action-queue" data-ppt-action-queue aria-label="PPT 工作隊列">
|
||
<div class="ppt-workbench-head">
|
||
<div>
|
||
<div class="ppt-label">Action Queue</div>
|
||
<h2 class="ppt-panel-title">接下來要處理的事</h2>
|
||
</div>
|
||
<small class="text-muted">把缺漏、預覽、視覺 QA、DB 寫入集中成工作隊列</small>
|
||
</div>
|
||
<div class="ppt-action-grid">
|
||
{% for lane in pipeline_view.action_lanes %}
|
||
<article class="ppt-action-lane is-{{ lane.status }}">
|
||
<div class="ppt-action-lane-head">
|
||
<span class="ppt-label">{{ lane.label }}</span>
|
||
<strong>{{ lane.count }}</strong>
|
||
</div>
|
||
<div class="ppt-action-list">
|
||
{% for item in lane.entries %}
|
||
<div class="ppt-action-item">
|
||
<div>
|
||
<strong>{{ item.title }}</strong>
|
||
<small>{{ item.meta }}</small>
|
||
<p>{{ item.detail }}</p>
|
||
</div>
|
||
<div class="ppt-action-foot">
|
||
<span class="ppt-run-status is-{{ lane.status }}">{{ item.status_label }}</span>
|
||
{% if item.filename and lane.key in ['preview', 'database'] %}
|
||
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('admin_observability.ppt_audit_file', filename=item.filename) }}" target="_blank" rel="noopener">
|
||
<i class="fas fa-eye me-1"></i>預覽
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<div class="ppt-action-empty">{{ lane.empty_text }}</div>
|
||
{% endfor %}
|
||
</div>
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
</section>
|
||
<section class="ppt-panel mt-3"
|
||
data-ppt-auto-generation
|
||
data-auto-start="{{ 'true' if auto_generation.can_auto_start else 'false' }}"
|
||
data-report-types="{{ auto_generation_missing_report_types | join(',') }}">
|
||
<div class="ppt-panel-head">
|
||
<div>
|
||
<div class="ppt-label">Production Command Center</div>
|
||
<h2 class="ppt-panel-title">簡報產線控制台</h2>
|
||
</div>
|
||
<button class="btn btn-sm btn-outline-primary" type="button" data-ppt-generate-missing {% if not auto_generation.enabled or auto_generation.missing_count == 0 %}disabled{% endif %}>
|
||
<i class="fas fa-wand-magic-sparkles me-1" aria-hidden="true"></i>補齊缺漏簡報
|
||
</button>
|
||
</div>
|
||
<div class="ppt-panel-body">
|
||
<div class="ppt-pipeline-layout">
|
||
<div class="ppt-cadence-rail" aria-label="PPT 定期產出節奏">
|
||
{% for cadence in auto_generation.cadences %}
|
||
<article class="ppt-cadence-tile is-{{ cadence.status }}">
|
||
<div class="ppt-cadence-top">
|
||
<span class="ppt-label">{{ cadence.label }}</span>
|
||
<strong>{{ cadence.schedule_text }}</strong>
|
||
</div>
|
||
<div class="ppt-cadence-meter" aria-hidden="true">
|
||
<span style="width: {{ cadence.progress_pct }}%"></span>
|
||
</div>
|
||
<div class="ppt-cadence-meta">
|
||
<span>{{ cadence.status_label }}</span>
|
||
<span>{{ cadence.coverage_text }}</span>
|
||
</div>
|
||
<p class="ppt-cadence-gate">{{ cadence.description }}</p>
|
||
{% if cadence.missing_count > 0 %}
|
||
<small class="text-muted">待補:{{ cadence.missing_report_labels[:3]|join('、') }}{% if cadence.missing_report_labels|length > 3 %} 等 {{ cadence.missing_report_labels|length }} 類{% endif %}</small>
|
||
{% else %}
|
||
<small class="status-good">{{ cadence.status_hint }}</small>
|
||
{% endif %}
|
||
</article>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="ppt-coverage-board">
|
||
<div class="ppt-coverage-score">
|
||
<div>
|
||
<span class="ppt-label">定義簡報</span>
|
||
<strong>{{ auto_generation.ready_count }}/{{ auto_generation.total }}</strong>
|
||
<small class="text-muted">目前缺漏 {{ auto_generation.missing_count }} 類</small>
|
||
</div>
|
||
<div>
|
||
<span class="ppt-label">DB 紀錄</span>
|
||
<strong>{{ generation_runs|length }}</strong>
|
||
<small class="text-muted">本月最近寫入</small>
|
||
</div>
|
||
<div>
|
||
<span class="ppt-label">線上預覽</span>
|
||
<strong>PDF</strong>
|
||
<small class="text-muted">PPTX 轉檔快取</small>
|
||
</div>
|
||
</div>
|
||
<div class="ppt-coverage-list" aria-label="定義簡報覆蓋明細">
|
||
{% for item in auto_generation_items %}
|
||
<div class="ppt-coverage-row">
|
||
<div>
|
||
<strong>{{ item.label }}</strong>
|
||
<small>{{ item.target_label or '最新資料' }}{% if item.latest_generated_at %} · {{ item.latest_generated_at }}{% endif %}</small>
|
||
<small>{{ item.status_hint }}</small>
|
||
</div>
|
||
<span class="ppt-run-status is-{{ item.status }}">
|
||
{{ item.status_label }}
|
||
</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="ppt-auto-status small text-muted mt-3" data-ppt-auto-status>
|
||
{% if auto_generation.enabled %}
|
||
{{ auto_generation.cadence_summary }} 會定期產出並寫入 DB;目前缺漏 {{ auto_generation.missing_count }} 類。
|
||
{% else %}
|
||
PPT_AUTO_GENERATION_ENABLED=false,已停用自動補齊。
|
||
{% endif %}
|
||
</div>
|
||
<div class="ppt-run-log mt-3">
|
||
<div class="ppt-run-log-head">
|
||
<div>
|
||
<div class="ppt-label">DB 產出紀錄</div>
|
||
<h3>最近寫入 ppt_generation_runs</h3>
|
||
</div>
|
||
<small class="text-muted">產出檔案、參數、狀態會同步保留在資料庫</small>
|
||
</div>
|
||
{% if generation_runs %}
|
||
<div class="ppt-run-list">
|
||
{% for run in generation_runs %}
|
||
<div class="ppt-run-row">
|
||
<span class="ppt-run-kind">{{ run.schedule_label }}</span>
|
||
<strong>{{ run.report_label }}</strong>
|
||
<span class="ppt-run-target">{{ run.target_label or '最新資料' }}</span>
|
||
<span class="ppt-run-status is-{{ run.status }}">{{ run.status_label }}</span>
|
||
<small class="text-muted">{{ run.started_at }}{% if run.finished_at %} → {{ run.finished_at }}{% endif %}</small>
|
||
{% if run.file_name %}
|
||
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('admin_observability.ppt_audit_file', filename=run.file_name) }}" target="_blank" rel="noopener">
|
||
<i class="fas fa-eye me-1"></i>預覽
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="ppt-empty ppt-run-empty">
|
||
目前查無本月 DB 產出紀錄;下一次自動排程或手動補齊後,會寫入 ppt_generation_runs 並顯示在這裡。
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="ppt-grid">
|
||
<div class="ppt-stack">
|
||
<article class="ppt-table-shell">
|
||
<div class="ppt-table-title">
|
||
<div>
|
||
<div class="ppt-label">審核歷史</div>
|
||
<h3>視覺審核歷史({{ report_month }})</h3>
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
{% if report_is_daily %}
|
||
<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>
|
||
</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">通過</span>
|
||
{% elif r.audit_status == 'failed' %}<span class="badge bg-warning">有問題</span>
|
||
{% elif r.audit_status == 'error' %}<span class="badge bg-danger">錯誤</span>
|
||
{% elif r.audit_status == 'skipped' %}<span class="badge bg-secondary">跳過</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"
|
||
type="button"
|
||
data-ppt-aider-heal
|
||
data-ppt-filename="{{ r.pptx_filename }}"
|
||
data-ppt-error="{{ r.error_msg or '' }}">
|
||
<i class="fas fa-wrench me-1"></i>AiderHeal
|
||
</button>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="8" class="text-center text-muted">目前無 daily 審核歷史;請確認 {{ report_month }} 是否已完成 22:00 排程。</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
{% else %}
|
||
<div class="p-3">
|
||
<div class="ppt-empty p-3">
|
||
<div class="mb-2"><span class="badge bg-info text-dark">非每日型資料</span></div>
|
||
<p class="mb-2"><strong>只有「每日日報」會進入視覺審核流程。</strong></p>
|
||
<p class="mb-2">目前此頁只顯示每日以外的簡報檔案;若要追蹤視覺結果,請切到「每日日報」。</p>
|
||
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('admin_observability.ppt_audit_history', month=report_month, report_type='daily') }}">
|
||
<i class="fas fa-calendar-day me-1"></i>切到每日日報
|
||
</a>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</article>
|
||
<article class="ppt-table-shell">
|
||
<div class="ppt-table-title">
|
||
<div>
|
||
<div class="ppt-label">已產檔案</div>
|
||
<h3>{{ report_month }} {{ selected_report_type.label }}</h3>
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm mb-0">
|
||
<thead class="table-light">
|
||
<tr><th>檔名</th><th class="text-end">KB</th><th>修改時間</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 if f.size_kb is not none else '—' }}</td>
|
||
<td><small>{{ f.mtime }}</small></td>
|
||
<td>
|
||
{% if f.source == 'database' %}
|
||
{% if f.file_exists %}
|
||
{% if f.is_valid_ppt %}
|
||
<span class="text-success">資料庫快取 + 檔案可預覽</span>
|
||
{% else %}
|
||
<span class="status-bad">資料庫快取 + 檔案損毀,建議重跑</span>
|
||
{% endif %}
|
||
{% else %}
|
||
<span class="text-muted">資料庫快取(檔案未落盤)</span>
|
||
{% endif %}
|
||
{% elif f.source == 'both' %}
|
||
{% if f.is_valid_ppt %}
|
||
<span class="text-success">檔案 + 資料庫</span>
|
||
{% else %}
|
||
<span class="status-bad">檔案 + 資料庫,檔案損毀</span>
|
||
{% endif %}
|
||
{% else %}
|
||
{% if f.is_valid_ppt %}
|
||
<span class="text-success">22:00 掃描落盤,可預覽</span>
|
||
{% else %}
|
||
<span class="status-bad">22:00 掃描落盤,檔案損毀</span>
|
||
{% endif %}
|
||
{% endif %}
|
||
{% if f.file_error %}
|
||
<div><small class="text-muted">{{ f.file_error }}</small></div>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<div class="ppt-file-actions">
|
||
{% if f.file_exists %}
|
||
{% if f.is_valid_ppt %}
|
||
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('admin_observability.ppt_audit_file', filename=f.name) }}" target="_blank" rel="noopener">
|
||
<i class="fas fa-eye me-1"></i>線上預覽
|
||
</a>
|
||
{% else %}
|
||
<span class="small status-bad">檔案不可預覽</span>
|
||
{% endif %}
|
||
<a class="btn btn-outline-secondary btn-sm" href="{{ url_for('admin_observability.ppt_audit_file', filename=f.name, action='download') }}">
|
||
<i class="fas fa-download me-1"></i>下載
|
||
</a>
|
||
{% else %}
|
||
<span class="small text-muted">本機暫無檔案,請先至來源回補</span>
|
||
{% endif %}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr><td colspan="5" class="text-center text-muted">本月無 {{ selected_report_type.label }} 簡報</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
<aside class="ppt-stack">
|
||
{% if report_is_daily %}
|
||
{% if audit_30d_stats and audit_30d_stats.total > 0 %}
|
||
<article class="ppt-panel">
|
||
<div class="ppt-panel-head">
|
||
<div><div class="ppt-label">{{ report_month }} 審核分布</div><h2 class="ppt-panel-title">審核結果分布</h2></div>
|
||
</div>
|
||
<div class="ppt-panel-body">
|
||
<div class="obs-chart-frame"><canvas id="pptAuditPieChart"></canvas></div>
|
||
<div class="ppt-mini-grid mt-3">
|
||
<div class="ppt-mini"><span class="ppt-label">通過</span><strong class="status-good">{{ audit_30d_stats.passed }}</strong></div>
|
||
<div class="ppt-mini"><span class="ppt-label">失敗</span><strong class="{% if audit_30d_stats.failed > 0 %}status-warn{% endif %}">{{ audit_30d_stats.failed }}</strong></div>
|
||
<div class="ppt-mini"><span class="ppt-label">錯誤</span><strong class="{% if audit_30d_stats.error > 0 %}status-bad{% endif %}">{{ audit_30d_stats.error }}</strong></div>
|
||
<div class="ppt-mini"><span class="ppt-label">信心分</span><strong>{{ "%.2f"|format(audit_30d_stats.avg_confidence) }}</strong></div>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
{% endif %}
|
||
{% if top_failure_files %}
|
||
<article class="ppt-panel">
|
||
<div class="ppt-panel-head">
|
||
<div><div class="ppt-label">失敗熱點</div><h2 class="ppt-panel-title">Top 失敗檔案</h2></div>
|
||
</div>
|
||
<div class="ppt-panel-body">
|
||
{% for f in top_failure_files %}<div class="fix-card"><code>{{ f.filename }}</code><div class="d-flex justify-content-between mt-1"><small class="text-muted">{{ f.last_audit }}</small><span class="badge bg-warning">{{ f.attempts }} 次</span></div><small class="text-muted">問題 {{ f.total_issues }}</small></div>{% endfor %}
|
||
</div>
|
||
</article>
|
||
{% endif %}
|
||
{% if not (audit_30d_stats and audit_30d_stats.total > 0) %}
|
||
<article class="ppt-panel">
|
||
<div class="ppt-panel-head">
|
||
<div><div class="ppt-label">審核摘要</div><h2 class="ppt-panel-title">本月審核狀態</h2></div>
|
||
</div>
|
||
<div class="ppt-panel-body">
|
||
<div class="ppt-empty p-3">
|
||
<p class="mb-1"><strong>尚未形成 daily 審核統計。</strong></p>
|
||
<p class="mb-0">排程完成後,這裡會顯示本月通過率、失敗檔案與修復建議。</p>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
{% endif %}
|
||
{% endif %}
|
||
</aside>
|
||
</section>
|
||
|
||
{% if rag_fixes %}
|
||
<section class="ppt-panel mt-3">
|
||
<div class="ppt-panel-head">
|
||
<div><div class="ppt-label">RAG 修法建議</div><h2 class="ppt-panel-title">RAG 自動修法建議</h2></div>
|
||
</div>
|
||
<div class="ppt-panel-body">
|
||
{% for fix in rag_fixes %}
|
||
<div class="fix-card">
|
||
<strong><code>{{ fix.pptx_filename }}</code></strong><small class="text-muted ms-2">{{ fix.audited_at }}</small>
|
||
<div class="small status-bad 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 me-1">{{ obs_label.insight(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>
|
||
</section>
|
||
{% endif %}
|
||
{% if not vision_enabled %}
|
||
<div class="alert alert-info mt-3">
|
||
<strong>為什麼這頁空?</strong>
|
||
<ul class="mb-0 small mt-2">
|
||
<li>PPT_VISION_ENABLED=false</li>
|
||
<li>188 主機需安裝 LibreOffice</li>
|
||
<li>需 Ollama 拉取 minicpm-v 模型</li>
|
||
<li>啟用後每日 22:00 排程寫入 ppt_audit_results</li>
|
||
</ul>
|
||
</div>
|
||
{% elif files|length == 0 %}
|
||
<div class="alert alert-warning mt-3">
|
||
<strong>本月無資料</strong>
|
||
<ul class="mb-0 small mt-2">
|
||
<li>若為「每日日報」,檢查 {{ report_month }} 是否由 188 排程成功落盤。</li>
|
||
<li>若為「週報 / 月報 / 策略 / 競品 / 促銷」,請確認 Telegram 任務是否有對應產出。</li>
|
||
<li>可回到上方月份切換器看先前月份,或調整報表類型重新查詢。</li>
|
||
</ul>
|
||
</div>
|
||
{% endif %}
|
||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — PPT 視覺 QA 產線</small></p>
|
||
</div>
|
||
|
||
<template id="obs-ppt-audit-data">{{ audit_30d_stats | default({}) | tojson }}</template>
|
||
<script src="{{ url_for('static', filename='js/analysis-chart-theme.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/observability-charts.js') }}"></script>
|
||
{% endblock %}
|