"""15 天不活跃清零相关表。 - inactivity_reset_log:每次清零一行,记清零前三桶余额 + 原因 + 判定时活跃时间/不活跃天数, 供纠纷排查(需求①)。清零同时另写 3 条钱包流水(biz_type=inactivity_reset),资金流可逐笔回溯。 - inactivity_notification_log:每次预警一行,记推送时余额快照 + 档位 + 通道 + 状态, 兼作"预警去重"依据(created_at > last_active)与"待推送"占位 outbox(v1 通道=log)。 append-only,不更新。user_id 只索引、不设外键(同 analytics_event,避免删用户级联/历史留痕)。 """ from __future__ import annotations from datetime import datetime from sqlalchemy import DateTime, Integer, String, func from sqlalchemy.orm import Mapped, mapped_column from app.db.base import Base class InactivityResetLog(Base): __tablename__ = "inactivity_reset_log" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(Integer, index=True, nullable=False) coin_balance_before: Mapped[int] = mapped_column(Integer, nullable=False) cash_balance_cents_before: Mapped[int] = mapped_column(Integer, nullable=False) invite_cash_balance_cents_before: Mapped[int] = mapped_column(Integer, nullable=False) last_active_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) inactive_days: Mapped[int] = mapped_column(Integer, nullable=False) reason: Mapped[str] = mapped_column(String(32), nullable=False) reset_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), index=True, nullable=False ) def __repr__(self) -> str: # pragma: no cover return f"" class InactivityNotificationLog(Base): __tablename__ = "inactivity_notification_log" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(Integer, index=True, nullable=False) stage: Mapped[int] = mapped_column(Integer, nullable=False) # 提前天数档(如 7 / 2) inactive_days: Mapped[int] = mapped_column(Integer, nullable=False) coin_balance: Mapped[int] = mapped_column(Integer, nullable=False) cash_balance_cents: Mapped[int] = mapped_column(Integer, nullable=False) invite_cash_balance_cents: Mapped[int] = mapped_column(Integer, nullable=False) channel: Mapped[str] = mapped_column(String(16), nullable=False) # log / jpush / sms status: Mapped[str] = mapped_column(String(16), nullable=False) # placeholder / sent / failed created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), index=True, nullable=False ) def __repr__(self) -> str: # pragma: no cover return f""