Files
ewoooc/templates/admin/observability_overview.html
ogt ea74a0c5d4
Some checks failed
CD Pipeline / deploy (push) Has been cancelled
style(obs): compact observability workbench copy
2026-07-02 19:15:56 +08:00

678 lines
25 KiB
HTML
Raw Permalink 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 %}AI 觀測戰情室{% endblock %}
{% block ewooo_content %}
<style>
.obs-war-room {
--obs-ink: var(--momo-ink, #302720);
--obs-muted: var(--momo-muted, #8b8077);
--obs-paper: var(--momo-paper, #fff8ef);
--obs-card: var(--momo-bg-surface, #faf7f0);
--obs-line: rgba(86, 64, 48, 0.14);
--obs-accent: var(--momo-accent, #c96442);
--obs-accent-soft: rgba(201, 100, 66, 0.12);
--obs-green: #4f8a5b;
--obs-amber: #b8792f;
--obs-red: #b94b45;
--obs-blue: #4f6f8f;
color: var(--obs-ink);
font-family: var(--momo-font-family, "Inter", "Noto Sans TC", system-ui, sans-serif);
}
.obs-hero {
position: relative;
overflow: hidden;
border: 1px solid var(--obs-line);
border-radius: var(--momo-radius-lg, 8px);
padding: clamp(1.05rem, 2.2vw, 1.65rem);
background-color: var(--momo-bg-surface, #faf7f0);
background-image: var(--obs-dot);
background-size: 13px 13px;
box-shadow: var(--momo-shadow-md, 0 2px 8px rgba(42, 37, 32, 0.06));
}
.obs-hero::after {
content: "";
position: absolute;
inset: auto 1rem 1rem auto;
width: 8.5rem;
height: 8.5rem;
border: 1px solid color-mix(in srgb, var(--obs-accent) 22%, transparent);
background-image: var(--obs-dot);
background-size: 10px 10px;
opacity: 0.46;
pointer-events: none;
}
.obs-kicker {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.36rem 0.58rem;
border: 1px solid rgba(201, 100, 66, 0.22);
border-radius: var(--momo-radius-lg, 8px);
background: var(--momo-bg-elevated, #fdfaf3);
color: var(--obs-accent);
font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: var(--momo-text-label, 0.6875rem);
font-weight: 800;
letter-spacing: 0.06em;
}
.obs-kicker-date {
color: var(--obs-muted);
}
.obs-title {
margin: 0.7rem 0 0.35rem;
max-width: 820px;
font-family: var(--momo-font-display, "JetBrains Mono", "Noto Sans TC", ui-monospace, monospace);
font-size: var(--obs-title-size, 1.8rem);
line-height: 1.18;
letter-spacing: 0;
font-weight: 800;
}
.obs-lede {
display: none;
}
.obs-mode {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
margin-top: 0.7rem;
}
.obs-mode span {
display: inline-flex;
align-items: center;
min-height: 24px;
border: 1px solid rgba(86, 64, 48, 0.12);
border-radius: 999px;
background: var(--momo-bg-elevated, #fdfaf3);
color: var(--obs-muted);
font-size: 0.68rem;
font-weight: 900;
padding: 0.24rem 0.55rem;
}
.obs-command-strip,
.obs-automation-strip {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.75rem;
margin-top: 1.45rem;
}
.obs-signal {
position: relative;
z-index: 1;
min-height: 78px;
padding: 0.92rem;
border: 1px solid var(--obs-line);
border-radius: var(--momo-radius-lg, 8px);
background: var(--momo-bg-surface, #faf7f0);
backdrop-filter: blur(8px);
}
.obs-signal-label,
.obs-section-eyebrow,
.obs-route-code {
color: color-mix(in srgb, var(--obs-accent) 76%, var(--obs-muted));
font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: var(--momo-text-label, 0.6875rem);
font-weight: 800;
letter-spacing: 0.06em;
}
.obs-signal-value {
margin-top: 0.45rem;
font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: var(--obs-value-size, 1.85rem);
font-weight: 850;
letter-spacing: 0;
}
.obs-signal-note {
display: none;
}
.obs-grid {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.65fr);
gap: 1rem;
margin-top: 1rem;
}
.obs-panel {
border: 1px solid var(--obs-line);
border-radius: var(--momo-radius-lg, 8px);
background: var(--obs-card);
box-shadow: var(--momo-shadow-md, 0 2px 8px rgba(42, 37, 32, 0.06));
}
.obs-panel-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
padding: 1.1rem 1.15rem 0.25rem;
}
.obs-panel-title {
margin: 0.18rem 0 0;
font-size: var(--momo-text-title, 1.0625rem);
font-weight: 800;
letter-spacing: 0;
}
.obs-panel-body {
padding: 1rem 1.15rem 1.15rem;
}
.obs-host-list {
display: grid;
gap: 0.75rem;
}
.obs-host-card {
display: grid;
grid-template-columns: minmax(0, 1fr) 150px;
gap: 0.85rem;
align-items: center;
padding: 0.9rem;
border: 1px solid var(--obs-line);
border-radius: var(--momo-radius-lg, 8px);
background: var(--momo-bg-elevated, #fdfaf3);
}
.obs-host-card.is-good { border-left: 4px solid var(--obs-green); }
.obs-host-card.is-warn { border-left: 4px solid var(--obs-amber); }
.obs-host-card.is-bad { border-left: 4px solid var(--obs-red); }
.obs-host-top {
display: flex;
justify-content: space-between;
gap: 0.7rem;
align-items: baseline;
}
.obs-host-name {
font-weight: 800;
}
.obs-host-pct {
font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: 1.35rem;
font-weight: 850;
letter-spacing: 0;
}
.obs-host-meta,
.obs-microcopy {
color: var(--obs-muted);
font-size: 0.82rem;
}
.obs-host-meta,
.obs-footer-source {
display: none;
}
.obs-sparkline {
height: 54px;
}
.obs-alert-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 0.48rem;
}
.obs-alert-table th {
color: var(--obs-muted);
font-size: 0.7rem;
letter-spacing: 0.08em;
text-transform: uppercase;
font-weight: 700;
}
.obs-alert-table td {
padding: 0.62rem;
background: rgba(255, 255, 255, 0.68);
border-top: 1px solid var(--obs-line);
border-bottom: 1px solid var(--obs-line);
}
.obs-alert-table td:first-child {
border-left: 1px solid var(--obs-line);
border-radius: 14px 0 0 14px;
}
.obs-alert-table td:last-child {
border-right: 1px solid var(--obs-line);
border-radius: 0 14px 14px 0;
text-align: right;
}
.obs-pill {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.32rem 0.55rem;
border-radius: var(--momo-radius-lg, 8px);
background: var(--obs-accent-soft);
color: var(--obs-accent);
font-size: 0.76rem;
font-weight: 750;
}
.obs-stack {
display: grid;
gap: 1rem;
}
.obs-metric-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.7rem;
}
.obs-mini {
padding: 0.85rem;
border: 1px solid var(--obs-line);
border-radius: var(--momo-radius-lg, 8px);
background: var(--momo-bg-elevated, #fdfaf3);
}
.obs-mini strong {
display: block;
margin-top: 0.25rem;
font-family: var(--momo-font-family-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: 1.45rem;
letter-spacing: 0;
}
.obs-status-good { color: var(--obs-green); }
.obs-status-warn { color: var(--obs-amber); }
.obs-status-bad { color: var(--obs-red); }
.obs-status-blue { color: var(--obs-blue); }
.obs-link-button {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.58rem 0.78rem;
border: 1px solid rgba(201, 100, 66, 0.25);
border-radius: var(--momo-radius-lg, 8px);
background: var(--momo-bg-elevated, #fdfaf3);
color: var(--obs-accent);
text-decoration: none;
font-size: 0.82rem;
font-weight: 750;
transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
.obs-link-button:hover {
transform: translateY(-1px);
background: rgba(255, 252, 247, 0.96);
box-shadow: 0 10px 24px rgba(201, 100, 66, 0.14);
color: var(--obs-accent);
}
.obs-route-map {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.9rem;
margin-top: 1rem;
}
.obs-route-group {
border: 1px solid var(--obs-line);
border-radius: var(--momo-radius-lg, 8px);
padding: 1rem;
background-color: var(--momo-bg-surface, #faf7f0);
background-image: var(--obs-dot);
background-size: 13px 13px;
}
.obs-route-group h3 {
margin: 0.25rem 0 0.85rem;
font-size: var(--momo-text-title, 1.0625rem);
font-weight: 850;
letter-spacing: 0;
}
.obs-route-list {
display: grid;
gap: 0.55rem;
}
.obs-route-card {
display: grid;
grid-template-columns: 2rem minmax(0, 1fr) auto;
gap: 0.72rem;
align-items: center;
padding: 0.72rem;
border: 1px solid transparent;
border-radius: var(--momo-radius-lg, 8px);
color: var(--obs-ink);
text-decoration: none;
background: var(--momo-bg-elevated, #fdfaf3);
transition: border-color 160ms ease, transform 160ms ease, background 160ms ease;
}
.obs-route-card:hover {
border-color: rgba(201, 100, 66, 0.28);
background: rgba(255, 252, 247, 0.96);
color: var(--obs-ink);
transform: translateX(3px);
}
.obs-route-icon {
display: inline-grid;
place-items: center;
width: 2rem;
height: 2rem;
border-radius: 6px;
background: var(--obs-accent-soft);
color: var(--obs-accent);
}
.obs-route-title {
display: block;
font-weight: 800;
}
.obs-route-desc {
display: none;
}
.obs-empty {
padding: 1rem;
border: 1px dashed rgba(184, 121, 47, 0.38);
border-radius: 18px;
background: rgba(184, 121, 47, 0.08);
color: var(--obs-amber);
}
@media (max-width: 1180px) {
.obs-command-strip,
.obs-automation-strip,
.obs-route-map {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.obs-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 720px) {
.obs-command-strip,
.obs-automation-strip,
.obs-route-map,
.obs-metric-grid {
grid-template-columns: 1fr;
}
.obs-host-card {
grid-template-columns: 1fr;
}
}
</style>
{% import "admin/_observability_labels.html" as obs_label %}
{% set ai = summary.ai_calls if summary.ai_calls else none %}
{% set host_count = summary.hosts|length if summary.hosts else 0 %}
{% set host_bad = namespace(value=0) %}
{% if summary.hosts %}
{% for h in summary.hosts %}
{% if h.uptime_pct < 90 %}{% set host_bad.value = host_bad.value + 1 %}{% endif %}
{% endfor %}
{% endif %}
{% set risk_count = (summary.budget_alerts|length if summary.budget_alerts else 0) + host_bad.value + (1 if ai and ai.error_rate >= 15 else 0) + (1 if summary.episodes and summary.episodes.pending > 0 else 0) %}
<div class="obs-war-room">
<section class="obs-hero" data-density-guardrail="compact-observability-workbench">
<span class="obs-kicker"><i class="fas fa-satellite-dish"></i> 01 指揮總覽 <span class="obs-kicker-date">· {{ today }}</span></span>
<h1 class="obs-title">AI 觀測戰情室</h1>
<div class="obs-mode" aria-label="AI 觀測模式">
<span>AI 觀測</span>
<span>風險優先</span>
<span>下一步</span>
</div>
<p class="obs-lede">
私有 AI 中樞的第一入口模型主機、AI 呼叫、知識學習、工具編排、自癒、預算與 PPT 視覺審核收斂到同一張工作台。所有數字只讀正式資料來源;缺資料時呈現可診斷空狀態。
</p>
<div class="obs-automation-strip" aria-label="AI 自動化狀態" data-benchmark-guardrail="golden-signals">
<div class="obs-signal" data-signal="automated-landing">
<div class="obs-signal-label">已自動落地</div>
<div class="obs-signal-value obs-status-good">已接入</div>
<div class="obs-signal-note">主機探測、AI 呼叫、自癒、預算與品質訊號已進總覽。</div>
</div>
<div class="obs-signal" data-signal="verified">
<div class="obs-signal-label">已驗證</div>
<div class="obs-signal-value {% if host_bad.value == 0 and (not ai or ai.error_rate < 15) %}obs-status-good{% else %}obs-status-warn{% endif %}">
{% if host_bad.value == 0 and (not ai or ai.error_rate < 15) %}正常{% else %}需追蹤{% endif %}
</div>
<div class="obs-signal-note">以正式資料回讀主機健康、錯誤率、成本與知識命中。</div>
</div>
<div class="obs-signal" data-signal="change-state">
<div class="obs-signal-label">異動狀態</div>
<div class="obs-signal-value {% if risk_count == 0 %}obs-status-good{% elif risk_count <= 2 %}obs-status-warn{% else %}obs-status-bad{% endif %}">{{ risk_count }}</div>
<div class="obs-signal-note">即時風險分層;詳細事件保留在各子頁。</div>
</div>
<div class="obs-signal" data-signal="next-machine-action">
<div class="obs-signal-label">下一步</div>
<div class="obs-signal-value">{% if risk_count == 0 %}維持監控{% else %}先處理風險{% endif %}</div>
<div class="obs-signal-note">{% if risk_count == 0 %}持續觀察健康、成本與自癒訊號。{% else %}依風險數進入主機、預算、呼叫或知識頁。{% endif %}</div>
</div>
</div>
<div class="obs-command-strip">
<div class="obs-signal">
<div class="obs-signal-label">即時風險</div>
<div class="obs-signal-value {% if risk_count == 0 %}obs-status-good{% elif risk_count <= 2 %}obs-status-warn{% else %}obs-status-bad{% endif %}">{{ risk_count }}</div>
<div class="obs-signal-note">主機、預算、錯誤率、待審核的即時風險數</div>
</div>
<div class="obs-signal">
<div class="obs-signal-label">24 小時呼叫</div>
<div class="obs-signal-value">{{ "{:,}".format(ai.total) if ai else '—' }}</div>
<div class="obs-signal-note">用量:{{ "{:,}".format(ai.tokens) if ai else '—' }}</div>
</div>
<div class="obs-signal">
<div class="obs-signal-label">成本水位</div>
<div class="obs-signal-value">${{ "%.2f"|format(ai.cost_24h) if ai else '0.00' }}</div>
<div class="obs-signal-note">當月累計 ${{ "%.2f"|format(summary.month_cost|default(0)) }}</div>
</div>
<div class="obs-signal">
<div class="obs-signal-label">知識命中率</div>
<div class="obs-signal-value obs-status-blue">{{ "%.1f"|format(ai.rag_rate) if ai else '—' }}{% if ai %}%{% endif %}</div>
<div class="obs-signal-note">快取命中 {{ "%.0f"|format(ai.cache_rate) if ai else '—' }}{% if ai %}%{% endif %}</div>
</div>
</div>
</section>
<section class="obs-grid">
<div class="obs-stack">
<article class="obs-panel">
<div class="obs-panel-head">
<div>
<div class="obs-section-eyebrow">02 主機級聯</div>
<h2 class="obs-panel-title">三主機生命線</h2>
</div>
<a class="obs-link-button" href="/observability/host_health"><i class="fas fa-arrow-right"></i>主機健康</a>
</div>
<div class="obs-panel-body">
{% if summary.hosts %}
<div class="obs-host-list">
{% for h in summary.hosts %}
<div class="obs-host-card {% if h.uptime_pct >= 99 %}is-good{% elif h.uptime_pct >= 90 %}is-warn{% else %}is-bad{% endif %}">
<div>
<div class="obs-host-top">
<span class="obs-host-name">{{ h.label }}</span>
<span class="obs-host-pct {% if h.uptime_pct >= 99 %}obs-status-good{% elif h.uptime_pct >= 90 %}obs-status-warn{% else %}obs-status-bad{% endif %}">{{ "%.1f"|format(h.uptime_pct) }}%</span>
</div>
<div class="obs-host-meta">{{ h.up }}/{{ h.total }} 次探測 · 平均 {{ h.avg_ms }} ms · 24 小時視窗</div>
</div>
<div class="obs-sparkline">
{% if host_sparkline.get(h.label) %}
<canvas data-host-sparkline="{{ h.label }}"></canvas>
{% else %}
<div class="obs-microcopy">尚無趨勢資料</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="obs-empty">
<i class="fas fa-triangle-exclamation me-1"></i>
主機探測尚無資料。請確認第 029 號資料遷移與排程探測任務是否已啟動。
</div>
{% endif %}
</div>
</article>
{% if summary.budget_alerts %}
<article class="obs-panel">
<div class="obs-panel-head">
<div>
<div class="obs-section-eyebrow">預算守門</div>
<h2 class="obs-panel-title">預算告警</h2>
</div>
<a class="obs-link-button" href="/observability/budget"><i class="fas fa-bolt"></i>處理預算</a>
</div>
<div class="obs-panel-body">
<table class="obs-alert-table">
<thead>
<tr><th>週期</th><th>供應商</th><th>已花費</th><th>預算</th><th>使用率</th></tr>
</thead>
<tbody>
{% for b in summary.budget_alerts %}
<tr>
<td><span class="obs-pill">{{ b.period }}</span></td>
<td><code>{{ obs_label.provider(b.provider) }}</code></td>
<td>${{ "%.2f"|format(b.spent) }}</td>
<td>${{ "%.2f"|format(b.budget) }}</td>
<td><strong class="{% if b.ratio >= 1.0 %}obs-status-bad{% else %}obs-status-warn{% endif %}">{{ "%.0f"|format(b.ratio * 100) }}%</strong></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</article>
{% endif %}
</div>
<aside class="obs-stack">
<article class="obs-panel">
<div class="obs-panel-head">
<div>
<div class="obs-section-eyebrow">AI 呼叫</div>
<h2 class="obs-panel-title">呼叫品質</h2>
</div>
<a class="obs-link-button" href="/observability/ai_calls"><i class="fas fa-chart-bar"></i>詳情</a>
</div>
<div class="obs-panel-body">
{% if ai %}
<div class="obs-metric-grid">
<div class="obs-mini"><span class="obs-microcopy">錯誤率</span><strong class="{% if ai.error_rate >= 15 %}obs-status-bad{% elif ai.error_rate >= 5 %}obs-status-warn{% else %}obs-status-good{% endif %}">{{ "%.1f"|format(ai.error_rate) }}%</strong></div>
<div class="obs-mini"><span class="obs-microcopy">失敗</span><strong>{{ ai.errors }}</strong></div>
<div class="obs-mini"><span class="obs-microcopy">知識命中</span><strong class="obs-status-blue">{{ ai.rag_hits }}</strong></div>
<div class="obs-mini"><span class="obs-microcopy">快取命中</span><strong>{{ ai.cache_hits }}</strong></div>
</div>
{% else %}
<div class="obs-empty">ai_calls 無 24h 資料。</div>
{% endif %}
</div>
</article>
<article class="obs-panel">
<div class="obs-panel-head">
<div>
<div class="obs-section-eyebrow">學習閉環</div>
<h2 class="obs-panel-title">知識學習閘</h2>
</div>
<a class="obs-link-button" href="/observability/promotion_review"><i class="fas fa-brain"></i>審核</a>
</div>
<div class="obs-panel-body">
{% if summary.episodes %}
<div class="obs-metric-grid">
<div class="obs-mini"><span class="obs-microcopy">待審核</span><strong class="{% if summary.episodes.pending > 0 %}obs-status-warn{% else %}obs-status-good{% endif %}">{{ summary.episodes.pending }}</strong></div>
<div class="obs-mini"><span class="obs-microcopy">30 日樣本</span><strong>{{ summary.episodes.total_30d }}</strong></div>
<div class="obs-mini"><span class="obs-microcopy">已晉升</span><strong class="obs-status-good">{{ summary.episodes.approved_30d }}</strong></div>
<div class="obs-mini"><span class="obs-microcopy">晉升率</span><strong>{{ "%.0f"|format(summary.episodes.approval_rate) }}%</strong></div>
</div>
{% else %}
<div class="obs-empty">learning_episodes 無 30d 資料。</div>
{% endif %}
</div>
</article>
<article class="obs-panel">
<div class="obs-panel-head">
<div>
<div class="obs-section-eyebrow">自動化後勤</div>
<h2 class="obs-panel-title">自動化後勤</h2>
</div>
</div>
<div class="obs-panel-body">
<div class="obs-metric-grid">
<div class="obs-mini"><span class="obs-microcopy">AIOps 未解</span><strong class="{% if summary.aiops and summary.aiops.incidents_open > 0 %}obs-status-bad{% else %}obs-status-good{% endif %}">{{ summary.aiops.incidents_open if summary.aiops else '—' }}</strong></div>
<div class="obs-mini"><span class="obs-microcopy">自癒成功率</span><strong>{{ "%.0f"|format(summary.aiops.heal_rate) if summary.aiops else '—' }}{% if summary.aiops %}%{% endif %}</strong></div>
<div class="obs-mini"><span class="obs-microcopy">工具呼叫</span><strong>{{ "{:,}".format(summary.mcp.total) if summary.mcp else '—' }}</strong></div>
<div class="obs-mini"><span class="obs-microcopy">PPT 通過率</span><strong>{{ "%.0f"|format(summary.ppt.pass_rate) if summary.ppt and summary.ppt.total > 0 else '—' }}{% if summary.ppt and summary.ppt.total > 0 %}%{% endif %}</strong></div>
</div>
</div>
</article>
</aside>
</section>
<section class="obs-route-map" aria-label="AI 觀測台子頁入口">
<div class="obs-route-group">
<div class="obs-section-eyebrow">03 戰情指揮</div>
<h3>戰情室</h3>
<div class="obs-route-list">
<a class="obs-route-card" href="/observability/agent_orchestration"><span class="obs-route-icon"><i class="fas fa-network-wired"></i></span><span><span class="obs-route-title">AI 分工矩陣</span><span class="obs-route-desc">四類 AI 分工、模型、工具與知識命中。</span></span><span class="obs-route-code">02</span></a>
<a class="obs-route-card" href="/observability/business_intel"><span class="obs-route-icon"><i class="fas fa-briefcase"></i></span><span><span class="obs-route-title">商業面 × AI</span><span class="obs-route-desc">AI 決策是否真的轉成生意動作。</span></span><span class="obs-route-code">03</span></a>
</div>
</div>
<div class="obs-route-group">
<div class="obs-section-eyebrow">系統成本</div>
<h3>系統與成本</h3>
<div class="obs-route-list">
<a class="obs-route-card" href="/observability/host_health"><span class="obs-route-icon"><i class="fas fa-heartbeat"></i></span><span><span class="obs-route-title">主機健康</span><span class="obs-route-desc">模型主機、工具服務與自癒。</span></span><span class="obs-route-code">04</span></a>
<a class="obs-route-card" href="/observability/ai_calls"><span class="obs-route-icon"><i class="fas fa-chart-bar"></i></span><span><span class="obs-route-title">AI 呼叫</span><span class="obs-route-desc">用量、成本、錯誤、知識與工具矩陣。</span></span><span class="obs-route-code">05</span></a>
<a class="obs-route-card" href="/observability/budget"><span class="obs-route-icon"><i class="fas fa-wallet"></i></span><span><span class="obs-route-title">預算控管</span><span class="obs-route-desc">供應商成本與強制節流。</span></span><span class="obs-route-code">06</span></a>
</div>
</div>
<div class="obs-route-group">
<div class="obs-section-eyebrow">知識品質</div>
<h3>知識與品質</h3>
<div class="obs-route-list">
<a class="obs-route-card" href="/observability/promotion_review"><span class="obs-route-icon"><i class="fas fa-brain"></i></span><span><span class="obs-route-title">知識晉升審核</span><span class="obs-route-desc">晉升守門與 AI 例外決策。</span></span><span class="obs-route-code">07</span></a>
<a class="obs-route-card" href="/observability/rag_queries"><span class="obs-route-icon"><i class="fas fa-magnifying-glass-chart"></i></span><span><span class="obs-route-title">知識召回詳情</span><span class="obs-route-desc">查詢命中、節省呼叫、反饋追蹤。</span></span><span class="obs-route-code">08</span></a>
<a class="obs-route-card" href="/observability/quality_trend"><span class="obs-route-icon"><i class="fas fa-comments"></i></span><span><span class="obs-route-title">反饋趨勢</span><span class="obs-route-desc">呼叫端品質、蒸餾池、根因建議。</span></span><span class="obs-route-code">09</span></a>
<a class="obs-route-card" href="/observability/ppt_audit_history"><span class="obs-route-icon"><i class="fas fa-search"></i></span><span><span class="obs-route-title">PPT 視覺審核</span><span class="obs-route-desc">PPT 審核、修法建議、修復流程。</span></span><span class="obs-route-code">10</span></a>
</div>
</div>
</section>
<p class="obs-microcopy obs-footer-source mt-3">
<i class="fas fa-database me-1"></i>
資料來源主機探測、AI 呼叫、預算、學習事件、知識查詢、工具呼叫、事件與自癒、PPT 審核。
</p>
</div>
<template id="obs-host-sparkline-data">{{ host_sparkline | 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 %}