"""预约留资服务(T-P3-22)。 topic 预留通用性,首期只有 full_report(全面报告上线提醒)。 幂等口径:UniqueConstraint(user_id, topic)——重复预约返回已有记录 (created=false,HTTP 仍 200),绝不堆行。 指标口径:预约率 = rp_reservation(topic='full_report') 行数 ÷ 埋点 full_report.card_view 去重曝光数;决策门槛 ≥8%(产品方案 §2.2.3)。 """ from __future__ import annotations from dataclasses import dataclass from sqlalchemy.ext.asyncio import AsyncSession from packages.domain.ids import next_id from packages.report import repo from packages.shared.models import RpReservation @dataclass(frozen=True, slots=True) class ReservationResult: reservation: RpReservation created: bool # false=幂等命中已有记录 class ReservationService: def __init__(self, session: AsyncSession) -> None: self._session = session async def reserve(self, *, user_id: int, topic: str) -> ReservationResult: """幂等预约:同 user+topic 已存在即返回原记录。""" existing = await repo.get_reservation(self._session, user_id=user_id, topic=topic) if existing is not None: return ReservationResult(reservation=existing, created=False) row = RpReservation(reservation_id=next_id(), user_id=user_id, topic=topic) self._session.add(row) await self._session.flush() return ReservationResult(reservation=row, created=True) async def list_topics(self, *, user_id: int, topic: str | None = None) -> list[str]: return await repo.list_reservation_topics(self._session, user_id=user_id, topic=topic)