357ba27499
- 两张表 + Alembic 迁移:coupon_prompt_engagement(device×日,弹窗频控源)/ coupon_claim_record(device×券×日,领券记录·资产) - coupon/step 透传顺手写库:step0 写 engagement(claim_started);响应解析 last_coupon_result/coupon_results 幂等写 claim record(按 coupon_id 去重,避免 done 帧 last 与 coupon_results 重复在 autoflush=False 下撞唯一约束回滚整批) - 新增 GET /coupon/prompt/should-show(弹窗判据)+ POST /coupon/prompt/dismiss(拒绝通知,透传链路看不到拒绝) - 判断按 device_id(券发到设备登录的外卖账号,比 user 更贴近登录环境);user_id 仅资产留痕、可空 - MVP 先不去重(队列照旧全发);pricebot 未改 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Reviewed-on: #23
78 lines
3.9 KiB
Python
78 lines
3.9 KiB
Python
"""coupon state tables (领券今日状态:弹窗频控 engagement + 领券记录 claim)
|
|
|
|
Revision ID: coupon_state_tables
|
|
Revises: 11a1d08c6f55
|
|
Create Date: 2026-06-08 00:00:00.000000
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = 'coupon_state_tables'
|
|
down_revision: Union[str, Sequence[str], None] = '11a1d08c6f55'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
# PG 上为 JSONB,其它(SQLite dev)为 JSON——与模型层 with_variant 对齐
|
|
_JSON = sa.JSON().with_variant(postgresql.JSONB(), 'postgresql')
|
|
|
|
|
|
def upgrade() -> None:
|
|
# 领券记录(资产层,当前不参与去重判断)
|
|
op.create_table(
|
|
'coupon_claim_record',
|
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
|
sa.Column('device_id', sa.String(length=64), nullable=False),
|
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
|
sa.Column('coupon_id', sa.String(length=64), nullable=False),
|
|
sa.Column('claim_date', sa.Date(), nullable=False),
|
|
sa.Column('status', sa.String(length=24), nullable=False),
|
|
sa.Column('vendor', sa.String(length=48), nullable=True),
|
|
sa.Column('coupon_name', sa.String(length=128), nullable=True),
|
|
sa.Column('claimed_count', sa.Integer(), nullable=True),
|
|
sa.Column('trace_id', sa.String(length=64), nullable=True),
|
|
sa.Column('reason', sa.String(length=255), nullable=True),
|
|
sa.Column('extra', _JSON, nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('device_id', 'coupon_id', 'claim_date', name='uq_coupon_claim_device_coupon_date'),
|
|
)
|
|
with op.batch_alter_table('coupon_claim_record', schema=None) as batch_op:
|
|
batch_op.create_index('ix_coupon_claim_device_date', ['device_id', 'claim_date'], unique=False)
|
|
batch_op.create_index(batch_op.f('ix_coupon_claim_record_user_id'), ['user_id'], unique=False)
|
|
batch_op.create_index(batch_op.f('ix_coupon_claim_record_trace_id'), ['trace_id'], unique=False)
|
|
|
|
# 弹窗频控(今天 engage 过就不再弹)
|
|
op.create_table(
|
|
'coupon_prompt_engagement',
|
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
|
sa.Column('device_id', sa.String(length=64), nullable=False),
|
|
sa.Column('user_id', sa.Integer(), nullable=True),
|
|
sa.Column('engage_date', sa.Date(), nullable=False),
|
|
sa.Column('engage_type', sa.String(length=16), nullable=False),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('device_id', 'engage_date', name='uq_coupon_engage_device_date'),
|
|
)
|
|
with op.batch_alter_table('coupon_prompt_engagement', schema=None) as batch_op:
|
|
batch_op.create_index(batch_op.f('ix_coupon_prompt_engagement_user_id'), ['user_id'], unique=False)
|
|
|
|
|
|
def downgrade() -> None:
|
|
with op.batch_alter_table('coupon_prompt_engagement', schema=None) as batch_op:
|
|
batch_op.drop_index(batch_op.f('ix_coupon_prompt_engagement_user_id'))
|
|
op.drop_table('coupon_prompt_engagement')
|
|
|
|
with op.batch_alter_table('coupon_claim_record', schema=None) as batch_op:
|
|
batch_op.drop_index(batch_op.f('ix_coupon_claim_record_trace_id'))
|
|
batch_op.drop_index(batch_op.f('ix_coupon_claim_record_user_id'))
|
|
batch_op.drop_index('ix_coupon_claim_device_date')
|
|
op.drop_table('coupon_claim_record')
|