Files
slogan/styleagent/service/scene_category_map_service.go
T
adminandClaude Opus 4.7 76100a7131 feat: CPS 引擎(三联盟适配器 + 方案驱动推荐 + 6 接口 + 降级)
美团/京东/淘宝联盟适配器(选品/搜索/转链,未配置 key 优雅降级),
CPS 四表五层骨架,方案驱动推荐零新增 LLM(发型/买同款/到店试穿/场合),
定时同步 + 点击日志 + 最近优惠;outfit_plan 容错迁移 occasion 列;
Dockerfile 补 mesa/nodejs 运行时。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-31 15:48:30 +08:00

120 lines
4.1 KiB
Go

package service
import (
"context"
"errors"
"slogan-agent/styleagent/consts"
"slogan-agent/styleagent/dao"
"slogan-agent/styleagent/model/entity"
)
// sceneCategoryMapService 方案驱动推荐(零新增 LLM:场景映射表 + 联盟选品/搜索)
type sceneCategoryMapService struct {
productSvc *cpsProductService // 测试注入;nil 时用全局 CpsProductService
}
var SceneCategoryMapService = new(sceneCategoryMapService)
// Recommend 按场景给方案推荐联盟商品;查不到返回空列表(客户端隐藏入口)
func (s *sceneCategoryMapService) Recommend(ctx context.Context, plan *entity.OutfitPlan, scene string) ([]*entity.CpsProduct, error) {
svc := s.productSvc
if svc == nil {
svc = CpsProductService
}
switch scene {
case consts.CpsSceneHaircut:
return s.recommendHaircut(ctx, svc, plan)
case consts.CpsSceneItemBuy:
return s.recommendItemBuy(ctx, svc, plan)
case consts.CpsSceneItemUpgrade:
return s.recommendItemUpgrade(ctx, svc, plan)
case consts.CpsSceneOccasion:
return s.recommendOccasion(ctx, svc, plan)
}
return nil, nil
}
// recommendHaircut 发型 → 映射表丽人(美团到店),城市过滤
func (s *sceneCategoryMapService) recommendHaircut(ctx context.Context, svc *cpsProductService, plan *entity.OutfitPlan) ([]*entity.CpsProduct, error) {
if plan.HairstyleId <= 0 {
return nil, nil
}
return s.byScene(ctx, svc, consts.CpsSceneHaircut, "", plan.Location)
}
// recommendOccasion 场合 → 映射表(通勤/约会/旅行/运动),城市过滤
func (s *sceneCategoryMapService) recommendOccasion(ctx context.Context, svc *cpsProductService, plan *entity.OutfitPlan) ([]*entity.CpsProduct, error) {
return s.byScene(ctx, svc, consts.CpsSceneOccasion, plan.Occasion, plan.Location)
}
// byScene 场景映射表(priority 最高者)→ 分页商品
func (s *sceneCategoryMapService) byScene(ctx context.Context, svc *cpsProductService, sceneType, occasion, city string) ([]*entity.CpsProduct, error) {
maps, err := dao.SceneCategoryMap.QueryByScene(ctx, sceneType, occasion)
if err != nil {
return nil, err
}
if len(maps) == 0 {
return nil, nil
}
list, _, err := svc.ListByCategory(ctx, maps[0].Source, maps[0].CategoryCode, city, 1, 10)
return list, err
}
// recommendItemBuy 买同款:推荐条目名作关键词 → 电商搜索
func (s *sceneCategoryMapService) recommendItemBuy(ctx context.Context, svc *cpsProductService, plan *entity.OutfitPlan) ([]*entity.CpsProduct, error) {
items, err := dao.PlanOutfitItem.ListByPlan(ctx, plan.Id)
if err != nil {
return nil, err
}
keyword := ""
for _, it := range items {
if it.Source == consts.PlanSourceRecommend && it.Name != "" {
keyword = it.Name
break
}
}
if keyword == "" {
return nil, nil
}
return s.search(ctx, svc, keyword, "")
}
// recommendItemUpgrade 到店试穿:首条条目名 → 服装类目(美团)
func (s *sceneCategoryMapService) recommendItemUpgrade(ctx context.Context, svc *cpsProductService, plan *entity.OutfitPlan) ([]*entity.CpsProduct, error) {
items, err := dao.PlanOutfitItem.ListByPlan(ctx, plan.Id)
if err != nil {
return nil, err
}
if len(items) == 0 {
return nil, nil
}
return s.search(ctx, svc, items[0].Name, "clothing")
}
// WardrobeUpgrade 衣橱升级款:物品品类作关键词 → 服装类目搜索
func (s *sceneCategoryMapService) WardrobeUpgrade(ctx context.Context, userId, itemId int64) ([]*entity.CpsProduct, error) {
item, err := dao.WardrobeItem.GetOne(ctx, itemId, userId)
if err != nil || item == nil {
return nil, errors.New("衣橱物品不存在")
}
svc := s.productSvc
if svc == nil {
svc = CpsProductService
}
return s.search(ctx, svc, item.Category, "clothing")
}
// search 联盟实时搜索,失败降级为空列表而非错误
func (s *sceneCategoryMapService) search(ctx context.Context, svc *cpsProductService, keyword, catCode string) ([]*entity.CpsProduct, error) {
raw, err := svc.Search(ctx, keyword, catCode, 1)
if err != nil {
return nil, nil
}
out := make([]*entity.CpsProduct, 0, len(raw))
for i := range raw {
out = append(out, toCpsProductEntity(&raw[i]))
}
return out, nil
}