diff --git a/api/app/controllers/user_memory_controllers.py b/api/app/controllers/user_memory_controllers.py index fbefe5ba..8afb0863 100644 --- a/api/app/controllers/user_memory_controllers.py +++ b/api/app/controllers/user_memory_controllers.py @@ -14,6 +14,7 @@ from app.core.error_codes import BizCode from app.services.user_memory_service import ( UserMemoryService, analytics_node_statistics, + analytics_memory_types, analytics_graph_data, ) from app.schemas.response_schema import ApiResponse @@ -185,21 +186,17 @@ async def get_node_statistics_api( api_logger.warning(f"用户 {current_user.username} 尝试查询节点统计但未选择工作空间") return fail(BizCode.INVALID_PARAMETER, "请先切换到一个工作空间", "current_workspace_id is None") - api_logger.info(f"节点统计请求: end_user_id={end_user_id}, user={current_user.username}, workspace={workspace_id}") + api_logger.info(f"记忆类型统计请求: end_user_id={end_user_id}, user={current_user.username}, workspace={workspace_id}") try: - result = await analytics_node_statistics(db, end_user_id) + # 调用新的记忆类型统计函数 + result = await analytics_memory_types(db, end_user_id) - # 检查是否有错误消息 - if "message" in result and result["total"] == 0: - api_logger.warning(f"节点统计查询返回空结果: {result.get('message')}") - return success(data=result, msg=result.get("message", "查询成功")) - - api_logger.info(f"成功获取节点统计: end_user_id={end_user_id}, total={result['total']}") + api_logger.info(f"成功获取记忆类型统计: end_user_id={end_user_id}, 感知记忆={result.get('感知记忆', 0)}") return success(data=result, msg="查询成功") except Exception as e: - api_logger.error(f"节点统计查询失败: end_user_id={end_user_id}, error={str(e)}") - return fail(BizCode.INTERNAL_ERROR, "节点统计查询失败", str(e)) + api_logger.error(f"记忆类型查询失败: end_user_id={end_user_id}, error={str(e)}") + return fail(BizCode.INTERNAL_ERROR, "记忆类型查询失败", str(e)) @router.get("/analytics/graph_data", response_model=ApiResponse) async def get_graph_data_api( @@ -293,7 +290,7 @@ async def get_end_user_profile( # 构建响应数据 profile_data = EndUserProfileResponse( id=end_user.id, - name=end_user.name, + other_name=end_user.other_name, position=end_user.position, department=end_user.department, contact=end_user.contact, @@ -364,7 +361,7 @@ async def update_end_user_profile( # 构建响应数据 profile_data = EndUserProfileResponse( id=end_user.id, - name=end_user.name, + other_name=end_user.other_name, position=end_user.position, department=end_user.department, contact=end_user.contact, diff --git a/api/app/models/end_user_model.py b/api/app/models/end_user_model.py index 0ef11ffa..1e1ce4f3 100644 --- a/api/app/models/end_user_model.py +++ b/api/app/models/end_user_model.py @@ -19,7 +19,6 @@ class EndUser(Base): updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now) # 用户基本信息字段 - name = Column(String, nullable=True, comment="姓名") position = Column(String, nullable=True, comment="职位") department = Column(String, nullable=True, comment="部门") contact = Column(String, nullable=True, comment="联系方式") diff --git a/api/app/schemas/end_user_schema.py b/api/app/schemas/end_user_schema.py index 939d2d3e..07188096 100644 --- a/api/app/schemas/end_user_schema.py +++ b/api/app/schemas/end_user_schema.py @@ -18,7 +18,6 @@ class EndUser(BaseModel): updated_at: datetime.datetime = Field(description="更新时间", default_factory=datetime.datetime.now) # 用户基本信息字段 - name: Optional[str] = Field(description="姓名", default=None) position: Optional[str] = Field(description="职位", default=None) department: Optional[str] = Field(description="部门", default=None) contact: Optional[str] = Field(description="联系方式", default=None) @@ -32,7 +31,7 @@ class EndUserProfileResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: uuid.UUID = Field(description="终端用户ID") - name: Optional[str] = Field(description="姓名", default=None) + other_name: Optional[str] = Field(description="其他名称", default="") position: Optional[str] = Field(description="职位", default=None) department: Optional[str] = Field(description="部门", default=None) contact: Optional[str] = Field(description="联系方式", default=None) @@ -44,7 +43,7 @@ class EndUserProfileResponse(BaseModel): class EndUserProfileUpdate(BaseModel): """终端用户基本信息更新请求模型""" end_user_id: str = Field(description="终端用户ID") - name: Optional[str] = Field(description="姓名", default=None) + other_name: Optional[str] = Field(description="其他名称", default="") position: Optional[str] = Field(description="职位", default=None) department: Optional[str] = Field(description="部门", default=None) contact: Optional[str] = Field(description="联系方式", default=None) diff --git a/api/app/services/emotion_analytics_service.py b/api/app/services/emotion_analytics_service.py index 4109c91b..e4b5bed6 100644 --- a/api/app/services/emotion_analytics_service.py +++ b/api/app/services/emotion_analytics_service.py @@ -65,19 +65,9 @@ class EmotionAnalyticsService: """获取情绪标签统计 查询指定用户的情绪类型分布,包括计数、百分比和平均强度。 - - Args: - end_user_id: 宿主ID(用户组ID) - emotion_type: 可选的情绪类型过滤 - start_date: 可选的开始日期(ISO格式) - end_date: 可选的结束日期(ISO格式) - limit: 返回结果的最大数量 - - Returns: - Dict: 包含情绪标签统计的响应数据: - - tags: 情绪标签列表 - - total_count: 总情绪数量 - - time_range: 时间范围信息 + 确保返回所有6个情绪维度(joy、sadness、anger、fear、surprise、neutral), + 即使某些维度没有数据也会返回count=0的记录。 + """ try: logger.info(f"获取情绪标签统计: user={end_user_id}, type={emotion_type}, " @@ -92,8 +82,34 @@ class EmotionAnalyticsService: limit=limit ) + # 定义所有6个情绪维度 + all_emotion_types = ['joy', 'sadness', 'anger', 'fear', 'surprise', 'neutral'] + + # 将查询结果转换为字典,方便查找 + tags_dict = {tag["emotion_type"]: tag for tag in tags} + + # 补全缺失的情绪维度 + complete_tags = [] + for emotion in all_emotion_types: + if emotion in tags_dict: + complete_tags.append(tags_dict[emotion]) + else: + # 如果该情绪类型不存在,添加默认值 + complete_tags.append({ + "emotion_type": emotion, + "count": 0, + "percentage": 0.0, + "avg_intensity": 0.0 + }) + # 计算总数 - total_count = sum(tag["count"] for tag in tags) + 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 = {} @@ -104,12 +120,12 @@ class EmotionAnalyticsService: # 格式化响应 response = { - "tags": tags, + "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(tags)}") + logger.info(f"情绪标签统计完成: total_count={total_count}, tags_count={len(complete_tags)}") return response except Exception as e: diff --git a/api/app/services/memory_dashboard_service.py b/api/app/services/memory_dashboard_service.py index 6acc699a..9a8ae9aa 100644 --- a/api/app/services/memory_dashboard_service.py +++ b/api/app/services/memory_dashboard_service.py @@ -272,7 +272,7 @@ async def get_workspace_total_memory_count( from app.repositories.end_user_repository import EndUserRepository repo = EndUserRepository(db) end_user = repo.get_by_id(uuid.UUID(end_user_id)) - user_name = end_user.name if end_user else None + user_name = end_user.other_name if end_user else None return { "total_memory_count": search_result.get("total", 0), diff --git a/api/app/services/user_memory_service.py b/api/app/services/user_memory_service.py index e1b4b6eb..f728fa6f 100644 --- a/api/app/services/user_memory_service.py +++ b/api/app/services/user_memory_service.py @@ -534,6 +534,91 @@ async def analytics_node_statistics( return data +async def analytics_memory_types( + db: Session, + end_user_id: Optional[str] = None +) -> Dict[str, Any]: + """ + 统计8种记忆类型的数量 + + 计算规则: + 1. 感知记忆 = statement + entity + 2. 工作记忆 = chunk + entity + 3. 短期记忆 = chunk + 4. 长期记忆 = entity + 5. 显性记忆 = 1/2 * entity + 6. 隐形记忆 = 1/3 * entity + 7. 情绪记忆 = statement + 8. 情景记忆 = memory_summary + + Args: + db: 数据库会话 + end_user_id: 可选的终端用户ID (UUID),用于过滤特定用户的节点 + + Returns: + { + "感知记忆": int, + "工作记忆": int, + "短期记忆": int, + "长期记忆": int, + "显性记忆": int, + "隐形记忆": int, + "情绪记忆": int, + "情景记忆": int + } + """ + # 定义需要查询的节点类型 + node_types = { + "Statement": "Statement", + "Entity": "ExtractedEntity", + "Chunk": "Chunk", + "MemorySummary": "MemorySummary" + } + + # 存储每种节点类型的计数 + node_counts = {} + + # 查询每种节点类型的数量 + for key, node_type in node_types.items(): + if end_user_id: + query = f""" + MATCH (n:{node_type}) + WHERE n.group_id = $group_id + RETURN count(n) as count + """ + result = await _neo4j_connector.execute_query(query, group_id=end_user_id) + else: + query = f""" + MATCH (n:{node_type}) + RETURN count(n) as count + """ + result = await _neo4j_connector.execute_query(query) + + # 提取计数结果 + count = result[0]["count"] if result and len(result) > 0 else 0 + node_counts[key] = count + + # 获取各节点类型的数量 + statement_count = node_counts.get("Statement", 0) + entity_count = node_counts.get("Entity", 0) + chunk_count = node_counts.get("Chunk", 0) + memory_summary_count = node_counts.get("MemorySummary", 0) + + # 按规则计算8种记忆类型 + memory_types = { + "感知记忆": statement_count + entity_count, + "工作记忆": chunk_count + entity_count, + "短期记忆": chunk_count, + "长期记忆": entity_count, + "显性记忆": entity_count // 2, # 1/2 entity,使用整除 + "隐形记忆": entity_count // 3, # 1/3 entity,使用整除 + "情绪记忆": statement_count, + "情景记忆": memory_summary_count + } + + return memory_types + + async def analytics_graph_data( db: Session, end_user_id: str,