""" 新闻流量数据库模块 用于存储和管理新闻流量监测数据 包含:快照、新闻、情绪、预警、AI分析、定时任务日志 """ import sqlite3 import json import logging from datetime import datetime, timedelta from typing import Dict, List, Optional from collections import Counter logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class NewsFlowDatabase: """新闻流量数据库管理类""" def __init__(self, db_path: str = "news_flow.db"): self.db_path = db_path self.init_database() def get_connection(self): """获取数据库连接""" conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row return conn def init_database(self): """初始化数据库表""" conn = self.get_connection() cursor = conn.cursor() # 1. 新闻流量快照表(记录每次监测的整体情况) cursor.execute(''' CREATE TABLE IF NOT EXISTS flow_snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, fetch_time TEXT NOT NULL, total_platforms INTEGER NOT NULL, success_count INTEGER NOT NULL, total_score INTEGER NOT NULL, flow_level TEXT NOT NULL, social_score INTEGER, news_score INTEGER, finance_score INTEGER, tech_score INTEGER, analysis TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) ''') # 2. 平台新闻表(存储各平台的新闻数据) cursor.execute(''' CREATE TABLE IF NOT EXISTS platform_news ( id INTEGER PRIMARY KEY AUTOINCREMENT, snapshot_id INTEGER NOT NULL, platform TEXT NOT NULL, platform_name TEXT NOT NULL, category TEXT NOT NULL, weight INTEGER NOT NULL, title TEXT NOT NULL, content TEXT, url TEXT, source TEXT, publish_time TEXT, rank INTEGER, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id) ) ''') # 3. 股票相关新闻表 cursor.execute(''' CREATE TABLE IF NOT EXISTS stock_related_news ( id INTEGER PRIMARY KEY AUTOINCREMENT, snapshot_id INTEGER NOT NULL, platform TEXT NOT NULL, platform_name TEXT NOT NULL, category TEXT NOT NULL, weight INTEGER NOT NULL, title TEXT NOT NULL, content TEXT, url TEXT, source TEXT, publish_time TEXT, matched_keywords TEXT, keyword_count INTEGER, score INTEGER, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id) ) ''') # 4. 热门话题表 cursor.execute(''' CREATE TABLE IF NOT EXISTS hot_topics ( id INTEGER PRIMARY KEY AUTOINCREMENT, snapshot_id INTEGER NOT NULL, topic TEXT NOT NULL, count INTEGER NOT NULL, heat INTEGER NOT NULL, cross_platform INTEGER, sources TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id) ) ''') # 5. 监测历史统计表(按天汇总) cursor.execute(''' CREATE TABLE IF NOT EXISTS flow_statistics ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL UNIQUE, avg_score INTEGER, max_score INTEGER, min_score INTEGER, snapshot_count INTEGER, top_topics TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ) ''') # 6. 情绪指标记录表【新增】 cursor.execute(''' CREATE TABLE IF NOT EXISTS sentiment_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, snapshot_id INTEGER, sentiment_index INTEGER NOT NULL, sentiment_class TEXT NOT NULL, flow_stage TEXT NOT NULL, momentum REAL, viral_k REAL, flow_type TEXT, stage_analysis TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id) ) ''') # 7. 预警记录表【新增】 cursor.execute(''' CREATE TABLE IF NOT EXISTS flow_alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, alert_type TEXT NOT NULL, alert_level TEXT NOT NULL, title TEXT NOT NULL, content TEXT, related_topics TEXT, trigger_value TEXT, threshold_value TEXT, is_notified INTEGER DEFAULT 0, snapshot_id INTEGER, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id) ) ''') # 8. AI分析记录表【新增】 cursor.execute(''' CREATE TABLE IF NOT EXISTS ai_analysis ( id INTEGER PRIMARY KEY AUTOINCREMENT, snapshot_id INTEGER, affected_sectors TEXT, recommended_stocks TEXT, risk_level TEXT, risk_factors TEXT, advice TEXT, confidence INTEGER, summary TEXT, raw_response TEXT, model_used TEXT, analysis_time REAL, created_at TEXT DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (snapshot_id) REFERENCES flow_snapshots(id) ) ''') # 9. 定时任务日志表【新增】 cursor.execute(''' CREATE TABLE IF NOT EXISTS scheduler_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_name TEXT NOT NULL, task_type TEXT, status TEXT NOT NULL, message TEXT, duration REAL, snapshot_id INTEGER, executed_at TEXT DEFAULT CURRENT_TIMESTAMP ) ''') # 10. 预警配置表【新增】 cursor.execute(''' CREATE TABLE IF NOT EXISTS alert_config ( id INTEGER PRIMARY KEY AUTOINCREMENT, config_key TEXT NOT NULL UNIQUE, config_value TEXT NOT NULL, description TEXT, updated_at TEXT DEFAULT CURRENT_TIMESTAMP ) ''') # 初始化预警配置默认值 default_configs = [ ('heat_threshold', '800', '热度飙升阈值'), ('rank_change_threshold', '10', '排名变化阈值'), ('sentiment_high_threshold', '90', '情绪高位阈值'), ('sentiment_low_threshold', '20', '情绪低位阈值'), ('viral_k_threshold', '1.5', 'K值阈值'), ('alert_enabled', 'true', '预警开关'), ('notification_enabled', 'true', '通知开关'), ] for key, value, desc in default_configs: cursor.execute(''' INSERT OR IGNORE INTO alert_config (config_key, config_value, description) VALUES (?, ?, ?) ''', (key, value, desc)) # 数据库迁移:添加缺失的列 self._migrate_database(cursor) conn.commit() conn.close() logger.info("✅ 新闻流量数据库初始化完成") def _migrate_database(self, cursor): """数据库迁移:添加缺失的列""" # 定义需要迁移的列 migrations = [ # (表名, 列名, 列定义) ('stock_related_news', 'score', 'INTEGER DEFAULT 0'), ('stock_related_news', 'rank', 'INTEGER'), ('platform_news', 'rank', 'INTEGER'), ('hot_topics', 'cross_platform', 'INTEGER'), ('hot_topics', 'sources', 'TEXT'), ] for table, column, column_def in migrations: try: # 检查列是否存在 cursor.execute(f"PRAGMA table_info({table})") columns = [row[1] for row in cursor.fetchall()] if column not in columns: cursor.execute(f"ALTER TABLE {table} ADD COLUMN {column} {column_def}") logger.info(f"✅ 迁移: 向 {table} 添加列 {column}") except Exception as e: logger.warning(f"迁移列 {table}.{column} 时出错: {e}") # ==================== 快照相关方法 ==================== def save_flow_snapshot(self, flow_data: Dict, platforms_data: List[Dict], stock_news: List[Dict], hot_topics: List[Dict]) -> int: """ 保存完整的流量快照 Args: flow_data: 流量得分数据 platforms_data: 平台新闻数据 stock_news: 股票相关新闻 hot_topics: 热门话题 Returns: snapshot_id: 快照ID """ conn = self.get_connection() cursor = conn.cursor() try: # 1. 保存快照主表 cursor.execute(''' INSERT INTO flow_snapshots (fetch_time, total_platforms, success_count, total_score, flow_level, social_score, news_score, finance_score, tech_score, analysis) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( datetime.now().strftime('%Y-%m-%d %H:%M:%S'), len(platforms_data), sum(1 for p in platforms_data if p.get('success')), flow_data['total_score'], flow_data['level'], flow_data.get('social_score', 0), flow_data.get('news_score', 0), flow_data.get('finance_score', 0), flow_data.get('tech_score', 0), flow_data.get('analysis', '') )) snapshot_id = cursor.lastrowid # 2. 保存平台新闻 for platform_data in platforms_data: if not platform_data.get('success'): continue for news in platform_data.get('data', []): cursor.execute(''' INSERT INTO platform_news (snapshot_id, platform, platform_name, category, weight, title, content, url, source, publish_time, rank) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( snapshot_id, platform_data['platform'], platform_data['platform_name'], platform_data['category'], platform_data['weight'], news.get('title') or '', news.get('content') or '', news.get('url') or '', news.get('source') or '', news.get('publish_time') or '', news.get('rank', 0) )) # 3. 保存股票相关新闻 for news in stock_news: cursor.execute(''' INSERT INTO stock_related_news (snapshot_id, platform, platform_name, category, weight, title, content, url, source, publish_time, matched_keywords, keyword_count, score) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( snapshot_id, news['platform'], news['platform_name'], news['category'], news['weight'], news['title'], news.get('content') or '', news.get('url') or '', news.get('source') or '', news.get('publish_time') or '', json.dumps(news.get('matched_keywords', []), ensure_ascii=False), news.get('keyword_count', 0), news.get('score', 0) )) # 4. 保存热门话题 for topic in hot_topics: cursor.execute(''' INSERT INTO hot_topics (snapshot_id, topic, count, heat, cross_platform, sources) VALUES (?, ?, ?, ?, ?, ?) ''', ( snapshot_id, topic['topic'], topic['count'], topic['heat'], topic.get('cross_platform', 0), json.dumps(topic.get('sources', []), ensure_ascii=False) )) # 5. 更新每日统计 self._update_daily_statistics(cursor, flow_data['total_score'], hot_topics) conn.commit() logger.info(f"✅ 保存流量快照成功,ID: {snapshot_id}") return snapshot_id except Exception as e: conn.rollback() logger.error(f"❌ 保存流量快照失败: {e}") raise finally: conn.close() def _update_daily_statistics(self, cursor, score: int, hot_topics: List[Dict]): """更新每日统计""" today = datetime.now().strftime('%Y-%m-%d') cursor.execute(''' SELECT avg_score, max_score, min_score, snapshot_count, top_topics FROM flow_statistics WHERE date = ? ''', (today,)) row = cursor.fetchone() if row: old_avg = row['avg_score'] or 0 old_count = row['snapshot_count'] or 0 new_avg = int((old_avg * old_count + score) / (old_count + 1)) new_max = max(row['max_score'] or 0, score) new_min = min(row['min_score'] or 999999, score) old_topics = json.loads(row['top_topics']) if row['top_topics'] else [] new_topics = old_topics + [t['topic'] for t in hot_topics[:10]] topic_counter = Counter(new_topics) top_topics = [topic for topic, _ in topic_counter.most_common(20)] cursor.execute(''' UPDATE flow_statistics SET avg_score = ?, max_score = ?, min_score = ?, snapshot_count = ?, top_topics = ? WHERE date = ? ''', (new_avg, new_max, new_min, old_count + 1, json.dumps(top_topics, ensure_ascii=False), today)) else: top_topics = [t['topic'] for t in hot_topics[:20]] cursor.execute(''' INSERT INTO flow_statistics (date, avg_score, max_score, min_score, snapshot_count, top_topics) VALUES (?, ?, ?, ?, ?, ?) ''', (today, score, score, score, 1, json.dumps(top_topics, ensure_ascii=False))) def get_latest_snapshot(self) -> Optional[Dict]: """获取最新的流量快照""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT * FROM flow_snapshots ORDER BY created_at DESC LIMIT 1 ''') row = cursor.fetchone() conn.close() if row: return dict(row) return None def get_recent_snapshots(self, limit: int = 10) -> List[Dict]: """获取最近的流量快照列表""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT * FROM flow_snapshots ORDER BY created_at DESC LIMIT ? ''', (limit,)) snapshots = [] for row in cursor.fetchall(): snapshots.append(dict(row)) conn.close() return snapshots def get_snapshot_detail(self, snapshot_id: int) -> Dict: """获取快照详细信息""" conn = self.get_connection() cursor = conn.cursor() cursor.execute('SELECT * FROM flow_snapshots WHERE id = ?', (snapshot_id,)) row = cursor.fetchone() if not row: conn.close() return {} snapshot = dict(row) cursor.execute(''' SELECT * FROM stock_related_news WHERE snapshot_id = ? ORDER BY COALESCE(score, 0) DESC, weight DESC ''', (snapshot_id,)) stock_news = [] for row in cursor.fetchall(): news = dict(row) news['matched_keywords'] = json.loads(news['matched_keywords']) if news['matched_keywords'] else [] stock_news.append(news) cursor.execute(''' SELECT * FROM hot_topics WHERE snapshot_id = ? ORDER BY heat DESC ''', (snapshot_id,)) hot_topics = [] for row in cursor.fetchall(): topic = dict(row) topic['sources'] = json.loads(topic['sources']) if topic['sources'] else [] hot_topics.append(topic) # 获取情绪记录 cursor.execute(''' SELECT * FROM sentiment_records WHERE snapshot_id = ? ORDER BY created_at DESC LIMIT 1 ''', (snapshot_id,)) sentiment_row = cursor.fetchone() sentiment = dict(sentiment_row) if sentiment_row else None # 获取AI分析 cursor.execute(''' SELECT * FROM ai_analysis WHERE snapshot_id = ? ORDER BY created_at DESC LIMIT 1 ''', (snapshot_id,)) ai_row = cursor.fetchone() ai_analysis = None if ai_row: ai_analysis = dict(ai_row) ai_analysis['affected_sectors'] = json.loads(ai_analysis['affected_sectors']) if ai_analysis['affected_sectors'] else [] ai_analysis['recommended_stocks'] = json.loads(ai_analysis['recommended_stocks']) if ai_analysis['recommended_stocks'] else [] ai_analysis['risk_factors'] = json.loads(ai_analysis['risk_factors']) if ai_analysis['risk_factors'] else [] conn.close() return { 'snapshot': snapshot, 'stock_news': stock_news, 'hot_topics': hot_topics, 'sentiment': sentiment, 'ai_analysis': ai_analysis, } def get_history_snapshots(self, limit: int = 50) -> List[Dict]: """获取历史快照列表""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT id, fetch_time, total_score, flow_level, success_count, total_platforms, analysis FROM flow_snapshots ORDER BY created_at DESC LIMIT ? ''', (limit,)) snapshots = [dict(row) for row in cursor.fetchall()] conn.close() return snapshots def get_daily_statistics(self, days: int = 7) -> List[Dict]: """获取每日统计数据""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT * FROM flow_statistics ORDER BY date DESC LIMIT ? ''', (days,)) stats = [] for row in cursor.fetchall(): stat = dict(row) stat['top_topics'] = json.loads(stat['top_topics']) if stat['top_topics'] else [] stats.append(stat) conn.close() return stats def get_recent_scores(self, hours: int = 24) -> List[Dict]: """获取最近N小时的得分记录""" conn = self.get_connection() cursor = conn.cursor() since = (datetime.now() - timedelta(hours=hours)).strftime('%Y-%m-%d %H:%M:%S') cursor.execute(''' SELECT id, fetch_time, total_score, flow_level FROM flow_snapshots WHERE fetch_time >= ? ORDER BY fetch_time ASC ''', (since,)) scores = [dict(row) for row in cursor.fetchall()] conn.close() return scores def search_stock_news(self, keyword: str, limit: int = 50) -> List[Dict]: """搜索股票相关新闻""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT srn.*, fs.fetch_time, fs.flow_level FROM stock_related_news srn JOIN flow_snapshots fs ON srn.snapshot_id = fs.id WHERE srn.title LIKE ? OR srn.content LIKE ? ORDER BY srn.created_at DESC LIMIT ? ''', (f'%{keyword}%', f'%{keyword}%', limit)) results = [] for row in cursor.fetchall(): news = dict(row) news['matched_keywords'] = json.loads(news['matched_keywords']) if news['matched_keywords'] else [] results.append(news) conn.close() return results # ==================== 情绪记录相关方法 ==================== def save_sentiment_record(self, snapshot_id: int, sentiment_data: Dict) -> int: """保存情绪记录""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO sentiment_records (snapshot_id, sentiment_index, sentiment_class, flow_stage, momentum, viral_k, flow_type, stage_analysis) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''', ( snapshot_id, sentiment_data.get('sentiment_index', 50), sentiment_data.get('sentiment_class', '中性'), sentiment_data.get('flow_stage', '未知'), sentiment_data.get('momentum', 0), sentiment_data.get('viral_k', 1.0), sentiment_data.get('flow_type', '未知'), sentiment_data.get('stage_analysis', '') )) record_id = cursor.lastrowid conn.commit() conn.close() return record_id def get_sentiment_history(self, limit: int = 50) -> List[Dict]: """获取情绪历史记录""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT sr.*, fs.fetch_time, fs.total_score FROM sentiment_records sr LEFT JOIN flow_snapshots fs ON sr.snapshot_id = fs.id ORDER BY sr.created_at DESC LIMIT ? ''', (limit,)) records = [dict(row) for row in cursor.fetchall()] conn.close() return records def get_latest_sentiment(self) -> Optional[Dict]: """获取最新情绪记录""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT sr.*, fs.fetch_time, fs.total_score, fs.flow_level FROM sentiment_records sr LEFT JOIN flow_snapshots fs ON sr.snapshot_id = fs.id ORDER BY sr.created_at DESC LIMIT 1 ''') row = cursor.fetchone() conn.close() return dict(row) if row else None # ==================== 预警相关方法 ==================== def save_alert(self, alert_data: Dict) -> int: """保存预警记录""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO flow_alerts (alert_type, alert_level, title, content, related_topics, trigger_value, threshold_value, is_notified, snapshot_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( alert_data['alert_type'], alert_data.get('alert_level', 'info'), alert_data['title'], alert_data.get('content', ''), json.dumps(alert_data.get('related_topics', []), ensure_ascii=False), str(alert_data.get('trigger_value', '')), str(alert_data.get('threshold_value', '')), 1 if alert_data.get('is_notified') else 0, alert_data.get('snapshot_id') )) alert_id = cursor.lastrowid conn.commit() conn.close() return alert_id def get_alerts(self, days: int = 7, alert_type: str = None) -> List[Dict]: """获取预警记录""" conn = self.get_connection() cursor = conn.cursor() since = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') if alert_type: cursor.execute(''' SELECT * FROM flow_alerts WHERE created_at >= ? AND alert_type = ? ORDER BY created_at DESC ''', (since, alert_type)) else: cursor.execute(''' SELECT * FROM flow_alerts WHERE created_at >= ? ORDER BY created_at DESC ''', (since,)) alerts = [] for row in cursor.fetchall(): alert = dict(row) alert['related_topics'] = json.loads(alert['related_topics']) if alert['related_topics'] else [] alerts.append(alert) conn.close() return alerts def get_unnotified_alerts(self) -> List[Dict]: """获取未通知的预警""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT * FROM flow_alerts WHERE is_notified = 0 ORDER BY created_at DESC ''') alerts = [] for row in cursor.fetchall(): alert = dict(row) alert['related_topics'] = json.loads(alert['related_topics']) if alert['related_topics'] else [] alerts.append(alert) conn.close() return alerts def mark_alert_notified(self, alert_id: int): """标记预警为已通知""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' UPDATE flow_alerts SET is_notified = 1 WHERE id = ? ''', (alert_id,)) conn.commit() conn.close() # ==================== AI分析相关方法 ==================== def save_ai_analysis(self, snapshot_id: int, analysis_data: Dict) -> int: """保存AI分析结果""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO ai_analysis (snapshot_id, affected_sectors, recommended_stocks, risk_level, risk_factors, advice, confidence, summary, raw_response, model_used, analysis_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( snapshot_id, json.dumps(analysis_data.get('affected_sectors', []), ensure_ascii=False), json.dumps(analysis_data.get('recommended_stocks', []), ensure_ascii=False), analysis_data.get('risk_level', '未知'), json.dumps(analysis_data.get('risk_factors', []), ensure_ascii=False), analysis_data.get('advice', '观望'), analysis_data.get('confidence', 50), analysis_data.get('summary', ''), analysis_data.get('raw_response', ''), analysis_data.get('model_used', 'deepseek-chat'), analysis_data.get('analysis_time', 0) )) analysis_id = cursor.lastrowid conn.commit() conn.close() return analysis_id def get_latest_ai_analysis(self) -> Optional[Dict]: """获取最新AI分析结果""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT aa.*, fs.fetch_time, fs.total_score, fs.flow_level FROM ai_analysis aa LEFT JOIN flow_snapshots fs ON aa.snapshot_id = fs.id ORDER BY aa.created_at DESC LIMIT 1 ''') row = cursor.fetchone() conn.close() if row: analysis = dict(row) analysis['affected_sectors'] = json.loads(analysis['affected_sectors']) if analysis['affected_sectors'] else [] analysis['recommended_stocks'] = json.loads(analysis['recommended_stocks']) if analysis['recommended_stocks'] else [] analysis['risk_factors'] = json.loads(analysis['risk_factors']) if analysis['risk_factors'] else [] return analysis return None def get_ai_analysis_history(self, limit: int = 20) -> List[Dict]: """获取AI分析历史""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT aa.*, fs.fetch_time, fs.total_score, fs.flow_level FROM ai_analysis aa LEFT JOIN flow_snapshots fs ON aa.snapshot_id = fs.id ORDER BY aa.created_at DESC LIMIT ? ''', (limit,)) results = [] for row in cursor.fetchall(): analysis = dict(row) analysis['affected_sectors'] = json.loads(analysis['affected_sectors']) if analysis['affected_sectors'] else [] analysis['recommended_stocks'] = json.loads(analysis['recommended_stocks']) if analysis['recommended_stocks'] else [] analysis['risk_factors'] = json.loads(analysis['risk_factors']) if analysis['risk_factors'] else [] results.append(analysis) conn.close() return results # ==================== 定时任务日志相关方法 ==================== def save_scheduler_log(self, task_name: str, task_type: str, status: str, message: str = '', duration: float = 0, snapshot_id: int = None) -> int: """保存定时任务日志""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' INSERT INTO scheduler_logs (task_name, task_type, status, message, duration, snapshot_id) VALUES (?, ?, ?, ?, ?, ?) ''', (task_name, task_type, status, message, duration, snapshot_id)) log_id = cursor.lastrowid conn.commit() conn.close() return log_id def get_scheduler_logs(self, days: int = 7, task_type: str = None) -> List[Dict]: """获取定时任务日志""" conn = self.get_connection() cursor = conn.cursor() since = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') if task_type: cursor.execute(''' SELECT * FROM scheduler_logs WHERE executed_at >= ? AND task_type = ? ORDER BY executed_at DESC ''', (since, task_type)) else: cursor.execute(''' SELECT * FROM scheduler_logs WHERE executed_at >= ? ORDER BY executed_at DESC ''', (since,)) logs = [dict(row) for row in cursor.fetchall()] conn.close() return logs # ==================== 预警配置相关方法 ==================== def get_alert_config(self, key: str) -> Optional[str]: """获取预警配置""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' SELECT config_value FROM alert_config WHERE config_key = ? ''', (key,)) row = cursor.fetchone() conn.close() return row['config_value'] if row else None def set_alert_config(self, key: str, value: str, description: str = None): """设置预警配置""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' INSERT OR REPLACE INTO alert_config (config_key, config_value, description, updated_at) VALUES (?, ?, ?, ?) ''', (key, value, description, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) conn.commit() conn.close() def get_all_alert_configs(self) -> Dict[str, str]: """获取所有预警配置""" conn = self.get_connection() cursor = conn.cursor() cursor.execute('SELECT config_key, config_value FROM alert_config') configs = {row['config_key']: row['config_value'] for row in cursor.fetchall()} conn.close() return configs # 全局数据库实例 news_flow_db = NewsFlowDatabase() # 测试代码 if __name__ == "__main__": print("=== 测试新闻流量数据库 ===") # 测试保存快照 flow_data = { 'total_score': 650, 'social_score': 200, 'news_score': 180, 'finance_score': 220, 'tech_score': 50, 'level': '高', 'analysis': '流量较高,市场活跃' } platforms_data = [{ 'success': True, 'platform': 'weibo', 'platform_name': '微博热搜', 'category': 'social', 'weight': 10, 'data': [ {'title': '某某股票大涨', 'content': '今日涨停', 'url': 'http://example.com', 'source': '微博', 'publish_time': '2026-01-25 10:00:00', 'rank': 1} ] }] stock_news = [{ 'platform': 'weibo', 'platform_name': '微博热搜', 'category': 'social', 'weight': 10, 'title': '某某股票大涨', 'content': '今日涨停', 'url': 'http://example.com', 'source': '微博', 'publish_time': '2026-01-25 10:00:00', 'matched_keywords': ['股票', '涨停'], 'keyword_count': 2, 'score': 100 }] hot_topics = [ {'topic': 'AI', 'count': 50, 'heat': 95, 'cross_platform': 5, 'sources': ['微博', '抖音']}, {'topic': '新能源', 'count': 30, 'heat': 80, 'cross_platform': 3, 'sources': ['微博']} ] snapshot_id = news_flow_db.save_flow_snapshot(flow_data, platforms_data, stock_news, hot_topics) print(f"✅ 保存快照成功,ID: {snapshot_id}") # 测试保存情绪记录 sentiment_data = { 'sentiment_index': 75, 'sentiment_class': '乐观', 'flow_stage': '加速', 'momentum': 1.5, 'viral_k': 1.2, 'flow_type': '增量流量型', 'stage_analysis': '流量正在快速上升' } sentiment_id = news_flow_db.save_sentiment_record(snapshot_id, sentiment_data) print(f"✅ 保存情绪记录成功,ID: {sentiment_id}") # 测试保存预警 alert_data = { 'alert_type': 'heat_surge', 'alert_level': 'warning', 'title': '热度飙升预警', 'content': '当前流量得分650,超过阈值500', 'related_topics': ['AI', '新能源'], 'trigger_value': 650, 'threshold_value': 500, 'snapshot_id': snapshot_id } alert_id = news_flow_db.save_alert(alert_data) print(f"✅ 保存预警成功,ID: {alert_id}") # 测试保存AI分析 ai_data = { 'affected_sectors': [{'name': 'AI', 'impact': '利好', 'reason': '政策支持'}], 'recommended_stocks': [{'code': '000001', 'name': '平安银行', 'reason': '龙头'}], 'risk_level': '中等', 'risk_factors': ['追高风险', '流动性风险'], 'advice': '观望', 'confidence': 75, 'summary': '当前市场热度较高,建议观望', 'model_used': 'deepseek-chat', 'analysis_time': 2.5 } ai_id = news_flow_db.save_ai_analysis(snapshot_id, ai_data) print(f"✅ 保存AI分析成功,ID: {ai_id}") # 测试保存任务日志 log_id = news_flow_db.save_scheduler_log( '热点同步', 'sync_hotspots', 'success', '成功同步22个平台', 5.2, snapshot_id ) print(f"✅ 保存任务日志成功,ID: {log_id}") # 测试获取详情 detail = news_flow_db.get_snapshot_detail(snapshot_id) print(f"\n快照详情:") print(f" 流量得分: {detail['snapshot']['total_score']}") print(f" 情绪指数: {detail['sentiment']['sentiment_index'] if detail['sentiment'] else 'N/A'}") print(f" AI建议: {detail['ai_analysis']['advice'] if detail['ai_analysis'] else 'N/A'}")