Files
MemoryBear/api/app/services/emotion_analytics_service.py
2026-03-03 15:33:38 +08:00

903 lines
36 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 -*-
"""情绪分析服务模块
本模块提供情绪数据的分析和统计功能,包括情绪标签、词云、健康指数计算等。
Classes:
EmotionAnalyticsService: 情绪分析服务,提供各种情绪分析功能
"""
import json
import statistics
from typing import Any, Dict, List, Optional
from app.core.logging_config import get_business_logger
from app.repositories.neo4j.emotion_repository import EmotionRepository
from app.repositories.neo4j.neo4j_connector import Neo4jConnector
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.utils.config_utils import resolve_config_id
logger = get_business_logger()
class EmotionSuggestion(BaseModel):
"""情绪建议模型"""
type: str = Field(...,
description="建议类型emotion_balance/activity_recommendation/social_connection/stress_management")
title: str = Field(..., description="建议标题")
content: str = Field(..., description="建议内容")
priority: str = Field(..., description="优先级high/medium/low")
actionable_steps: List[str] = Field(..., description="可执行步骤列表3个")
class EmotionSuggestionsResponse(BaseModel):
"""情绪建议响应模型"""
health_summary: str = Field(..., description="健康状态摘要不超过50字")
suggestions: List[EmotionSuggestion] = Field(..., description="建议列表3-5条")
class EmotionAnalyticsService:
"""情绪分析服务
提供情绪数据的分析和统计功能,包括:
- 情绪标签统计
- 情绪词云数据
- 情绪健康指数计算
- 个性化情绪建议生成
Attributes:
emotion_repo: 情绪数据仓储实例
"""
def __init__(self):
"""初始化情绪分析服务"""
connector = Neo4jConnector()
self.emotion_repo = EmotionRepository(connector)
logger.info("情绪分析服务初始化完成")
# 情绪类型的中英文映射
EMOTION_TYPE_TRANSLATIONS = {
'joy': {'zh': '喜悦', 'en': 'Joy'},
'sadness': {'zh': '悲伤', 'en': 'Sadness'},
'anger': {'zh': '愤怒', 'en': 'Anger'},
'fear': {'zh': '恐惧', 'en': 'Fear'},
'surprise': {'zh': '惊讶', 'en': 'Surprise'},
'neutral': {'zh': '中性', 'en': 'Neutral'}
}
def _translate_emotion_type(self, emotion_type: str, language: str = "zh") -> str:
"""将情绪类型翻译成指定语言
Args:
emotion_type: 情绪类型英文key
language: 目标语言 ("zh""en")
Returns:
翻译后的情绪类型名称
"""
if emotion_type in self.EMOTION_TYPE_TRANSLATIONS:
return self.EMOTION_TYPE_TRANSLATIONS[emotion_type].get(language, emotion_type)
return emotion_type
async def get_emotion_tags(
self,
end_user_id: str,
emotion_type: Optional[str] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
limit: int = 10,
language: str = "zh"
) -> Dict[str, Any]:
"""获取情绪标签统计
查询指定用户的情绪类型分布,包括计数、百分比和平均强度。
确保返回所有6个情绪维度joy、sadness、anger、fear、surprise、neutral
即使某些维度没有数据也会返回count=0的记录。
Args:
end_user_id: 用户ID
emotion_type: 情绪类型过滤
start_date: 开始日期
end_date: 结束日期
limit: 返回数量限制
language: 输出语言 ("zh" 中文, "en" 英文)
"""
try:
logger.info(f"获取情绪标签统计: user={end_user_id}, type={emotion_type}, "
f"start={start_date}, end={end_date}, limit={limit}, language={language}")
# 调用仓储层查询
tags = await self.emotion_repo.get_emotion_tags(
end_user_id=end_user_id,
emotion_type=emotion_type,
start_date=start_date,
end_date=end_date,
limit=limit
)
# 定义所有6个情绪维度
all_emotion_types = ['joy', 'sadness', 'anger', 'fear', 'surprise', 'neutral']
# 将查询结果转换为字典,方便查找
tags_dict = {tag["emotion_type"]: tag for tag in tags}
# 补全缺失的情绪维度直接使用英文枚举key前端自行翻译
complete_tags = []
for emotion in all_emotion_types:
if emotion in tags_dict:
tag = tags_dict[emotion].copy()
tag["emotion_type"] = emotion
complete_tags.append(tag)
else:
# 如果该情绪类型不存在,添加默认值
complete_tags.append({
"emotion_type": emotion,
"count": 0,
"percentage": 0.0,
"avg_intensity": 0.0
})
# 计算总数
total_count = sum(tag["count"] for tag in complete_tags)
# 如果有数据重新计算百分比因为补全了0值项
if total_count > 0:
for tag in complete_tags:
if tag["count"] > 0:
tag["percentage"] = round((tag["count"] / total_count) * 100, 2)
# 构建时间范围信息
time_range = {}
if start_date:
time_range["start_date"] = start_date
if end_date:
time_range["end_date"] = end_date
# 格式化响应
response = {
"tags": complete_tags,
"total_count": total_count,
"time_range": time_range if time_range else None
}
logger.info(f"情绪标签统计完成: total_count={total_count}, tags_count={len(complete_tags)}")
return response
except Exception as e:
logger.error(f"获取情绪标签统计失败: {str(e)}", exc_info=True)
raise
async def get_emotion_wordcloud(
self,
end_user_id: str,
emotion_type: Optional[str] = None,
limit: int = 50
) -> Dict[str, Any]:
"""获取情绪词云数据
查询情绪关键词及其频率,用于生成词云可视化。
Args:
end_user_id: 宿主ID用户组ID
emotion_type: 可选的情绪类型过滤
limit: 返回关键词的最大数量
Returns:
Dict: 包含情绪词云数据的响应:
- keywords: 关键词列表
- total_keywords: 总关键词数量
"""
try:
logger.info(f"获取情绪词云数据: user={end_user_id}, type={emotion_type}, limit={limit}")
# 调用仓储层查询
keywords = await self.emotion_repo.get_emotion_wordcloud(
end_user_id=end_user_id,
emotion_type=emotion_type,
limit=limit
)
# 计算总关键词数量
total_keywords = len(keywords)
# 格式化响应
response = {
"keywords": keywords,
"total_keywords": total_keywords
}
logger.info(f"情绪词云数据获取完成: total_keywords={total_keywords}")
return response
except Exception as e:
logger.error(f"获取情绪词云数据失败: {str(e)}", exc_info=True)
raise
def _calculate_positivity_rate(self, emotions: List[Dict[str, Any]]) -> Dict[str, Any]:
"""计算积极率
根据情绪类型分类正面、负面和中性情绪,计算积极率。
当存在非中性情绪时:(正面数 / (正面数 + 负面数)) * 100
当只有中性情绪时:基于中性情绪的存在给出基准分数
当完全没有情绪数据时score 为 None表示无法计算
Args:
emotions: 情绪数据列表,每个包含 emotion_type 字段
Returns:
Dict: 包含积极率计算结果:
- score: 积极率分数0-100无数据时为 None
- positive_count: 正面情绪数量
- negative_count: 负面情绪数量
- neutral_count: 中性情绪数量
"""
# 定义情绪分类
positive_emotions = {'joy', 'surprise'}
negative_emotions = {'sadness', 'anger', 'fear'}
# 统计各类情绪数量
positive_count = sum(1 for e in emotions if e.get('emotion_type') in positive_emotions)
negative_count = sum(1 for e in emotions if e.get('emotion_type') in negative_emotions)
neutral_count = sum(1 for e in emotions if e.get('emotion_type') == 'neutral')
# 计算积极率
total_non_neutral = positive_count + negative_count
if total_non_neutral > 0:
score = (positive_count / total_non_neutral) * 100
elif neutral_count > 0:
# 只有中性情绪,说明情绪状态平稳,给予基准分 50
score = 50.0
else:
# 完全没有情绪数据,无法计算积极率
score = None
score_display = f"{score:.2f}" if score is not None else "N/A"
logger.debug(f"积极率计算: positive={positive_count}, negative={negative_count}, "
f"neutral={neutral_count}, score={score_display}")
return {
"score": round(score, 2) if score is not None else None,
"positive_count": positive_count,
"negative_count": negative_count,
"neutral_count": neutral_count
}
def _calculate_stability(self, emotions: List[Dict[str, Any]]) -> Dict[str, Any]:
"""计算稳定性
基于情绪强度的标准差计算情绪稳定性。
公式:(1 - min(std_deviation, 1.0)) * 100
Args:
emotions: 情绪数据列表,每个包含 emotion_intensity 字段
Returns:
Dict: 包含稳定性计算结果:
- score: 稳定性分数0-100
- std_deviation: 标准差
"""
# 提取所有情绪强度
intensities = [e.get('emotion_intensity', 0.0) for e in emotions if e.get('emotion_intensity') is not None]
# 计算标准差
if len(intensities) >= 2:
std_deviation = statistics.stdev(intensities)
elif len(intensities) == 1:
std_deviation = 0.0 # 只有一个数据点标准差为0
else:
std_deviation = 0.0 # 没有数据标准差为0
# 计算稳定性分数
# 标准差越小,稳定性越高
score = (1 - min(std_deviation, 1.0)) * 100
logger.debug(f"稳定性计算: intensities_count={len(intensities)}, "
f"std_deviation={std_deviation:.3f}, score={score:.2f}")
return {
"score": round(score, 2),
"std_deviation": round(std_deviation, 3)
}
def _calculate_resilience(self, emotions: List[Dict[str, Any]]) -> Dict[str, Any]:
"""计算恢复力
分析情绪转换模式,统计从负面情绪恢复到正面情绪的能力。
公式:(负面到正面转换次数 / 总负面情绪数) * 100
Args:
emotions: 情绪数据列表,每个包含 emotion_type 和 created_at 字段
应该按时间顺序排列
Returns:
Dict: 包含恢复力计算结果:
- score: 恢复力分数0-100
- recovery_rate: 恢复率(转换次数/负面情绪数)
"""
# 定义情绪分类
positive_emotions = {'joy', 'surprise'}
negative_emotions = {'sadness', 'anger', 'fear'}
# 统计负面到正面的转换次数
recovery_count = 0
negative_count = 0
for i in range(len(emotions)):
current_emotion = emotions[i].get('emotion_type')
# 统计负面情绪总数
if current_emotion in negative_emotions:
negative_count += 1
# 检查下一个情绪是否为正面
if i + 1 < len(emotions):
next_emotion = emotions[i + 1].get('emotion_type')
if next_emotion in positive_emotions:
recovery_count += 1
# 计算恢复力分数
if negative_count > 0:
recovery_rate = recovery_count / negative_count
score = recovery_rate * 100
else:
# 如果没有负面情绪恢复力设为100最佳状态
recovery_rate = 1.0
score = 100.0
logger.debug(f"恢复力计算: negative_count={negative_count}, "
f"recovery_count={recovery_count}, score={score:.2f}")
return {
"score": round(score, 2),
"recovery_rate": round(recovery_rate, 3)
}
async def calculate_emotion_health_index(
self,
end_user_id: str,
time_range: str = "30d"
) -> Dict[str, Any]:
"""计算情绪健康指数
综合积极率、稳定性和恢复力计算情绪健康指数。
Args:
end_user_id: 宿主ID用户组ID
time_range: 时间范围7d/30d/90d
Returns:
Dict: 包含情绪健康指数的完整响应:
- health_score: 综合健康分数0-100
- level: 健康等级(优秀/良好/一般/较差)
- dimensions: 各维度详细数据
- positivity_rate: 积极率
- stability: 稳定性
- resilience: 恢复力
- emotion_distribution: 情绪分布统计
- time_range: 时间范围
"""
try:
logger.info(f"计算情绪健康指数: user={end_user_id}, time_range={time_range}")
# 获取时间范围内的情绪数据
emotions = await self.emotion_repo.get_emotions_in_range(
end_user_id=end_user_id,
time_range=time_range
)
# 如果指定时间范围内没有数据,尝试更大的时间范围
if not emotions and time_range != "90d":
logger.info(f"用户 {end_user_id}{time_range} 内无数据尝试90天范围")
emotions = await self.emotion_repo.get_emotions_in_range(
end_user_id=end_user_id,
time_range="90d"
)
if emotions:
time_range = "90d"
# 如果没有数据,返回默认值
if not emotions:
logger.warning(f"用户 {end_user_id} 在时间范围 {time_range} 内没有情绪数据")
return {
"health_score": None,
"level": "无数据",
"dimensions": {
"positivity_rate": {"score": None, "positive_count": 0, "negative_count": 0, "neutral_count": 0},
"stability": {"score": None, "std_deviation": 0.0},
"resilience": {"score": None, "recovery_rate": 0.0}
},
"emotion_distribution": {},
"time_range": time_range
}
# 计算各维度指标
positivity_rate = self._calculate_positivity_rate(emotions)
stability = self._calculate_stability(emotions)
resilience = self._calculate_resilience(emotions)
# 计算综合健康分数
# 公式positivity_rate * 0.4 + stability * 0.3 + resilience * 0.3
# 如果积极率无法计算(无数据),视为 0 参与加权
positivity_score = positivity_rate["score"] if positivity_rate["score"] is not None else 0.0
health_score = (
positivity_score * 0.4 +
stability["score"] * 0.3 +
resilience["score"] * 0.3
)
# 确定健康等级
if health_score >= 80:
level = "优秀"
elif health_score >= 60:
level = "良好"
elif health_score >= 40:
level = "一般"
else:
level = "较差"
# 统计情绪分布
emotion_distribution = {}
for emotion_type in ['joy', 'sadness', 'anger', 'fear', 'surprise', 'neutral']:
count = sum(1 for e in emotions if e.get('emotion_type') == emotion_type)
emotion_distribution[emotion_type] = count
# 格式化响应
response = {
"health_score": round(health_score, 2),
"level": level,
"dimensions": {
"positivity_rate": positivity_rate,
"stability": stability,
"resilience": resilience
},
"emotion_distribution": emotion_distribution,
"time_range": time_range
}
logger.info(f"情绪健康指数计算完成: score={health_score:.2f}, level={level}")
return response
except Exception as e:
logger.error(f"计算情绪健康指数失败: {str(e)}", exc_info=True)
raise
def _analyze_emotion_patterns(self, emotions: List[Dict[str, Any]]) -> Dict[str, Any]:
"""分析情绪模式
识别主要负面情绪、情绪触发因素和波动时段。
Args:
emotions: 情绪数据列表,每个包含 emotion_type、emotion_intensity、created_at 字段
Returns:
Dict: 包含情绪模式分析结果:
- dominant_negative_emotion: 主要负面情绪类型
- high_intensity_emotions: 高强度情绪列表
- emotion_volatility: 情绪波动性(高/中/低)
"""
negative_emotions = {'sadness', 'anger', 'fear'}
# 统计负面情绪分布
negative_emotion_counts = {}
for emotion in emotions:
emotion_type = emotion.get('emotion_type')
if emotion_type in negative_emotions:
negative_emotion_counts[emotion_type] = negative_emotion_counts.get(emotion_type, 0) + 1
# 识别主要负面情绪
dominant_negative_emotion = None
if negative_emotion_counts:
dominant_negative_emotion = max(negative_emotion_counts, key=negative_emotion_counts.get)
# 识别高强度情绪(强度 >= 0.7
high_intensity_emotions = [
{
"type": e.get('emotion_type'),
"intensity": e.get('emotion_intensity'),
"created_at": e.get('created_at')
}
for e in emotions
if e.get('emotion_intensity', 0) >= 0.7
]
# 评估情绪波动性
intensities = [e.get('emotion_intensity', 0.0) for e in emotions if e.get('emotion_intensity') is not None]
if len(intensities) >= 2:
std_dev = statistics.stdev(intensities)
if std_dev > 0.3:
volatility = ""
elif std_dev > 0.15:
volatility = ""
else:
volatility = ""
else:
volatility = "未知"
logger.debug(f"情绪模式分析: dominant_negative={dominant_negative_emotion}, "
f"high_intensity_count={len(high_intensity_emotions)}, volatility={volatility}")
return {
"dominant_negative_emotion": dominant_negative_emotion,
"high_intensity_emotions": high_intensity_emotions[:5], # 最多返回5个
"emotion_volatility": volatility
}
async def generate_emotion_suggestions(
self,
end_user_id: str,
db: Session,
language: str = "zh",
) -> Dict[str, Any]:
"""生成个性化情绪建议
基于情绪健康数据和用户画像生成个性化建议。
Args:
end_user_id: 宿主ID用户组ID
db: 数据库会话
language: 输出语言 ("zh" 中文, "en" 英文)
Returns:
Dict: 包含个性化建议的响应:
- health_summary: 健康状态摘要
- suggestions: 建议列表3-5条
"""
try:
logger.info(f"生成个性化情绪建议: user={end_user_id}")
# 1. 从 end_user_id 获取关联的 memory_config_id
llm_client = None
try:
from app.services.memory_agent_service import (
get_end_user_connected_config,
)
connected_config = get_end_user_connected_config(end_user_id, db)
config_id = connected_config.get("memory_config_id")
workspace_id = connected_config.get("workspace_id")
config_id = resolve_config_id(config_id, db) if config_id else None
if config_id is not None or workspace_id is not None:
from app.services.memory_config_service import (
MemoryConfigService,
)
config_service = MemoryConfigService(db)
memory_config = config_service.load_memory_config(
config_id=config_id,
workspace_id=workspace_id,
service_name="EmotionAnalyticsService.generate_emotion_suggestions"
)
from app.core.memory.utils.llm.llm_utils import MemoryClientFactory
factory = MemoryClientFactory(db)
llm_client = factory.get_llm_client(str(memory_config.llm_model_id))
except Exception as e:
logger.warning(f"无法获取 end_user {end_user_id} 的配置,将使用默认配置: {e}")
# 2. 获取情绪健康数据
health_data = await self.calculate_emotion_health_index(end_user_id, time_range="30d")
# 3. 获取情绪数据用于模式分析
emotions = await self.emotion_repo.get_emotions_in_range(
end_user_id=end_user_id,
time_range="30d"
)
# 3.1 如果30天内没有数据尝试获取90天的数据
if not emotions:
logger.info(f"用户 {end_user_id} 30天内无情绪数据尝试获取90天数据")
emotions = await self.emotion_repo.get_emotions_in_range(
end_user_id=end_user_id,
time_range="90d"
)
health_data = await self.calculate_emotion_health_index(end_user_id, time_range="90d")
# 3.2 如果仍然没有时间范围内的数据,从情绪标签统计获取(无时间过滤)
if not emotions:
logger.info(f"用户 {end_user_id} 90天内也无情绪数据从标签统计获取全量数据")
tags_data = await self.get_emotion_tags(end_user_id=end_user_id)
if tags_data.get("total_count", 0) > 0:
# 用标签统计数据构建简化的 health_data
health_data["emotion_distribution"] = {
tag["emotion_type"]: tag["count"]
for tag in tags_data.get("tags", [])
}
health_data["total_emotion_count"] = tags_data["total_count"]
# 4. 分析情绪模式
patterns = self._analyze_emotion_patterns(emotions)
# 5. 获取用户画像数据简化版直接从Neo4j获取
user_profile = await self._get_simple_user_profile(end_user_id)
# 6. 构建LLM prompt
prompt = await self._build_suggestion_prompt(health_data, patterns, user_profile, language)
# 7. 调用LLM生成建议使用配置中的LLM
if llm_client is None:
# 无法获取配置时,抛出错误而不是使用默认配置
raise ValueError("无法获取LLM配置请确保end_user关联了有效的memory_config")
# 将 prompt 转换为 messages 格式
messages = [
{"role": "user", "content": prompt}
]
# 8. 使用结构化输出直接获取 Pydantic 模型
try:
suggestions_response = await llm_client.response_structured(
messages=messages,
response_model=EmotionSuggestionsResponse
)
except Exception as e:
logger.error(f"LLM 结构化输出失败: {str(e)}")
# 返回默认建议
suggestions_response = self._get_default_suggestions(health_data, language)
# 8. 验证建议数量3-5条
if len(suggestions_response.suggestions) < 3:
logger.warning(f"建议数量不足: {len(suggestions_response.suggestions)}")
suggestions_response = self._get_default_suggestions(health_data, language)
elif len(suggestions_response.suggestions) > 5:
logger.warning(f"建议数量过多: {len(suggestions_response.suggestions)}")
suggestions_response.suggestions = suggestions_response.suggestions[:5]
# 9. 格式化响应
response = {
"health_summary": suggestions_response.health_summary,
"suggestions": [
{
"type": s.type,
"title": s.title,
"content": s.content,
"priority": s.priority,
"actionable_steps": s.actionable_steps
}
for s in suggestions_response.suggestions
]
}
logger.info(f"个性化建议生成完成: suggestions_count={len(response['suggestions'])}")
return response
except Exception as e:
logger.error(f"生成个性化建议失败: {str(e)}", exc_info=True)
raise
async def _get_simple_user_profile(self, end_user_id: str) -> Dict[str, Any]:
"""获取简化的用户画像数据
Args:
end_user_id: 用户ID
Returns:
Dict: 用户画像数据
"""
try:
connector = Neo4jConnector()
# 查询用户的实体和标签
query = """
MATCH (e:Entity)
WHERE e.end_user_id = $end_user_id
RETURN e.name as name, e.type as type
ORDER BY e.created_at DESC
LIMIT 20
"""
entities = await connector.execute_query(query, end_user_id=end_user_id)
# 提取兴趣标签
interests = [e["name"] for e in entities if e.get("type") in ["INTEREST", "HOBBY"]][:5]
# 后期会引入用户的习惯。。
return {
"interests": interests if interests else ["未知"]
}
except Exception as e:
logger.error(f"获取用户画像失败: {str(e)}")
return {"interests": ["未知"]}
async def _build_suggestion_prompt(
self,
health_data: Dict[str, Any],
patterns: Dict[str, Any],
user_profile: Dict[str, Any],
language: str = "zh"
) -> str:
"""构建情绪建议生成的prompt
Args:
health_data: 情绪健康数据
patterns: 情绪模式分析结果
user_profile: 用户画像数据
language: 输出语言 ("zh" 中文, "en" 英文)
Returns:
str: LLM prompt
"""
from app.core.memory.utils.prompt.prompt_utils import (
render_emotion_suggestions_prompt,
)
prompt = await render_emotion_suggestions_prompt(
health_data=health_data,
patterns=patterns,
user_profile=user_profile,
language=language
)
return prompt
def _get_default_suggestions(self, health_data: Dict[str, Any], language: str = "zh") -> EmotionSuggestionsResponse:
"""获取默认建议当LLM调用失败时使用
Args:
health_data: 情绪健康数据
language: 输出语言 ("zh" 中文, "en" 英文)
Returns:
EmotionSuggestionsResponse: 默认建议
"""
health_score = health_data.get('health_score') or 0
if language == "en":
if health_score >= 80:
summary = "Your emotional health is excellent. Keep up the positive attitude."
elif health_score >= 60:
summary = "Your emotional health is good. Some adjustments can further improve it."
elif health_score >= 40:
summary = "Your emotional health needs attention. Consider taking improvement measures."
else:
summary = "Your emotional health needs serious attention. Consider seeking professional help."
suggestions = [
EmotionSuggestion(
type="Emotion Balance",
title="Maintain Emotional Balance",
content="Through mindfulness meditation and deep breathing exercises, help you better manage emotional fluctuations and improve emotional stability.",
priority="High",
actionable_steps=[
"Practice 5-10 minutes of mindfulness meditation every morning",
"Take 3 deep breaths when feeling emotional fluctuations",
"Record daily emotional changes to identify triggers"
]
),
EmotionSuggestion(
type="Activity Recommendation",
title="Increase Outdoor Activities",
content="Moderate outdoor exercise can effectively improve mood and enhance physical and mental health. Recommend 3-4 outdoor activities per week.",
priority="Medium",
actionable_steps=[
"Schedule 2-3 30-minute walks per week",
"Try outdoor sports like cycling or hiking on weekends",
"Focus on surroundings and relax during outdoor activities"
]
),
EmotionSuggestion(
type="Social Connection",
title="Strengthen Social Connections",
content="Maintaining good social connections with friends and family can provide emotional support and improve emotional health.",
priority="Medium",
actionable_steps=[
"Have a deep conversation with at least one friend or family member weekly",
"Join social activities or interest groups you enjoy",
"Actively share your feelings and thoughts"
]
)
]
else:
if health_score >= 80:
summary = "您的情绪健康状况优秀,请继续保持积极的生活态度。"
elif health_score >= 60:
summary = "您的情绪健康状况良好,可以通过一些调整进一步提升。"
elif health_score >= 40:
summary = "您的情绪健康需要关注,建议采取一些改善措施。"
else:
summary = "您的情绪健康需要重点关注,建议寻求专业帮助。"
suggestions = [
EmotionSuggestion(
type="情绪平衡",
title="保持情绪平衡",
content="通过正念冥想和深呼吸练习,帮助您更好地管理情绪波动,提升情绪稳定性。",
priority="",
actionable_steps=[
"每天早晨进行5-10分钟的正念冥想",
"感到情绪波动时进行3次深呼吸",
"记录每天的情绪变化,识别触发因素"
]
),
EmotionSuggestion(
type="活动建议",
title="增加户外活动",
content="适度的户外运动可以有效改善情绪增强身心健康。建议每周进行3-4次户外活动。",
priority="",
actionable_steps=[
"每周安排2-3次30分钟的散步",
"周末尝试户外运动如骑行或爬山",
"在户外活动时关注周围环境,放松心情"
]
),
EmotionSuggestion(
type="社交联系",
title="加强社交联系",
content="与朋友和家人保持良好的社交联系,可以提供情感支持,改善情绪健康。",
priority="",
actionable_steps=[
"每周至少与一位朋友或家人深入交流",
"参加感兴趣的社交活动或兴趣小组",
"主动分享自己的感受和想法"
]
)
]
return EmotionSuggestionsResponse(
health_summary=summary,
suggestions=suggestions
)
async def get_cached_suggestions(
self,
end_user_id: str,
db: Session,
) -> Optional[Dict[str, Any]]:
"""从数据库获取个性化情绪建议
Args:
end_user_id: 宿主ID用户组ID
db: 数据库会话
Returns:
Dict: 存储的建议数据,如果不存在返回 None
"""
try:
from app.repositories.implicit_emotions_storage_repository import ImplicitEmotionsStorageRepository
logger.info(f"尝试从数据库获取情绪建议: user={end_user_id}")
# 从数据库获取存储记录
repo = ImplicitEmotionsStorageRepository(db)
storage = repo.get_by_end_user_id(end_user_id)
if storage is None or storage.emotion_suggestions is None:
logger.info(f"用户 {end_user_id} 的建议数据不存在")
return None
logger.info(f"成功从数据库获取建议: user={end_user_id}")
return storage.emotion_suggestions
except Exception as e:
logger.error(f"从数据库获取建议失败: {str(e)}", exc_info=True)
return None
async def save_suggestions_cache(
self,
end_user_id: str,
suggestions_data: Dict[str, Any],
db: Session,
expires_hours: int = 24 # 参数保留以保持接口兼容性
) -> None:
"""保存建议到数据库
Args:
end_user_id: 宿主ID用户组ID
suggestions_data: 建议数据
db: 数据库会话
expires_hours: 保留参数(兼容性)
"""
try:
from app.repositories.implicit_emotions_storage_repository import ImplicitEmotionsStorageRepository
logger.info(f"保存建议到数据库: user={end_user_id}")
repo = ImplicitEmotionsStorageRepository(db)
repo.update_emotion_suggestions(end_user_id, suggestions_data)
db.commit()
logger.info(f"建议保存成功: user={end_user_id}")
except Exception as e:
db.rollback()
logger.error(f"保存建议失败: {str(e)}", exc_info=True)