新增 /top-sales 接口:销量最高从离线库 meituan_coupon 取(不再实时打美团)

「销量最高」改为从本地离线库按销量降序返回,替代实时打美团同城热销(外卖只有~20条还波动):
- POST /api/v1/meituan/top-sales:WHERE sale_volume_num IS NOT NULL,按 sale_volume_num 降序、
  同销量再按 commission_percent 降序;用 dedup_key 跨源去重;用 raw(整条原始返回)重建 CouponCard,
  字段与实时接口完全一致,前端无需改渲染。
- schemas 加 TopSalesRequest(page / page_size / 可选 platform)

离线库由定时 ETL(scripts/pull_meituan_coupons.py)灌入(见本分支前序提交)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
chenshuobo
2026-06-07 23:18:06 +08:00
parent 3a017cfacb
commit 60157507ad
2 changed files with 47 additions and 1 deletions
+40 -1
View File
@@ -7,10 +7,14 @@ from __future__ import annotations
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from fastapi import APIRouter, HTTPException
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core.config import settings
from app.db.session import get_db
from app.integrations.meituan import MeituanCpsError, get_referral_link, query_coupon
from app.models.meituan_coupon import MeituanCoupon
from app.schemas.meituan import (
CouponCard,
CouponListRequest,
@@ -19,6 +23,7 @@ from app.schemas.meituan import (
FeedResponse,
ReferralLinkRequest,
ReferralLinkResponse,
TopSalesRequest,
)
logger = logging.getLogger("shagua.meituan")
@@ -162,3 +167,37 @@ def referral_link(req: ReferralLinkRequest) -> ReferralLinkResponse:
link_map = raw.get("referralLinkMap") or {}
link = raw.get("data") or link_map.get("1") or link_map.get("3") or next(iter(link_map.values()), "")
return ReferralLinkResponse(link=link, link_map=link_map)
@router.post("/top-sales", response_model=CouponListResponse,
summary="销量最高(从离线库 meituan_coupon 按销量降序 + 跨源去重,不实时打美团)")
def top_sales(req: TopSalesRequest, db: Session = Depends(get_db)) -> CouponListResponse:
# 只取有销量档的(美团很多券没销量,排不了);按销量降序、同销量再按佣金降序
stmt = select(MeituanCoupon).where(MeituanCoupon.sale_volume_num.isnot(None))
if req.platform is not None:
stmt = stmt.where(MeituanCoupon.platform == req.platform)
stmt = stmt.order_by(
MeituanCoupon.sale_volume_num.desc(),
MeituanCoupon.commission_percent.desc(),
)
rows = db.execute(stmt).scalars().all()
# 跨源去重(dedup_key = 品牌|名|价):按销量降序遍历,每个 dedup_key 只留第一条(=销量最高那条)。
# 用 raw(整条原始返回)重建 CouponCard,字段与实时接口完全一致,前端无需改渲染。
seen: set[str] = set()
cards: list[CouponCard] = []
for row in rows:
if row.dedup_key in seen:
continue
seen.add(row.dedup_key)
try:
card = CouponCard.from_raw(row.raw or {})
except Exception: # noqa: BLE001
continue
if card.product_view_sign:
cards.append(card)
start = (req.page - 1) * req.page_size
page_items = cards[start:start + req.page_size]
has_next = start + req.page_size < len(cards)
return CouponListResponse(items=page_items, has_next=has_next, search_id=None)
+7
View File
@@ -148,6 +148,13 @@ class FeedResponse(BaseModel):
page: int = 1
class TopSalesRequest(BaseModel):
"""销量最高 tab:从离线库 meituan_coupon 按销量降序取(不实时打美团)。"""
page: int = Field(1, ge=1)
page_size: int = Field(20, ge=1, le=50)
platform: int | None = Field(None, description="可选: 1只外卖 / 2只到店; 不填=全部(全城销量)")
# ───────────────── 换链 请求 / 响应 ─────────────────
class ReferralLinkRequest(BaseModel):