#!/usr/bin/env python3 """ 选题研究:信息收集与结构化整理 基于选题方向,收集相关数据/案例/趋势,输出结构化研究笔记供大纲生成使用 """ import json, datetime, logging, sys, re from pathlib import Path from typing import Dict, List PROJECT_ROOT = Path(__file__).parent.parent sys.path.insert(0, str(PROJECT_ROOT)) sys.path.insert(0, str(PROJECT_ROOT / "platform" / "backend")) from db_helper import get_topic_by_id try: from app.core.nvidia_client import call_llm HAVE_LLM = True except ImportError: HAVE_LLM = False from trends import get_trend_context from web_search import enrich_topic_research DATA_DIR = PROJECT_ROOT / "automation" / "data" CASES_FILE = DATA_DIR / "sustainability_cases.json" OUTPUT_DIR = DATA_DIR / "research" LOGS_DIR = PROJECT_ROOT / "automation" / "logs" TODAY = datetime.datetime.now().strftime("%Y-%m-%d") logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[logging.FileHandler(LOGS_DIR / f"research_{TODAY}.log"), logging.StreamHandler()]) logger = logging.getLogger(__name__) class Researcher: def __init__(self, topic_id: str): self.topic_id = topic_id self.topic = self._load_topic() self.cases = self._load_cases() self.output_dir = OUTPUT_DIR / TODAY self.output_dir.mkdir(parents=True, exist_ok=True) def _load_topic(self) -> Dict: topic = get_topic_by_id(self.topic_id) if not topic: raise ValueError(f"Topic {self.topic_id} not found") return topic def _load_cases(self) -> List[Dict]: if CASES_FILE.exists(): return json.loads(CASES_FILE.read_text(encoding='utf-8')) return [] def find_relevant_cases(self, top_k: int = 5) -> List[Dict]: field = self.topic.get('field', '').lower() title = self.topic.get('title', '').lower() scored = [] for case in self.cases: case_date = case.get('date', '') if case_date: m = re.search(r'(\d{4})', str(case_date)) if m and int(m.group(1)) < 2025: continue score = 0 if field and field in case.get('field', '').lower(): score += 3 case_title = case.get('title', '').lower() for word in title.split(): if len(word) > 2 and word in case_title: score += 1 if score > 0: scored.append((score, case)) scored.sort(key=lambda x: x[0], reverse=True) return [c for _, c in scored[:top_k]] def _llm_summary(self, cases: List[Dict]) -> str: if not HAVE_LLM: return "" cases_text = json.dumps(cases, ensure_ascii=False, indent=2) # 获取实时搜索数据作为 LLM 参考 search_data = enrich_topic_research(self.topic) search_section = f"\n## 实时搜索结果\n{search_data}\n" if search_data else "" _now = datetime.datetime.now() prompt = f"""你是一个行业研究员+内容策略师,擅长从案例中发现真洞察+抢占热点的敏锐嗅觉,能判断什么内容对真实读者最有价值且正被市场热议。 ⚠️ 今天日期:{_now.strftime('%Y年%m月%d日')}。当前年份:{_now.year}年。 基于以下选题和相关案例,写出能支撑文章核心观点、对读者真正有用的研究发现。 ## 选题 标题:{self.topic['title']} 领域:{self.topic.get('field', '')} 核心观点:{self.topic.get('core_concept', '')} 受众痛点:{self.topic.get('audience_pain', '')} 独特视角:{self.topic.get('unique_angle', '')} {search_section} ## 相关案例({len(cases)}个) {cases_text} ## 输出要求(按顺序): 1. **国内外最新热点关联**:当前该领域正在讨论什么、国内外哪些事件/政策/数据在发酵、为什么现在这个话题值得关注。**所有数据必须是{_now.year-1}-{_now.year}年最新数据,禁用一切过时数据** 2. **核心发现**:2-3个真正有价值的洞察。每条需包含这个发现对读者意味着什么,以及支撑数据(附数据来源) 3. **独特观点储备**:哪些角度别人没写过、可以讲出差异化?提供至少一个反向/冷门视角 4. **SEO关键词建议**:重点布局哪些搜索词(3-5个,含1-2个长尾词),以及各平台近期搜索上升趋势 5. **讨论点**:哪个观点最有争议或最可能引发讨论/转发/评论? 6. **市场价值判断**:读完这篇文章,读者能得到什么实际改变/方案/能力? 风格:说人话,直击要点,像资深编辑在给作者做 briefing。避免「首先其次最后」「综上所述」。直接输出内容,不要输出思考过程。""" try: return call_llm(prompt, temperature=0.5, system_prompt="你是一个行业研究员,擅长从案例中发现真洞察。") except Exception as e: logger.warning(f"LLM 研究发现摘要生成失败: {e}") return "" def generate_notes(self) -> str: cases = self.find_relevant_cases() trend_context = get_trend_context(self.topic.get('field')) search_data = enrich_topic_research(self.topic) lines = [ f"# 研究笔记:{self.topic['title']}", f"\n## 选题信息", f"- **ID**: {self.topic['id']}", f"- **领域**: {self.topic.get('field')}", f"- **核心观点**: {self.topic.get('core_concept', '待补充')}", f"- **受众痛点**: {self.topic.get('audience_pain', '待补充')}", f"- **独特视角**: {self.topic.get('unique_angle', '待补充')}", f"\n{trend_context}", f"\n{search_data}" if search_data else "", f"\n## 相关案例({len(cases)}个)\n" ] for i, case in enumerate(cases, 1): lines.extend([ f"### 案例 {i}: {case.get('title')}", f"- **来源**: {case.get('source', '未知')}", f"- **日期**: {case.get('date', '未知')}", f"- **摘要**: {case.get('summary', case.get('description', '无'))}", f"- **关键数据**: {case.get('key_metrics', '无')}", "" ]) llm_summary = self._llm_summary(cases) if llm_summary: lines.extend([ "## 研究发现摘要(LLM 生成)", llm_summary, "" ]) else: insights = [] for case in cases[:3]: summary = case.get('summary', case.get('description', '')) metrics = case.get('key_metrics', '') if summary: insight = f"- {case.get('title', '相关案例')}:{summary[:100]}" if metrics: insight += f"({metrics[:80]})" insights.append(insight) if not insights: insights = ["- 暂未匹配到高度相关的历史案例"] pain_text = self.topic.get('audience_pain', '') topic_insights = [f"- {pain_text[:100]}"] if pain_text else [] lines.extend([ "## 研究发现摘要", "", ]) lines.extend(insights) if topic_insights: lines.extend(topic_insights) lines.extend([ "", "## 待深入研究的问题", ]) if cases: lines.append("- [ ] 验证以上案例在当前选题背景下的适用性") lines.extend([ "- [ ] 收集更多本土一手数据", "- [ ] 确认目标受众的实际反馈", "" ]) lines.append(f"*生成时间:{TODAY}*") return "\n".join(lines) def save(self): notes = self.generate_notes() out_path = self.output_dir / f"{self.topic_id}_research.md" out_path.write_text(notes, encoding='utf-8') logger.info(f"研究笔记已保存: {out_path}") return out_path def main(): import argparse parser = argparse.ArgumentParser() parser.add_argument('--topic-id', required=True, help='选题ID') args = parser.parse_args() r = Researcher(args.topic_id) r.save() print(f"SUCCESS: Research notes created for {args.topic_id}") sys.exit(0) if __name__ == "__main__": main()