stock-monitor/app/dao/ai_analysis_dao.py
ycg 569c1c8813 重构股票监控系统:数据库架构升级与功能完善
- 重构数据访问层:引入DAO模式,支持MySQL/SQLite双数据库
- 新增数据库架构:完整的股票数据、AI分析、自选股管理表结构
- 升级AI分析服务:集成豆包大模型,支持多维度分析
- 优化API路由:分离市场数据API,提供更清晰的接口设计
- 完善项目文档:添加数据库迁移指南、新功能指南等
- 清理冗余文件:删除旧的缓存文件和无用配置
- 新增调度器:支持定时任务和数据自动更新
- 改进前端模板:简化的股票展示页面

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 15:44:25 +08:00

219 lines
9.7 KiB
Python

"""
AI分析数据访问对象
"""
from typing import Dict, List, Optional, Any
import json
from datetime import datetime, date
from .base_dao import BaseDAO
class AIAnalysisDAO(BaseDAO):
"""AI分析数据访问对象"""
def save_analysis(self, stock_code: str, analysis_type: str, analysis_data: Dict,
analysis_date: str = None) -> bool:
"""保存AI分析结果"""
if analysis_date is None:
analysis_date = self.get_today_date()
try:
# 检查是否已存在当日的分析
existing = self.get_analysis(stock_code, analysis_type, analysis_date)
investment_summary = analysis_data.get('investment_suggestion', {})
investment_key_points = json.dumps(investment_summary.get('key_points', []), ensure_ascii=False)
investment_summary_text = investment_summary.get('summary', '')
investment_action = investment_summary.get('action', '')
price_analysis = analysis_data.get('price_analysis', {})
if existing:
# 更新现有分析
query = """
UPDATE ai_analysis SET
investment_summary = %s,
investment_action = %s,
investment_key_points = %s,
valuation_analysis = %s,
financial_analysis = %s,
growth_analysis = %s,
risk_analysis = %s,
reasonable_price_min = %s,
reasonable_price_max = %s,
target_market_value_min = %s,
target_market_value_max = %s,
from_cache = %s,
updated_at = CURRENT_TIMESTAMP
WHERE stock_code = %s AND analysis_type = %s AND analysis_date = %s
"""
self._execute_update(query, (
investment_summary_text,
investment_action,
investment_key_points,
analysis_data.get('analysis', {}).get('估值分析', ''),
analysis_data.get('analysis', {}).get('财务健康状况', ''),
analysis_data.get('analysis', {}).get('成长潜力', ''),
analysis_data.get('analysis', {}).get('风险评估', ''),
self.parse_float(price_analysis.get('合理价格区间', [None, None])[0]),
self.parse_float(price_analysis.get('合理价格区间', [None, None])[1]),
self.parse_float(price_analysis.get('目标市值区间', [None, None])[0]),
self.parse_float(price_analysis.get('目标市值区间', [None, None])[1]),
bool(analysis_data.get('from_cache', False)),
stock_code,
analysis_type,
analysis_date
))
else:
# 插入新分析
query = """
INSERT INTO ai_analysis (
stock_code, analysis_type, analysis_date,
investment_summary, investment_action, investment_key_points,
valuation_analysis, financial_analysis, growth_analysis, risk_analysis,
reasonable_price_min, reasonable_price_max,
target_market_value_min, target_market_value_max, from_cache
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
self._execute_insert(query, (
stock_code, analysis_type, analysis_date,
investment_summary_text, investment_action, investment_key_points,
analysis_data.get('analysis', {}).get('估值分析', ''),
analysis_data.get('analysis', {}).get('财务健康状况', ''),
analysis_data.get('analysis', {}).get('成长潜力', ''),
analysis_data.get('analysis', {}).get('风险评估', ''),
self.parse_float(price_analysis.get('合理价格区间', [None, None])[0]),
self.parse_float(price_analysis.get('合理价格区间', [None, None])[1]),
self.parse_float(price_analysis.get('目标市值区间', [None, None])[0]),
self.parse_float(price_analysis.get('目标市值区间', [None, None])[1]),
bool(analysis_data.get('from_cache', False))
))
self.log_data_update(f'ai_analysis_{analysis_type}', stock_code, 'success', 'Analysis saved')
return True
except Exception as e:
self.logger.error(f"保存AI分析失败: {stock_code}, {analysis_type}, 错误: {e}")
self.log_data_update(f'ai_analysis_{analysis_type}', stock_code, 'failed', str(e))
return False
def get_analysis(self, stock_code: str, analysis_type: str, analysis_date: str = None) -> Optional[Dict]:
"""获取AI分析结果"""
if analysis_date is None:
analysis_date = self.get_today_date()
query = """
SELECT * FROM ai_analysis
WHERE stock_code = %s AND analysis_type = %s AND analysis_date = %s
"""
return self._execute_single_query(query, (stock_code, analysis_type, analysis_date))
def get_latest_analysis(self, stock_code: str, analysis_type: str) -> Optional[Dict]:
"""获取最新的AI分析结果"""
query = """
SELECT * FROM ai_analysis
WHERE stock_code = %s AND analysis_type = %s
ORDER BY analysis_date DESC
LIMIT 1
"""
return self._execute_single_query(query, (stock_code, analysis_type))
def get_all_analysis_types(self, stock_code: str, analysis_date: str = None) -> List[Dict]:
"""获取股票的所有类型分析"""
if analysis_date is None:
analysis_date = self.get_today_date()
query = """
SELECT * FROM ai_analysis
WHERE stock_code = %s AND analysis_date = %s
ORDER BY analysis_type
"""
return self._execute_query(query, (stock_code, analysis_date))
def format_analysis_data(self, analysis_record: Dict) -> Dict:
"""将数据库记录格式化为原始分析数据格式"""
if not analysis_record:
return {}
# 解析JSON字段
key_points = []
if analysis_record.get('investment_key_points'):
try:
key_points = json.loads(analysis_record['investment_key_points'])
except json.JSONDecodeError:
key_points = []
# 构建投资建议
investment_suggestion = {
'summary': analysis_record.get('investment_summary', ''),
'action': analysis_record.get('investment_action', ''),
'key_points': key_points
}
# 构建分析详情
analysis = {}
if analysis_record.get('valuation_analysis'):
analysis['估值分析'] = analysis_record['valuation_analysis']
if analysis_record.get('financial_analysis'):
analysis['财务健康状况'] = analysis_record['financial_analysis']
if analysis_record.get('growth_analysis'):
analysis['成长潜力'] = analysis_record['growth_analysis']
if analysis_record.get('risk_analysis'):
analysis['风险评估'] = analysis_record['risk_analysis']
# 构建价格分析
price_analysis = {}
if analysis_record.get('reasonable_price_min') or analysis_record.get('reasonable_price_max'):
price_analysis['合理价格区间'] = [
analysis_record.get('reasonable_price_min'),
analysis_record.get('reasonable_price_max')
]
if analysis_record.get('target_market_value_min') or analysis_record.get('target_market_value_max'):
price_analysis['目标市值区间'] = [
analysis_record.get('target_market_value_min'),
analysis_record.get('target_market_value_max')
]
return {
'investment_suggestion': investment_suggestion,
'analysis': analysis,
'price_analysis': price_analysis,
'from_cache': analysis_record.get('from_cache', False)
}
def get_analysis_history(self, stock_code: str, analysis_type: str,
days: int = 30) -> List[Dict]:
"""获取分析历史"""
query = """
SELECT * FROM ai_analysis
WHERE stock_code = %s AND analysis_type = %s
AND analysis_date >= DATE_SUB(CURDATE(), INTERVAL %s DAY)
ORDER BY analysis_date DESC
"""
return self._execute_query(query, (stock_code, analysis_type, days))
def delete_analysis(self, stock_code: str, analysis_type: str,
before_date: str = None) -> int:
"""删除分析数据"""
if before_date:
query = """
DELETE FROM ai_analysis
WHERE stock_code = %s AND analysis_type = %s AND analysis_date < %s
"""
return self._execute_update(query, (stock_code, analysis_type, before_date))
else:
query = """
DELETE FROM ai_analysis
WHERE stock_code = %s AND analysis_type = %s
"""
return self._execute_update(query, (stock_code, analysis_type))
def get_analysis_count(self, analysis_type: str = None) -> int:
"""获取分析数量"""
if analysis_type:
query = "SELECT COUNT(*) as count FROM ai_analysis WHERE analysis_type = %s"
result = self._execute_single_query(query, (analysis_type,))
else:
query = "SELECT COUNT(*) as count FROM ai_analysis"
result = self._execute_single_query(query)
return result['count'] if result else 0