Files
daily_stock_analysis/src/services/analysis_service.py
Krane 9847157980 Feature/React web support 新的WebUI (#256)
* feat(web): add FastAPI & React web connect

* feat: add FastAPI package

* feat: system base struct

* feat: frontend base struct

* feat: 基础分析接口,历史记录接口

* fix: 使用正确的 FastAPI 方法定义,避免线程阻塞

* fix: 对接历史报告页、详情页

* fix: 修复接口问题

* fix: 网络配置 允许公网访问 跨域配置

* fix: 页面打包 & 提供 Server 静态访问

* fix: 优化dock栏样式

* fix: 优化图标样式

* fix: 修改部分配色

* fix: 删除垃圾文档

* fix: 删除垃圾代码

* fix: 驼峰转换工具

* fix: 历史记录列表滚动加载(分页)

* feat: 显示分析中任务

* fix: 调整布局

* fix: 优化 Market Sentiment 组件动效

* fix: 历史列表组件bug

* fix: FastAPI 日志配置

* feat: 新闻历史接口

* feat: 资讯列表

* feat: 优化布局

* fix: 修复页面元素变宽问题

* fix: 中文标题

* fix: 任务列表状态显示问题

* fix: 抽取日志配置

* fix: 优化报告价格显示

* fix: 修复编译错误

* fix: FastAPI 启动提取到 main.py

* fix: 补充新web-ui启动文档

* fix: 默认发送通知

* fix: FastAPI 模式适配 docker

* fix: FastAPI 模式文档

* Update api/v1/endpoints/stocks.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: package-lock.json

* fix: 修改错别字

* fix: 更新文档说明

* fix: 更新文档说明

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-05 20:56:38 +08:00

179 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
===================================
分析服务层
===================================
职责:
1. 封装股票分析逻辑
2. 调用 analyzer 和 pipeline 执行分析
3. 保存分析结果到数据库
"""
import logging
import uuid
from typing import Optional, Dict, Any
from src.repositories.analysis_repo import AnalysisRepository
logger = logging.getLogger(__name__)
class AnalysisService:
"""
分析服务
封装股票分析相关的业务逻辑
"""
def __init__(self):
"""初始化分析服务"""
self.repo = AnalysisRepository()
def analyze_stock(
self,
stock_code: str,
report_type: str = "detailed",
force_refresh: bool = False,
query_id: Optional[str] = None,
send_notification: bool = True
) -> Optional[Dict[str, Any]]:
"""
执行股票分析
Args:
stock_code: 股票代码
report_type: 报告类型 (simple/detailed)
force_refresh: 是否强制刷新
query_id: 查询 ID可选
send_notification: 是否发送通知API 触发默认发送)
Returns:
分析结果字典,包含:
- stock_code: 股票代码
- stock_name: 股票名称
- report: 分析报告
"""
try:
# 导入分析相关模块
from src.config import get_config
from src.core.pipeline import StockAnalysisPipeline
from src.enums import ReportType
# 生成 query_id
if query_id is None:
query_id = uuid.uuid4().hex
# 获取配置
config = get_config()
# 创建分析流水线
pipeline = StockAnalysisPipeline(
config=config,
query_id=query_id,
query_source="api"
)
# 确定报告类型
rt = ReportType.FULL if report_type == "detailed" else ReportType.SIMPLE
# 执行分析
result = pipeline.process_single_stock(
code=stock_code,
skip_analysis=False,
single_stock_notify=send_notification,
report_type=rt
)
if result is None:
logger.warning(f"分析股票 {stock_code} 返回空结果")
return None
# 构建响应
return self._build_analysis_response(result, query_id)
except Exception as e:
logger.error(f"分析股票 {stock_code} 失败: {e}", exc_info=True)
return None
def _build_analysis_response(
self,
result: Any,
query_id: str
) -> Dict[str, Any]:
"""
构建分析响应
Args:
result: AnalysisResult 对象
query_id: 查询 ID
Returns:
格式化的响应字典
"""
# 获取狙击点位
sniper_points = {}
if hasattr(result, 'get_sniper_points'):
sniper_points = result.get_sniper_points() or {}
# 计算情绪标签
sentiment_label = self._get_sentiment_label(result.sentiment_score)
# 构建报告结构
report = {
"meta": {
"query_id": query_id,
"stock_code": result.code,
"stock_name": result.name,
"report_type": "detailed",
"current_price": result.current_price,
"change_pct": result.change_pct,
},
"summary": {
"analysis_summary": result.analysis_summary,
"operation_advice": result.operation_advice,
"trend_prediction": result.trend_prediction,
"sentiment_score": result.sentiment_score,
"sentiment_label": sentiment_label,
},
"strategy": {
"ideal_buy": sniper_points.get("ideal_buy"),
"secondary_buy": sniper_points.get("secondary_buy"),
"stop_loss": sniper_points.get("stop_loss"),
"take_profit": sniper_points.get("take_profit"),
},
"details": {
"news_summary": result.news_summary,
"technical_analysis": result.technical_analysis,
"fundamental_analysis": result.fundamental_analysis,
"risk_warning": result.risk_warning,
}
}
return {
"stock_code": result.code,
"stock_name": result.name,
"report": report,
}
def _get_sentiment_label(self, score: int) -> str:
"""
根据评分获取情绪标签
Args:
score: 情绪评分 (0-100)
Returns:
情绪标签
"""
if score >= 80:
return "极度乐观"
elif score >= 60:
return "乐观"
elif score >= 40:
return "中性"
elif score >= 20:
return "悲观"
else:
return "极度悲观"