Просмотр исходного кода

Merge branch 'cooperation_video_candidate_pool_improved_lld_0509' of Server/growth-manager into master

liulidong 6 часов назад
Родитель
Сommit
7f33f0b29b

+ 25 - 0
api-module/src/main/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImpl.java

@@ -135,6 +135,14 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
     @Value("${video.min.score:0.0}")
     private Double videoMinScore;
 
+    /**
+     * 曝光下发过滤阈值(全渠道、全向量召回池统一生效):match_exposure_pv 有值时要求 > 该阈值才下发。
+     * NULL(无曝光数据,如「场景已看视频」池 100% 无此字段)一律放行,不受影响。
+     * <=0 表示关闭该过滤(全放行)。Apollo 可调,改阈值不发版。
+     */
+    @Value("${demand.min.exposure.pv:2000}")
+    private Long demandMinExposurePv;
+
     @Value("${video.title.search.max.count:500}")
     private int videoTitleSearchMaxCount;
 
@@ -725,6 +733,19 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
                 : Arrays.asList("早中晚好", "节日祝福");
     }
 
+    /**
+     * 曝光下发过滤的纯逻辑(可单测,全渠道统一口径):
+     *   - minExposurePv 为 null 或 <=0:关闭过滤,全放行;
+     *   - match_exposure_pv 为 NULL(无曝光数据,如「场景已看视频」池):放行,不受影响;
+     *   - 有曝光数据:要求 > minExposurePv 才下发。
+     */
+    static boolean passesExposureFilter(Long matchExposurePv, Long minExposurePv) {
+        if (minExposurePv == null || minExposurePv <= 0) {
+            return true;
+        }
+        return matchExposurePv == null || matchExposurePv > minExposurePv;
+    }
+
     @Override
     public Page<VideoContentItemVO> getVideoContentList(VideoContentListParam param) {
         ContentPlatformAccount user = LoginUserContext.getUser();
@@ -1053,6 +1074,8 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         for (ContentPlatformDemandVideo r : bestPerVideo.values()) {
             if (r.getRov() == null || r.getRov() <= 0) continue;
             if (r.getSceneSumRov() == null || r.getSceneSumRov() < PRIOR_SCENE_MIN_SUM_ROV) continue;
+            // 曝光下发过滤:场景已看视频池 match_exposure_pv 100% 为 NULL,此处恒放行;保留以统一全池口径。
+            if (!passesExposureFilter(r.getMatchExposurePv(), demandMinExposurePv)) continue;
             filtered.add(r);
         }
         // 3. 按 sceneSumRov DESC 排序,次级 total_rov DESC
@@ -1117,6 +1140,7 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
 
         rows = rows.stream()
                 .filter(r -> r.getRov() != null && r.getRov() >= DEMAND_MIN_ROV)
+                .filter(r -> passesExposureFilter(r.getMatchExposurePv(), demandMinExposurePv))
                 .collect(Collectors.toList());
 
         Function<ContentPlatformDemandVideo, String> keyFn = r ->
@@ -1197,6 +1221,7 @@ public class ContentPlatformPlanServiceImpl implements ContentPlatformPlanServic
         // 近 7 日 rov 下限,与 prior 池一致(DEMAND_MIN_ROV,统一到 0.03)
         rows = rows.stream()
                 .filter(r -> r.getRov() != null && r.getRov() >= DEMAND_MIN_ROV)
+                .filter(r -> passesExposureFilter(r.getMatchExposurePv(), demandMinExposurePv))
                 .collect(Collectors.toList());
 
         Function<ContentPlatformDemandVideo, String> keyFn = r ->

+ 44 - 0
api-module/src/test/java/com/tzld/piaoquan/api/service/contentplatform/impl/ContentPlatformPlanServiceImplExposureFilterTest.java

@@ -0,0 +1,44 @@
+package com.tzld.piaoquan.api.service.contentplatform.impl;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * 曝光下发过滤纯逻辑单测(不依赖 Spring/DB)。
+ * 语义见 {@link ContentPlatformPlanServiceImpl#passesExposureFilter(Long, Long)}。
+ */
+public class ContentPlatformPlanServiceImplExposureFilterTest {
+
+    /** 阈值为 null:过滤关闭,任何值(含 NULL)都放行。 */
+    @Test
+    public void thresholdNull_passesAll() {
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(null, null));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(0L, null));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(999999L, null));
+    }
+
+    /** 阈值 <=0:过滤关闭,全放行。 */
+    @Test
+    public void thresholdNonPositive_passesAll() {
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(null, 0L));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(1L, 0L));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(null, -1L));
+    }
+
+    /** 无曝光数据(NULL,如「场景已看视频」池)在阈值开启时仍放行——核心豁免语义。 */
+    @Test
+    public void nullExposure_alwaysPasses_whenThresholdOn() {
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(null, 2000L));
+    }
+
+    /** 有曝光数据:严格大于阈值才放行,边界值(==阈值)被砍。 */
+    @Test
+    public void hasExposure_strictlyGreaterThanThreshold() {
+        assertFalse(ContentPlatformPlanServiceImpl.passesExposureFilter(1000L, 2000L));
+        assertFalse(ContentPlatformPlanServiceImpl.passesExposureFilter(2000L, 2000L)); // 边界:==2000 砍掉
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(2001L, 2000L));
+        assertTrue(ContentPlatformPlanServiceImpl.passesExposureFilter(50000L, 2000L));
+    }
+}