supabase/functions/report-analytics/index.ts 已實作 7 個演算法(SMA-7 / Shewhart 2σ / EWMA / BOM / Pearson / Ridge),直接當 Layer 1 backbone。high/medium/low confidence + INDUSTRY_DEFAULTS fallback,冷啟動 pattern 早就跑通,直接 generalize。┌─────────────────────────────────────────────────────────────────┐
│ Layer 4 — UI (Widget Renderer) │
│ - 對話介面 + 預設問題 chip + 動態 widget canvas │
│ - 每個 widget 來自固定 library,不讓 AI 自由畫 │
│ - NotebookLM-style「資料來源 panel」可展開 │
└──────────────────────▲──────────────────────────────────────────┘
│ Report Manifest (JSON)
┌──────────────────────┴──────────────────────────────────────────┐
│ Layer 3 — Manifest Generator (AI agent) │
│ - Claude / OpenAI 收老闆問題 → 寫 Report Manifest │
│ - 規範化 schema 約束 LLM 只能輸出合法 Widget IDs │
│ - 包含 trust_score / data_sources / explanations │
└──────────────────────▲──────────────────────────────────────────┘
│ Tool calls (whitelist functions)
┌──────────────────────┴──────────────────────────────────────────┐
│ Layer 2 — AI Query Orchestrator │
│ - Intent classification(revenue/forecast/comparison/...) │
│ - Tool selection + parameter validation │
│ - Cold start fallback(no data → industry defaults) │
│ - Trust score 計算(data_volume × algorithm_grade) │
└──────────────────────▲──────────────────────────────────────────┘
│ Named tool functions (schema enforced)
┌──────────────────────┴──────────────────────────────────────────┐
│ Layer 1 — Data Mart Tools(AI 不直接 SQL) │
│ - 既有 report-analytics 7 演算法 wrap 成 tool function │
│ - 新加:weather、holiday、年同期對比、客人 segment、菜單熱賣 │
│ - Materialized views for hot questions │
│ - 每個 tool 有明確 input/output schema,RLS 嚴格 gate │
└──────────────────────▲──────────────────────────────────────────┘
│ SQL / RPC
┌────┴─────┐
│ Supabase │
└──────────┘
原則: AI 永遠不能下 raw SQL。所有資料 access 走「白名單 named tool function」,每個 tool 有明確 input schema + output schema。違反 schema = orchestrator 直接拒絕。
| 類別 | Tool name | 已有 / 新加 | Output schema |
|---|---|---|---|
| 營收 / 成本 | get_revenue_summary(period, granularity) |
新 wrap report-analytics | 總營收 / 客單價 / 來客數 / 折扣率 |
get_cost_breakdown(period) |
既有 compute_cost_rate_chart | 食材% / 人事% / 營運% / 利潤% | |
get_revenue_per_guest_trend(days=30) |
既有 SMA-7 + percentile | 客單價時序 + 百分位帶 | |
get_profit_comparison(period_a, period_b) |
既有 EWMA | 兩期利潤對比 + 變化率 | |
| 來客 / 訂位 | get_guest_volume(period, dimensions[]) |
既有 + 擴充 dimensions | 來客數 + 熱力圖 + 維度切片 |
get_guest_forecast(date_range) |
既有 Ridge regression | 預測值 + 信賴區間 | |
get_no_show_rate(period) |
新(從 reservation_status_history 算) | No-show % + 取消 % | |
get_customer_segments() |
既有 Pearson | 分群統計 | |
| 外部因子 | get_weather_forecast(restaurant_id, date_range) |
新(對接 CWA 中央氣象局) | 溫度 / 雨機率 / 天氣分類 |
get_holiday_calendar(date_range) |
新(對接政府開放資料) | 國定假日 / 連假 / 節氣 | |
get_local_events(restaurant_geocode, date_range) |
新(Phase 2,可接 Google Events API) | 附近活動 / 演唱會 / 展覽 | |
| 關聯分析 | compute_correlation(metric, factor[]) |
既有 Pearson 擴充 | 相關係數 + p-value |
compare_period(metric, period_a, period_b) |
新(YoY / WoW / DoD 通用) | 同期對比表 | |
find_outliers(metric, period) |
既有 Shewhart 2σ wrap | 異常點清單 + 原因 hint | |
| 菜單 / 庫存 | get_top_selling_items(period, top_n) |
新 | 熱賣品 ranking + 毛利率 |
get_dead_inventory(period_days) |
新(滯銷品) | 低銷量品 + 庫存成本 |
// tools/get_revenue_summary.ts
export const TOOL_SPEC = {
name: 'get_revenue_summary',
description: '取得指定期間總營收 / 客單價 / 來客數',
parameters: {
period: { type: 'string', enum: ['today', 'yesterday', 'this_week',
'last_week', 'this_month', 'last_month', 'custom'] },
custom_start: { type: 'string', format: 'date', required: false },
custom_end: { type: 'string', format: 'date', required: false },
granularity: { type: 'string', enum: ['day', 'week', 'month'] },
},
output: {
type: 'object',
properties: {
total_revenue: { type: 'number' },
guest_count: { type: 'number' },
avg_per_guest: { type: 'number' },
discount_rate: { type: 'number' },
data_points: { type: 'integer', description: '實際有資料的天數' },
data_completeness:{ type: 'number', description: '0-1,期間內有資料天數比' },
},
},
}
export async function execute(params, ctx) {
// 1. 驗證 restaurant_id 屬於 ctx.user
// 2. 從 materialized view 撈
// 3. 回 schema-compliant object
}
老闆問題進來,先 LLM 做 lightweight 分類(便宜模型,例 Haiku):
| Intent | 觸發詞例 | 對應 tool combo |
|---|---|---|
revenue_lookup | 今天 / 上週 / 這月 營業額 | get_revenue_summary |
forecast | 明天 / 下週 / 預測 / 大概多少人 | get_guest_forecast + get_weather_forecast |
compare_period | 跟去年 / 跟上週比 / 同期 | compare_period |
impact_analysis | 天氣影響 / 假日 / 雨天 / 影響 | compute_correlation + get_weather_forecast / get_holiday_calendar |
top_items | 賣得最好 / 熱賣 / 滯銷 | get_top_selling_items / get_dead_inventory |
cost_analysis | 成本 / 食材% / 利潤 | get_cost_breakdown |
customer_insight | 客人是誰 / 回流 / 客群 | get_customer_segments |
operations | 取消率 / no-show / 預訂 | get_no_show_rate |
casual | 閒聊 / 不在領域內 | 不呼叫 tool,直接 LLM 回答 + 標「未從資料生成」 |
分類完後,Claude(較強模型)做 multi-tool call:
impact_analysis,factor=weather| Tool 回 data_completeness | Orchestrator 行為 | Manifest 上 widget 顯示 |
|---|---|---|
| ≥ 0.7 | 用真實資料 | 正常 chart + 信任度 高 |
| 0.3 - 0.7 | 混合真實 + industry default | chart + 信任度 中 + 提示「N 天歷史,參考用」 |
| < 0.3 | 純 industry default | chart + 信任度 低 + 紅字「資料不足,僅為餐飲業平均」 |
| = 0(完全無資料) | 不畫圖,直接顯示「累積 X 天後可看到結果」 | EmptyState widget + 預期可解鎖日期 |
關鍵:AI 回的不是 HTML,不是自由 markdown。是嚴格 JSON schema 的 Manifest,UI 才有辦法限制 AI 不亂畫。
{
"version": "1.0",
"question": "明天天氣會不會影響訂位?",
"intent": "impact_analysis",
"trust": {
"tier": "silver", // bronze / silver / gold
"score": 72, // 0-100
"reason": "過去 90 天歷史 (data_completeness=0.95) + Pearson r=0.61"
},
"summary_md": "**明天降雨機率 70%,根據過去 3 個月資料,雨天訂位平均減少 23%。** 建議:備料量可以調降 10-15%,排班可以少 1 個外場。",
"sources": [
{ "tool": "get_weather_forecast", "params": {"date": "2026-05-19"} },
{ "tool": "compute_correlation", "params": {"metric": "guest_count", "factor": "weather"} },
{ "tool": "get_guest_volume", "params": {"period": "90d", "dimensions": ["weather_class"]} }
],
"widgets": [
{
"id": "w1",
"type": "KPICard",
"props": {
"title": "明天預測來客數",
"value": "32-38 人",
"delta": "-25%",
"delta_label": "vs 一般週六",
"trust_badge": "silver"
}
},
{
"id": "w2",
"type": "InsightCallout",
"props": {
"icon": "weather-rain",
"title": "預估影響",
"body_md": "降雨機率 70% + 過去雨天平均訂位下降 23%(p<0.01,顯著)。",
"actions": [
{ "label": "看歷史雨天紀錄", "open_source": "w4" }
]
}
},
{
"id": "w3",
"type": "BarComparison",
"props": {
"title": "雨天 vs 晴天 訂位數對比",
"series": [
{ "name": "晴天平均", "value": 42 },
{ "name": "雨天平均", "value": 32 }
],
"footnote": "資料區間:2026-02-18 ~ 2026-05-18 (90 天)"
}
},
{
"id": "w4",
"type": "DataTableWithFootnote",
"collapsed": true, // 預設摺疊,點開才展開
"props": {
"headers": ["日期", "天氣", "訂位數", "實到"],
"rows": [/* ... */],
"footnote": "來源:reservations + 中央氣象局 CWA API"
}
}
],
"dimension_selectors": [ // 老闆可切換 UI 的維度
{
"id": "weather-dim",
"label": "影響因子",
"options": [
{ "value": "weather", "label": "天氣", "active": true },
{ "value": "holiday", "label": "節假日" },
{ "value": "weekday", "label": "星期幾" },
{ "value": "combined", "label": "綜合分析" }
]
}
],
"followup_prompts": [
"那如果是連假?",
"去年同期(5/19)賣多少?",
"雨天最賣的菜是?"
]
}
原則:參考麥肯錫 / BCG / 一般 BI tool(Looker, Mode, Metabase)常用的報告元件,自製 ChefsMate Stitch 風格版。AI 只能挑這些,不能自繪。
| Prop | 說明 |
|---|---|
trust_badge | bronze / silver / gold,右上角顯示 |
source_ref | 對應 Manifest.sources index — 點 icon 展開 source panel(NotebookLM 風) |
collapsed | 預設摺疊?常用於 DataTable raw 資料 |
annotation | AI 加的小說明(底部 caption) |
計算公式:
trust_score =
data_volume_score × 0.50 # 你有多少歷史資料?
+ algorithm_grade × 0.30 # 用什麼演算法計算?
+ statistical_quality × 0.20 # p-value / R² / 異常率
| 歷史資料天數 | 分數 | tier |
|---|---|---|
| 0 天 | 0 | 無 |
| 1-7 天 | 25 | bronze |
| 8-30 天 | 50 | bronze/silver |
| 31-90 天 | 75 | silver |
| 91-365 天 | 90 | silver/gold |
| 365+ 天 | 100 | gold(可做 YoY) |
| 演算法 | 分數 | 說明 |
|---|---|---|
| 純 industry default | 0 | 沒有 personalize |
| 簡單平均 / 中位數 | 30 | baseline |
| SMA-7(7 日移動平均) | 60 | 有 smoothing |
| Shewhart 2σ / EWMA | 75 | 有統計檢驗 |
| Ridge regression / Bayesian | 90 | 學習模型 |
| 外接 LLM 解釋(non-numerical) | 50 | 解釋性高但無 ground truth |
| tier | 分數區間 | 視覺 | 老闆看到的暗示 |
|---|---|---|---|
| gold | 85+ | 金色徽章 | 「這個你可以放心參考做決策」 |
| silver | 60-84 | 銀色徽章 | 「方向對,細節可能 ±15%」 |
| bronze | 30-59 | 銅色徽章 | 「參考用,資料還在累積」 |
| 無 | < 30 | 「資料不足」灰標 | 不顯示數字,改顯示「再 N 天可看到結果」 |
report-analytics RPC 已回 high/medium/low,直接 map:high=gold, medium=silver, low=bronze。smart-seating 用 0.3-1.0 numeric 也直接 ×100 映射。不重複造輪。
| 階段 | 歷史 | 能回答什麼 | 不能回答什麼 | UI 預設 |
|---|---|---|---|---|
| Day 0(註冊當天) | 無 | 「餐飲業平均食材成本約 30-35%,你的目標應該設多少?」 純 industry default + LLM 知識 |
YoY / 趨勢 / 預測 | 歡迎卡 + 「累積 7 天可看到第一份報告」countdown |
| Week 1-4(1 個月內) | 7-30 天 | 當期營收統計 / 熱賣前 5 / 簡單平均對比 | 顯著性檢驗 / 預測模型(資料太少) | bronze 標籤普遍 + 提示「資料累積中」 |
| Month 2-3 | 30-90 天 | SMA-7 趨勢 / EWMA 平滑 / 簡單 Pearson 相關 | YoY 對比 / 罕見事件預測 | silver 大宗 + 部分 gold(營收統計類) |
| 3 個月+ | 90 天以上 | Ridge regression 預測 / 顯著性檢驗 / 細分群分析 | (到 365 天前)YoY 對比 | 主要 gold + 少量 silver |
| 1 年+ | 365 天以上 | YoY / 節氣循環 / 多年趨勢 | — | 全 gold |
impact_analysis(factor=weather)| Phase | 範圍 | 預估時程 | 產出 |
|---|---|---|---|
| Phase 0(基礎) 1-2 週 |
把現有 report-analytics 7 個演算法 wrap 成 tool function 規格,定義 input/output schema。建立 Data Mart materialized views。 | 1-2 週 | Tool registry + schema validator |
| Phase 1(MVP) 2-3 週 |
5 個 widget(KPICard / LineChart / BarComparison / InsightCallout / EmptyState)+ Orchestrator + 接 Claude API + 信任分數 + 5 個 intent(revenue / forecast / compare / impact / casual) | 2-3 週 | 能問 5 類問題,動態 render 5 種 widget |
| Phase 2(擴充) 2 週 |
剩下 7 個 widget + 接 CWA 天氣 + 政府假日 API + dimension selector 切換 | 2 週 | 外部因子完整接 |
| Phase 3(NotebookLM-style) 1-2 週 |
每個 widget 加 source panel / 老闆可 pin 報告 / 可分享 link / followup_prompts chip | 1-2 週 | 互動性接近 NotebookLM |
| Phase 4(進階) 後續 |
多店比較 / 集團報告 / Audio overview(TTS)/ 自動生成 weekly digest email | 後續 | — |
| 風險 | 緩解 |
|---|---|
| LLM 成本失控 | Intent classifier 用便宜 Haiku;只有 manifest 生成用 Sonnet;cache 同問題 24h |
| LLM 幻覺(編造數字) | Manifest 內的數字必須引用 Layer 1 tool result,prompt 嚴格約束「不可自己算 / 不可猜測」;Schema validator 拒絕沒有 source 的 widget |
| 冷啟動資料不足 → 老闆覺得沒用 | 明顯標 tier;industry default 也是有用洞察;onboarding 問店類預載對應 default |
| 老闆問題分類錯 | Intent classifier 不確定時走「general」走 LLM 自由回答 + 標「無法用資料生成」 |
| UI 元件不夠用 → AI 想自繪 | Widget library 漸進擴充;不夠用先用 InsightCallout 文字 + 圖片連結 fallback,絕不放 raw HTML |