Browse Source

ADD: 新增节日内容召回

sunxy 1 year ago
parent
commit
2a43b31c69

+ 3 - 1
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/rank/RankRouter.java

@@ -26,6 +26,8 @@ public class RankRouter {
     private RankStrategy4RegionMergeModelV4 rankStrategy4RegionMergeModelV4;
     @Autowired
     private RankStrategy4RegionMergeModelV5 rankStrategy4RegionMergeModelV5;
+    @Autowired
+    private FestivalStrategy4RankModel festivalStrategy4RankModel;
 
     @Autowired
     private RankStrategyFlowThompsonModel rankStrategyFlowThompsonModel;
@@ -70,7 +72,7 @@ public class RankRouter {
                 return rankStrategy4RegionMergeModelV5.rank(param);
             case "60130":
                 // 先走默认排序,后续需要优化祝福类的视频排序
-                return rankService.rank(param);
+                return festivalStrategy4RankModel.rank(param);
             default:
                 break;
         }

+ 1 - 5
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/rank/RankService.java

@@ -83,7 +83,7 @@ public class RankService {
         return mergeAndSort(param, rovRecallRank, flowPoolRank);
     }
 
-    public void rankFilter(RankParam param, List<Video> rovRecallRank, List<Video> flowPoolRank){
+    public void rankFilter(RankParam param, List<Video> rovRecallRank, List<Video> flowPoolRank) {
         return;
     }
 
@@ -148,7 +148,6 @@ public class RankService {
                 || param.getAbCode().equals("60117")
                 || param.getAbCode().equals("60118")
                 || param.getAbCode().equals("60119")
-                || param.getAbCode().equals("60130")
         ) {
             // 地域召回要做截取,再做融合排序
             removeDuplicate(rovRecallRank);
@@ -166,9 +165,6 @@ public class RankService {
                 rovRecallRank.addAll(extractAndSort(param, FlowPoolLastDayTopRecallStrategy.PUSH_FORM));
                 rovRecallRank.addAll(extractAndSort(param, TopGoodPerformanceVideoRecallStrategy.PUSH_FORM));
             }
-            if (param.getAbCode().equals("60130")) {
-                rovRecallRank.addAll(extractAndSort(param, FestivalRecallStrategyV1.PUSH_FORM));
-            }
             removeDuplicate(rovRecallRank);
 
             // 融合排序

+ 53 - 0
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/rank/strategy/FestivalStrategy4RankModel.java

@@ -0,0 +1,53 @@
+package com.tzld.piaoquan.recommend.server.service.rank.strategy;
+
+import com.tzld.piaoquan.recommend.server.model.Video;
+import com.tzld.piaoquan.recommend.server.service.rank.RankParam;
+import com.tzld.piaoquan.recommend.server.service.rank.RankService;
+import com.tzld.piaoquan.recommend.server.service.recall.strategy.FestivalRecallStrategyV1;
+import com.tzld.piaoquan.recommend.server.service.recall.strategy.ReturnVideoRecallStrategy;
+import com.tzld.piaoquan.recommend.server.service.recall.strategy.SimHotVideoRecallStrategy;
+import com.tzld.piaoquan.recommend.server.util.JSONUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author sunxy
+ */
+@Service
+@Slf4j
+public class FestivalStrategy4RankModel extends RankService {
+
+    @Override
+    public List<Video> mergeAndRankRovRecall(RankParam param) {
+        List<Video> rovRecallRank = new ArrayList<>();
+        rovRecallRank.addAll(extractAndSort(param, SimHotVideoRecallStrategy.PUSH_FORM));
+        rovRecallRank.addAll(extractAndSort(param, ReturnVideoRecallStrategy.PUSH_FORM));
+        rovRecallRank.addAll(extractAndSort(param, FestivalRecallStrategyV1.PUSH_FORM));
+        removeDuplicate(rovRecallRank);
+        // 融合排序
+        List<String> videoIdKeys = rovRecallRank.stream()
+                .map(t -> param.getRankKeyPrefix() + t.getVideoId())
+                .collect(Collectors.toList());
+        List<String> videoScores = redisTemplate.opsForValue().multiGet(videoIdKeys);
+//        log.info("FestivalStrategy4RankModel mergeAndRankRovRecall videoIdKeys={}, videoScores={}", JSONUtils.toJson(videoIdKeys),
+//                JSONUtils.toJson(videoScores));
+        if (CollectionUtils.isNotEmpty(videoScores)
+                && videoScores.size() == rovRecallRank.size()) {
+            for (int i = 0; i < videoScores.size(); i++) {
+                rovRecallRank.get(i).setSortScore(NumberUtils.toDouble(videoScores.get(i), 0.0));
+            }
+            Collections.sort(rovRecallRank,
+                    Comparator.comparingDouble(o -> -(o.getSortScore() * 0.4 * (o.getRovScore() / 100))));
+        }
+
+        return rovRecallRank;
+    }
+}

+ 43 - 0
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/score4recall/strategy/DynamicGaussianFunction.java

@@ -0,0 +1,43 @@
+package com.tzld.piaoquan.recommend.server.service.score4recall.strategy;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+/**
+ * 动态高斯函数
+ *
+ * @author sunxy
+ */
+public class DynamicGaussianFunction {
+    private static final double SIGMA = 24.0; // 标准差,可以根据需要调整这个值
+
+    public static double calculateValue(LocalDateTime testTime, LocalDateTime startTime, LocalDateTime endTime,
+                                        LocalDateTime peakTime) {
+        long testTimestamp = testTime.toEpochSecond(ZoneOffset.UTC) * 1000;
+        // 检查时间是否在指定范围内
+        if (testTimestamp < startTime.toEpochSecond(ZoneOffset.UTC) * 1000 ||
+                testTimestamp > endTime.toEpochSecond(ZoneOffset.UTC) * 1000) {
+            return 0;
+        }
+
+        // 计算 t_0,即从开始时间到峰值时间的小时数
+        double t_0 = (peakTime.toEpochSecond(ZoneOffset.UTC) - startTime.toEpochSecond(ZoneOffset.UTC)) / 3600.0;
+
+        // 计算时间差(以小时为单位)
+        double t = (testTimestamp / 1000.0 - startTime.toEpochSecond(ZoneOffset.UTC)) / 3600.0;
+
+        // 计算高斯函数值
+        return 100 * Math.exp(-Math.pow(t - t_0, 2) / (2 * Math.pow(SIGMA, 2)));
+    }
+
+    public static void main(String[] args) {
+        // 示例:计算2024-02-03 09:00的值
+        LocalDateTime startTime = LocalDateTime.of(2024, 1, 29, 0, 0);
+        LocalDateTime endTime = LocalDateTime.of(2024, 2, 3, 23, 59);
+        LocalDateTime peakTime = LocalDateTime.of(2024, 2, 3, 9, 0);
+        LocalDateTime testTime = LocalDateTime.of(2024, 1, 31, 22, 0);
+
+        double value = calculateValue(testTime, startTime, endTime, peakTime);
+        System.out.println("Function value at " + testTime + ": " + value);
+    }
+}

+ 68 - 11
recommend-server-service/src/main/java/com/tzld/piaoquan/recommend/server/service/score4recall/strategy/FestivalRecallScore.java

@@ -9,17 +9,23 @@ import org.apache.commons.lang3.tuple.Pair;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.stream.Collectors;
 
 
 public class FestivalRecallScore extends AbstractScorer4Recall {
 
-    private static final Map<String, List<String>> FESTIVAL_TIME_MAP = new HashMap<String, List<String>>() {
+    private static final Map<String, String> DAILY_BLESSING_TIME_MAP = new HashMap<String, String>() {
+        {
+            put("晚安", "daily 21:00-24:00");
+            put("晚上好", "daily 18:00-20:00");
+            put("下午好", "daily 15:00-16:00");
+            put("中午好 ", "daily 11:00-13:00");
+            put("早上好", "daily 00:00-08:00");
+        }
+    };
+
+    private static final Map<String, List<String>> YEARLY_FESTIVAL_TIME_MAP = new HashMap<String, List<String>>() {
         {
-            put("晚安", Arrays.asList("daily 21:00-24:00"));
-            put("晚上好", Arrays.asList("daily 18:00-20:00"));
-            put("下午好", Arrays.asList("daily 15:00-16:00"));
-            put("中午好 ", Arrays.asList("daily 11:00-13:00"));
-            put("早上好", Arrays.asList("daily 00:00-08:00"));
             put("圣诞节", Arrays.asList("2024-12-20 00:00~2024-12-25 08:00", "2025-12-20 00:00~2025-12-25 08:00", "2026-12-20 " +
                     "00:00~2026-12-25 08:00"));
             put("平安夜", Arrays.asList("2024-12-19 00:00~2024-12-24 08:00", "2025-12-19 00:00~2025-12-24 08:00", "2026-12-19 " +
@@ -134,7 +140,7 @@ public class FestivalRecallScore extends AbstractScorer4Recall {
     }
 
     @Override
-    public List<Pair<Long, Double>> recall(Map<String, String> params){
+    public List<Pair<Long, Double>> recall(Map<String, String> params) {
         // 节假日、时效性,判断
         Model4RecallKeyValue model = (Model4RecallKeyValue) this.getModel();
         if (model == null || model.kv == null) {
@@ -142,27 +148,78 @@ public class FestivalRecallScore extends AbstractScorer4Recall {
         }
         List<Pair<Long, Double>> result = new ArrayList<>();
         LocalDateTime now = LocalDateTime.now();
-        for (Map.Entry<String, List<String>> entry : FESTIVAL_TIME_MAP.entrySet()) {
+        // 节日祝福-每年
+        for (Map.Entry<String, List<String>> entry : YEARLY_FESTIVAL_TIME_MAP.entrySet()) {
             String festival = entry.getKey();
             List<String> timeRangeList = entry.getValue();
             if (isFestivalTime(now, timeRangeList)) {
+                Pair<LocalDateTime, LocalDateTime> startTimeAndEndTime = getStartTimeAndEndTime(timeRangeList.get(0));
+                if (startTimeAndEndTime == null) {
+                    continue;
+                }
+                // 节日峰值设置为结束时间的当天的9点
+                double weight = DynamicGaussianFunction.calculateValue(LocalDateTime.now(), startTimeAndEndTime.getLeft(),
+                        startTimeAndEndTime.getRight(), startTimeAndEndTime.getRight().withHour(9));
+
+                List<Pair<Long, Double>> festivalLists = model.kv.getOrDefault(festival, new ArrayList<>());
+                if (festivalLists.isEmpty()) {
+                    continue;
+                }
+                festivalLists = festivalLists.stream().map(pair -> Pair.of(pair.getLeft(), weight))
+                        .limit(Math.min(50, festivalLists.size()))
+                        .collect(Collectors.toList());
+                result.addAll(festivalLists);
+            }
+        }
+        // 每日祝福-每天固定时间段
+        for (Map.Entry<String, String> entry : DAILY_BLESSING_TIME_MAP.entrySet()) {
+            String festival = entry.getKey();
+            String timeRange = entry.getValue();
+            if (isFestivalTime(now, Collections.singletonList(timeRange))) {
                 List<Pair<Long, Double>> festivalLists = model.kv.getOrDefault(festival, new ArrayList<>());
                 if (festivalLists.isEmpty()) {
                     continue;
                 }
-                festivalLists = festivalLists.subList(0, Math.min(100, festivalLists.size()));
+                festivalLists = festivalLists.stream().map(pair -> Pair.of(pair.getLeft(), 0.0))
+                        .limit(Math.min(50, festivalLists.size()))
+                        .collect(Collectors.toList());
                 result.addAll(festivalLists);
             }
         }
-        // 固定获取常规祝福类的小程序
+        // 常规祝福类的小程序-任意时间
         List<Pair<Long, Double>> festivalLists = model.kv.getOrDefault("祝福", new ArrayList<>());
         if (!festivalLists.isEmpty()) {
-            festivalLists = festivalLists.subList(0, Math.min(100, festivalLists.size()));
+            festivalLists = festivalLists.stream().map(pair -> Pair.of(pair.getLeft(), 0.0))
+                    .limit(Math.min(50, festivalLists.size()))
+                    .collect(Collectors.toList());
             result.addAll(festivalLists);
         }
         return result;
     }
 
+    public Pair<LocalDateTime, LocalDateTime> getStartTimeAndEndTime(String timeRangeList) {
+        if (timeRangeList == null || timeRangeList.isEmpty()) {
+            return null;
+        }
+        // 时间格式 2024-12-20 00:00~2024-12-25 08:00
+        if (StringUtils.startsWith(timeRangeList, "daily")) {
+            // 判断是否是 daily 开头
+            return null;
+        } else {
+            String[] split = StringUtils.split(timeRangeList, "~");
+            if (split.length != 2) {
+                return null;
+            }
+            String startTime = split[0];
+            String endTime = split[1];
+            // 解析 startTime endTime
+            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+            LocalDateTime startLocalDateTime = LocalDateTime.parse(startTime, dateTimeFormatter);
+            LocalDateTime endLocalDateTime = LocalDateTime.parse(endTime, dateTimeFormatter);
+            return Pair.of(startLocalDateTime, endLocalDateTime);
+        }
+    }
+
     public boolean isFestivalTime(LocalDateTime now, List<String> timeRangeList) {
         if (timeRangeList == null || timeRangeList.isEmpty()) {
             return false;