Fix PPT auto generation and analytics fallbacks
Some checks failed
CD Pipeline / deploy (push) Failing after 26s

This commit is contained in:
OoO
2026-05-18 11:52:31 +08:00
parent 3284b1cf82
commit c420d48263
26 changed files with 1559 additions and 258 deletions

View File

@@ -2,159 +2,11 @@
{% block title %}PPT 視覺 QA 產線{% endblock %}
{% block ewooo_content %}
<style>
.ppt-hero, .ppt-panel, .ppt-table-shell {
border: 1px solid var(--obs-line);
border-radius: 26px;
background: var(--obs-card);
box-shadow: 0 16px 38px rgba(70, 46, 28, .08);
}
.ppt-hero {
padding: 1.4rem;
background: radial-gradient(circle at 12% 14%, rgba(201,100,66,.18), transparent 24rem),
radial-gradient(circle at 88% 8%, rgba(79,111,143,.14), transparent 22rem),
linear-gradient(135deg, rgba(255,248,239,.98), rgba(255,255,255,.74));
}
.ppt-kicker {
color: var(--obs-accent);
font-size: .76rem;
letter-spacing: .13em;
text-transform: uppercase;
font-weight: 850;
}
.ppt-title {
margin: .45rem 0 .25rem;
font-family: var(--momo-font-display, "Inter", "Noto Sans TC", system-ui, sans-serif);
font-size: var(--obs-title-size);
letter-spacing: 0;
line-height: .98;
}
.ppt-subtitle {
color: var(--obs-muted);
max-width: 860px;
line-height: 1.7;
}
.ppt-command {
display: grid;
grid-template-columns: repeat(4, minmax(0,1fr));
gap: .75rem;
margin-top: 1rem;
}
.ppt-signal {
padding: .95rem;
border: 1px solid var(--obs-line);
border-radius: 20px;
background: rgba(255,255,255,.62);
}
.ppt-label {
color: var(--obs-muted);
font-size: .72rem;
letter-spacing: .1em;
text-transform: uppercase;
}
.ppt-value {
display: block;
margin-top: .28rem;
font-size: var(--obs-value-size);
font-weight: 880;
letter-spacing: 0;
}
.ppt-toolbar {
margin-top: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: .75rem;
flex-wrap: wrap;
}
.ppt-type-tabs {
display: flex;
flex-wrap: wrap;
gap: .45rem;
}
.ppt-type-chip {
display: inline-flex;
align-items: center;
gap: .35rem;
}
.ppt-grid {
display: grid;
grid-template-columns: minmax(0,1.2fr) minmax(330px,.8fr);
gap: 1rem;
margin-top: 1rem;
}
.ppt-stack { display: grid; gap: 1rem; }
.ppt-panel-head, .ppt-table-title {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
padding: 1.05rem 1.1rem .25rem;
}
.ppt-panel-title, .ppt-table-title h3 {
margin: .15rem 0 0;
font-size: 1.1rem;
font-weight: 850;
letter-spacing: 0;
}
.ppt-panel-body {
padding: 1rem 1.1rem 1.1rem;
}
.ppt-table-shell {
overflow: hidden;
margin-top: 1rem;
}
.ppt-table-shell .table {
min-width: 760px;
}
.ppt-mini-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0,1fr));
gap: .7rem;
}
.ppt-mini {
padding: .85rem;
border: 1px solid var(--obs-line);
border-radius: 18px;
background: rgba(255,255,255,.58);
}
.ppt-mini strong {
display: block;
margin-top: .24rem;
font-size: 1.35rem;
letter-spacing: 0;
}
.fix-card {
padding: .85rem;
border: 1px solid var(--obs-line);
border-radius: 18px;
background: rgba(255,255,255,.58);
margin-bottom: .7rem;
}
.status-good { color: var(--obs-green); }
.status-warn { color: var(--obs-amber); }
.status-bad { color: var(--obs-red); }
.status-blue { color: var(--obs-blue); }
.ppt-file-actions {
display: flex;
gap: .4rem;
flex-wrap: wrap;
}
.ppt-file-actions .btn {
display: inline-flex;
align-items: center;
gap: .3rem;
}
@media(max-width:1100px) {
.ppt-command, .ppt-mini-grid { grid-template-columns: repeat(2,minmax(0,1fr)); }
.ppt-grid { grid-template-columns: 1fr; }
}
@media(max-width:720px) {
.ppt-command { grid-template-columns: 1fr; }
}
</style>
{% 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' %}
@@ -188,6 +40,13 @@
</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 error %}<div class="alert alert-warning mt-3"><strong><i class="fas fa-triangle-exclamation me-1"></i></strong>{{ error }}</div>{% endif %}
@@ -214,6 +73,40 @@
{% 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">自動產生定義</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-auto-grid">
{% for item in auto_generation_items %}
<div class="ppt-mini">
<span class="ppt-label">{{ item.label }}</span>
<strong class="{% if item.ready %}status-good{% else %}status-warn{% endif %}">
{{ '已產生' if item.ready else '待補齊' }}
</strong>
<small class="text-muted">{{ item.count }} 筆{% if item.sources %} · {{ item.sources|join(' + ') }}{% endif %}</small>
</div>
{% endfor %}
</div>
<div class="ppt-auto-status small text-muted mt-3" data-ppt-auto-status>
{% if auto_generation.enabled %}
每日 20:30 會自動補齊定義簡報;目前缺漏 {{ auto_generation.missing_count }} 類。
{% else %}
PPT_AUTO_GENERATION_ENABLED=false已停用自動補齊。
{% endif %}
</div>
</div>
</section>
<section class="ppt-grid">
<div class="ppt-stack">
<article class="ppt-table-shell">
@@ -245,7 +138,17 @@
<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" onclick="triggerAiderHeal({{ r.pptx_filename|tojson }}, {{ (r.error_msg or '')|tojson }})"><i class="fas fa-wrench me-1"></i>AiderHeal</button>{% endif %}</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>

View File

@@ -1,57 +1,5 @@
{# 分析報表第二層分頁:保留頁面內容與圖表邏輯,只提供一致的報表切換入口。 #}
{% set _analysis_active = active_page|default('') %}
<style>
.analysis-report-tabs {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin: 0 0 18px;
padding: 8px;
border: 1px solid var(--momo-border-light, rgba(42, 37, 32, 0.16));
border-radius: 8px;
background: var(--momo-bg-elevated, #fdfaf2);
box-shadow: var(--momo-shadow-md, 0 0 0 1px rgba(42, 37, 32, 0.10));
}
.analysis-report-tabs-spacer {
flex: 1 1 auto;
min-width: 8px;
}
.analysis-report-tab {
display: inline-flex;
align-items: center;
gap: 7px;
min-height: 34px;
padding: 0 12px;
border: 1px solid transparent;
border-radius: 7px;
color: var(--momo-text-secondary, #645c52);
text-decoration: none;
font-family: var(--momo-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
font-size: var(--momo-text-body-sm, 13px);
font-weight: 700;
transition: background-color 160ms ease, border-color 160ms ease, color 160ms ease;
}
.analysis-report-tab:hover {
border-color: var(--momo-border-light, rgba(42, 37, 32, 0.16));
background: var(--momo-bg-surface, #faf7f0);
color: var(--momo-text-primary, #2a2520);
}
.analysis-report-tab.is-active {
border-color: var(--momo-page-accent-dark, #a95846);
background: var(--momo-page-accent, #c89043);
color: var(--momo-page-inverse, #fff8ef);
}
.analysis-report-tab.is-external {
border-color: var(--momo-border-light, rgba(42, 37, 32, 0.16));
background: var(--momo-bg-surface, #faf6ec);
font-family: var(--momo-font-family-mono, "SF Mono", Menlo, Consolas, monospace);
font-size: 0.78rem;
}
.analysis-report-tab i {
color: currentColor !important;
}
</style>
<nav class="analysis-report-tabs" aria-label="分析報表分頁">
<a class="analysis-report-tab {% if _analysis_active == 'sales' %}is-active{% endif %}"
{% if _analysis_active == 'sales' %}aria-current="page"{% endif %}
@@ -77,12 +25,12 @@
<span class="analysis-report-tabs-spacer" aria-hidden="true"></span>
{% endif %}
{% if metabase_url %}
<a class="analysis-report-tab is-external" href="{{ metabase_url }}" target="_blank" rel="noopener">
<a class="analysis-report-tab is-external {% if _analysis_active == 'metabase' %}is-active{% endif %}" href="{{ metabase_url }}">
<i class="fas fa-chart-pie"></i>Metabase <i class="fas fa-up-right-from-square"></i>
</a>
{% endif %}
{% if grist_url %}
<a class="analysis-report-tab is-external" href="{{ grist_url }}" target="_blank" rel="noopener">
<a class="analysis-report-tab is-external {% if _analysis_active == 'grist' %}is-active{% endif %}" href="{{ grist_url }}">
<i class="fas fa-table"></i>Grist <i class="fas fa-up-right-from-square"></i>
</a>
{% endif %}

View File

@@ -82,12 +82,12 @@
<i class="fas fa-table"></i><span>月份總表</span><span class="momo-nav-code">04</span>
</a>
{% if metabase_url %}
<a class="momo-nav-sublink" href="{{ metabase_url }}" target="_blank" rel="noopener">
<a class="momo-nav-sublink {% if _active_page == 'metabase' %}is-active{% endif %}" href="{{ metabase_url }}">
<i class="fas fa-chart-pie"></i><span>自訂圖表</span><span class="momo-nav-code">外部</span>
</a>
{% endif %}
{% if grist_url %}
<a class="momo-nav-sublink" href="{{ grist_url }}" target="_blank" rel="noopener">
<a class="momo-nav-sublink {% if _active_page == 'grist' %}is-active{% endif %}" href="{{ grist_url }}">
<i class="fas fa-table-cells"></i><span>資料協作</span><span class="momo-nav-code">外部</span>
</a>
{% endif %}

View File

@@ -201,17 +201,15 @@
{% if metabase_url %}
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="{{ metabase_url }}" target="_blank">
<a class="dropdown-item" href="{{ metabase_url }}">
<i class="fas fa-chart-pie me-2"></i>自訂圖表 (Metabase)
<i class="fas fa-external-link-alt ms-1 small text-muted"></i>
</a>
</li>
{% endif %}
{% if grist_url %}
<li>
<a class="dropdown-item" href="{{ grist_url }}" target="_blank">
<a class="dropdown-item" href="{{ grist_url }}">
<i class="fas fa-table me-2"></i>資料協作 (Grist)
<i class="fas fa-external-link-alt ms-1 small text-muted"></i>
</a>
</li>
{% endif %}

View File

@@ -51,6 +51,25 @@
</div>
{% endmacro %}
{% macro chart_snapshot(labels, values, mode='currency', limit=12) %}
{% set total = labels|length %}
<div class="chart-fallback-list">
{% for label in labels %}
{% if loop.index > total - limit %}
{% set val = values[loop.index0]|default(0) %}
<span class="chart-fallback-item">
<b>{{ label }}</b>
<strong>
{% if mode == 'pct' %}{{ "{:+.1f}%".format(val) }}
{% elif mode == 'number' %}{{ "{:,.0f}".format(val) }}
{% else %}${{ "{:,.0f}".format(val) }}{% endif %}
</strong>
</span>
{% endif %}
{% endfor %}
</div>
{% endmacro %}
{% block ewooo_content %}
<div
class="daily-sales-page"
@@ -262,13 +281,13 @@
<div class="col-lg-8">
<div class="card chart-card">
<div class="card-header"><i class="fas fa-chart-area"></i> 每日業績趨勢(近 30 天)</div>
<div class="card-body"><div class="chart-container"><canvas id="trendChart"></canvas></div></div>
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="trendChart"></canvas>{{ chart_snapshot(chart_data.labels, chart_data.revenue, 'currency') }}</div></div>
</div>
</div>
<div class="col-lg-4">
<div class="card chart-card">
<div class="card-header"><i class="fas fa-percentage"></i> 日成長率 (DoD %)</div>
<div class="card-body"><div class="chart-container"><canvas id="dodChart"></canvas></div></div>
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="dodChart"></canvas>{{ chart_snapshot(chart_data.labels, chart_data.dod_revenue, 'pct') }}</div></div>
</div>
</div>
</div>
@@ -278,7 +297,7 @@
<div class="col-lg-8">
<div class="card chart-card">
<div class="card-header"><i class="fas fa-chart-bar"></i> 週成長對比 (WoW)</div>
<div class="card-body"><div class="chart-container"><canvas id="wowChart"></canvas></div></div>
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="wowChart"></canvas>{{ chart_snapshot(chart_data.labels, chart_data.wow_revenue, 'pct') }}</div></div>
</div>
</div>
<div class="col-lg-4">
@@ -289,8 +308,9 @@
<i class="fas fa-hand-pointer"></i> 左右滑動查看完整圖表
</div>
<div class="chart-responsive">
<div class="chart-container" id="top10ChartContainer">
<div class="chart-container has-html-chart" id="top10ChartContainer">
<canvas id="top10Chart"></canvas>
{{ chart_snapshot(chart_data.top10_labels, chart_data.top10_values, 'currency', 10) }}
</div>
</div>
</div>
@@ -314,7 +334,7 @@
<div class="col-lg-6 mb-4">
<h6 class="marketing-subhead"><i class="fas fa-tags"></i> 折扣活動 Top 10</h6>
{% if marketing_data.discount %}
<div class="chart-container chart-container--md"><canvas id="discountChart"></canvas></div>
<div class="chart-container chart-container--md has-html-chart"><canvas id="discountChart"></canvas>{{ chart_snapshot(marketing_data.discount['labels'], marketing_data.discount['values'], 'currency', 10) }}</div>
{% else %}
<div class="chart-empty"><i class="fas fa-info-circle"></i><p>暫無折扣活動數據</p></div>
{% endif %}
@@ -322,7 +342,7 @@
<div class="col-lg-6 mb-4">
<h6 class="marketing-subhead"><i class="fas fa-ticket-alt"></i> 折價券活動 Top 10</h6>
{% if marketing_data.coupon %}
<div class="chart-container chart-container--md"><canvas id="couponChart"></canvas></div>
<div class="chart-container chart-container--md has-html-chart"><canvas id="couponChart"></canvas>{{ chart_snapshot(marketing_data.coupon['labels'], marketing_data.coupon['values'], 'currency', 10) }}</div>
{% else %}
<div class="chart-empty"><i class="fas fa-info-circle"></i><p>暫無折價券活動數據</p></div>
{% endif %}

View File

@@ -25,6 +25,7 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/ewoooc-tokens-v2-alias.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/ewoooc-shell.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/ewoooc-dotmatrix.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/analysis-report-tabs.css') }}">
{% if active_page|default('') in [
'obs_overview', 'obs_agent_orchestration', 'obs_business_intel',
'obs_host_health', 'obs_ai_calls', 'obs_budget',
@@ -49,7 +50,7 @@
{# 群組映射 — Jinja 計算 [data-page-group] #}
{% set _page = active_page|default('') %}
{% set _group_monitor = ['dashboard', 'edm', 'campaigns'] %}
{% set _group_analytics = ['sales', 'daily_sales', 'monthly', 'growth'] %}
{% set _group_analytics = ['sales', 'daily_sales', 'monthly', 'growth', 'metabase', 'grist'] %}
{% set _group_ops = ['vendor_stockout', 'auto_import', 'market_intel'] %}
{% set _group_ai = ['ai_recommend', 'ai_history', 'ai_intelligence',
'pchome_crawler', 'price_comparison', 'trends',

View File

@@ -0,0 +1,33 @@
{% extends "ewoooc_base.html" %}
{% block title %}{{ tool.title }} - EwoooC{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-external-tools.css') }}">
{% endblock %}
{% block ewooo_content %}
<main class="external-tool-page" data-screen-label="{{ tool.key }}">
<section class="external-tool-hero">
<div>
<div class="external-tool-eyebrow">
<i class="fas fa-link" aria-hidden="true"></i>
{{ tool.eyebrow }}
</div>
<h1>{{ tool.title }}</h1>
<p>{{ tool.summary }}</p>
</div>
<span class="external-tool-status">{{ tool.status_label }}</span>
</section>
<section class="external-tool-panel">
<div class="external-tool-panel__body">
<span class="external-tool-kicker">路由狀態</span>
<h2>入口已由 momo-pro 接管</h2>
<p>{{ tool.detail }}</p>
</div>
<a class="btn btn-primary" href="{{ tool.primary_href }}">
<i class="fas fa-arrow-left" aria-hidden="true"></i>{{ tool.primary_label }}
</a>
</section>
</main>
{% endblock %}

View File

@@ -6,9 +6,23 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-growth-bem.css') }}">
{% endblock %}
{% macro ga_chart_snapshot(labels, values, mode='currency') %}
<div class="ga-chart-snapshot">
{% for label in labels %}
{% set val = values[loop.index0]|default(0) %}
<span class="ga-chart-snapshot__item">
<b>{{ label }}</b>
<strong>
{% if mode == 'pct' %}{{ "{:+.1f}%".format(val) }}
{% else %}${{ "{:,.0f}".format(val) }}{% endif %}
</strong>
</span>
{% endfor %}
</div>
{% endmacro %}
{% block ewooo_content %}
<div class="momo-app" data-page-group="analytics">
<div class="growth-analysis-page">
<div class="growth-analysis-page" data-page-group="analytics">
{% include 'components/_analysis_report_tabs.html' %}
{# ── Page head ──────────────────────────────────── #}
@@ -67,8 +81,9 @@
<header class="ga-chart-card__head">
<span class="ga-chart-card__title"><i class="fas fa-chart-bar"></i> 月營收與年增率 (Revenue & YoY)</span>
</header>
<div class="ga-chart-card__body" style="--ga-chart-h: 350px;">
<div class="ga-chart-card__body ga-chart-card__body--lg has-html-chart">
<canvas id="revenueChart"></canvas>
{{ ga_chart_snapshot(chart_data.labels, chart_data.revenue, 'currency') }}
</div>
</article>
@@ -76,8 +91,9 @@
<header class="ga-chart-card__head">
<span class="ga-chart-card__title"><i class="fas fa-percentage"></i> 月增率分析 (MoM)</span>
</header>
<div class="ga-chart-card__body" style="--ga-chart-h: 350px;">
<div class="ga-chart-card__body ga-chart-card__body--lg has-html-chart">
<canvas id="momChart"></canvas>
{{ ga_chart_snapshot(chart_data.labels, chart_data.mom, 'pct') }}
</div>
</article>
</section>
@@ -88,8 +104,9 @@
<header class="ga-chart-card__head">
<span class="ga-chart-card__title"><i class="fas fa-wallet"></i> 平均單價趨勢</span>
</header>
<div class="ga-chart-card__body" style="--ga-chart-h: 300px;">
<div class="ga-chart-card__body ga-chart-card__body--md has-html-chart">
<canvas id="aovChart"></canvas>
{{ ga_chart_snapshot(chart_data.labels, chart_data.aov, 'currency') }}
</div>
</article>
@@ -97,12 +114,12 @@
<header class="ga-chart-card__head">
<span class="ga-chart-card__title"><i class="fas fa-hand-holding-usd"></i> 獲利能力分析 (Gross Margin %)</span>
</header>
<div class="ga-chart-card__body" style="--ga-chart-h: 300px;">
<div class="ga-chart-card__body ga-chart-card__body--md has-html-chart">
<canvas id="marginChart"></canvas>
{{ ga_chart_snapshot(chart_data.labels, chart_data.margin_rate, 'pct') }}
</div>
</article>
</section>
</div>
</div>
{% endblock %}