feat(ppt): market_intel_weekly external signal aggregation v3.1.0
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:
OoO
2026-05-03 12:39:57 +08:00
parent 95a74c3502
commit fe3cba8496
3 changed files with 254 additions and 2 deletions

View File

@@ -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 自訂追蹤天數

View File

@@ -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')),
])

View File

@@ -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.1CEO/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.1PM/採購用)