ece41086cd
背景:美团搜索/供给对销量排序支持差(实测乱序)、且有 402 限流和单次召回上限,不适合 每次实时打接口排序。改为定时把券抓进本地 meituan_coupon 表,查询时从库里捞、自己按 销量/佣金排。 本次内容: - app/models/meituan_coupon.py:新表模型。字段尽量全(售价/原价/销量档/佣金率/佣金额/ 门店/距离/品牌/图 等),raw 列存整条原始返回避免漏字段;(source, product_view_sign) 唯一。 - alembic/versions/meituan_coupon_table.py:建表迁移(挂在 withdraw_review_ad_watch 之后)。 - scripts/pull_meituan_coupons.py:ETL,抓 3 路并按 (source, product_view_sign) upsert: 1) 外卖·搜"外卖"(platform=1)翻到尽头 2) 外卖·搜"美食"(platform=1)翻到尽头 3) 到店·多业务线供给(到餐+到综+酒店+门票)翻到尽头 支持 --once(单轮,给 cron;线上每 1h)/ --loop --interval(本地循环,默认 10min) + 运行锁防重叠 + --prune-hours 清理陈旧券(默认 24h)。 - app/models/__init__.py:注册新模型。 实测:北京一轮约 2.5 分钟、入库约 2700 条;销量/佣金可直接从库里排序。 给接手同事: - 仅北京(city_id 固定);v2 再加 per商圈 union 突破全城单次召回上限。 - 销量仅粗档位且约 51% 的券才有,查询需 WHERE sale_volume_num IS NOT NULL (PG 的 DESC 默认把 NULL 排最前);佣金、价格 100% 有值。 - productViewSign/skuViewId 跨渠道会变,不能当全局 id;跨源去重用 dedup_key=md5(品牌|名|价)。 - 待做:查询接口(从库捞 + dedup_key 去重 + 按销量/佣金排,返回前端)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: chenshuobo <1119780489@qq.com> Reviewed-on: #18
63 lines
3.3 KiB
Python
63 lines
3.3 KiB
Python
"""meituan_coupon table(美团 CPS 券本地缓存,供销量/佣金排序从库里捞、本地排序)
|
|
|
|
Revision ID: meituan_coupon_table
|
|
Revises: withdraw_review_ad_watch
|
|
Create Date: 2026-06-06 12: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 = 'meituan_coupon_table'
|
|
down_revision: Union[str, Sequence[str], None] = 'withdraw_review_ad_watch'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
'meituan_coupon',
|
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
|
sa.Column('source', sa.String(length=16), nullable=False),
|
|
sa.Column('platform', sa.Integer(), nullable=False),
|
|
sa.Column('biz_line', sa.Integer(), nullable=True),
|
|
sa.Column('city_id', sa.String(length=32), nullable=False),
|
|
sa.Column('product_view_sign', sa.String(length=128), nullable=False),
|
|
sa.Column('sku_view_id', sa.String(length=128), nullable=True),
|
|
sa.Column('name', sa.String(length=256), nullable=True),
|
|
sa.Column('brand_name', sa.String(length=128), nullable=True),
|
|
sa.Column('sell_price_cents', sa.Integer(), nullable=True),
|
|
sa.Column('original_price_cents', sa.Integer(), nullable=True),
|
|
sa.Column('head_url', sa.String(length=512), nullable=True),
|
|
sa.Column('sale_volume', sa.String(length=32), nullable=True),
|
|
sa.Column('sale_volume_num', sa.Integer(), nullable=True),
|
|
sa.Column('commission_percent', sa.Float(), nullable=True),
|
|
sa.Column('commission_amount_cents', sa.Integer(), nullable=True),
|
|
sa.Column('poi_name', sa.String(length=128), nullable=True),
|
|
sa.Column('available_poi_num', sa.Integer(), nullable=True),
|
|
sa.Column('delivery_distance_m', sa.Float(), nullable=True),
|
|
sa.Column('dedup_key', sa.String(length=64), nullable=False),
|
|
sa.Column('raw', sa.JSON().with_variant(postgresql.JSONB(), 'postgresql'), nullable=False),
|
|
sa.Column('first_seen', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
|
|
sa.Column('last_seen', 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('source', 'product_view_sign', name='uq_meituan_coupon_source_sign'),
|
|
)
|
|
op.create_index('ix_meituan_coupon_source', 'meituan_coupon', ['source'])
|
|
op.create_index('ix_meituan_coupon_city_id', 'meituan_coupon', ['city_id'])
|
|
op.create_index('ix_meituan_coupon_brand_name', 'meituan_coupon', ['brand_name'])
|
|
op.create_index('ix_meituan_coupon_sale_volume_num', 'meituan_coupon', ['sale_volume_num'])
|
|
op.create_index('ix_meituan_coupon_commission_percent', 'meituan_coupon', ['commission_percent'])
|
|
op.create_index('ix_meituan_coupon_dedup_key', 'meituan_coupon', ['dedup_key'])
|
|
op.create_index('ix_meituan_coupon_last_seen', 'meituan_coupon', ['last_seen'])
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table('meituan_coupon')
|