feat: 内容数据迁移至数据库,合规审查全链路打通
- 文章 HTML 存储从文件系统迁移至 articles 表,删除 releases 目录 - 合规审查从 DB 读取 HTML,审查结果写回 DB,通过后自动推进至待发布 - 新增 todayCount 筛选按钮,与系统概览统计数据一致 - 全屏预览修复:提升 z-index 超过侧边栏,添加退出全屏/关闭按钮 - 统一 '优化' → '审查' 命名,消除前后端术语不一致 - 调度器创作完成后自动触发审查(生成 → 审查 → 待发布) - 清理旧备份/调试文件、过期大纲和研究笔记
This commit is contained in:
+88
-21
@@ -1,21 +1,25 @@
|
||||
#!/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" # 研究笔记输出目录
|
||||
OUTPUT_DIR = DATA_DIR / "research"
|
||||
LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
|
||||
TODAY = datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
@@ -43,12 +47,10 @@ class Researcher:
|
||||
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:
|
||||
# 日期过滤:仅保留 2025 年及以后(支持 YYYY-MM-DD 或 YYYY 格式)
|
||||
case_date = case.get('date', '')
|
||||
if case_date:
|
||||
m = re.search(r'(\d{4})', str(case_date))
|
||||
@@ -57,7 +59,6 @@ class Researcher:
|
||||
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:
|
||||
@@ -67,9 +68,43 @@ class Researcher:
|
||||
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 ""
|
||||
prompt = f"""你是一个行业研究员。基于以下选题和相关案例,写一段研究发现。
|
||||
|
||||
## 选题
|
||||
标题:{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}
|
||||
|
||||
## 相关案例({len(cases)}个)
|
||||
{cases_text}
|
||||
|
||||
## 要求
|
||||
- 提炼2-3个真正有价值的洞察,不是每个案例都硬凑一条
|
||||
- 每条洞察1-2句话,说人话,不要列1/2/3
|
||||
- 避免「首先其次最后」「综上所述」
|
||||
- 指出1-2个你不太确定的方向,作为后续研究的提示"""
|
||||
try:
|
||||
return call_llm(prompt, temperature=0.5, max_tokens=1200, system_prompt="你是一个行业研究员,擅长从案例中发现真洞察。")
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM 研究发现摘要生成失败: {e}")
|
||||
return ""
|
||||
|
||||
def generate_notes(self) -> str:
|
||||
"""生成研究笔记 Markdown"""
|
||||
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## 选题信息",
|
||||
@@ -78,6 +113,8 @@ class Researcher:
|
||||
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):
|
||||
@@ -89,17 +126,47 @@ class Researcher:
|
||||
f"- **关键数据**: {case.get('key_metrics', '无')}",
|
||||
""
|
||||
])
|
||||
lines.extend([
|
||||
"## 研究发现摘要",
|
||||
"- 待补充:从案例中提炼的趋势和洞察",
|
||||
"- 待补充:数据支撑",
|
||||
"",
|
||||
"## 待深入研究的问题",
|
||||
"- [ ] 需要更多本土数据",
|
||||
"- [ ] 需要验证某些结论的适用性",
|
||||
"",
|
||||
f"*生成时间:{TODAY}*"
|
||||
])
|
||||
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user