- 重构数据访问层:引入DAO模式,支持MySQL/SQLite双数据库 - 新增数据库架构:完整的股票数据、AI分析、自选股管理表结构 - 升级AI分析服务:集成豆包大模型,支持多维度分析 - 优化API路由:分离市场数据API,提供更清晰的接口设计 - 完善项目文档:添加数据库迁移指南、新功能指南等 - 清理冗余文件:删除旧的缓存文件和无用配置 - 新增调度器:支持定时任务和数据自动更新 - 改进前端模板:简化的股票展示页面 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
219 lines
9.7 KiB
Python
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 |