diff --git a/services/openclaw_strategist_service.py b/services/openclaw_strategist_service.py index 1732907..a4bc1bc 100644 --- a/services/openclaw_strategist_service.py +++ b/services/openclaw_strategist_service.py @@ -7,6 +7,54 @@ from database.manager import get_session from sqlalchemy import text from services.openclaw_learning_service import build_rag_context_by_date, store_insight + +def _build_citation_footer(start_date: str, end_date: str) -> str: + """ + 查詢 ai_insights 中 [start_date, end_date] 區間的洞察來源, + 回傳結構化引用區塊供週報末尾附加。 + """ + session = get_session() + try: + rows = session.execute(text(""" + SELECT + DATE(created_at)::text AS day, + insight_type, + COUNT(*) AS cnt + FROM ai_insights + WHERE DATE(created_at) BETWEEN :s AND :e + AND status NOT IN ('archived') + GROUP BY DATE(created_at), insight_type + ORDER BY DATE(created_at), insight_type + """), {"s": start_date, "e": end_date}).fetchall() + + if not rows: + return "" + + TYPE_LABEL = { + "price_alert": "競價告警", + "human_review": "人工覆核", + "recommendation": "推薦商品", + "km_price_competition": "KM競價情報", + "km_sales_anomaly": "KM銷量異常", + "km_promotion_opportunity": "KM促銷機會", + "km_market_trend": "KM市場趨勢", + "relearn_event": "重新學習事件", + "backup_status": "備份狀態", + } + + lines = ["\n\n---", "📚 **本報告引用來源:**"] + for day, itype, cnt in rows: + label = TYPE_LABEL.get(itype, itype) + lines.append(f"• {day} 的「{label}」洞察({cnt} 筆)") + lines.append(f"\n> 資料區間:{start_date} ~ {end_date}," + f"由 Hermes / NemoTron / OpenClaw 三層 AI 系統自動蒐集") + return "\n".join(lines) + except Exception as e: + sys_log.warning(f"[OCStrategist] citation footer 查詢失敗: {e}") + return "" + finally: + session.close() + sys_log = SystemLogger("OCStrategist").get_logger() try: @@ -128,11 +176,17 @@ def generate_weekly_strategy_report(force_tg_alert: bool = False) -> str: (根據上述現象,具體給出下週該執行的行銷加碼、特價活動或降價策略) 請用繁體中文,語氣保持專業、精煉、具備行動力。 +在報告的每個具體數據或告警描述後,若來自「資料二」,請在句末標注【引用自 {start_date_str} ~ {end_date_str} 的洞察】。 """ # 3. 呼叫 Gemini report_md = _call_gemini_flash(prompt) - + + # 附加 KM 引用來源區塊(無論 Gemini 是否成功,皆嘗試附加) + citation_footer = _build_citation_footer(start_date_str, end_date_str) + if citation_footer: + report_md = report_md + citation_footer + # 4. Dual-Write 存入 ai_insights 知識庫 if not report_md.startswith("⚠️ 呼叫 Gemini 失敗"): insight_id = store_insight(