"""运行时数据访问。""" from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.models.runtime import Event, Run async def get_run(session: AsyncSession, run_id: str) -> Run | None: return await session.get(Run, run_id) async def list_runs( session: AsyncSession, *, character_id: str | None = None, status: str | None = None, page: int = 1, page_size: int = 20, ) -> tuple[list[Run], int]: """按创建时间倒序分页返回 Run;可选按人物/状态过滤,返回 (条目, 总数)。""" conds = [] if character_id is not None: conds.append(Run.character_id == character_id) if status is not None: conds.append(Run.status == status) count_stmt = select(func.count()).select_from(Run) list_stmt = select(Run) if conds: count_stmt = count_stmt.where(*conds) list_stmt = list_stmt.where(*conds) total = int((await session.execute(count_stmt)).scalar_one()) result = await session.execute( list_stmt.order_by(Run.created_at.desc()).offset((page - 1) * page_size).limit(page_size) ) return list(result.scalars().all()), total async def count_events(session: AsyncSession, run_id: str) -> int: result = await session.execute( select(func.count()).select_from(Event).where(Event.run_id == run_id) ) return int(result.scalar_one()) async def next_event_sequence(session: AsyncSession, run_id: str) -> int: result = await session.execute(select(func.max(Event.sequence)).where(Event.run_id == run_id)) current = result.scalar_one_or_none() return int(current or 0) + 1 async def get_event(session: AsyncSession, event_id: str) -> Event | None: return await session.get(Event, event_id) async def has_active_runs( session: AsyncSession, character_id: str, ) -> bool: """检查指定角色是否有活跃的进行中 run(大周天任务中断防护用)。""" result = await session.execute( select(func.count()).select_from(Run).where( Run.character_id == character_id, Run.status == "running", ) ) return int(result.scalar_one()) > 0 async def list_events_by_run( session: AsyncSession, run_id: str, *, visibility: str | None = None, limit: int = 200, offset: int = 0, ) -> tuple[list[Event], int]: """按 run_id 查询事件,按 sequence 升序;可选 visibility 过滤。返回 (条目, 总数)。""" conditions = [Event.run_id == run_id] if visibility is not None: conditions.append(Event.visibility == visibility) total = int( ( await session.execute(select(func.count()).select_from(Event).where(*conditions)) ).scalar_one() ) result = await session.execute( select(Event).where(*conditions).order_by(Event.sequence.asc()).offset(offset).limit(limit) ) return list(result.scalars().all()), total async def list_public_events_after( session: AsyncSession, run_id: str, *, after_sequence: int = 0, limit: int = 100 ) -> list[Event]: result = await session.execute( select(Event) .where( Event.run_id == run_id, Event.visibility == "public", Event.sequence > after_sequence, ) .order_by(Event.sequence.asc()) .limit(limit) ) return list(result.scalars().all()) async def list_events_by_trace(session: AsyncSession, trace_id: str) -> list[Event]: """链路追踪:按 trace_id 查询运行事件,按 created_at 升序(与审计/幂等归并排序)。""" result = await session.execute( select(Event).where(Event.trace_id == trace_id).order_by(Event.created_at.asc()) ) return list(result.scalars().all()) async def summarize_by_trace(session: AsyncSession): """GROUP BY trace_id:(trace_id, 计数, 最近时间),仅 trace_id 非空。供链路追踪列表聚合。""" result = await session.execute( select(Event.trace_id, func.count(), func.max(Event.created_at)) .where(Event.trace_id.is_not(None)) .group_by(Event.trace_id) ) return [(row[0], int(row[1]), row[2]) for row in result.all()]