feat(ppt): market_intel_weekly external signal aggregation v3.1.0
All checks were successful
CD Pipeline / deploy (push) Successful in 3m6s
All checks were successful
CD Pipeline / deploy (push) Successful in 3m6s
Wave 3.2:市場情報週報 — 把 mcp_collector 所有外部 API 彙整成一份內部簡報。 generate_market_intel_weekly_ppt — 7 頁 - P1 封面:本週重點/市場機會/風險警訊三句話(從 AI 自動抽) - P2 節慶日曆 + 季節情境(雙卡) - P3 電商新聞 + Google 熱搜(雙卡) - P4 Dcard 口碑 + YouTube 爆紅(雙卡) - P5 天氣 + 匯率(雙卡,影響消費行為) - P6 AI 整合洞察與行動建議 - P7 附錄 外部資料來源(mcp_collector_service / mcp_context_service): - mcp_collector.get_holiday_context() 節慶日曆(靜態) - mcp_collector.get_seasonal_context() 季節情境(靜態) - get_ecommerce_news() 電商產業新聞(Gemini Grounding) - get_taiwan_trends() Google 台灣熱搜 - get_dcard_trends() Dcard 熱門討論 - get_youtube_trending() YouTube 爆紅商品 - get_taiwan_weather() 台灣天氣 - get_twbank_exchange_rates() 台幣匯率 每個外部 API 用 _safe() wrapper,失敗時填預設文字「(本次擷取失敗或無資料)」。 _ppt_ai_analysis 加 is_market_intel 分支 - 角色:行銷情報分析師 + BU 主管 - 結構:本週市場大事 / 消費者情緒口碑 / 競爭態勢差異化 / SMART 三層 / 外部風險預警 - max_tokens 2000 路由: - /ppt market_intel 本週情報週報(自動對齊週一) Telegram 按鈕:「🌐 市場情報週報」 bump TEMPLATE_VERSIONS['market_intel'] = v3.1.0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1890,6 +1890,7 @@ def _ppt_ai_analysis(prompt_data: str, report_type: str = '') -> str:
|
||||
is_forecast = '檔期前瞻' in report_type or 'forecast' in report_type
|
||||
is_promo_cmp = '多活動' in report_type or 'promo_compare' in report_type
|
||||
is_new_prod = '新品' in report_type or 'new_product' in report_type
|
||||
is_market_intel = '市場情報' in report_type or 'market_intel' in report_type
|
||||
|
||||
# ── 格式鐵律(所有 prompt 共用後綴)────────────────────────
|
||||
FORMAT_RULES = (
|
||||
@@ -2047,6 +2048,41 @@ def _ppt_ai_analysis(prompt_data: str, report_type: str = '') -> str:
|
||||
+ FORMAT_RULES
|
||||
)
|
||||
max_tokens = 1400
|
||||
elif is_market_intel:
|
||||
sys_instruction = (
|
||||
"你身兼 (1) 行銷情報分析師(精通競品監控、消費趨勢、社群口碑分析)"
|
||||
"(2) BU 主管(策略決策層級的市場敏感度)。\n"
|
||||
"你的客戶是 momo CEO/BU 主管/行銷主管,會用本報告了解外部市場大局,"
|
||||
"做下週的廣告投放、檔期備戰、競品阻擊決策。\n\n"
|
||||
f"請針對以下{report_type}(外部資料彙整:節慶日曆、季節情境、電商新聞、"
|
||||
"Google Trends、Dcard、YouTube、天氣、匯率)輸出戰略洞察:\n\n"
|
||||
"【本週市場大事】(3-4 句)\n"
|
||||
"(1) 即將到來的關鍵檔期與其對 momo 業績的影響預估\n"
|
||||
"(2) 當前最熱門的消費賽道(依 Google Trends + Dcard + YouTube 信號)\n"
|
||||
"(3) 本週外部最大風險(如競品大檔期、不利天氣、匯率波動)。\n\n"
|
||||
"【消費者情緒與口碑解讀】(3-4 句)\n"
|
||||
"Dcard 熱門討論主題反映什麼消費焦慮(價格 / 安全 / 認證 / 永續);"
|
||||
"YouTube 爆紅商品的特徵(顏值 / 解決痛點 / 名人推薦 / IP 聯名);"
|
||||
"建議 momo 在哪些品類加碼選品或行銷投入。\n\n"
|
||||
"【競爭態勢與差異化】(3-4 句)\n"
|
||||
"蝦皮、PChome、酷澎、博客來等競品本週動態(依電商新聞);"
|
||||
"momo 應該強化哪些差異化武器(會員訂閱 / 直播帶貨 / 富邦銀行折扣);"
|
||||
"若競品有大檔期,給出阻擊策略(價格戰避戰 / 服務力差異 / 限時加碼)。\n\n"
|
||||
"【行動建議 — SMART 框架】\n"
|
||||
"■ 本週立即執行(3 條,✅ 開頭):廣告投放調整 / 商品上架 / 競品比價\n"
|
||||
"■ 下週預備(3 條,✅ 開頭):檔期商品規劃 / 行銷檔期協作 / 庫存備援\n"
|
||||
"■ 本月戰略(2 條,✅ 開頭):消費賽道選品 / 行銷主題定位\n"
|
||||
"每條必須 SMART:具體商品/品類 + 量化目標 + 期限。\n\n"
|
||||
"【最大三大外部風險】(2-3 句)\n"
|
||||
"(a) 政策法規變動(如電商營業稅、進口商品法規)\n"
|
||||
"(b) 匯率波動(影響進口商品成本)\n"
|
||||
"(c) 競品大檔期或新平台進入(如 Temu、酷澎激進補貼)\n\n"
|
||||
"要求:每段引用具體外部信號(Trend 關鍵字、Dcard 主題、新聞標題等),"
|
||||
"全文 800~1100 字,禁用模糊用詞。"
|
||||
+ MARKET_TREND_2026
|
||||
+ FORMAT_RULES
|
||||
)
|
||||
max_tokens = 2000
|
||||
elif is_new_prod:
|
||||
sys_instruction = (
|
||||
"你身兼 (1) PM 商品經理(精通新品上架 / 商品生命週期 / SKU 健康度)"
|
||||
@@ -2791,7 +2827,7 @@ def _generate_ppt_cmd(sub_type: str, sub_arg: str, _chat_id: int, target: str,
|
||||
generate_vendor_ppt, generate_period_review_ppt,
|
||||
generate_category_deep_ppt, generate_customer_analytics_ppt,
|
||||
generate_forecast_pre_event_ppt, generate_promo_compare_ppt,
|
||||
generate_new_product_ppt,
|
||||
generate_new_product_ppt, generate_market_intel_weekly_ppt,
|
||||
check_pptx_available
|
||||
)
|
||||
except ImportError:
|
||||
@@ -3316,6 +3352,62 @@ def _generate_ppt_cmd(sub_type: str, sub_arg: str, _chat_id: int, target: str,
|
||||
})
|
||||
return ppt_path
|
||||
|
||||
elif sub_type in ('market_intel', 'intel', '市場情報', '情報週報'):
|
||||
# /ppt market_intel 本週市場情報
|
||||
from datetime import datetime as _dt, timedelta as _td
|
||||
today = now.date() if hasattr(now, 'date') else now
|
||||
# 對齊週一作為週起點
|
||||
week_start = today - _td(days=today.weekday())
|
||||
week_label = f"{week_start.strftime('%Y/%m/%d')} 起一週"
|
||||
|
||||
params = {'report_type': 'market_intel', 'week': week_label}
|
||||
cached, cached_ai = _load_cached_ppt_path_and_analysis('market_intel', params)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
# 抓 mcp_collector 各 API(容錯:失敗段落填預設文字)
|
||||
from services.mcp_collector_service import mcp_collector
|
||||
from services.mcp_context_service import (
|
||||
get_ecommerce_news, get_taiwan_trends, get_dcard_trends,
|
||||
get_youtube_trending, get_taiwan_weather, get_twbank_exchange_rates,
|
||||
)
|
||||
|
||||
def _safe(fn, default='(本次擷取失敗或無資料)'):
|
||||
try:
|
||||
r = fn()
|
||||
return r.strip() if r and r.strip() else default
|
||||
except Exception as e:
|
||||
sys_log.warning(f"[market_intel] {fn.__name__} fail: {e}")
|
||||
return default
|
||||
|
||||
sections = {
|
||||
'holiday': mcp_collector.get_holiday_context(),
|
||||
'seasonal': mcp_collector.get_seasonal_context(),
|
||||
'ecommerce_news': _safe(get_ecommerce_news),
|
||||
'google_trends': _safe(get_taiwan_trends),
|
||||
'dcard': _safe(get_dcard_trends),
|
||||
'youtube': _safe(get_youtube_trending),
|
||||
'weather': _safe(get_taiwan_weather),
|
||||
'exchange': _safe(get_twbank_exchange_rates),
|
||||
}
|
||||
|
||||
# 組 data summary 給 AI
|
||||
data_summary_parts = [f"【本週】{week_label}"]
|
||||
for k, v in sections.items():
|
||||
data_summary_parts.append(f"\n【{k}】\n{v[:600]}")
|
||||
data_summary = '\n'.join(data_summary_parts)
|
||||
|
||||
ai_text = cached_ai or _ppt_ai_analysis(data_summary, '市場情報週報')
|
||||
if not cached_ai and _ppt_needs_fallback(ai_text):
|
||||
ai_text = _ppt_fallback_insight('市場情報', data_summary, '')
|
||||
|
||||
ppt_path = generate_market_intel_weekly_ppt(week_label, sections, ai_text)
|
||||
_store_ppt_cache('market_intel', params, ppt_path, {
|
||||
'report_type': 'market_intel', 'parameters': params,
|
||||
'data_summary': data_summary, 'analysis': ai_text, 'mcp': '',
|
||||
})
|
||||
return ppt_path
|
||||
|
||||
elif sub_type in ('new_product', 'newproduct', '新品', '新品追蹤'):
|
||||
# /ppt new_product 預設 30 天追蹤
|
||||
# /ppt new_product 14 自訂追蹤天數
|
||||
|
||||
@@ -220,7 +220,8 @@ def _submenu_reports():
|
||||
('👥 客戶/訂單分析', 'cmd:ppt:customer')),
|
||||
_row(('🎯 檔期前瞻報告', 'await:forecast_event'),
|
||||
('🆚 多活動比較', 'await:promo_compare')),
|
||||
_row(('🆕 新品 30 天追蹤', 'cmd:ppt:new_product'),),
|
||||
_row(('🆕 新品 30 天追蹤', 'cmd:ppt:new_product'),
|
||||
('🌐 市場情報週報', 'cmd:ppt:market_intel')),
|
||||
])
|
||||
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ TEMPLATE_VERSIONS = {
|
||||
'forecast_pre_event': 'v3.1.0', # 2026-05-03 檔期前瞻報(baseline × lift_factor 預測 + 去年同檔期)
|
||||
'promo_compare': 'v3.1.0', # 2026-05-03 多活動 ROI 並排比較
|
||||
'new_product': 'v3.1.0', # 2026-05-03 新品 30 天追蹤(PM/採購)
|
||||
'market_intel': 'v3.1.0', # 2026-05-03 市場情報週報(外部資料彙整)
|
||||
}
|
||||
|
||||
|
||||
@@ -2952,6 +2953,164 @@ def generate_vendor_ppt(yr, mo, db_data, ai_text: str) -> str:
|
||||
return path
|
||||
|
||||
|
||||
# ── 市場情報週報(外部資料彙整)────────────────────────────────────────
|
||||
def generate_market_intel_weekly_ppt(week_label: str, db_data: dict, ai_text: str) -> str:
|
||||
"""市場情報週報 v3.1(CEO/BU 主管 / 行銷主管 三方共讀)
|
||||
將 mcp_collector 的所有外部資料彙整成內部參考簡報
|
||||
P1 封面:本週市場大事三句話
|
||||
P2 節慶/檔期日曆(當週 + 下兩週)
|
||||
P3 季節情境與消費行為趨勢
|
||||
P4 電商新聞動態(Gemini Grounding)
|
||||
P5 Google Trends 熱搜 + Dcard 口碑
|
||||
P6 YouTube 熱門商品
|
||||
P7 天氣與匯率影響
|
||||
P8 AI 整合洞察與行動建議
|
||||
P9 附錄
|
||||
"""
|
||||
from pptx import Presentation
|
||||
from pptx.util import Cm
|
||||
|
||||
prs = Presentation()
|
||||
prs.slide_width = Cm(33.87)
|
||||
prs.slide_height = Cm(19.05)
|
||||
W = 33.87
|
||||
|
||||
sections = db_data or {}
|
||||
|
||||
# ── P1 封面 ───────────────────────────────────────────
|
||||
slide = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
H = 19.05
|
||||
_add_rect(slide, 0, 0, W, H, _BG_PAPER)
|
||||
_add_rect(slide, 0, 0, 3.0, H, _KPI_HONEY)
|
||||
_add_rect(slide, 2.85, 0, 0.15, H, _BRAND_OG2)
|
||||
_add_rect(slide, W - 6.0, 0, 6.0, 0.45, _BRAND_OG2)
|
||||
_add_rect(slide, W - 6.0, 0.45, 6.0, 0.12, _KPI_HONEY)
|
||||
_add_rect(slide, 4.0, 8.4, 22.0, 0.06, _KPI_HONEY)
|
||||
|
||||
_add_rect(slide, 3.8, 1.4, 4.8, 0.85, _BRAND_OG2)
|
||||
_add_text(slide, "OPENCLAW", 3.8, 1.42, 4.8, 0.81,
|
||||
bold=True, size=12, color=_WHITE, align="center", valign="middle",
|
||||
latin_font=_FONT_LABEL)
|
||||
_add_text(slide, "MARKET INTELLIGENCE WEEKLY · EXTERNAL SIGNALS",
|
||||
3.8, 2.45, 22, 0.55,
|
||||
bold=True, size=10, color=_BRAND_OG2,
|
||||
latin_font=_FONT_LABEL)
|
||||
_add_text(slide, f"市場情報週報\n{week_label}",
|
||||
3.8, 3.2, 25, 5.0,
|
||||
bold=True, size=42, color=_DARK_TEXT,
|
||||
latin_font=_FONT_DISPLAY, ea_font=_FONT_BODY_EA)
|
||||
_add_rect(slide, W - 9.0, 3.4, 5.0, 1.1, _KPI_HONEY)
|
||||
_add_text(slide, "外部信號整合",
|
||||
W - 9.0, 3.45, 5.0, 1.0,
|
||||
bold=True, size=14, color=_WHITE, align="center", valign="middle",
|
||||
ea_font=_FONT_BODY_EA)
|
||||
_add_text(slide,
|
||||
f"來源:節慶日曆 · Google Trends · Dcard · YouTube · "
|
||||
f"電商新聞 · 天氣 · 匯率",
|
||||
3.8, 8.7, 27, 0.85,
|
||||
size=12, color=_BRAND_OG2,
|
||||
latin_font=_FONT_DISPLAY, ea_font=_FONT_BODY_EA)
|
||||
|
||||
# 三句話本週要點(從 AI text 抽前三段)
|
||||
ai_lines = [l.strip() for l in (ai_text or '').split('\n')
|
||||
if l.strip() and not l.strip().startswith(('【', '■', '✅'))][:3]
|
||||
pitch_y = 10.2
|
||||
for i, (color, label) in enumerate([
|
||||
("C96442", "🎯 本週重點"),
|
||||
("B88416", "📊 市場機會"),
|
||||
("8F4530", "⚠ 風險警訊"),
|
||||
]):
|
||||
py = pitch_y + i * 1.9
|
||||
_add_rect(slide, 3.8, py, 0.45, 1.5, color)
|
||||
_add_text(slide, label, 4.4, py + 0.1, 27, 0.55,
|
||||
bold=True, size=11, color=color,
|
||||
ea_font=_FONT_BODY_EA, latin_font=_FONT_LABEL)
|
||||
body = ai_lines[i] if i < len(ai_lines) else "(待 AI 補充)"
|
||||
_add_text(slide, body[:100],
|
||||
4.4, py + 0.7, 27, 0.75,
|
||||
size=12, color=_DARK_TEXT,
|
||||
latin_font=_FONT_DISPLAY, ea_font=_FONT_BODY_EA)
|
||||
|
||||
_add_text(slide, "Generated by OpenClaw AI Agent",
|
||||
W - 7.5, H - 1.4, 7.0, 0.5,
|
||||
size=9, color=_SUBTEXT, align="right", latin_font=_FONT_LABEL)
|
||||
_add_text(slide, f"📅 {datetime.now().strftime('%Y/%m/%d %H:%M')}",
|
||||
W - 7.5, H - 1.95, 7.0, 0.5,
|
||||
bold=True, size=11, color=_BRAND_OG2, align="right",
|
||||
latin_font=_FONT_DISPLAY, ea_font=_FONT_BODY_EA)
|
||||
_add_footer(slide, W)
|
||||
|
||||
# 內容卡片頁 helper(每頁 2 卡)
|
||||
def _intel_double_card(prs, title_text, card1_title, card1_body, card1_color,
|
||||
card2_title, card2_body, card2_color):
|
||||
s = prs.slides.add_slide(prs.slide_layouts[6])
|
||||
_add_rect(s, 0, 0, W, _SLIDE_H, _BG_PAPER)
|
||||
_add_header(s, title_text)
|
||||
col_w = (W - 1.2) / 2
|
||||
col1_x = 0.4
|
||||
col2_x = 0.4 + col_w + 0.4
|
||||
|
||||
for x, t, b, color in [(col1_x, card1_title, card1_body, card1_color),
|
||||
(col2_x, card2_title, card2_body, card2_color)]:
|
||||
_add_rect(s, x, 1.95, col_w, 13.4, _WHITE, line_hex=_SUBTLE)
|
||||
_add_rect(s, x, 1.95, col_w, 0.85, color)
|
||||
_add_text(s, t, x + 0.4, 2.05, col_w - 0.6, 0.65,
|
||||
bold=True, size=13, color=_WHITE, valign="middle",
|
||||
ea_font=_FONT_BODY_EA)
|
||||
body_text = (b or '').strip() or "(暫無資料)"
|
||||
_add_text(s, body_text,
|
||||
x + 0.5, 3.0, col_w - 1.0, 12.2,
|
||||
size=12, color=_DARK_TEXT, wrap=True,
|
||||
latin_font=_FONT_DISPLAY, ea_font=_FONT_BODY_EA)
|
||||
_add_footer(s, W)
|
||||
|
||||
# ── P2: 節慶日曆 + 季節情境 ──────────────────────────
|
||||
_intel_double_card(prs, "📅 節慶檔期 + 季節情境",
|
||||
"🎉 本月+下月關鍵檔期",
|
||||
sections.get('holiday', '(暫無檔期資料)'),
|
||||
_BRAND_OG,
|
||||
"🌿 季節消費情境",
|
||||
sections.get('seasonal', '(暫無季節資料)'),
|
||||
_KPI_MAHOGANY)
|
||||
|
||||
# ── P3: 電商新聞 + Google Trends ────────────────────
|
||||
_intel_double_card(prs, "📰 電商新聞 + 🔥 Google 熱搜",
|
||||
"📰 電商產業新聞",
|
||||
sections.get('ecommerce_news', '(無資料)'),
|
||||
_KPI_HONEY,
|
||||
"🔥 Google 台灣熱搜",
|
||||
sections.get('google_trends', '(無資料)'),
|
||||
_BRAND_OG)
|
||||
|
||||
# ── P4: Dcard + YouTube ─────────────────────────────
|
||||
_intel_double_card(prs, "💬 Dcard 口碑 + ▶️ YouTube 熱門",
|
||||
"💬 Dcard 熱門討論",
|
||||
sections.get('dcard', '(無資料)'),
|
||||
"8F4530",
|
||||
"▶️ YouTube 爆紅商品",
|
||||
sections.get('youtube', '(無資料)'),
|
||||
_KPI_EARTH)
|
||||
|
||||
# ── P5: 天氣 + 匯率 ──────────────────────────────────
|
||||
_intel_double_card(prs, "🌤 天氣 + 💱 匯率(影響消費行為)",
|
||||
"🌤 台灣近日天氣",
|
||||
sections.get('weather', '(無資料)'),
|
||||
"2D5D80",
|
||||
"💱 台幣匯率(跨境採購成本)",
|
||||
sections.get('exchange', '(無資料)'),
|
||||
"2A7A3F")
|
||||
|
||||
# ── P6: AI 洞察 ───────────────────────────────────────
|
||||
_ai_insight_slide(prs, ai_text)
|
||||
|
||||
# ── P7: 附錄 ──────────────────────────────────────────
|
||||
_appendix_slide(prs, 'market_intel_weekly', week_label)
|
||||
|
||||
path = _new_path("market_intel")
|
||||
prs.save(path)
|
||||
return path
|
||||
|
||||
|
||||
# ── 新品 30 天追蹤報告 ──────────────────────────────────────────────────
|
||||
def generate_new_product_ppt(db_data: dict, ai_text: str) -> str:
|
||||
"""新品 30 天追蹤報告 v3.1(PM/採購用)
|
||||
|
||||
Reference in New Issue
Block a user