# Superset 功能實作指南

> 本指南詳細說明如何在 Superset 中複製現有頁面的分析功能
> 建立日期: 2026-02-08

---

## 存取資訊

| 項目 | 值 |
|------|-----|
| URL | https://monitor.wooo.work/superset/ |
| 帳號 | admin |
| 密碼 | <SUPERSET_ADMIN_PASSWORD> |
| 資料庫 | MOMO_UAT |

---

## 已建立的資料集

| 資料集 | 資料表 | 用途 |
|--------|--------|------|
| daily_sales_snapshot | public.daily_sales_snapshot | 當日業績 |
| realtime_sales_monthly | public.realtime_sales_monthly | 銷售分析、成長分析 |
| monthly_summary_analysis | public.monthly_summary_analysis | 月度總結 |
| products | public.products | 商品資料、ABC 分析 |
| price_records | public.price_records | 價格趨勢 |

---

## 第一部分：當日業績 (Daily Sales)

**對應頁面**: `/daily_sales`

### 1.1 SQL Lab 建立虛擬資料集

在 SQL Lab 執行以下查詢，然後儲存為資料集：

```sql
-- 資料集名稱: daily_sales_kpi
-- 用途: 每日 KPI 彙總

SELECT
    snapshot_date,
    DATE_TRUNC('month', snapshot_date) as month,
    EXTRACT(DOW FROM snapshot_date) as day_of_week,
    COUNT(DISTINCT "商品代碼") as sku_count,
    SUM("銷售金額") as total_revenue,
    SUM("總成本") as total_cost,
    SUM("銷售金額") - SUM("總成本") as gross_margin,
    SUM("銷售數量") as total_qty,
    CASE
        WHEN SUM("銷售金額") > 0
        THEN (SUM("銷售金額") - SUM("總成本")) / SUM("銷售金額") * 100
        ELSE 0
    END as margin_rate,
    CASE
        WHEN SUM("銷售數量") > 0
        THEN SUM("銷售金額") / SUM("銷售數量")
        ELSE 0
    END as avg_price
FROM daily_sales_snapshot
GROUP BY snapshot_date
ORDER BY snapshot_date DESC
```

### 1.2 建議圖表

| 圖表名稱 | 類型 | 說明 |
|----------|------|------|
| 當日業績 Big Number | Big Number with Trendline | 顯示最新日期的 total_revenue |
| 當日毛利 Big Number | Big Number with Trendline | 顯示最新日期的 gross_margin |
| 30 天業績趨勢 | Line Chart | X軸: snapshot_date, Y軸: total_revenue |
| DoD 比較 | Bar Chart | 比較今日與昨日 |
| WoW 比較 | Bar Chart | 比較今日與上週同日 |
| 分類業績圓餅圖 | Pie Chart | 依商品分類分組 |
| 日曆熱力圖 | Calendar Heatmap | 每日業績視覺化 |

### 1.3 建立步驟

1. **SQL Lab** → 執行上述 SQL
2. 點擊 **Save** → **Save Dataset**
3. 命名為 `daily_sales_kpi`
4. 前往 **Charts** → **+ Chart**
5. 選擇 `daily_sales_kpi` 資料集
6. 依序建立各圖表

---

## 第二部分：銷售分析 (Sales Analysis)

**對應頁面**: `/sales_analysis`

### 2.1 SQL Lab 建立虛擬資料集

```sql
-- 資料集名稱: sales_analysis_detail
-- 用途: 銷售明細分析

SELECT
    "日期" as order_date,
    "商品名稱" as product_name,
    "商品代碼" as product_code,
    "館別" as category,
    "品牌" as brand,
    "廠商名稱" as vendor,
    "總業績" as amount,
    "總成本" as cost,
    "總業績" - "總成本" as profit,
    "銷量" as qty,
    CASE
        WHEN "總業績" > 0
        THEN ("總業績" - "總成本") / "總業績" * 100
        ELSE 0
    END as margin_rate,
    EXTRACT(DOW FROM "日期"::date) as day_of_week,
    EXTRACT(HOUR FROM "訂單時間"::time) as order_hour,
    DATE_TRUNC('month', "日期"::date) as month,
    DATE_TRUNC('week', "日期"::date) as week
FROM realtime_sales_monthly
WHERE "日期" IS NOT NULL
```

### 2.2 建議圖表

| 圖表名稱 | 類型 | 設定 |
|----------|------|------|
| 總業績 KPI | Big Number | SUM(amount) |
| 總毛利 KPI | Big Number | SUM(profit) |
| 毛利率 KPI | Big Number | AVG(margin_rate) |
| 業績 TOP 20 商品 | Bar Chart (Horizontal) | GROUP BY product_name, ORDER BY SUM(amount) DESC LIMIT 20 |
| 分類業績分佈 | Pie Chart | GROUP BY category |
| 品牌業績排行 | Bar Chart | GROUP BY brand |
| 廠商業績排行 | Bar Chart | GROUP BY vendor |
| 星期銷售熱力圖 | Heatmap | X: day_of_week, Y: order_hour, Value: SUM(amount) |
| 月度趨勢 | Line Chart | X: month, Y: SUM(amount) |
| 週趨勢 | Line Chart | X: week, Y: SUM(amount) |
| 價格區間分佈 | Histogram | amount 分佈 |
| BCG 矩陣 | Scatter Plot | X: SUM(qty), Y: margin_rate, Size: SUM(amount) |
| 樹狀圖 | Treemap | 分類 → 品牌 階層 |

### 2.3 篩選器設定

建立以下 Filter Box:
- 日期範圍 (order_date)
- 分類 (category)
- 品牌 (brand)
- 廠商 (vendor)
- 星期 (day_of_week)
- 時段 (order_hour)

---

## 第三部分：成長分析 (Growth Analysis)

**對應頁面**: `/growth_analysis`

### 3.1 SQL Lab 建立虛擬資料集

```sql
-- 資料集名稱: growth_analysis_monthly
-- 用途: 月度成長分析 (MoM, YoY, AOV)

WITH monthly_data AS (
    SELECT
        DATE_TRUNC('month', "日期"::date) as month,
        SUM("總業績") as revenue,
        SUM("總成本") as cost,
        SUM("總業績") - SUM("總成本") as profit,
        COUNT(DISTINCT "訂單編號") as orders
    FROM realtime_sales_monthly
    WHERE "日期" IS NOT NULL
    GROUP BY DATE_TRUNC('month', "日期"::date)
),
with_growth AS (
    SELECT
        month,
        revenue,
        profit,
        orders,
        revenue / NULLIF(orders, 0) as aov,
        profit / NULLIF(revenue, 0) * 100 as margin_rate,
        -- MoM (月增率)
        (revenue - LAG(revenue) OVER (ORDER BY month)) /
            NULLIF(LAG(revenue) OVER (ORDER BY month), 0) * 100 as mom,
        -- YoY (年增率)
        (revenue - LAG(revenue, 12) OVER (ORDER BY month)) /
            NULLIF(LAG(revenue, 12) OVER (ORDER BY month), 0) * 100 as yoy
    FROM monthly_data
)
SELECT
    month,
    revenue,
    profit,
    orders,
    ROUND(aov::numeric, 0) as aov,
    ROUND(margin_rate::numeric, 1) as margin_rate,
    COALESCE(ROUND(mom::numeric, 2), 0) as mom,
    COALESCE(ROUND(yoy::numeric, 2), 0) as yoy
FROM with_growth
ORDER BY month DESC
```

### 3.2 建議圖表

| 圖表名稱 | 類型 | 說明 |
|----------|------|------|
| YTD 業績 | Big Number | 今年累計業績 |
| YTD 成長率 | Big Number | 與去年同期比較 |
| 近 30 天客單價 | Big Number | 最近客單價 |
| 月度業績趨勢 | Line Chart | X: month, Y: revenue |
| 月度毛利趨勢 | Line Chart | X: month, Y: profit |
| MoM 月增率 | Bar Chart | X: month, Y: mom (紅/綠顏色區分正負) |
| YoY 年增率 | Bar Chart | X: month, Y: yoy |
| 客單價趨勢 | Line Chart | X: month, Y: aov |
| 毛利率趨勢 | Line Chart | X: month, Y: margin_rate |
| 綜合指標雙軸圖 | Mixed Chart | 左軸: revenue, 右軸: margin_rate |

---

## 第四部分：月度總結 (Monthly Summary)

**對應頁面**: `/monthly_summary_analysis`

### 4.1 使用現有資料集

直接使用 `monthly_summary_analysis` 資料集。

### 4.2 建議圖表

| 圖表名稱 | 類型 | 說明 |
|----------|------|------|
| 本月業績 | Big Number | 最新月份的業績 |
| 月度業績比較 | Bar Chart | 12 個月業績對比 |
| 月環比 | Line Chart | MoM 變化 |
| 月度彙總表 | Pivot Table | 月份 x 各項指標 |
| 季度彙總 | Bar Chart | 依季度分組 |

---

## 第五部分：ABC 分析

**對應頁面**: `/abc_analysis/detail`

### 5.1 SQL Lab 建立虛擬資料集

```sql
-- 資料集名稱: abc_analysis
-- 用途: 商品 ABC 分類

WITH product_sales AS (
    SELECT
        p.i_code,
        p.name as product_name,
        p.category,
        COALESCE(SUM(d."銷售金額"), 0) as total_sales,
        COALESCE(SUM(d."銷售數量"), 0) as total_qty
    FROM products p
    LEFT JOIN daily_sales_snapshot d ON p.i_code = d."商品代碼"
    GROUP BY p.i_code, p.name, p.category
    HAVING COALESCE(SUM(d."銷售金額"), 0) > 0
),
ranked AS (
    SELECT
        *,
        SUM(total_sales) OVER (ORDER BY total_sales DESC) as cumulative_sales,
        SUM(total_sales) OVER () as grand_total,
        ROW_NUMBER() OVER (ORDER BY total_sales DESC) as rank
    FROM product_sales
)
SELECT
    i_code,
    product_name,
    category,
    total_sales,
    total_qty,
    cumulative_sales,
    grand_total,
    rank,
    cumulative_sales / grand_total * 100 as cumulative_pct,
    CASE
        WHEN cumulative_sales / grand_total <= 0.7 THEN 'A'
        WHEN cumulative_sales / grand_total <= 0.9 THEN 'B'
        ELSE 'C'
    END as abc_class
FROM ranked
ORDER BY rank
```

### 5.2 建議圖表

| 圖表名稱 | 類型 | 說明 |
|----------|------|------|
| ABC 分類圓餅圖 | Pie Chart | GROUP BY abc_class |
| ABC 分類商品數 | Bar Chart | COUNT BY abc_class |
| 帕累托曲線 | Dual Axis Line | 銷售額 + 累計百分比 |
| A 類商品列表 | Table | FILTER abc_class = 'A' |
| B 類商品列表 | Table | FILTER abc_class = 'B' |
| C 類商品列表 | Table | FILTER abc_class = 'C' |
| 分類 ABC 分佈 | Stacked Bar | X: category, Y: COUNT, Color: abc_class |

---

## 第六部分：價格趨勢

**對應頁面**: 商品看板價格歷史

### 6.1 使用現有資料集

直接使用 `price_records` 資料集。

### 6.2 建議圖表

| 圖表名稱 | 類型 | 說明 |
|----------|------|------|
| 價格歷史折線圖 | Line Chart | X: timestamp, Y: current_price, Filter: product_id |
| 今日價格變動 | Table | WHERE DATE(timestamp) = CURRENT_DATE |
| 漲價商品數 | Big Number | COUNT WHERE price_change > 0 |
| 降價商品數 | Big Number | COUNT WHERE price_change < 0 |
| 價格變動分佈 | Histogram | price_change_pct 分佈 |

---

## 儀表板建立順序

### 建議順序（由簡到繁）

1. **成長分析儀表板** - 圖表較少，資料結構簡單
2. **月度總結儀表板** - 使用現有資料集
3. **當日業績儀表板** - 需要 DoD/WoW 計算
4. **銷售分析儀表板** - 圖表最多，篩選器複雜
5. **ABC 分析儀表板** - 需要進階 SQL
6. **價格趨勢儀表板** - 需要時間序列處理

---

## 驗證對照表

每個儀表板建立完成後，請與現有頁面比對以下項目：

| 驗證項目 | 檢查點 |
|----------|--------|
| 數據一致性 | KPI 數值是否與現有頁面一致 |
| 圖表呈現 | 圖表類型是否適當呈現資料 |
| 篩選功能 | 篩選器是否正常運作 |
| 效能 | 載入時間是否可接受 |
| 互動性 | 點擊鑽取是否正常 |

---

## 常用 Superset 操作

### 建立圖表快速步驟

1. **Charts** → **+ Chart**
2. 選擇資料集
3. 選擇圖表類型
4. 設定 Metrics (指標) 和 Dimensions (維度)
5. 設定篩選條件
6. **Run Query** 預覽
7. **Save** 儲存

### 建立儀表板

1. **Dashboards** → **+ Dashboard**
2. 輸入名稱
3. **Edit Dashboard**
4. 從右側拖曳圖表
5. 調整佈局
6. 新增篩選器
7. **Save**

### 設定篩選器

1. 在儀表板編輯模式
2. 點擊 **+ Add filter**
3. 選擇欄位和類型
4. 設定影響的圖表

---

## 注意事項

1. **欄位名稱**: PostgreSQL 區分大小寫，中文欄位需用雙引號包起來
2. **日期格式**: 確保日期欄位正確轉換為 DATE 類型
3. **效能**: 大資料集建議加入時間篩選限制
4. **快取**: Superset 有快取機制，測試時可能需要清除快取

---

## 故障排除

### 頁面無限載入 (Infinite Loading)

**症狀**: 訪問 Superset 頁面時，畫面顯示無限載入中

**原因**: Superset Docker 映像 (3.1.0/3.1.1) 中的 `theme.5ab95322dc4a489d8e8f.entry.js` 檔案大小為 0 bytes (映像構建問題)

**解決方案**: 已在 docker-compose.yml 的啟動命令中自動修復：

```bash
echo '(function(){console.log("Theme loaded");})();' > /app/superset/static/assets/theme.5ab95322dc4a489d8e8f.entry.js
```

**手動修復** (如果需要):

```bash
docker exec momo-superset sh -c 'echo "(function(){console.log(\"Theme loaded\");})();" > /app/superset/static/assets/theme.5ab95322dc4a489d8e8f.entry.js'
```

### 子路徑 404 錯誤

**症狀**: 訪問 `/superset/` 返回 404

**原因**: Nginx 子路徑配置需要特別處理 URL 重寫

**解決方案**: 參考 `nginx-superset.conf` 配置，關鍵設定：
- `proxy_redirect / /superset/;` - 重寫重定向
- `sub_filter` - 重寫 HTML 中的靜態資源路徑
- `gzip off;` - 禁用 gzip 讓 sub_filter 生效

### 雙重前綴問題 (/superset/superset/) (2026-02-08 修復)

**症狀**:
- 訪問 `https://monitor.wooo.work/superset/` 被重定向到 `/superset/superset/welcome/`
- 頁面無限載入

**根本原因**:
Superset 內部 Flask blueprints 路由已經是 `/superset/...`（例如 `/superset/welcome/`）。
如果 Nginx 使用 `proxy_redirect / /superset/;`，會把 `/superset/welcome/` 再次加前綴變成 `/superset/superset/welcome/`。

**解決方案**:

1. **superset_config.py** - 禁用 x_prefix:
```python
ENABLE_PROXY_FIX = True
PROXY_FIX_CONFIG = {
    "x_for": 1,
    "x_proto": 1,
    "x_host": 1,
    "x_prefix": 0,   # 必須為 0！
}
```

2. **Nginx 配置** - 智能 proxy_redirect:
```nginx
location /superset/ {
    proxy_pass http://127.0.0.1:8088/;

    # 關鍵：已是 /superset/ 開頭的路徑保持不變
    proxy_redirect /superset/ /superset/;
    # 其他路徑才添加 /superset/ 前綴
    proxy_redirect ~^/(?!superset)(.*)$ /superset/$1;

    # 只重寫 static 路徑
    sub_filter '"/static/' '"/superset/static/';
    sub_filter "'/static/" "'/superset/static/";
    sub_filter_once off;
}
```

3. **驗證**:
```bash
# 應該返回 302 到 /superset/welcome/ (不是 /superset/superset/welcome/)
curl -sI https://monitor.wooo.work/superset/ | grep -i location
```
