scenario_topic_service.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. """
  2. 情景话题对话记录服务:查询已完成话题、写入对话记录
  3. """
  4. import logging
  5. from typing import Dict, List, Optional, TypedDict
  6. from sqlalchemy import select, func, desc
  7. from sqlalchemy.exc import IntegrityError
  8. from database import db_manager
  9. from models_db import UserSpecialScenarioTopicLog
  10. logger = logging.getLogger(__name__)
  11. class JournalLatestTopicPayload(TypedDict):
  12. scenario_key: str
  13. topic_key: str
  14. conversed_at: str
  15. class ScenarioTopicService:
  16. """用户情景话题对话记录"""
  17. def __init__(self):
  18. self.db = db_manager
  19. async def set_db_manager(self, manager):
  20. self.db = manager
  21. async def get_completed_count_per_scenario(self, user_id: str) -> Dict[str, int]:
  22. """
  23. 按场景统计该用户已完成(已对话)的话题数。
  24. Returns: {"window": 2, "reading": 0, "eating": 3},仅包含特殊场景。
  25. """
  26. try:
  27. async with self.db.get_session() as session:
  28. result = await session.execute(
  29. select(
  30. UserSpecialScenarioTopicLog.scenario_key,
  31. func.count(UserSpecialScenarioTopicLog.id).label("cnt"),
  32. )
  33. .where(UserSpecialScenarioTopicLog.user_id == user_id)
  34. .group_by(UserSpecialScenarioTopicLog.scenario_key)
  35. )
  36. rows = result.all()
  37. return {row.scenario_key: row.cnt for row in rows}
  38. except Exception as e:
  39. logger.warning(f"[ScenarioTopic] get_completed_count_per_scenario failed: user_id={user_id}, e={e}")
  40. return {}
  41. async def get_done_topic_keys(self, user_id: str, scenario_key: str) -> List[str]:
  42. """获取该用户在该场景下已对话过的话题 key 列表。"""
  43. try:
  44. async with self.db.get_session() as session:
  45. result = await session.execute(
  46. select(UserSpecialScenarioTopicLog.topic_key).where(
  47. UserSpecialScenarioTopicLog.user_id == user_id,
  48. UserSpecialScenarioTopicLog.scenario_key == scenario_key,
  49. )
  50. )
  51. rows = result.all()
  52. return [r.topic_key for r in rows]
  53. except Exception as e:
  54. logger.warning(f"[ScenarioTopic] get_done_topic_keys failed: user_id={user_id}, scenario={scenario_key}, e={e}")
  55. return []
  56. async def get_latest_log_for_journal(self, user_id: str) -> Optional[JournalLatestTopicPayload]:
  57. """按 conversed_at 取该用户最近一条特殊情景话题记录(个人页日记本左侧图)。"""
  58. if not user_id:
  59. return None
  60. try:
  61. async with self.db.get_session() as session:
  62. result = await session.execute(
  63. select(UserSpecialScenarioTopicLog)
  64. .where(UserSpecialScenarioTopicLog.user_id == user_id)
  65. .order_by(
  66. desc(UserSpecialScenarioTopicLog.conversed_at),
  67. desc(UserSpecialScenarioTopicLog.id),
  68. )
  69. .limit(1)
  70. )
  71. row = result.scalar_one_or_none()
  72. if row is None:
  73. return None
  74. ca = row.conversed_at
  75. conversed_at_iso = ca.isoformat() if ca else ""
  76. return {
  77. "scenario_key": row.scenario_key,
  78. "topic_key": row.topic_key,
  79. "conversed_at": conversed_at_iso,
  80. }
  81. except Exception as e:
  82. logger.warning(f"[ScenarioTopic] get_latest_log_for_journal failed: user_id={user_id}, e={e}")
  83. return None
  84. async def record_conversation(self, user_id: str, scenario_key: str, topic_key: str) -> bool:
  85. """
  86. 记录用户在该场景该话题下进行了对话。同一 (user_id, scenario_key, topic_key) 只插入一次。
  87. """
  88. if not user_id or not scenario_key or not topic_key:
  89. return False
  90. try:
  91. async with self.db.get_session() as session:
  92. log = UserSpecialScenarioTopicLog(
  93. user_id=user_id,
  94. scenario_key=scenario_key,
  95. topic_key=topic_key,
  96. )
  97. session.add(log)
  98. await session.commit()
  99. logger.info(f"[ScenarioTopic] recorded: user_id={user_id}, scenario={scenario_key}, topic={topic_key}")
  100. return True
  101. except IntegrityError:
  102. # 唯一约束冲突,说明已记录过,视为成功
  103. return True
  104. except Exception as e:
  105. logger.warning(f"[ScenarioTopic] record_conversation failed: user_id={user_id}, scenario={scenario_key}, topic={topic_key}, e={e}")
  106. return False
  107. scenario_topic_service = ScenarioTopicService()