Forráskód Böngészése

feat:添加解构特征

zhaohaipeng 3 hete
szülő
commit
e06d7d2f36

+ 4 - 0
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/model/RootSessionIdExpConfig.java

@@ -12,5 +12,9 @@ public class RootSessionIdExpConfig {
 
     private Set<Integer> appType = new HashSet<>();
 
+    // TODO 只做排序或者召回实验,后续废弃
     private String expCode;
+
+    // 兼容一个配置,同时开启排序和召回两个实验的情况
+    private Set<String> expCodes = new HashSet<>();
 }

+ 59 - 0
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/ExperimentService.java

@@ -0,0 +1,59 @@
+package com.tzld.piaoquan.recommend.server.service;
+
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.tzld.piaoquan.recommend.server.model.RootSessionIdExpConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Set;
+
+@Slf4j
+@Service
+public class ExperimentService {
+
+    @ApolloJsonValue("${rank.root.session.id.exp.config:[]}")
+    private List<RootSessionIdExpConfig> rootSessionIdExpConfigs;
+
+    /**
+     * 是否命中某个实验号
+     *
+     * @param appType       appType
+     * @param rootSessionId rootSessionId
+     * @param abExpCodes    AB实验号集合
+     * @param expCode       要判断的实验号
+     * @return true: 命中要判断的实验号; false: 未命中要判断的实验号
+     */
+    public boolean judgeHitExp(int appType, String rootSessionId, Set<String> abExpCodes, String expCode) {
+        // 要判断的实验号为空,直接返回false
+        if (StringUtils.isBlank(expCode)) {
+            return false;
+        }
+        // 先判断AB实验号集合中,是否包含要判断的实验号
+        if (CollectionUtils.isNotEmpty(abExpCodes) && abExpCodes.contains(expCode)) {
+            return true;
+        }
+
+        // rootSessionId尾号实验配置为空或者rootSessionId为空,表示没有尾号实验。直接返回false
+        if (CollectionUtils.isEmpty(rootSessionIdExpConfigs) || StringUtils.isBlank(rootSessionId)) {
+            return false;
+        }
+
+        // 根据appType和rootSessionId尾号,判断是否命中实验
+        String tail = rootSessionId.substring(rootSessionId.length() - 1);
+        for (RootSessionIdExpConfig config : rootSessionIdExpConfigs) {
+            if (config.getAppType().contains(appType) && config.getTail().contains(tail)) {
+                if (StringUtils.equals(config.getExpCode(), expCode)) {
+                    return true;
+                }
+                if (CollectionUtils.isNotEmpty(config.getExpCodes()) && config.getExpCodes().contains(expCode)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+}

+ 10 - 32
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/rank/RankRouter.java

@@ -1,28 +1,28 @@
 package com.tzld.piaoquan.recommend.server.service.rank;
 
-import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
-import com.tzld.piaoquan.recommend.server.model.RootSessionIdExpConfig;
+import com.tzld.piaoquan.recommend.server.service.ExperimentService;
 import com.tzld.piaoquan.recommend.server.service.ServiceBeanFactory;
 import com.tzld.piaoquan.recommend.server.service.rank.strategy.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Resource;
 import java.util.*;
 
 @Service
 @Slf4j
 public class RankRouter {
 
-    @ApolloJsonValue("${rank.root.session.id.exp.config:[]}")
-    private List<RootSessionIdExpConfig> rootSessionIdExpConfigs;
 
     @Value("${rank.strategy.default:567}")
     private String rankStrategyDefault;
 
+    @Resource
+    private ExperimentService experimentService;
+
     private LinkedHashMap<String, RankService> strategyMap;
 
     private static final String relevantRank = "relevant";
@@ -60,9 +60,11 @@ public class RankRouter {
         }
 
         // 裂变层实验,使用RootSessionId尾号进行实验
-        String rootSessionIdExpCode = this.findRootSessionIdExpCode(param);
-        if (StringUtils.isNotBlank(rootSessionIdExpCode) && strategyMap.containsKey(rootSessionIdExpCode)) {
-            return strategyMap.get(rootSessionIdExpCode).rank(param);
+        for (Map.Entry<String, RankService> entry : strategyMap.entrySet()) {
+            boolean hitExp = experimentService.judgeHitExp(param.getAppType(), param.getRootSessionId(), null, entry.getKey());
+            if (hitExp) {
+                return entry.getValue().rank(param);
+            }
         }
 
         Set<String> abExpCodes = param.getAbExpCodes();
@@ -87,29 +89,5 @@ public class RankRouter {
             return null;
         }
     }
-
-    private String findRootSessionIdExpCode(RankParam param) {
-        String expCode = "";
-        if (CollectionUtils.isEmpty(rootSessionIdExpConfigs)) {
-            return expCode;
-        }
-
-        String rootSessionId = param.getRootSessionId();
-        if (StringUtils.isBlank(rootSessionId)) {
-            return expCode;
-        }
-
-        int appType = param.getAppType();
-        String tail = rootSessionId.substring(rootSessionId.length() - 1);
-        for (RootSessionIdExpConfig config : rootSessionIdExpConfigs) {
-            if (config.getAppType().contains(appType)
-                    && config.getTail().contains(tail)
-                    && strategyMap.containsKey(config.getExpCode())) {
-                return config.getExpCode();
-            }
-        }
-
-        return expCode;
-    }
 }
 

+ 5 - 0
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/rank/bo/UserShareReturnProfile.java

@@ -22,6 +22,7 @@ public class UserShareReturnProfile {
     private List<UserSRBO> l_r1_s;   // last_return_seq(最近1层回流序列)
     private Map<String, VideoAttrSRBO> c1_s;   // cate1_seq(merge_first_level_cate序列-回流率)
     private Map<String, VideoAttrSRBO> c2_s;   // cate2_seq(merge_second_level_cate序列-回流率)
+    private Map<String, VideoAttrSRBO> d_k_s;
     private Map<String, VideoAttrSRBO> l1_s;   // label1_seq(festive_label1序列-回流率)
     private Map<String, VideoAttrSRBO> l2_s;   // label2_seq(festive_label2序列-回流率)
 
@@ -53,6 +54,10 @@ public class UserShareReturnProfile {
         this.c2_s = parseVideoAttrSR(data);
     }
 
+    public void setD_k_s(String data) {
+        this.d_k_s = parseVideoAttrSR(data);
+    }
+
     public void setL1_s(String data) {
         this.l1_s = parseVideoAttrSR(data);
     }

+ 13 - 23
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/rank/strategy/RankStrategy4RegionMergeModelV568.java

@@ -10,7 +10,6 @@ import com.tzld.piaoquan.recommend.server.service.FeatureService;
 import com.tzld.piaoquan.recommend.server.service.rank.RankParam;
 import com.tzld.piaoquan.recommend.server.service.rank.bo.UserShareReturnProfile;
 import com.tzld.piaoquan.recommend.server.service.rank.extractor.ExtractVideoMergeCate;
-import com.tzld.piaoquan.recommend.server.service.rank.extractor.ExtractorUtils;
 import com.tzld.piaoquan.recommend.server.service.rank.tansform.FeatureV6;
 import com.tzld.piaoquan.recommend.server.service.recall.strategy.*;
 import com.tzld.piaoquan.recommend.server.service.score.ScorerUtils;
@@ -79,8 +78,9 @@ public class RankStrategy4RegionMergeModelV568 extends RankStrategy4RegionMergeM
         RecallUtils.extractRecall(mergeWeight.getOrDefault("return1Cate2Ros", 5.0).intValue(), param, Return1Cate2RosRecallStrategy.PUSH_FORM, setVideo, rovRecallRank);
         //-------------------return1 cate2 str------------------
         RecallUtils.extractRecall(mergeWeight.getOrDefault("return1Cate2Str", 5.0).intValue(), param, Return1Cate2StrRecallStrategy.PUSH_FORM, setVideo, rovRecallRank);
-        //-------------------priori premium rovn------------------
-        RecallUtils.extractRecall(mergeWeight.getOrDefault("prioriPremiumRovn", 0.0).intValue(), param, PrioriPremiumRovnRecallStrategy.PUSH_FORM, setVideo, rovRecallRank);
+        //--------------deconstruction keywords ros-------------
+        RecallUtils.extractRecall(mergeWeight.getOrDefault("deconstructionKeywordsRos", 5.0).intValue(), param, UserDeconstructionKeywordsRecallStrategy.PUSH_FORM, setVideo, rovRecallRank);
+
 
         // 记录召回源中的视频
         this.rankBeforePostProcessor(rovRecallRank);
@@ -115,16 +115,12 @@ public class RankStrategy4RegionMergeModelV568 extends RankStrategy4RegionMergeM
 
         // 4. 排序模型计算
         Map<String, Float> sceneFeatureMap = new HashMap<>(0);
-        List<RankItem> items = ScorerUtils.getScorerPipeline("feeds_score_config_fm_xgb_20250729.conf").scoring(sceneFeatureMap, userFeatureMap, userFeatureMap, rankItems);
+        List<RankItem> items = ScorerUtils.getScorerPipeline("feeds_score_config_fm_xgb_20250317.conf").scoring(sceneFeatureMap, userFeatureMap, userFeatureMap, rankItems);
 
         // 5. 排序公式特征
         double xgbRovNegRate = mergeWeight.getOrDefault("xgbRovNegRate", 0.059);
         double xgbNorPowerWeight = mergeWeight.getOrDefault("xgbNorPowerWeight", 1.22);
         double xgbNorPowerExp = mergeWeight.getOrDefault("xgbNorPowerExp", 1.15);
-        double prioriHighValue = mergeWeight.getOrDefault("prioriHighValue", 1.5);
-        double prioriHighWeight = mergeWeight.getOrDefault("prioriHighWeight", 1.0);
-        double prioriLowValue = mergeWeight.getOrDefault("prioriLowValue", 0.5);
-        double prioriLowWeight = mergeWeight.getOrDefault("prioriLowWeight", 0.8);
         Map<String, Map<String, String>> vid2MapFeature = this.getVideoRedisFeature(vids, "redis:vid_hasreturn_vor:");
 
         // 获取权重
@@ -139,33 +135,27 @@ public class RankStrategy4RegionMergeModelV568 extends RankStrategy4RegionMergeM
         }
         Double cate2CoefficientDenominator = mergeWeight.getOrDefault("cate2CoefficientDenominator", 1d);
         Map<String, String> contextInfo = getContextInfo(param);
-        Map<String, Double> prioriVidProvinceRovn = this.getPrioriVidProvinceRovn(param.getProvince(), items, videoBaseInfoMap);
 
         List<Video> result = new ArrayList<>();
         for (RankItem item : items) {
             double score;
-            String vid = String.valueOf(item.getVideoId());
             double fmRovOrigin = item.getScoreRov();
             item.getScoresMap().put("fmRovOrigin", fmRovOrigin);
             double fmRov = restoreScore(fmRovOrigin, xgbRovNegRate);
             item.getScoresMap().put("fmRov", fmRov);
-            double hasReturnRovScore = Double.parseDouble(vid2MapFeature.getOrDefault(vid, new HashMap<>()).getOrDefault("rov", "0"));
+            double hasReturnRovScore = Double.parseDouble(vid2MapFeature.getOrDefault(item.getVideoId() + "", new HashMap<>()).getOrDefault("rov", "0"));
             item.getScoresMap().put("hasReturnRovScore", hasReturnRovScore);
             double norXGBScore = item.getScoresMap().getOrDefault("NorXGBScore", 0d);
             double newNorXGBScore = norPowerCalibration(xgbNorPowerWeight, xgbNorPowerExp, norXGBScore);
-            double vor = Double.parseDouble(vid2MapFeature.getOrDefault(vid, new HashMap<>()).getOrDefault("vor", "0"));
+            double vor = Double.parseDouble(vid2MapFeature.getOrDefault(item.getVideoId() + "", new HashMap<>()).getOrDefault("vor", "0"));
             item.getScoresMap().put("vor", vor);
 
-            String vidMergeCate2 = this.findVideoMergeCate2(videoBaseInfoMap, vid);
+            String vidMergeCate2 = this.findVideoMergeCate2(videoBaseInfoMap, String.valueOf(item.getVideoId()));
             Double scoreCoefficient = cate2Coefficient.getOrDefault(vidMergeCate2, 0d);
             item.getScoresMap().put("scoreCoefficient", scoreCoefficient);
             item.getScoresMap().put("cate2CoefficientDenominator", cate2CoefficientDenominator);
 
-            double prioriWeight = getPrioriVidProvinceWeight(prioriHighValue, prioriHighWeight, prioriLowValue, prioriLowWeight, vid, prioriVidProvinceRovn);
-            score = prioriWeight * fmRov * (0.1 + newNorXGBScore) * (0.1 + vor) * (1 + scoreCoefficient / cate2CoefficientDenominator);
-            if (!ExtractorUtils.isDoubleEqualToZero(prioriWeight - 1.0)) {
-                item.getScoresMap().put("prioriWeight", prioriWeight);
-            }
+            score = fmRov * (0.1 + newNorXGBScore) * (0.1 + vor) * (1 + scoreCoefficient / cate2CoefficientDenominator);
 
             Video video = item.getVideo();
             video.setScore(score);
@@ -173,16 +163,16 @@ public class RankStrategy4RegionMergeModelV568 extends RankStrategy4RegionMergeM
             video.setScoresMap(item.getScoresMap());
             // video.setAllFeatureMap(item.getAllFeatureMap());
 
-            String mergeCate2 = ExtractVideoMergeCate.parseMergeCate2(vid, videoBaseInfoMap);
+            String mergeCate2 = ExtractVideoMergeCate.parseMergeCate2(String.valueOf(item.getVideoId()), videoBaseInfoMap);
             if (StringUtils.isNotBlank(mergeCate2)) {
                 video.getMergeCateList().add(mergeCate2);
             }
 
-            if (MapUtils.isNotEmpty(feature.getVideoFeature()) && MapUtils.isNotEmpty(feature.getVideoFeature().get(vid))) {
-                video.getMetaFeatureMap().putAll(feature.getVideoFeature().get(vid));
+            if (MapUtils.isNotEmpty(feature.getVideoFeature()) && MapUtils.isNotEmpty(feature.getVideoFeature().get(item.getVideoId() + ""))) {
+                video.getMetaFeatureMap().putAll(feature.getVideoFeature().get(item.getVideoId() + ""));
             }
-            if (MapUtils.isNotEmpty(videoBaseInfoMap) && MapUtils.isNotEmpty(videoBaseInfoMap.get(vid))) {
-                video.getMetaFeatureMap().putAll(videoBaseInfoMap.get(vid));
+            if (MapUtils.isNotEmpty(videoBaseInfoMap) && MapUtils.isNotEmpty(videoBaseInfoMap.get(item.getVideoId() + ""))) {
+                video.getMetaFeatureMap().putAll(videoBaseInfoMap.get(item.getVideoId() + ""));
             }
             if (MapUtils.isNotEmpty(headVideoInfo)) {
                 video.getMetaFeatureMap().put("head_video", headVideoInfo);

+ 25 - 0
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/rank/tansform/FeatureV6.java

@@ -274,6 +274,18 @@ public class FeatureV6 {
         getVideoAttrSRCrossFeature("c9_c2s", rankVideo.getOrDefault("merge_second_level_cate", ""), profile.getC2_s(), featMap);
         getVideoAttrSRCrossFeature("c9_l1s", rankVideo.getOrDefault("festive_label1", ""), profile.getL1_s(), featMap);
         getVideoAttrSRCrossFeature("c9_l2s", rankVideo.getOrDefault("festive_label2", ""), profile.getL2_s(), featMap);
+
+        // 视频解构的关键词
+        if (rankVideo.containsKey("dk_keywords")) {
+            String dkKeywords = rankVideo.get("dk_keywords");
+            if (Objects.isNull(dkKeywords) || dkKeywords.isEmpty()) {
+                return;
+            }
+            for (String kw : dkKeywords.split("[,,、]")) {
+                kw = kw.replaceAll("(\\s+|\\t|:)", "");
+                getVideoAttrSRCrossFeature("c9_dks", kw, profile.getD_k_s(), featMap);
+            }
+        }
     }
 
     private static void getRSCrossFeature(boolean flag, String prefix, long currentMs, int maxN, List<UserSRBO> list, Map<String, String> rankVideo, Map<String, Map<String, String>> hVideoMap, Map<String, Double> featMap) {
@@ -379,6 +391,19 @@ public class FeatureV6 {
                 }
             }
         }
+        // 视频解构的关键词 ID特征
+        if (videoInfo.containsKey("dk_keywords")) {
+            String dkKeywords = videoInfo.get("dk_keywords");
+            if (Objects.nonNull(dkKeywords) && !dkKeywords.isEmpty()) {
+                for (String kw : dkKeywords.split("[,,、]")) {
+                    kw = kw.replaceAll("(\\s+|\\t|:)", "");
+                    if (!kw.isEmpty()) {
+                        String featKey = String.format("%s@dkw@%s", prefix, kw);
+                        featMap.put(featKey, 1.0);
+                    }
+                }
+            }
+        }
     }
 
     private static void getTwoVideoCrossFeature(String prefix, List<String> attrs, Map<String, String> video1, Map<String, String> video2, Map<String, Double> featMap) {

+ 18 - 8
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/recall/RecallService.java

@@ -4,6 +4,7 @@ import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
 import com.tzld.piaoquan.recommend.server.common.ThreadPoolFactory;
 import com.tzld.piaoquan.recommend.server.common.enums.AppTypeEnum;
 import com.tzld.piaoquan.recommend.server.model.Video;
+import com.tzld.piaoquan.recommend.server.service.ExperimentService;
 import com.tzld.piaoquan.recommend.server.service.recall.strategy.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
@@ -14,6 +15,7 @@ import org.springframework.context.ApplicationContextAware;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
 import java.util.*;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
@@ -27,6 +29,9 @@ import java.util.concurrent.TimeUnit;
 @Slf4j
 public class RecallService implements ApplicationContextAware {
 
+    @Resource
+    private ExperimentService experimentService;
+
     private final Map<String, RecallStrategy> strategyMap = new HashMap<>();
     private ApplicationContext applicationContext;
     private final ExecutorService pool = ThreadPoolFactory.recallPool();
@@ -125,14 +130,19 @@ public class RecallService implements ApplicationContextAware {
         strategies.add(strategyMap.get(Return1Cate2StrRecallStrategy.class.getSimpleName()));
         Set<String> abExpCodes = param.getAbExpCodes();
         if (CollectionUtils.isNotEmpty(abExpCodes)) {
-            if (abExpCodes.contains("568")) {
-                strategies.add(strategyMap.get(PrioriPremiumRovnRecallStrategy.class.getSimpleName()));
-            }
-            if (abExpCodes.contains("566")){
-                strategies.add(strategyMap.get(SocialI2IDirectRecallStrategy.class.getSimpleName()));
-                strategies.add(strategyMap.get(SocialI2IHistoryShareRecallStrategy.class.getSimpleName()));
-                strategies.add(strategyMap.get(SocialI2IHistoryClickRecallStrategy.class.getSimpleName()));
-            }
+            // if (abExpCodes.contains("568")) {
+            //     strategies.add(strategyMap.get(PrioriPremiumRovnRecallStrategy.class.getSimpleName()));
+            // }
+            // if (abExpCodes.contains("566")){
+            //     strategies.add(strategyMap.get(SocialI2IDirectRecallStrategy.class.getSimpleName()));
+            //     strategies.add(strategyMap.get(SocialI2IHistoryShareRecallStrategy.class.getSimpleName()));
+            //     strategies.add(strategyMap.get(SocialI2IHistoryClickRecallStrategy.class.getSimpleName()));
+            // }
+        }
+
+        boolean isHit842Exp = experimentService.judgeHitExp(param.getAppType(), param.getRootSessionId(), abExpCodes, "842");
+        if (isHit842Exp) {
+            strategies.add(strategyMap.get(UserDeconstructionKeywordsRecallStrategy.class.getSimpleName()));
         }
 
         // 命中用户黑名单不走流量池

+ 164 - 0
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/recall/strategy/UserDeconstructionKeywordsRecallStrategy.java

@@ -0,0 +1,164 @@
+package com.tzld.piaoquan.recommend.server.service.recall.strategy;
+
+import com.tzld.piaoquan.recommend.server.model.Video;
+import com.tzld.piaoquan.recommend.server.service.filter.FilterParam;
+import com.tzld.piaoquan.recommend.server.service.filter.FilterResult;
+import com.tzld.piaoquan.recommend.server.service.filter.FilterService;
+import com.tzld.piaoquan.recommend.server.service.rank.bo.UserShareReturnProfile;
+import com.tzld.piaoquan.recommend.server.service.rank.bo.VideoAttrSRBO;
+import com.tzld.piaoquan.recommend.server.service.recall.FilterParamFactory;
+import com.tzld.piaoquan.recommend.server.service.recall.RecallParam;
+import com.tzld.piaoquan.recommend.server.service.recall.RecallStrategy;
+import com.tzld.piaoquan.recommend.server.util.FeatureUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.math3.util.Pair;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+public class UserDeconstructionKeywordsRecallStrategy implements RecallStrategy {
+    private final String CLASS_NAME = this.getClass().getSimpleName();
+
+    @Autowired
+    private FilterService filterService;
+    @Autowired
+    @Qualifier("redisTemplate")
+    public RedisTemplate<String, String> redisTemplate;
+
+    public static final int topN = 2;
+    public static final String PUSH_FORM = "recall_strategy_deconstruction_keywords";
+    public static final String redisKeyPrefix = "deconstruction_keywords_recall:";
+
+    @Override
+    public String pushFrom() {
+        return PUSH_FORM;
+    }
+
+    @Override
+    public List<Video> recall(RecallParam param) {
+
+        List<Video> videos = new ArrayList<>();
+        try {
+            UserShareReturnProfile userProfile = param.getUserProfile();
+            if (Objects.isNull(userProfile) || MapUtils.isEmpty(userProfile.getD_k_s())) {
+                return videos;
+            }
+            List<String> keys = this.getRedisKey(userProfile.getD_k_s());
+            if (CollectionUtils.isEmpty(keys)) {
+                return videos;
+            }
+            List<String> values = redisTemplate.opsForValue().multiGet(keys);
+            List<Long> vids = this.recall(param.getVideoId(), values);
+            FilterParam filterParam = FilterParamFactory.create(param, vids);
+            FilterResult filterResult = filterService.filter(filterParam);
+            if (Objects.isNull(filterResult) || CollectionUtils.isEmpty(filterResult.getVideoIds())) {
+                return videos;
+            }
+            List<Long> filterIds = filterResult.getVideoIds();
+            int n = filterIds.size();
+            for (int i = 0; i < n; i++) {
+                Video video = new Video();
+                video.setVideoId(filterIds.get(i));
+                video.setRovScore(n - i);
+                video.setPushFrom(pushFrom());
+                videos.add(video);
+            }
+        } catch (Exception e) {
+            log.error("recall is wrong in {}, error={}", CLASS_NAME, e);
+        }
+        return videos;
+    }
+
+    private List<String> getRedisKey(Map<String, VideoAttrSRBO> map) {
+        List<String> keys = new ArrayList<>();
+        if (MapUtils.isEmpty(map)) {
+            return keys;
+        }
+
+        List<Pair<String, Double>> keywordsList = getOrderedList(map);
+        for (Pair<String, Double> pair : keywordsList) {
+            keys.add(String.format("%s:%s", redisKeyPrefix, pair.getFirst()));
+            if (keys.size() >= topN) {
+                break;
+            }
+        }
+        return keys;
+    }
+
+    private List<Pair<String, Double>> getOrderedList(Map<String, VideoAttrSRBO> map) {
+        List<Pair<String, Double>> pairList = new ArrayList<>();
+        if (MapUtils.isEmpty(map)) {
+            return pairList;
+        }
+
+        for (Map.Entry<String, VideoAttrSRBO> entry : map.entrySet()) {
+            String name = entry.getKey();
+            VideoAttrSRBO videoAttrSRBO = entry.getValue();
+            if (Objects.nonNull(videoAttrSRBO) && videoAttrSRBO.getRu() > 0) {
+                long sharePv = videoAttrSRBO.getSp();
+                long returnUv = videoAttrSRBO.getRu();
+                double score = FeatureUtils.plusSmooth(returnUv, sharePv, 5);
+                Pair<String, Double> pair = Pair.create(name, score);
+                pairList.add(pair);
+            }
+        }
+        if (pairList.size() > 1) {
+            pairList.sort(Comparator.comparingDouble(o -> -o.getSecond()));
+        }
+        return pairList;
+    }
+
+    private List<Long> recall(Long headVid, List<String> values) {
+        List<Long> vids = new ArrayList<>();
+        if (CollectionUtils.isEmpty(values)) {
+            return vids;
+        }
+
+        Set<Long> hits = new HashSet<>();
+        hits.add(headVid);
+        List<Pair<Long, Double>> list = new ArrayList<>();
+        for (String value : values) {
+            if (StringUtils.isBlank(value)) {
+                continue;
+            }
+            String[] cells = value.split("\t");
+            if (cells.length != 2) {
+                continue;
+            }
+            List<Long> ids = Arrays.stream(cells[0].split(",")).map(Long::valueOf).collect(Collectors.toList());
+            List<Double> scores = Arrays.stream(cells[1].split(",")).map(Double::valueOf).collect(Collectors.toList());
+            if (CollectionUtils.isEmpty(ids) || CollectionUtils.isEmpty(scores)) {
+                continue;
+            }
+            if (ids.size() != scores.size()) {
+                continue;
+            }
+            for (int i = 0; i < ids.size(); ++i) {
+                long id = ids.get(i);
+                double score = scores.get(i);
+                if (hits.contains(id)) {
+                    continue;
+                }
+                hits.add(id);
+                list.add(Pair.create(id, score));
+            }
+        }
+        if (CollectionUtils.isEmpty(list)) {
+            return vids;
+        }
+        list.sort(Comparator.comparingDouble(o -> -o.getSecond()));
+        for (Pair<Long, Double> pair : list) {
+            vids.add(pair.getFirst());
+        }
+        return vids;
+    }
+}

+ 9 - 3
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/score/ScorerUtils.java

@@ -31,19 +31,25 @@ public final class ScorerUtils {
     public static void warmUp() {
         log.info("scorer warm up ");
         ScorerUtils.init(VIDEO_SCORE_CONF_FOR_AD);
-        ScorerUtils.init("feeds_score_config_20240609.conf");
-        ScorerUtils.init("feeds_score_config_20240807.conf");
+        // 排序配置
+        // ScorerUtils.init("feeds_score_config_20240609.conf");
+        // ScorerUtils.init("feeds_score_config_20240807.conf");
+        // ScorerUtils.init("feeds_score_config_fm_xgb_20250317.conf");
+        // ScorerUtils.init("feeds_score_config_xgb_ros_binary_20250319.conf");
         ScorerUtils.init("feeds_score_config_fm_xgb_20250317.conf");
         ScorerUtils.init("feeds_score_config_fm_xgb_20250729.conf");
         ScorerUtils.init("feeds_score_config_fm_xgb_20260116.conf");
         ScorerUtils.init("feeds_score_config_fm_xgb_20260123.conf");
         ScorerUtils.init("feeds_score_config_xgb_ros_20250311.conf");
-        ScorerUtils.init("feeds_score_config_xgb_ros_binary_20250319.conf");
+
+        // 召回配置
         ScorerUtils.init4Recall("feeds_recall_config_region_v1.conf");
         ScorerUtils.init4Recall("feeds_recall_config_region_ros.conf");
         ScorerUtils.init4Recall("feeds_score_config_bless.conf");
         ScorerUtils.init4Recall("feeds_recall_config_tomson.conf");
         ScorerUtils.init4Recall("feeds_recall_config_region_v7_longterm.conf");
+
+        // 分桶文件
         List<String> bucketFileList = Arrays.asList("20250218_bucket_322.txt", "20250306_ros_bucket_229.txt");
         FeatureBucketUtils.init(bucketFileList);
         FestiveUtil.init();

+ 1 - 1
recommend-server-service/src/main/resources/feeds_score_config_fm_xgb_20250317.conf

@@ -2,7 +2,7 @@ scorer-config = {
   rov-score-config = {
      scorer-name = "com.tzld.piaoquan.recommend.server.service.score.VlogRovFMScorer"
      scorer-priority = 96
-     model-path = "zhangbo/model_fm_for_recsys_v6_rov.txt"
+     model-path = "zhangbo/model_fm_for_recsys_deconstruction_str.txt"
   }
   nor-score-config = {
     scorer-name = "com.tzld.piaoquan.recommend.server.service.score.NorXGBRegressionScorer"