preferences.py 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. """
  2. 智能股票分析助手 — 用户偏好API路由
  3. 提供偏好的读取、更新和投资画像查询接口。
  4. """
  5. from fastapi import APIRouter, Depends
  6. from sqlalchemy.ext.asyncio import AsyncSession
  7. from pydantic import BaseModel, Field
  8. from typing import Optional, List
  9. from app.models.database import get_db_session
  10. from app.services import preference_service
  11. from app.utils.response import success_response, error_response
  12. router = APIRouter(prefix="/preferences", tags=["用户偏好"])
  13. # =========================================================================
  14. # 请求体模型
  15. # =========================================================================
  16. class PreferenceUpdateRequest(BaseModel):
  17. """偏好更新请求体——所有字段可选,支持部分更新"""
  18. risk_tolerance: Optional[str] = Field(None, description="风险承受度: conservative/moderate/aggressive", pattern="^(conservative|moderate|aggressive)$")
  19. investment_style: Optional[str] = Field(None, description="投资风格: value/growth/momentum/dividend/blend", pattern="^(value|growth|momentum|dividend|blend)$")
  20. investment_horizon: Optional[str] = Field(None, description="投资期限: short/medium/long", pattern="^(short|medium|long)$")
  21. target_return_rate: Optional[float] = Field(None, description="目标年化收益率(%)", ge=0, le=100)
  22. max_position_ratio: Optional[float] = Field(None, description="单票最大仓位(%)", ge=1, le=100)
  23. max_drawdown_limit: Optional[float] = Field(None, description="最大回撤预警线(%)", le=0)
  24. notification_enabled: Optional[bool] = Field(None, description="是否启用通知")
  25. notification_channels: Optional[List[str]] = Field(None, description="通知渠道")
  26. market_alert_threshold: Optional[float] = Field(None, description="异动提醒阈值(%)", ge=0, le=100)
  27. language: Optional[str] = Field(None, description="界面语言: zh/en", pattern="^(zh|en)$")
  28. theme: Optional[str] = Field(None, description="主题: light/dark/auto", pattern="^(light|dark|auto)$")
  29. default_view: Optional[str] = Field(None, description="默认首页: dashboard/watchlist", pattern="^(dashboard|watchlist)$")
  30. preferred_sectors: Optional[List[str]] = Field(None, description="偏好行业列表")
  31. excluded_sectors: Optional[List[str]] = Field(None, description="排除行业列表")
  32. # =========================================================================
  33. # API接口
  34. # =========================================================================
  35. @router.get("/")
  36. async def get_preferences(
  37. user_id: str = "default",
  38. db: AsyncSession = Depends(get_db_session),
  39. ):
  40. """获取用户偏好配置"""
  41. try:
  42. result = await preference_service.get_preference(db, user_id)
  43. return success_response(data=result)
  44. except Exception as e:
  45. return error_response(code=500, message=f"获取偏好失败: {str(e)}")
  46. @router.put("/")
  47. async def update_preferences(
  48. request: PreferenceUpdateRequest,
  49. user_id: str = "default",
  50. db: AsyncSession = Depends(get_db_session),
  51. ):
  52. """更新用户偏好(支持部分更新,仅传入需要修改的字段)"""
  53. try:
  54. # 仅提交非None的字段
  55. update_data = request.model_dump(exclude_none=True)
  56. if not update_data:
  57. return error_response(code=400, message="未提供需要更新的字段")
  58. result = await preference_service.update_preference(db, user_id, update_data)
  59. return success_response(data=result, message="偏好更新成功")
  60. except Exception as e:
  61. return error_response(code=500, message=f"更新偏好失败: {str(e)}")
  62. @router.get("/profile")
  63. async def get_profile(
  64. user_id: str = "default",
  65. db: AsyncSession = Depends(get_db_session),
  66. ):
  67. """获取用户投资画像摘要"""
  68. try:
  69. result = await preference_service.get_profile_summary(db, user_id)
  70. return success_response(data=result)
  71. except Exception as e:
  72. return error_response(code=500, message=f"获取画像失败: {str(e)}")