首页 feed 智能推荐/距离最近 改由后端筛选排序(把客户端 filter/sort 下沉到后端)

mentor 指出筛选放前端不合理:前端只能筛"已加载的几十条",不是完整池子。改为 /feed 加 tab 参数,
后端按 tab 处理后返回,前端只渲染:
- tab=rec(智能推荐):榜单混合 feed 过滤掉佣金率 < 3%(分页)
- tab=distance(距离最近):拉齐全部榜单轮次,在【完整池】上全局按距离由近及远排,一次性返回(has_next=False)
- 留空/其它:返回原混合 feed 不筛(老客户端兼容;新 app 会显式传 tab)
(销量最高走 /coupons 同城热销,已是后端,不在本次)

实测北京:distance 拉到 118 条且全局升序;rec 第1页佣金率全部 ≥3%。

改动:app/schemas/meituan.py(FeedRequest.tab)、app/api/v1/meituan.py(feed 按 tab 筛选/排序 + _commission_pct)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
chenshuobo
2026-06-07 21:52:43 +08:00
parent 4e351d2f4e
commit 834a2c434a
2 changed files with 41 additions and 3 deletions
+35 -3
View File
@@ -79,13 +79,21 @@ def _interleave(waimai: list[dict], daodian: list[dict]) -> list[CouponCard]:
return items
@router.post("/feed", response_model=FeedResponse, summary="混合feed(外卖+到店交叉, 无限流)")
def _commission_pct(card: CouponCard) -> float:
"""'1.4%' → 1.4;解析失败按 0(会被智能推荐过滤掉)。"""
try:
return float(card.commission_rate.rstrip("%"))
except (ValueError, AttributeError):
return 0.0
@router.post("/feed", response_model=FeedResponse, summary="混合feed(外卖+到店交叉);tab=rec智能推荐/distance距离最近")
def feed(req: FeedRequest) -> FeedResponse:
if not settings.mt_cps_configured:
return FeedResponse(items=[], has_next=False, page=req.page)
page_idx = req.page - 1
lon, lat = req.longitude, req.latitude
logger.info("[feed] page=%s lon=%.6f lat=%.6f", req.page, lon, lat)
tab = (req.tab or "").strip()
logger.info("[feed] tab=%s page=%s lon=%.6f lat=%.6f", tab or "(default)", req.page, lon, lat)
def _fetch_topic(platform: int, biz_line: int | None, topic: int) -> list[dict]:
try:
@@ -97,6 +105,28 @@ def feed(req: FeedRequest) -> FeedResponse:
except MeituanCpsError:
return []
# 距离最近:拉齐全部榜单轮次,合并去重,后端在【完整池子】上全局按距离由近及远排,一次性返回。
# (距离排序必须在完整池上做、不能逐页排——这正是之前放前端不合理的根因。)
if tab == "distance":
with ThreadPoolExecutor(max_workers=len(_TOPIC_ROUNDS) * 2) as pool:
futs = []
for wm_topic, dd_topic in _TOPIC_ROUNDS:
futs.append(pool.submit(_fetch_topic, 1, None, wm_topic))
futs.append(pool.submit(_fetch_topic, 2, 1, dd_topic))
raws = [f.result() for f in futs]
seen: set[str] = set()
cards: list[CouponCard] = []
for raw_list in raws:
for it in raw_list:
card = CouponCard.from_raw(it)
if card.product_view_sign and card.product_view_sign not in seen:
seen.add(card.product_view_sign)
cards.append(card)
cards.sort(key=lambda c: c.distance_meters if c.distance_meters is not None else float("inf"))
return FeedResponse(items=cards, has_next=False, page=1)
# 智能推荐(rec,默认):沿用逐轮分页的混合 feed,后端过滤掉佣金率 < 3%。
page_idx = req.page - 1
if page_idx >= len(_TOPIC_ROUNDS):
return FeedResponse(items=[], has_next=False, page=req.page)
@@ -107,6 +137,8 @@ def feed(req: FeedRequest) -> FeedResponse:
waimai, daodian = f_wm.result(), f_dd.result()
items = _interleave(waimai, daodian)
if tab == "rec":
items = [c for c in items if _commission_pct(c) >= 3.0]
has_next = page_idx + 1 < len(_TOPIC_ROUNDS)
return FeedResponse(items=items, has_next=has_next, page=req.page)
+6
View File
@@ -134,6 +134,12 @@ class FeedRequest(BaseModel):
latitude: float = Field(..., description="纬度")
page: int = Field(1, ge=1)
page_size: int = Field(20, ge=1, le=20)
# 筛选/排序口径,后端据此处理后返回(前端不再自己筛/排):
# rec = 智能推荐:榜单混合 feed 去掉佣金率 < 3%(分页)
# distance = 距离最近:拉齐全部轮次后全局按距离由近及远(一次性返回, has_next=False)
# 留空/其它 = 原混合 feed 不筛(老客户端兼容,新 app 会显式传 tab)
# (销量最高 sales 走 /coupons 同城热销,不在本接口)
tab: str = Field("", description="rec 智能推荐 / distance 距离最近 / 空=不筛(兼容)")
class FeedResponse(BaseModel):