| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122 |
- """
- 情景话题对话记录服务:查询已完成话题、写入对话记录
- """
- import logging
- from typing import Dict, List, Optional, TypedDict
- from sqlalchemy import select, func, desc
- from sqlalchemy.exc import IntegrityError
- from database import db_manager
- from models_db import UserSpecialScenarioTopicLog
- logger = logging.getLogger(__name__)
- class JournalLatestTopicPayload(TypedDict):
- scenario_key: str
- topic_key: str
- conversed_at: str
- class ScenarioTopicService:
- """用户情景话题对话记录"""
- def __init__(self):
- self.db = db_manager
- async def set_db_manager(self, manager):
- self.db = manager
- async def get_completed_count_per_scenario(self, user_id: str) -> Dict[str, int]:
- """
- 按场景统计该用户已完成(已对话)的话题数。
- Returns: {"window": 2, "reading": 0, "eating": 3},仅包含特殊场景。
- """
- try:
- async with self.db.get_session() as session:
- result = await session.execute(
- select(
- UserSpecialScenarioTopicLog.scenario_key,
- func.count(UserSpecialScenarioTopicLog.id).label("cnt"),
- )
- .where(UserSpecialScenarioTopicLog.user_id == user_id)
- .group_by(UserSpecialScenarioTopicLog.scenario_key)
- )
- rows = result.all()
- return {row.scenario_key: row.cnt for row in rows}
- except Exception as e:
- logger.warning(f"[ScenarioTopic] get_completed_count_per_scenario failed: user_id={user_id}, e={e}")
- return {}
- async def get_done_topic_keys(self, user_id: str, scenario_key: str) -> List[str]:
- """获取该用户在该场景下已对话过的话题 key 列表。"""
- try:
- async with self.db.get_session() as session:
- result = await session.execute(
- select(UserSpecialScenarioTopicLog.topic_key).where(
- UserSpecialScenarioTopicLog.user_id == user_id,
- UserSpecialScenarioTopicLog.scenario_key == scenario_key,
- )
- )
- rows = result.all()
- return [r.topic_key for r in rows]
- except Exception as e:
- logger.warning(f"[ScenarioTopic] get_done_topic_keys failed: user_id={user_id}, scenario={scenario_key}, e={e}")
- return []
- async def get_latest_log_for_journal(self, user_id: str) -> Optional[JournalLatestTopicPayload]:
- """按 conversed_at 取该用户最近一条特殊情景话题记录(个人页日记本左侧图)。"""
- if not user_id:
- return None
- try:
- async with self.db.get_session() as session:
- result = await session.execute(
- select(UserSpecialScenarioTopicLog)
- .where(UserSpecialScenarioTopicLog.user_id == user_id)
- .order_by(
- desc(UserSpecialScenarioTopicLog.conversed_at),
- desc(UserSpecialScenarioTopicLog.id),
- )
- .limit(1)
- )
- row = result.scalar_one_or_none()
- if row is None:
- return None
- ca = row.conversed_at
- conversed_at_iso = ca.isoformat() if ca else ""
- return {
- "scenario_key": row.scenario_key,
- "topic_key": row.topic_key,
- "conversed_at": conversed_at_iso,
- }
- except Exception as e:
- logger.warning(f"[ScenarioTopic] get_latest_log_for_journal failed: user_id={user_id}, e={e}")
- return None
- async def record_conversation(self, user_id: str, scenario_key: str, topic_key: str) -> bool:
- """
- 记录用户在该场景该话题下进行了对话。同一 (user_id, scenario_key, topic_key) 只插入一次。
- """
- if not user_id or not scenario_key or not topic_key:
- return False
- try:
- async with self.db.get_session() as session:
- log = UserSpecialScenarioTopicLog(
- user_id=user_id,
- scenario_key=scenario_key,
- topic_key=topic_key,
- )
- session.add(log)
- await session.commit()
- logger.info(f"[ScenarioTopic] recorded: user_id={user_id}, scenario={scenario_key}, topic={topic_key}")
- return True
- except IntegrityError:
- # 唯一约束冲突,说明已记录过,视为成功
- return True
- except Exception as e:
- logger.warning(f"[ScenarioTopic] record_conversation failed: user_id={user_id}, scenario={scenario_key}, topic={topic_key}, e={e}")
- return False
- scenario_topic_service = ScenarioTopicService()
|