65 Commits 5ad54adaf8 ... 7e0c78ec0a

Auteur SHA1 Bericht Datum
  yaodaoseng 7e0c78ec0a Merge branch '20250813_feature_fjy_calander' into test 1 maand geleden
  yaodaoseng 7818a6d1a4 update 1 maand geleden
  xueyiming 8e76aded4c Merge branch 'dev-xym-add-filter-config' of algorithm/ad-engine into master 1 maand geleden
  xueyiming e1a8f6737e Merge branch 'refs/heads/master' into dev-xym-add-filter-config 1 maand geleden
  fanjinyang c688ecae24 Merge branch '20250814_feature_fjy_crowdlayerenumadd' of algorithm/ad-engine into master 1 maand geleden
  yaodaoseng 932ac68510 update 1 maand geleden
  yaodaoseng 411e63d003 Merge branch 'master' into 20250814_feature_fjy_crowdlayerenumadd 1 maand geleden
  yaodaoseng 5eb61f123e update 1 maand geleden
  xueyiming 35db42cdab 增加过滤 1 maand geleden
  zhaohaipeng f2c31a4be8 Merge branch 'feature_20250814_user_layer_4l' of algorithm/ad-engine into master 1 maand geleden
  zhaohaipeng 5ba6acd785 feat:修改获取用户分群逻辑 1 maand geleden
  xueyiming 742e5560b6 Merge branch 'dev-xym-update-layer' of algorithm/ad-engine into master 1 maand geleden
  xueyiming fae9b6a229 切换人群 1 maand geleden
  xueyiming 4ece1fff92 Merge branch 'refs/heads/master' into dev-xym-update-layer 1 maand geleden
  xueyiming b2c9d38076 切换人群 1 maand geleden
  xueyiming 8ee229fda1 切换人群 1 maand geleden
  zhaohaipeng ddcc77100b Merge branch 'feature_20250814_user_layer_4l' of algorithm/ad-engine into master 1 maand geleden
  zhaohaipeng 3033772d22 feat:修改获取用户分群逻辑 1 maand geleden
  xueyiming 2b07dc59ab Merge branch 'dev-xym-add-category' of algorithm/ad-engine into master 1 maand geleden
  xueyiming b1c939e674 增加品类 1 maand geleden
  zhaohaipeng 678d5e2ac1 Merge branch 'feature_20250812_zhaohaipeng_793' of algorithm/ad-engine into master 1 maand geleden
  zhaohaipeng 0d396de9ad feat:添加出广告按用户分群配置实验 1 maand geleden
  zhaohaipeng 61e44064fd Merge branch 'feature_20250812_zhaohaipeng_793' of algorithm/ad-engine into master 1 maand geleden
  zhaohaipeng e4e419fa3f feat:添加出广告按用户分群配置实验 1 maand geleden
  zhaohaipeng 2f20d2ed2d feat:添加出广告按用户分群配置实验 1 maand geleden
  zhaohaipeng 213fa2da8b feat:添加出广告按用户分群配置实验 1 maand geleden
  zhaohaipeng 8544609754 feat:添加出广告按用户分群配置实验 1 maand geleden
  xueyiming 8a1e690312 Merge branch 'dev-xym-calibration' of algorithm/ad-engine into master 1 maand geleden
  xueyiming 0732949af8 增加行业 1 maand geleden
  xueyiming 39229411c1 增加判断 1 maand geleden
  xueyiming 341dd3e84f 增加过滤 1 maand geleden
  xueyiming 2efb4e3326 增加行业信息 1 maand geleden
  xueyiming 2195e3a67f 增加行业校准实验 1 maand geleden
  xueyiming 848d6ad09c Merge branch 'dev-xym-add-log1' of algorithm/ad-engine into master 2 maanden geleden
  xueyiming 89057f315a 增加error日志 2 maanden geleden
  xueyiming 074979c95f Merge branch 'dev-xym-update' of algorithm/ad-engine into master 2 maanden geleden
  xueyiming bcc6ce23f1 修改模型校准 2 maanden geleden
  xueyiming 84491dd8a5 Merge branch 'dev-xym-checkout-model' of algorithm/ad-engine into master 2 maanden geleden
  xueyiming d56e00cd62 修改新模型地址 2 maanden geleden
  xueyiming e86f958d54 Merge branch 'dev-xym-add-model' of algorithm/ad-engine into master 2 maanden geleden
  xueyiming 84d142469e 修改上传模型版本 2 maanden geleden
  xueyiming bdaf990619 Merge branch 'dev-xym-add-model' of algorithm/ad-engine into master 2 maanden geleden
  xueyiming 66e3bd798e 修改配置文件 2 maanden geleden
  xueyiming e1ded6427f Merge branch 'refs/heads/master' into dev-xym-add-model 2 maanden geleden
  xueyiming 834cfdb31d Merge branch 'dev-xym-update-ecpm' of algorithm/ad-engine into master 2 maanden geleden
  xueyiming f25a0cfc84 增加过滤值返回 2 maanden geleden
  xueyiming 0c1e4db020 修改字段 2 maanden geleden
  xueyiming 725f8d75f1 790实验返回修正后的ecpm值 2 maanden geleden
  xueyiming 1fb469bc92 790实验返回修正后的ecpm值 2 maanden geleden
  zhaohaipeng 5050e90500 Merge branch 'feature_20250804_zhaohaipeng_rootsessionid_tail_predict' of algorithm/ad-engine into master 2 maanden geleden
  zhaohaipeng cd91f14f3b feat:人群选择添加rootSessionId尾号实验机制 2 maanden geleden
  zhaohaipeng 8817b17140 Merge branch 'feature_20250804_zhaohaipeng_rootsessionid_tail_predict' of algorithm/ad-engine into master 2 maanden geleden
  zhaohaipeng 4981043fe4 feat:人群选择添加rootSessionId尾号实验机制 2 maanden geleden
  xueyiming 3b0ef06004 Merge branch 'refs/heads/master' into dev-xym-add-model 2 maanden geleden
  xueyiming 49d56d1368 683实验修改为新模型 2 maanden geleden
  zhaohaipeng b115725174 Merge branch 'feature_20250804_zhaohaipeng_rootsessionid_tail_predict' of algorithm/ad-engine into master 2 maanden geleden
  zhaohaipeng 162966b51b feat:人群选择添加rootSessionId尾号实验机制 2 maanden geleden
  zhaohaipeng d9a23e6714 feat:人群选择添加rootSessionId尾号实验机制 2 maanden geleden
  zhaohaipeng a619b62aae feat:人群选择添加rootSessionId尾号实验机制 2 maanden geleden
  xueyiming 65e21bef2b Merge branch 'dev-xym-add-filter' of algorithm/ad-engine into master 2 maanden geleden
  xueyiming b50b2e0847 687实验增加分人群过滤 2 maanden geleden
  yaodaoseng 81c7f448ad Merge branch '20250729_feature_fjy_guarantee' 2 maanden geleden
  xueyiming 925837b706 Merge branch 'dev-xym-add-log' of algorithm/ad-engine into master 2 maanden geleden
  xueyiming fa78627951 增加保量类型 2 maanden geleden
  yaodaoseng d17993a94b update 2 maanden geleden
37 gewijzigde bestanden met toevoegingen van 1755 en 225 verwijderingen
  1. 1 0
      .gitignore
  2. 2 0
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/dto/AdPlatformCreativeDTO.java
  3. 3 2
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/enums/CrowdLayerEnum.java
  4. 23 0
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/enums/FilterTypeEnum.java
  5. 9 3
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/CommonResponse.java
  6. 54 0
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/manager/HolidayCalendarFeign.java
  7. 60 0
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/manager/fallback/HolidayCalendarFeignFallbackFactory.java
  8. 87 0
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/manager/service/HolidayService.java
  9. 72 0
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/manager/vo/HolidayCalendarVO.java
  10. 22 9
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/helper/DnnCidDataHelper.java
  11. 3 0
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/score/ScorerUtils.java
  12. 2 2
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/score/model/PAIModelV1.java
  13. 210 0
      ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/score/model/PAIModelV2.java
  14. 11 26
      ad-engine-server/src/main/java/com/tzld/piaoquan/ad/engine/server/controller/AdRecommendController.java
  15. 134 0
      ad-engine-server/src/main/java/com/tzld/piaoquan/ad/engine/server/controller/HolidayTestController.java
  16. 6 0
      ad-engine-server/src/main/resources/ad_score_config_pai_20250804.conf
  17. 4 0
      ad-engine-server/src/main/resources/application-dev.yml
  18. 4 0
      ad-engine-server/src/main/resources/application-pre.yml
  19. 4 0
      ad-engine-server/src/main/resources/application-prod.yml
  20. 4 0
      ad-engine-server/src/main/resources/application-test.yml
  21. 14 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/entity/FilterConfig.java
  22. 73 4
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/impl/PredictModelServiceImpl.java
  23. 6 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/param/request/ThresholdPredictModelRequestParam.java
  24. 43 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/BasicPredict.java
  25. 5 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/ConvertUtil.java
  26. 11 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/PredictContext.java
  27. 28 41
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/PredictServiceV2.java
  28. 86 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/RootSessionIdPredict.java
  29. 96 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/UserLayerRootSessionIdPredict.java
  30. 89 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/scorer/PAIScorerV2.java
  31. 230 116
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBasic.java
  32. 1 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBy680.java
  33. 246 18
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBy683.java
  34. 2 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBy687.java
  35. 28 4
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBy688.java
  36. 9 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/user/UserService.java
  37. 73 0
      ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/user/impl/UserServiceImpl.java

+ 1 - 0
.gitignore

@@ -18,6 +18,7 @@ target/
 *.iws
 *.iml
 *.ipr
+*.md
 
 ### NetBeans ###
 /nbproject/private/

+ 2 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/dto/AdPlatformCreativeDTO.java

@@ -52,4 +52,6 @@ public class AdPlatformCreativeDTO {
     private String customer;
 
     private Long customerId;
+
+    private String categoryName;
 }

+ 3 - 2
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/enums/CrowdLayerEnum.java

@@ -7,8 +7,9 @@ public enum CrowdLayerEnum {
 
 
     NO_EXPOSURE(1, "无曝光"),
-    WITH_CONVERSION(2, "有转化"),
-    WITH_EXPOSURE_NO_CONVERSION(3, "有曝光无转化"),
+    WITH_EXPOSURE_NO_CLICK(2, "有曝光无点击"),
+    WITH_CLICK_NO_CONVERSION(3, "有点击无转化"),
+    WITH_CONVERSION(4, "有转化"),
     ;
     private Integer code;
     private String desc;

+ 23 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/enums/FilterTypeEnum.java

@@ -0,0 +1,23 @@
+package com.tzld.piaoquan.ad.engine.commons.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum FilterTypeEnum {
+    CREATIVE("创意", 1),
+    AD("广告", 2),
+    ADVERTISER("广告", 3),
+    PROFESSION("行业", 4),
+    CUSTOMER("客户", 5);
+
+    FilterTypeEnum(String name, Integer code) {
+        this.name = name;
+        this.code = code;
+    }
+
+    private final String name;
+
+    private final Integer code;
+
+
+}

+ 9 - 3
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/CommonResponse.java

@@ -11,7 +11,7 @@ import lombok.Data;
 @Data
 public class CommonResponse<T> {
     /**
-     * 返回状态码,0 表示业务成功
+     * 返回状态码,0 表示业务成功,200 也表示成功
      */
     private int code = 0;
     /**
@@ -27,6 +27,12 @@ public class CommonResponse<T> {
      */
     private String redirect;
 
-
-
+    /**
+     * 判断响应是否成功
+     *
+     * @return true-成功,false-失败
+     */
+    public boolean isSuccess() {
+        return code == 0 || code == 200;
+    }
 }

+ 54 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/manager/HolidayCalendarFeign.java

@@ -0,0 +1,54 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright © 2021 xrv <xrg@live.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.tzld.piaoquan.ad.engine.commons.feign.manager;
+
+import com.tzld.piaoquan.ad.engine.commons.feign.CommonResponse;
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.fallback.HolidayCalendarFeignFallbackFactory;
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.vo.HolidayCalendarVO;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+/**
+ * 节日日历服务 Feign 客户端
+ *
+ * @author ad-engine
+ * @since 2025-08-22
+ */
+@FeignClient(
+    value = "manager-server",
+    url = "${manager.feign.url:}",
+    path = "/platform/holidayCalendar",
+    fallbackFactory = HolidayCalendarFeignFallbackFactory.class
+)
+public interface HolidayCalendarFeign {
+    
+    /**
+     * 判断今日是否是节日
+     *
+     * @return 今日节日信息,如果不是节日则data为null
+     */
+    @GetMapping("/isHoliday")
+    CommonResponse<HolidayCalendarVO> isHoliday();
+}

+ 60 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/manager/fallback/HolidayCalendarFeignFallbackFactory.java

@@ -0,0 +1,60 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright © 2021 xrv <xrg@live.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.tzld.piaoquan.ad.engine.commons.feign.manager.fallback;
+
+import com.tzld.piaoquan.ad.engine.commons.feign.CommonResponse;
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.HolidayCalendarFeign;
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.vo.HolidayCalendarVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.openfeign.FallbackFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * 节日日历服务 Feign 客户端降级工厂
+ *
+ * @author ad-engine
+ * @since 2025-08-22
+ */
+@Slf4j
+@Component
+public class HolidayCalendarFeignFallbackFactory implements FallbackFactory<HolidayCalendarFeign> {
+
+    @Override
+    public HolidayCalendarFeign create(Throwable cause) {
+        return new HolidayCalendarFeign() {
+            @Override
+            public CommonResponse<HolidayCalendarVO> isHoliday() {
+                log.error("调用节日查询接口失败,执行降级逻辑", cause);
+                
+                CommonResponse<HolidayCalendarVO> response = new CommonResponse<>();
+                response.setCode(-1);
+                response.setMsg("节日查询服务不可用");
+                response.setData(null);
+                
+                return response;
+            }
+        };
+    }
+}

+ 87 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/manager/service/HolidayService.java

@@ -0,0 +1,87 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright © 2021 xrv <xrg@live.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.tzld.piaoquan.ad.engine.commons.feign.manager.service;
+
+import com.tzld.piaoquan.ad.engine.commons.feign.CommonResponse;
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.HolidayCalendarFeign;
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.vo.HolidayCalendarVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 节日查询服务
+ *
+ * @author ad-engine
+ * @since 2025-08-22
+ */
+@Slf4j
+@Service
+public class HolidayService {
+    
+    @Autowired
+    private HolidayCalendarFeign holidayCalendarFeign;
+    
+    /**
+     * 判断今日是否是节日
+     *
+     * @return 节日信息,如果不是节日返回null
+     */
+    public HolidayCalendarVO getTodayHoliday() {
+        try {
+            log.info("调用节日查询接口");
+            CommonResponse<HolidayCalendarVO> response = holidayCalendarFeign.isHoliday();
+            
+            if (response != null && response.isSuccess()) {
+                HolidayCalendarVO holiday = response.getData();
+                if (holiday != null) {
+                    log.info("今日是节日:{}", holiday.getHolidayName());
+                } else {
+                    log.info("今日不是节日");
+                }
+                return holiday;
+            } else {
+                log.error("节日查询接口调用失败,错误码:{},错误信息:{}", 
+                    response != null ? response.getCode() : "null", 
+                    response != null ? response.getMsg() : "response is null");
+                return null;
+            }
+        } catch (Exception e) {
+            log.error("调用节日查询接口异常", e);
+            return null;
+        }
+    }
+    
+    /**
+     * 判断今日是否是节日(简化版)
+     *
+     * @return true-是节日,false-不是节日
+     */
+    public boolean isTodayHoliday() {
+        return getTodayHoliday() != null;
+    }
+    
+
+}

+ 72 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/feign/manager/vo/HolidayCalendarVO.java

@@ -0,0 +1,72 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright © 2021 xrv <xrg@live.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.tzld.piaoquan.ad.engine.commons.feign.manager.vo;
+
+import lombok.Data;
+
+/**
+ * 节日日历VO
+ *
+ * @author ad-engine
+ * @since 2025-08-22
+ */
+@Data
+public class HolidayCalendarVO {
+    
+    /**
+     * 节日ID
+     */
+    private Long id;
+    
+    /**
+     * 节日日期,格式:yyyy-MM-dd
+     */
+    private String solarDate;
+    
+    /**
+     * 节日名称
+     */
+    private String holidayName;
+    
+    /**
+     * 启用状态,1-启用,0-禁用
+     */
+    private Integer enabled;
+    
+    /**
+     * 备注信息
+     */
+    private String remark;
+    
+    /**
+     * 创建时间,格式:yyyy-MM-dd HH:mm:ss
+     */
+    private String createTime;
+    
+    /**
+     * 更新时间,格式:yyyy-MM-dd HH:mm:ss
+     */
+    private String updateTime;
+}

+ 22 - 9
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/helper/DnnCidDataHelper.java

@@ -36,32 +36,45 @@ public class DnnCidDataHelper {
 
     private static final String cidPath = "fengzhoutian/pai_model_trained_cids/";
 
+    private static final String modelVersionV2Path = "fengzhoutian/pai_model_trained_cids/model_version_v2.json";
+
 
     // 提供获取CID集合的方法
     // 全局变量,存储CSV文件中的数据
     @Getter
     private volatile static Set<Long> cidSet = Collections.emptySet();
 
+    @Getter
+    private volatile static Set<Long> cidSetV2 = Collections.emptySet();
+
     // 服务启动时初始化数据
     @PostConstruct
     public void init() {
-        updateCidSet();
+        Set<Long> cidSet1 = updateCidSet(cidPath, modelVersionPath);
+        cidSet = Collections.unmodifiableSet(cidSet1);
+
+        Set<Long> cidSet2 = updateCidSet(cidPath, modelVersionV2Path);
+        cidSetV2 = Collections.unmodifiableSet(cidSet2);
     }
 
     // 每10分钟更新一次数据
     @Scheduled(fixedRate = 10 * 60 * 1000)
     public void scheduledUpdate() {
-        updateCidSet();
+        Set<Long> cidSet1 = updateCidSet(cidPath, modelVersionPath);
+        cidSet = Collections.unmodifiableSet(cidSet1);
+
+        Set<Long> cidSet2 = updateCidSet(cidPath, modelVersionV2Path);
+        cidSetV2 = Collections.unmodifiableSet(cidSet2);
     }
 
     // 更新CID集合的方法
-    private synchronized void updateCidSet() {
+    private Set<Long> updateCidSet(String cidPath, String modelVersionPath) {
         try {
             log.info("start init cid data");
-            String modelVersion = readCsvPathFromOss();
+            String modelVersion = readCsvPathFromOss(modelVersionPath);
             if (StringUtils.isEmpty(modelVersion)) {
                 log.error("modelVersion is null");
-                return;
+                return Collections.emptySet();
             }
             String csvPath = cidPath + modelVersion;
 
@@ -105,19 +118,19 @@ public class DnnCidDataHelper {
             } else {
                 log.error("not found csv file");
             }
-            // 使用volatile保证可见性,一次性替换整个集合
-            cidSet = Collections.unmodifiableSet(newCidSet);
-            log.info("cid set init success size={}", cidSet.size());
+            log.info("cid set init success size={}", newCidSet.size());
+            return newCidSet;
         } catch (Exception e) {
             log.error("update cid error", e);
             // 发生异常时保持原数据不变
         }
+        return Collections.emptySet();
     }
 
     /**
      * 从OSS配置文件中读取CSV路径(第一行)
      */
-    private String readCsvPathFromOss() {
+    private String readCsvPathFromOss(String modelVersionPath) {
         try {
             // 检查配置文件是否存在
             if (!ossClient.doesObjectExist(BUCKET_NAME, modelVersionPath)) {

+ 3 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/score/ScorerUtils.java

@@ -30,6 +30,8 @@ public final class ScorerUtils {
     public static String XGBOOST_SCORE_CONF_20240909 = "ad_score_config_xgboost_20240909.conf";
     public static String XGBOOST_SCORE_CONF_20241105 = "ad_score_config_xgboost_20241105.conf";
     public static String PAI_SCORE_CONF_20250214 = "ad_score_config_pai_20250214.conf";
+    public static String PAI_SCORE_CONF_20250804 = "ad_score_config_pai_20250804.conf";
+
 
     public static void warmUp() {
         log.info("scorer warm up ");
@@ -41,6 +43,7 @@ public final class ScorerUtils {
         ScorerUtils.init(XGBOOST_SCORE_CONF_20240909);
         ScorerUtils.init(XGBOOST_SCORE_CONF_20241105);
         ScorerUtils.init(PAI_SCORE_CONF_20250214);
+        ScorerUtils.init(PAI_SCORE_CONF_20250804);
     }
 
     private ScorerUtils() {

+ 2 - 2
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/score/model/PAIModelV1.java

@@ -43,7 +43,7 @@ public class PAIModelV1 {
             "user_vid_return_tags_2h", "user_vid_return_tags_1d", "user_vid_return_tags_3d", "user_vid_return_tags_7d",
             "user_vid_return_tags_14d", "root_source_scene", "root_source_channel", "title_split", "user_vid_share_tags_1d",
             "user_vid_share_tags_14d", "user_vid_return_cate1_14d", "user_vid_return_cate2_14d", "user_vid_share_cate1_14d",
-            "user_vid_share_cate2_14d", "user_conver_ad_class", "user_layer_class"
+            "user_vid_share_cate2_14d", "user_conver_ad_class"
     };
 
     private static final String[] sparseUserLongFeatures = {
@@ -65,7 +65,7 @@ public class PAIModelV1 {
     };
 
     private static final String[] sparseAdStrFeatures = {
-            "profession"
+            "profession", "category_name"
     };
 
     private final String[] userFeatures = {

+ 210 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/score/model/PAIModelV2.java

@@ -0,0 +1,210 @@
+package com.tzld.piaoquan.ad.engine.commons.score.model;
+
+import com.aliyun.openservices.eas.predict.http.HttpConfig;
+import com.aliyun.openservices.eas.predict.http.PredictClient;
+import com.aliyun.openservices.eas.predict.request.TFDataType;
+import com.aliyun.openservices.eas.predict.request.TFRequest;
+import com.aliyun.openservices.eas.predict.response.TFResponse;
+import com.tzld.piaoquan.recommend.feature.domain.ad.base.AdRankItem;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang.math.NumberUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+
+
+public class PAIModelV2 {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(PAIModelV2.class);
+
+    private PAIModelV2() {
+    }
+
+    private static final PAIModelV2 model;
+
+    public static PAIModelV2 getModel() {
+        return model;
+    }
+
+    private static final PredictClient client;
+
+    static {
+        model = new PAIModelV2();
+        client = new PredictClient(new HttpConfig());
+        client.setEndpoint("1894469520484605.vpc.cn-hangzhou.pai-eas.aliyuncs.com");
+        client.setToken("M2Y0ZGJlOGRiNDJiYmJlM2E2MGRiNzk4NTdhN2MxOWUzYWMxNzdjYg==");
+        client.setModelName("ad_rank_dnn_v11_easyrec_v2");
+    }
+
+    private static final String[] sparseUserStrFeatures = {
+            "brand", "region", "city", "cate1", "cate2", "user_cid_click_list", "user_cid_conver_list",
+            "user_vid_return_tags_2h", "user_vid_return_tags_1d", "user_vid_return_tags_3d", "user_vid_return_tags_7d",
+            "user_vid_return_tags_14d", "root_source_scene", "root_source_channel", "title_split", "user_vid_share_tags_1d",
+            "user_vid_share_tags_14d", "user_vid_return_cate1_14d", "user_vid_return_cate2_14d", "user_vid_share_cate1_14d",
+            "user_vid_share_cate2_14d", "user_conver_ad_class"
+    };
+
+    private static final String[] sparseUserLongFeatures = {
+            "vid", "apptype", "is_first_layer", "user_has_conver_1y"
+    };
+
+    private static final String[] sparseSceneLongFeatures = {
+            "hour", "hour_quarter"
+    };
+
+    private static final String[] sparseAdLongFeatures = {
+            "cid", "adid", "adverid",
+            "user_adverid_view_3d", "user_adverid_view_7d", "user_adverid_view_30d",
+            "user_adverid_click_3d", "user_adverid_click_7d", "user_adverid_click_30d",
+            "user_adverid_conver_3d", "user_adverid_conver_7d", "user_adverid_conver_30d",
+            "user_skuid_view_3d", "user_skuid_view_7d", "user_skuid_view_30d",
+            "user_skuid_click_3d", "user_skuid_click_7d", "user_skuid_click_30d",
+            "user_skuid_conver_3d", "user_skuid_conver_7d", "user_skuid_conver_30d"
+    };
+
+    private static final String[] sparseAdStrFeatures = {
+            "profession", "category_name"
+    };
+
+    private final String[] userFeatures = {
+            "viewAll", "clickAll", "converAll", "incomeAll", "ctr_all", "ctcvr_all", "cvr_all", "ecpm_all"
+    };
+
+    private final String[] itemFeatures = {
+            "actionstatic_click", "actionstatic_ctcvr", "actionstatic_ctr", "actionstatic_view", "b2_12h_click", "b2_12h_conver",
+            "b2_12h_conver_x_ctcvr", "b2_12h_conver_x_log_view", "b2_12h_ctcvr", "b2_12h_ctr", "b2_12h_cvr", "b2_12h_ecpm",
+            "b2_1d_click", "b2_1d_conver", "b2_1d_conver_x_ctcvr", "b2_1d_conver_x_log_view", "b2_1d_ctcvr", "b2_1d_ctr",
+            "b2_1d_cvr", "b2_1d_ecpm", "b2_3d_click", "b2_3d_conver", "b2_3d_conver_x_ctcvr", "b2_3d_conver_x_log_view",
+            "b2_3d_ctcvr", "b2_3d_ctr", "b2_3d_cvr", "b2_3d_ecpm", "b2_3h_click", "b2_3h_conver", "b2_3h_conver_x_ctcvr",
+            "b2_3h_conver_x_log_view", "b2_3h_ctcvr", "b2_3h_ctr", "b2_3h_cvr", "b2_3h_ecpm", "b2_6h_click", "b2_6h_conver",
+            "b2_6h_conver_x_ctcvr", "b2_6h_conver_x_log_view", "b2_6h_ctcvr", "b2_6h_ctr", "b2_6h_cvr", "b2_6h_ecpm", "b2_7d_click",
+            "b2_7d_conver", "b2_7d_conver_x_ctcvr", "b2_7d_conver_x_log_view", "b2_7d_ctcvr", "b2_7d_ctr", "b2_7d_cvr",
+            "b2_7d_ecpm", "b3_12h_click", "b3_12h_conver", "b3_12h_conver_x_ctcvr", "b3_12h_ctcvr", "b3_12h_ctr", "b3_12h_cvr",
+            "b3_12h_ecpm", "b3_1d_click", "b3_1d_conver", "b3_1d_conver_x_ctcvr", "b3_1d_conver_x_log_view", "b3_1d_ctcvr",
+            "b3_1d_ctr", "b3_1d_cvr", "b3_1d_ecpm", "b3_3d_click", "b3_3d_conver", "b3_3d_conver_x_ctcvr",
+            "b3_3d_conver_x_log_view", "b3_3d_ctcvr", "b3_3d_ctr", "b3_3d_cvr", "b3_3d_ecpm", "b3_3h_click", "b3_3h_conver",
+            "b3_3h_conver_x_ctcvr", "b3_3h_ctcvr", "b3_3h_ctr", "b3_3h_cvr", "b3_3h_ecpm", "b3_6h_click", "b3_6h_conver_x_ctcvr",
+            "b3_6h_ctcvr", "b3_6h_ctr", "b3_6h_cvr", "b3_6h_ecpm", "b3_7d_click", "b3_7d_conver", "b3_7d_conver_x_ctcvr",
+            "b3_7d_conver_x_log_view", "b3_7d_ctcvr", "b3_7d_ctr", "b3_7d_cvr", "b3_7d_ecpm", "b4_12h_click",
+            "b4_12h_conver_x_ctcvr", "b4_12h_conver_x_log_view", "b4_12h_ctcvr", "b4_12h_ctr", "b4_12h_cvr", "b4_12h_ecpm",
+            "b4_1d_click", "b4_1d_conver_x_ctcvr", "b4_1d_conver_x_log_view", "b4_1d_ctcvr", "b4_1d_ctr", "b4_1d_cvr", "b4_1d_ecpm",
+            "b4_3d_click", "b4_3d_conver_x_ctcvr", "b4_3d_conver_x_log_view", "b4_3d_ctcvr", "b4_3d_ctr", "b4_3d_cvr", "b4_3d_ecpm",
+            "b4_3h_click", "b4_3h_conver_x_ctcvr", "b4_3h_conver_x_log_view", "b4_3h_ctcvr", "b4_3h_ctr", "b4_3h_cvr", "b4_3h_ecpm",
+            "b4_6h_click", "b4_6h_conver_x_ctcvr", "b4_6h_conver_x_log_view", "b4_6h_ctcvr", "b4_6h_ctr", "b4_6h_cvr", "b4_6h_ecpm",
+            "b4_7d_click", "b4_7d_conver", "b4_7d_conver_x_ctcvr", "b4_7d_conver_x_log_view", "b4_7d_ctcvr", "b4_7d_ctr",
+            "b4_7d_cvr", "b4_7d_ecpm", "b5_12h_click", "b5_12h_conver", "b5_12h_conver_x_ctcvr", "b5_12h_ctcvr", "b5_12h_ctr",
+            "b5_12h_cvr", "b5_12h_ecpm", "b5_1d_click", "b5_1d_conver", "b5_1d_conver_x_ctcvr", "b5_1d_conver_x_log_view",
+            "b5_1d_ctcvr", "b5_1d_ctr", "b5_1d_cvr", "b5_1d_ecpm", "b5_3d_click", "b5_3d_conver", "b5_3d_conver_x_ctcvr",
+            "b5_3d_conver_x_log_view", "b5_3d_ctcvr", "b5_3d_ctr", "b5_3d_cvr", "b5_3d_ecpm", "b5_3h_click", "b5_3h_conver_x_ctcvr",
+            "b5_3h_conver_x_log_view", "b5_3h_ctcvr", "b5_3h_ctr", "b5_3h_cvr", "b5_3h_ecpm", "b5_6h_click",
+            "b5_6h_conver_x_log_view", "b5_6h_ctcvr", "b5_6h_ctr", "b5_6h_cvr", "b5_6h_ecpm", "b5_7d_click", "b5_7d_conver",
+            "b5_7d_conver_x_ctcvr", "b5_7d_conver_x_log_view", "b5_7d_ctcvr", "b5_7d_ctr", "b5_7d_cvr", "b5_7d_ecpm", "b6_7d_click",
+            "b6_7d_conver", "b6_7d_conver_x_log_view", "b6_7d_ctcvr", "b6_7d_ctr", "b6_7d_cvr", "b6_7d_ecpm", "b7_14d_ctr",
+            "b7_7d_click", "b7_7d_conver_x_ctcvr", "b7_7d_conver_x_log_view", "b7_7d_ctcvr", "b7_7d_ctr", "b7_7d_cvr", "b7_7d_ecpm",
+            "b8_12h_click", "b8_12h_conver_x_ctcvr", "b8_12h_ctcvr", "b8_12h_ctr", "b8_12h_cvr", "b8_12h_ecpm", "b8_1d_click",
+            "b8_1d_conver", "b8_1d_conver_x_ctcvr", "b8_1d_conver_x_log_view", "b8_1d_ctcvr", "b8_1d_ctr", "b8_1d_cvr",
+            "b8_1d_ecpm", "b8_3d_click", "b8_3d_conver", "b8_3d_conver_x_ctcvr", "b8_3d_ctcvr", "b8_3d_ctr", "b8_3d_cvr",
+            "b8_3d_ecpm", "b8_3h_click", "b8_3h_conver_x_ctcvr", "b8_3h_ctcvr", "b8_3h_ctr", "b8_3h_cvr", "b8_3h_ecpm",
+            "b8_6h_click", "b8_6h_conver_x_ctcvr", "b8_6h_ctcvr", "b8_6h_ctr", "b8_6h_cvr", "b8_6h_ecpm", "b8_7d_click",
+            "b8_7d_conver_x_ctcvr", "b8_7d_conver_x_log_view", "b8_7d_ctcvr", "b8_7d_ctr", "b8_7d_cvr", "b8_7d_ecpm", "cpa",
+            "d1_feature_12h_conver", "d1_feature_12h_ctcvr", "d1_feature_12h_ctr", "d1_feature_12h_cvr", "d1_feature_12h_ecpm",
+            "d1_feature_1d_conver", "d1_feature_1d_ctcvr", "d1_feature_1d_ctr", "d1_feature_1d_cvr", "d1_feature_1d_ecpm",
+            "d1_feature_3d_conver", "d1_feature_3d_ctcvr", "d1_feature_3d_ctr", "d1_feature_3d_ecpm", "d1_feature_3h_conver",
+            "d1_feature_3h_ctcvr", "d1_feature_3h_ctr", "d1_feature_3h_cvr", "d1_feature_3h_ecpm", "d1_feature_6h_ctcvr",
+            "d1_feature_6h_ctr", "d1_feature_6h_ecpm", "d1_feature_7d_conver", "d1_feature_7d_ctcvr", "d1_feature_7d_ctr",
+            "d1_feature_7d_cvr", "d1_feature_7d_ecpm", "e1_tags_14d_avgscore", "e1_tags_14d_maxscore", "e1_tags_3d_avgscore",
+            "e1_tags_3d_maxscore", "e1_tags_7d_avgscore", "e1_tags_7d_maxscore", "e2_tags_14d_avgscore", "e2_tags_14d_maxscore",
+            "e2_tags_3d_avgscore", "e2_tags_3d_maxscore", "e2_tags_7d_avgscore", "e2_tags_7d_maxscore", "timediff_conver",
+            "timediff_view", "vid_rank_ctcvr_14d", "vid_rank_ctcvr_1d", "vid_rank_ctcvr_3d", "vid_rank_ctcvr_7d",
+            "vid_rank_ctr_14d", "vid_rank_ctr_1d", "vid_rank_ctr_3d", "vid_rank_ctr_7d", "vid_rank_ecpm_14d", "vid_rank_ecpm_1d",
+            "vid_rank_ecpm_3d", "vid_rank_ecpm_7d"
+    };
+
+
+    public List<Float> score(final List<AdRankItem> items,
+                             final Map<String, String> userFeatureMap,
+                             final Map<String, String> sceneFeatureMap) {
+        try {
+            TFRequest request = new TFRequest();
+
+            for (String feature : sparseUserStrFeatures) {
+                String v = userFeatureMap.getOrDefault(feature, "");
+                request.addFeed(feature, TFDataType.DT_STRING, new long[]{1}, new String[]{v});
+            }
+
+            for (String feature : sparseUserLongFeatures) {
+                long v = NumberUtils.toLong(userFeatureMap.getOrDefault(feature, "0"), 0);
+                request.addFeed(feature, TFDataType.DT_INT64, new long[]{1}, new long[]{v});
+            }
+
+            for (String feature : sparseSceneLongFeatures) {
+                long v = NumberUtils.toLong(sceneFeatureMap.getOrDefault(feature, "0"), 0);
+                request.addFeed(feature, TFDataType.DT_INT64, new long[]{1}, new long[]{v});
+            }
+
+
+            for (String feature : userFeatures) {
+                double v = NumberUtils.toDouble(userFeatureMap.getOrDefault(feature, "0.0"), 0.0);
+                request.addFeed(feature.toLowerCase(), TFDataType.DT_DOUBLE, new long[]{1}, new double[]{v});
+            }
+            Map<String, double[]> doubleFeed = new HashMap<>();
+            Map<String, long[]> longFeed = new HashMap<>();
+            Map<String, String[]> strFeed = new HashMap<>();
+            for (int i = 0; i < items.size(); i++) {
+                for (String feature : itemFeatures) {
+                    String key = feature.replace("_x_", "*").replace("_view", "(view)");
+                    double[] doubles = doubleFeed.computeIfAbsent(feature, k -> new double[items.size()]);
+                    if (MapUtils.isEmpty(items.get(i).getFeatureMap())) {
+                        doubles[i] = 0.0;
+                        continue;
+                    }
+                    double v = NumberUtils.toDouble(items.get(i).getFeatureMap().getOrDefault(key, "0.0"), 0.0);
+                    doubles[i] = v;
+                }
+
+                for (String feature : sparseAdLongFeatures) {
+                    long[] longs = longFeed.computeIfAbsent(feature, k -> new long[items.size()]);
+                    if (MapUtils.isEmpty(items.get(i).getFeatureMap())) {
+                        longs[i] = 0L;
+                        continue;
+                    }
+                    long v = NumberUtils.toLong(items.get(i).getFeatureMap().getOrDefault(feature, "0"), 0L);
+                    longs[i] = v;
+                }
+
+                for (String feature : sparseAdStrFeatures) {
+                    String[] strs = strFeed.computeIfAbsent(feature, k -> new String[items.size()]);
+                    if (MapUtils.isEmpty(items.get(i).getFeatureMap())) {
+                        strs[i] = "";
+                        continue;
+                    }
+                    String v = items.get(i).getFeatureMap().getOrDefault(feature, "");
+                    strs[i] = v;
+                }
+            }
+            for (Map.Entry<String, double[]> entry : doubleFeed.entrySet()) {
+                request.addFeed(entry.getKey(), TFDataType.DT_DOUBLE, new long[]{items.size()}, entry.getValue());
+            }
+
+            for (Map.Entry<String, long[]> entry : longFeed.entrySet()) {
+                request.addFeed(entry.getKey(), TFDataType.DT_INT64, new long[]{items.size()}, entry.getValue());
+            }
+
+            for (Map.Entry<String, String[]> entry : strFeed.entrySet()) {
+                request.addFeed(entry.getKey(), TFDataType.DT_STRING, new long[]{items.size()}, entry.getValue());
+            }
+            request.addFetch("probs");
+            TFResponse response = client.predict(request);
+            List<Float> result = response.getFloatVals("probs");
+            if (!CollectionUtils.isEmpty(result)) {
+                return result;
+            }
+        } catch (Exception e) {
+            LOGGER.error("PAIModel score error", e);
+        }
+        return new ArrayList<>(Collections.nCopies(items.size(), 0.0f));
+    }
+
+}

+ 11 - 26
ad-engine-server/src/main/java/com/tzld/piaoquan/ad/engine/server/controller/AdRecommendController.java

@@ -10,6 +10,7 @@ import com.tzld.piaoquan.ad.engine.commons.param.RankRecommendRequestParam;
 import com.tzld.piaoquan.ad.engine.commons.redis.AlgorithmRedisHelper;
 import com.tzld.piaoquan.ad.engine.service.score.RankService;
 import com.tzld.piaoquan.ad.engine.service.score.deprecated.BidRankRecommendRequestParam;
+import com.tzld.piaoquan.ad.engine.service.user.UserService;
 import com.tzld.piaoquan.recommend.feature.domain.ad.base.AdRankItem;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -33,7 +34,7 @@ public class AdRecommendController {
     RankService rankService;
 
     @Autowired
-    protected AlgorithmRedisHelper algRedisHelper;
+    private UserService userService;
 
     @RequestMapping("/top1/basic")
     public Map<String, Object> adRecommendTop1Basic(@RequestBody RankRecommendRequestParam request) {
@@ -52,9 +53,13 @@ public class AdRecommendController {
                 contentMap.put("adId", rankResult.getAdId());
                 contentMap.put("adScore", rankResult.getScore());
                 Double ctcvrScore = rankResult.getScoreMap().get("ctcvrScore");
-                Double modelCtcvrScore = rankResult.getScoreMap().get("modelCtcvrScore");
                 if (ctcvrScore != null && ctcvrScore > 0) {
-                    contentMap.put("ecpm", modelCtcvrScore * rankResult.getCpa() * 1000);
+                    if (rankResult.getExt().get("ecpm") != null) {
+                        contentMap.put("ecpm", rankResult.getExt().get("ecpm"));
+                    }
+                    if (rankResult.getExt().get("filterEcpm") != null) {
+                        contentMap.put("filterEcpm", rankResult.getExt().get("filterEcpm"));
+                    }
                     if (rankResult.getExt().get("correctionFactor") != null) {
                         contentMap.put("revisedBid", ctcvrScore * rankResult.getCpa() * (double) rankResult.getExt().get("correctionFactor"));
                     } else {
@@ -132,29 +137,9 @@ public class AdRecommendController {
         }
         map.put("code", "0");
         map.put("msg", "success");
-        Map<String, Object> result = new HashMap<>();
-        map.put("content", result);
-        String key = String.format("ad:engine:mid:layer:%s", request.getMid());
-        String value = algRedisHelper.get(key);
-        if (StringUtils.isEmpty(value)) {
-            result.put("layer", "无曝光");
-            return map;
-        }
-        try {
-            Map<String, String> layerMap = JSON.parseObject(value, new TypeReference<Map<String, String>>() {
-            });
-            String layer = layerMap.getOrDefault("layer", "无曝光");
-            if (Objects.equals(layer, "已转化")) {
-                layer = "有转化";
-            }
-            result.put("layer", layer);
-            return map;
-        } catch (Exception e) {
-            log.error("getMidWithLayer error. mid: {}, \n", request.getMid(), e);
-            map.put("code", "1");
-            map.put("msg", "get mid layer error");
-            return map;
-        }
+        Map<String, String> userLayer = userService.getUserLayer(request.getMid());
+        map.put("content", userLayer);
+        return map;
     }
 
 

+ 134 - 0
ad-engine-server/src/main/java/com/tzld/piaoquan/ad/engine/server/controller/HolidayTestController.java

@@ -0,0 +1,134 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright © 2021 xrv <xrg@live.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.tzld.piaoquan.ad.engine.server.controller;
+
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.service.HolidayService;
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.vo.HolidayCalendarVO;
+import com.tzld.piaoquan.ad.engine.service.predict.impl.PredictModelServiceImpl;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 节日查询测试控制器
+ *
+ * @author ad-engine
+ * @since 2025-08-22
+ */
+@Slf4j
+@RestController
+@RequestMapping("/test/holiday")
+@Api(tags = "节日查询测试接口")
+public class HolidayTestController {
+    
+    @Autowired
+    private HolidayService holidayService;
+
+    @Autowired
+    private PredictModelServiceImpl predictModelService;
+    
+
+    /**
+     * 健康检查接口
+     */
+    @GetMapping("/health")
+    @ApiOperation("节日服务健康检查")
+    public Map<String, Object> healthCheck() {
+        Map<String, Object> result = new HashMap<>();
+        
+        try {
+            boolean isHoliday = holidayService.isTodayHoliday();
+            result.put("status", "healthy");
+            result.put("service", "holiday-service");
+            result.put("timestamp", System.currentTimeMillis());
+            result.put("test_result", isHoliday ? "有节日数据" : "无节日数据");
+            
+        } catch (Exception e) {
+            log.error("节日服务健康检查失败", e);
+            result.put("status", "unhealthy");
+            result.put("service", "holiday-service");
+            result.put("timestamp", System.currentTimeMillis());
+            result.put("error", e.getMessage());
+        }
+        
+        return result;
+    }
+
+    /**
+     * 测试早晨节日时间判断函数
+     */
+    @GetMapping("/earlyMorningHoliday")
+    @ApiOperation("测试6-8点节日时间判断")
+    public Map<String, Object> testEarlyMorningHoliday() {
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            // 调用新增的函数
+            boolean isEarlyMorningHoliday = predictModelService.isEarlyMorningHoliday();
+
+            // 获取当前时间信息用于调试
+            int currentHour = com.tzld.piaoquan.ad.engine.commons.util.DateUtils.getCurrentHour();
+            boolean isHoliday = holidayService.isTodayHoliday();
+            String holidayName = "不是节日";
+            if (isHoliday) {
+                holidayName = holidayService.getTodayHoliday().getHolidayName();
+            }
+            result.put("success", true);
+            result.put("isEarlyMorningHoliday", isEarlyMorningHoliday);
+            result.put("currentHour", currentHour);
+            result.put("isInTimeRange", currentHour >= 6 && currentHour < 8);
+            result.put("isHoliday", isHoliday);
+            result.put("holidayName", holidayName);
+            result.put("timestamp", System.currentTimeMillis());
+
+            // 详细说明
+            if (isEarlyMorningHoliday) {
+                result.put("message", String.format("当前时间%d点在6-8点范围内且今日是节日(%s),返回true",
+                    currentHour, holidayName != null ? holidayName : "未知节日"));
+            } else if (currentHour >= 6 && currentHour < 8) {
+                result.put("message", String.format("当前时间%d点在6-8点范围内但今日不是节日,返回false", currentHour));
+            } else {
+                result.put("message", String.format("当前时间%d点不在6-8点范围内,返回false", currentHour));
+            }
+
+            log.info("早晨节日时间判断测试结果: {}", isEarlyMorningHoliday);
+
+        } catch (Exception e) {
+            log.error("早晨节日时间判断测试失败", e);
+            result.put("success", false);
+            result.put("error", e.getMessage());
+            result.put("isEarlyMorningHoliday", false);
+        }
+
+        return result;
+    }
+}

+ 6 - 0
ad-engine-server/src/main/resources/ad_score_config_pai_20250804.conf

@@ -0,0 +1,6 @@
+scorer-config = {
+  pai-score-config = {
+    scorer-name = "com.tzld.piaoquan.ad.engine.service.score.scorer.PAIScorerV2"
+    scorer-priority = 99
+  }
+}

+ 4 - 0
ad-engine-server/src/main/resources/application-dev.yml

@@ -118,6 +118,10 @@ longvideo:
   feign:
     url: videotest-internal.yishihui.com
 
+manager:
+  feign:
+    url: https://testadmin.piaoquantv.com
+
 feign:
   client:
     config:

+ 4 - 0
ad-engine-server/src/main/resources/application-pre.yml

@@ -114,6 +114,10 @@ longvideo:
   feign:
     url: videopre-internal.piaoquantv.com
 
+manager:
+  feign:
+    url: https://preadmin.piaoquantv.com
+
 feign:
   client:
     config:

+ 4 - 0
ad-engine-server/src/main/resources/application-prod.yml

@@ -116,6 +116,10 @@ longvideo:
   feign:
     url: longvideoapi-internal.piaoquantv.com
 
+manager:
+  feign:
+    url: https://admin.piaoquantv.com
+
 feign:
   client:
     config:

+ 4 - 0
ad-engine-server/src/main/resources/application-test.yml

@@ -110,6 +110,10 @@ longvideo:
   feign:
     url: videotest-internal.yishihui.com
 
+manager:
+  feign:
+    url: https://testadmin.piaoquantv.com
+
 feign:
   client:
     config:

+ 14 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/entity/FilterConfig.java

@@ -0,0 +1,14 @@
+package com.tzld.piaoquan.ad.engine.service.entity;
+
+import lombok.Data;
+import lombok.ToString;
+
+@Data
+@ToString
+public class FilterConfig {
+    private String layer;
+    private String clazz;
+    private Integer type;
+    private String value;
+    private String expId;
+}

+ 73 - 4
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/impl/PredictModelServiceImpl.java

@@ -23,10 +23,11 @@ import com.tzld.piaoquan.ad.engine.service.predict.param.RoiThresholdPredictMode
 import com.tzld.piaoquan.ad.engine.service.predict.param.ThresholdPredictModelParam;
 import com.tzld.piaoquan.ad.engine.service.predict.param.request.RoiPredictModelRequestParam;
 import com.tzld.piaoquan.ad.engine.service.predict.param.request.ThresholdPredictModelRequestParam;
-import com.tzld.piaoquan.ad.engine.service.predict.v2.ConvertUtil;
-import com.tzld.piaoquan.ad.engine.service.predict.v2.PredictContext;
-import com.tzld.piaoquan.ad.engine.service.predict.v2.PredictServiceV2;
+import com.tzld.piaoquan.ad.engine.service.predict.v2.*;
+import com.tzld.piaoquan.ad.engine.service.user.UserService;
+import com.tzld.piaoquan.ad.engine.commons.feign.manager.service.HolidayService;
 import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -63,6 +64,14 @@ public class PredictModelServiceImpl implements PredictModelService {
 
     @Autowired
     private PredictServiceV2 predictServiceV2;
+    @Autowired
+    private RootSessionIdPredict rootSessionIdPredict;
+    @Autowired
+    private UserLayerRootSessionIdPredict userLayerRootSessionIdPredict;
+
+    @Autowired
+    private UserService userService;
+
     @Autowired
     private LogHubService logHubService;
 
@@ -95,6 +104,9 @@ public class PredictModelServiceImpl implements PredictModelService {
     @Autowired
     private AdRedisHelper adRedisHelper;
 
+    @Autowired
+    private HolidayService holidayService;
+
     List<Integer> appIdArr = Arrays.asList(new Integer[]{0, 3, 4, 5, 6, 17, 18, 19, 21, 22});
 
 
@@ -163,7 +175,7 @@ public class PredictModelServiceImpl implements PredictModelService {
 
             if (AbUtil.isInAbExp(expCodes, requestParam.getAppType(), requestParam.getNewExpGroup(), "713")) {
                 PredictContext context = ConvertUtil.predictParam2Context(requestParam);
-                Map<String, Object> resultMap = predictServiceV2.adPredict(context);
+                Map<String, Object> resultMap = predictServiceV2.predict(context);
                 logHubService.crowdChooseLogUpload(context);
                 resultMap.put("pqtId", requestParam.getPqtId());
                 return resultMap;
@@ -266,6 +278,21 @@ public class PredictModelServiceImpl implements PredictModelService {
             modelParam.addUserExtraFuture("shareType", shareType);
             setExtraParam(modelParam);
 
+            requestParam.setShareType(shareType);
+            String userLayer = userService.getUserLayer4Level(requestParam.getMid());
+            requestParam.setUserLayer(userLayer);
+
+            // 先走rootSessionId 实验
+            Map<String, Object> predict = rootSessionIdPredict.predict(ConvertUtil.predictParam2Context(requestParam));
+            if (MapUtils.isNotEmpty(predict)) {
+                return predict;
+            }
+
+            Map<String, Object> userLayerPredict = userLayerRootSessionIdPredict.predict(ConvertUtil.predictParam2Context(requestParam));
+            if (MapUtils.isNotEmpty(userLayerPredict)) {
+                return userLayerPredict;
+            }
+
             String appTypeStr = requestParam.getAppType().toString();
 
             List<String> userSourceAndLayerAdRateExpIds = Arrays.asList(userSourceLayerAdRateExpIds.split(","));
@@ -409,6 +436,12 @@ public class PredictModelServiceImpl implements PredictModelService {
      * @return true-提前出,false-不提前出
      */
     private boolean isAdvanceShowAd() {
+
+        // 节日期间,6点开始出广告,不是节日,走下面的逻辑
+        if (isEarlyMorningHoliday()) {
+            return true;
+        }
+
         // 提前出广告是否生效,不生效表示不用提前出广告,返回false。默认生效
         if (!advanceShowAdSwitch) {
             return false;
@@ -426,4 +459,40 @@ public class PredictModelServiceImpl implements PredictModelService {
         log.info("提前出广告标识: {} ---> {}", redisKey, flag);
         return StringUtils.equals("1", flag);
     }
+
+    /**
+     * 判断当前时间是否在6点之后且为节日
+     *
+     * @return true-当前时间在6点之后且为节日,false-不满足条件
+     */
+    public boolean isEarlyMorningHoliday() {
+        try {
+            // 获取当前小时
+            int currentHour = DateUtils.getCurrentHour();
+
+            // 判断时间是否在6点之后
+            if (currentHour >= 6) {
+                log.info("当前时间{}点在6点之后,开始检查是否为节日", currentHour);
+
+                // 调用节日服务判断今日是否为节日
+                boolean isHoliday = holidayService.isTodayHoliday();
+
+                if (isHoliday) {
+                    log.info("当前时间{}点6点之后且今日是节日,返回true", currentHour);
+                    return true;
+                } else {
+                    log.info("当前时间{}点在6点之后但今日不是节日,返回false", currentHour);
+                    return false;
+                }
+            } else {
+                log.debug("当前时间{}点不在6点之后,返回false", currentHour);
+                return false;
+            }
+
+        } catch (Exception e) {
+            log.error("判断早晨节日时间异常", e);
+            // 异常情况下返回false,确保系统稳定性
+            return false;
+        }
+    }
 }

+ 6 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/param/request/ThresholdPredictModelRequestParam.java

@@ -39,4 +39,10 @@ public class ThresholdPredictModelRequestParam {
      * 2. otherLayer: 裂变层
      */
     private String shareLayer;
+
+    private String rootSessionId;
+
+    private String shareType;
+
+    private String userLayer;
 }

+ 43 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/BasicPredict.java

@@ -0,0 +1,43 @@
+package com.tzld.piaoquan.ad.engine.service.predict.v2;
+
+import com.tzld.piaoquan.ad.engine.service.predict.container.RandWContainer;
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class BasicPredict {
+
+    public abstract Map<String, Object> predict(PredictContext ctx);
+
+    protected Map<String, Object> rtnNoAdPredict(PredictContext ctx) {
+        Map<String, Object> rtnMap = new HashMap<>();
+        rtnMap.put("ad_predict", 1);
+        rtnMap.put("no_ad_strategy", "713_exp");
+        rtnMap.put("pqtId", ctx.getPqtId());
+        return rtnMap;
+    }
+
+    protected Map<String, Object> rtnAdPredict(PredictContext ctx) {
+        Map<String, Object> rtnMap = new HashMap<>();
+        rtnMap.put("ad_predict", 2);
+        rtnMap.putAll(ctx.getLogParam().getScoreMap());
+        rtnMap.put("pqtId", ctx.getPqtId());
+        return rtnMap;
+    }
+
+    protected double calcScoreByMid(String mid) {
+        int hash = mid.hashCode();
+        hash = hash < 0 ? -hash : hash;
+        return (hash + RandWContainer.getRandW()) % 100 / 100d;
+    }
+
+    @Data
+    protected static class RootSessionIdTailConfigItem {
+        private Set<String> appType = new HashSet<>();
+        private Set<String> tail = new HashSet<>();
+        private Map<String, Double> config = new HashMap<>();
+    }
+}

+ 5 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/ConvertUtil.java

@@ -27,6 +27,11 @@ public class ConvertUtil {
         context.setShareLayer(param.getShareLayer());
         context.setMachineInfo(param.getMachineInfo());
 
+        context.setRootSessionId(param.getRootSessionId());
+        context.setAdPlatformType(param.getAdPlatformType());
+        context.setShareType(param.getShareType());
+        context.setUserLayer(param.getUserLayer());
+
         JSONObject abExpInfo = param.getAbExpInfo();
         Map<String, JSONObject> expConfigMap = ConvertUtil.parseAbTest002Info(abExpInfo);
         context.setExpCodeSet(expConfigMap.keySet());

+ 11 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/PredictContext.java

@@ -64,5 +64,16 @@ public class PredictContext {
      */
     private String shareLayer;
 
+    /**
+     * 用户分享和回流的分组
+     */
+    private String shareType;
+
+    private String rootSessionId;
+
+    private String adPlatformType;
+
+    private String userLayer;
+
     private PredictLogParam logParam = new PredictLogParam();
 }

+ 28 - 41
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/PredictServiceV2.java

@@ -14,7 +14,7 @@ import java.util.Map;
 
 @Slf4j
 @Service
-public class PredictServiceV2 {
+public class PredictServiceV2 extends BasicPredict {
 
     @Autowired
     private FeatureService featureService;
@@ -22,15 +22,16 @@ public class PredictServiceV2 {
     @ApolloJsonValue("${exp.713.config:{}}")
     private Map<String, Double> exp713Config;
 
-    public Map<String, Object> adPredict(PredictContext context) {
-        Feature feature = featureService.getPredictFeature(context);
+    @Override
+    public Map<String, Object> predict(PredictContext ctx) {
+        Feature feature = featureService.getPredictFeature(ctx);
         Map<String, Map<String, String>> userFeature = feature.getUserFeature();
         Map<String, String> featureMap = userFeature.getOrDefault("alg_ad_crowd_choose_feature_v2", new HashMap<>());
         double minScore = exp713Config.getOrDefault("minScore", 0.1d);
         double maxScore = exp713Config.getOrDefault("maxScore", 0.8d);
         double score = maxScore;
 
-        context.getLogParam().setBIsNewUser(true);
+        ctx.getLogParam().setBIsNewUser(true);
 
         if (MapUtils.isNotEmpty(featureMap)) {
 
@@ -62,23 +63,23 @@ public class PredictServiceV2 {
             score = NumUtil.softmax(new double[]{hasAdValue * hasRate, noAdValue * noRate})[0];
 
 
-            context.getLogParam().getMetaFeature().putAll(feature.getUserFeature());
+            ctx.getLogParam().getMetaFeature().putAll(feature.getUserFeature());
             for (Map.Entry<String, String> entry : featureMap.entrySet()) {
-                context.getLogParam().getAllFeature().put(entry.getKey(), Double.parseDouble(entry.getValue()));
+                ctx.getLogParam().getAllFeature().put(entry.getKey(), Double.parseDouble(entry.getValue()));
             }
-            context.getLogParam().getScoreMap().put("adClickValue", NumUtil.round(adClickValue, 6));
-            context.getLogParam().getScoreMap().put("adConverValue", NumUtil.round(adConverValue, 6));
-            context.getLogParam().getScoreMap().put("hasAdShareValue", NumUtil.round(hasAdShareValue, 6));
-            context.getLogParam().getScoreMap().put("hasAdReturnValue", NumUtil.round(hasAdReturnValue, 6));
-            context.getLogParam().getScoreMap().put("hasAdValue", NumUtil.round(hasAdValue, 6));
-            context.getLogParam().getScoreMap().put("noAdShareValue", NumUtil.round(noAdShareValue, 6));
-            context.getLogParam().getScoreMap().put("noAdReturnValue", NumUtil.round(noAdReturnValue, 6));
-            context.getLogParam().getScoreMap().put("noAdValue", NumUtil.round(noAdValue, 6));
-            context.getLogParam().getScoreMap().put("originScore", NumUtil.round(score, 6));
-            context.getLogParam().getScoreMap().put("hasRate", NumUtil.round(hasRate, 6));
-            context.getLogParam().getScoreMap().put("noRate", NumUtil.round(noRate, 6));
-
-            context.getLogParam().setBIsNewUser(false);
+            ctx.getLogParam().getScoreMap().put("adClickValue", NumUtil.round(adClickValue, 6));
+            ctx.getLogParam().getScoreMap().put("adConverValue", NumUtil.round(adConverValue, 6));
+            ctx.getLogParam().getScoreMap().put("hasAdShareValue", NumUtil.round(hasAdShareValue, 6));
+            ctx.getLogParam().getScoreMap().put("hasAdReturnValue", NumUtil.round(hasAdReturnValue, 6));
+            ctx.getLogParam().getScoreMap().put("hasAdValue", NumUtil.round(hasAdValue, 6));
+            ctx.getLogParam().getScoreMap().put("noAdShareValue", NumUtil.round(noAdShareValue, 6));
+            ctx.getLogParam().getScoreMap().put("noAdReturnValue", NumUtil.round(noAdReturnValue, 6));
+            ctx.getLogParam().getScoreMap().put("noAdValue", NumUtil.round(noAdValue, 6));
+            ctx.getLogParam().getScoreMap().put("originScore", NumUtil.round(score, 6));
+            ctx.getLogParam().getScoreMap().put("hasRate", NumUtil.round(hasRate, 6));
+            ctx.getLogParam().getScoreMap().put("noRate", NumUtil.round(noRate, 6));
+
+            ctx.getLogParam().setBIsNewUser(false);
         }
 
         // 分数截断,避免过长或过短
@@ -90,33 +91,19 @@ public class PredictServiceV2 {
 
         double random = Math.random();
         boolean isShowAd = random < score;
-        context.getLogParam().setExpId("713");
-        context.getLogParam().setScore(score);
-        context.getLogParam().getScoreMap().put("score", NumUtil.round(score, 6));
-        context.getLogParam().setAIsShowAd(isShowAd);
-        context.getLogParam().getScoreMap().put("minScore", minScore);
-        context.getLogParam().getScoreMap().put("maxScore", maxScore);
-        context.getLogParam().getScoreMap().put("random", random);
+        ctx.getLogParam().setExpId("713");
+        ctx.getLogParam().setScore(score);
+        ctx.getLogParam().getScoreMap().put("score", NumUtil.round(score, 6));
+        ctx.getLogParam().setAIsShowAd(isShowAd);
+        ctx.getLogParam().getScoreMap().put("minScore", minScore);
+        ctx.getLogParam().getScoreMap().put("maxScore", maxScore);
+        ctx.getLogParam().getScoreMap().put("random", random);
 
 
-        return isShowAd ? rtnAdPredict(context) : rtnNoAdPredict(context);
+        return isShowAd ? rtnAdPredict(ctx) : rtnNoAdPredict(ctx);
     }
 
 
-    private Map<String, Object> rtnNoAdPredict(PredictContext context) {
-        Map<String, Object> rtnMap = new HashMap<>();
-        rtnMap.put("ad_predict", 1);
-        rtnMap.put("no_ad_strategy", "713_exp");
-        return rtnMap;
-    }
-
-    private Map<String, Object> rtnAdPredict(PredictContext context) {
-        Map<String, Object> rtnMap = new HashMap<>();
-        rtnMap.put("ad_predict", 2);
-        rtnMap.putAll(context.getLogParam().getScoreMap());
-        return rtnMap;
-    }
-
     // public Map<String, Object> adPredictV1(PredictContext context){
     //
     //     Feature feature = featureService.getPredictFeature(context);

+ 86 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/RootSessionIdPredict.java

@@ -0,0 +1,86 @@
+package com.tzld.piaoquan.ad.engine.service.predict.v2;
+
+import com.alibaba.fastjson.JSONObject;
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.tzld.piaoquan.ad.engine.commons.util.JSONUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class RootSessionIdPredict extends BasicPredict {
+
+    @ApolloJsonValue("${root.session.id.tail.predict.config:[]}")
+    private List<RootSessionIdTailConfigItem> configItems;
+
+    @Override
+    public Map<String, Object> predict(PredictContext ctx) {
+        String rootSessionId = ctx.getRootSessionId();
+        if (CollectionUtils.isEmpty(configItems) || StringUtils.isEmpty(rootSessionId) || StringUtils.isEmpty(ctx.getShareType())) {
+            return Collections.emptyMap();
+        }
+
+
+        double score = this.calcScoreByMid(ctx.getMid());
+
+        String appType = ctx.getAppType();
+        String tail = rootSessionId.substring(rootSessionId.length() - 1);
+        String shareType = ctx.getShareType()
+                .replace("return", "")
+                .replace("mids", "");
+
+
+        for (RootSessionIdTailConfigItem item : configItems) {
+            if (item.getAppType().contains(appType) && item.getTail().contains(tail)) {
+                double threshold;
+                if (item.getConfig().containsKey(shareType)) {
+                    threshold = item.getConfig().getOrDefault(shareType, 0d);
+                } else {
+                    threshold = item.getConfig().getOrDefault("threshold", 0d);
+                }
+
+                Map<String, Object> returnMap = new HashMap<>();
+
+                if (score < threshold) {
+                    returnMap.putAll(rtnAdPredict(ctx));
+                    returnMap.put("model", "rootSessionIdTailModel");
+                } else {
+                    returnMap.putAll(rtnNoAdPredict(ctx));
+                    returnMap.put("no_ad_strategy", "rootSessionIdTailModel");
+                }
+
+                returnMap.put("score", score);
+                returnMap.put("threshold", threshold);
+
+                JSONObject logJson = new JSONObject();
+                logJson.putAll(returnMap);
+                logJson.put("mid", ctx.getMid());
+                logJson.put("appType", appType);
+                logJson.put("rootSessionIdTail", tail);
+                logJson.put("shareType", shareType);
+
+                logJson.put("expId", "rootSessionIdTailExp");
+                logJson.put("thresholdParamKey", shareType);
+                logJson.put("adPlatformType", ctx.getAdPlatformType());
+                logJson.put("abCode", ctx.getAdAbCode());
+                logJson.put("extraParam", ctx.getExpConfigMap());
+                logJson.put("configItem", item);
+                logJson.put("rootSessionId", ctx.getRootSessionId());
+
+                log.info("广告跳出选择 -- rootSessionId尾号实验结果: {}, 参数: {}",
+                        JSONUtils.toJson(returnMap), logJson.toJSONString());
+
+                return returnMap;
+            }
+        }
+
+        return Collections.emptyMap();
+    }
+}

+ 96 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/predict/v2/UserLayerRootSessionIdPredict.java

@@ -0,0 +1,96 @@
+package com.tzld.piaoquan.ad.engine.service.predict.v2;
+
+import com.alibaba.fastjson.JSONObject;
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.tzld.piaoquan.ad.engine.commons.util.JSONUtils;
+import com.tzld.piaoquan.ad.engine.service.user.UserService;
+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.stereotype.Component;
+
+import java.util.*;
+
+@Slf4j
+@Component
+public class UserLayerRootSessionIdPredict extends BasicPredict {
+
+    @Autowired
+    private UserService userService;
+
+    @ApolloJsonValue("${user.layer.root.session.id.tail.predict.config:[]}")
+    private List<RootSessionIdTailConfigItem> configItems;
+
+    @Override
+    public Map<String, Object> predict(PredictContext ctx) {
+
+        String rootSessionId = ctx.getRootSessionId();
+        String userLayer = ctx.getUserLayer();
+        String shareType = ctx.getShareType();
+        if (CollectionUtils.isEmpty(configItems) || StringUtils.isAnyBlank(rootSessionId, userLayer, shareType)) {
+            return Collections.emptyMap();
+        }
+
+        double score = this.calcScoreByMid(ctx.getMid());
+        String appType = ctx.getAppType();
+        String tail = rootSessionId.substring(rootSessionId.length() - 1);
+        shareType = ctx.getShareType()
+                .replace("return", "")
+                .replace("mids", "");
+
+        for (RootSessionIdTailConfigItem item : configItems) {
+            if (item.getAppType().contains(appType) && item.getTail().contains(tail)) {
+                Map<String, Object> returnMap = new HashMap<>();
+                double threshold;
+                String thresholdKey = "";
+
+                // 生成可用的Key列表
+                List<String> keys = Arrays.asList(userLayer + "_" + shareType, "default_" + shareType, "default_threshold");
+                for (String key : keys) {
+                    if (item.getConfig().containsKey(key)) {
+                        thresholdKey = key;
+                        break;
+                    }
+                }
+
+
+                threshold = item.getConfig().getOrDefault(thresholdKey, 0.0);
+
+                if (score < threshold) {
+                    returnMap.putAll(rtnAdPredict(ctx));
+                    returnMap.put("model", "userLayerRootSessionIdTailModel");
+                } else {
+                    returnMap.putAll(rtnNoAdPredict(ctx));
+                    returnMap.put("no_ad_strategy", "userLayerRootSessionIdTailModel");
+                }
+
+                returnMap.put("score", score);
+                returnMap.put("threshold", threshold);
+                returnMap.put("userLayer", userLayer);
+
+                JSONObject logJson = new JSONObject();
+                logJson.putAll(returnMap);
+                logJson.put("mid", ctx.getMid());
+                logJson.put("appType", appType);
+                logJson.put("rootSessionIdTail", tail);
+                logJson.put("shareType", shareType);
+
+                logJson.put("expId", "userLayerRootSessionIdTailExp");
+                logJson.put("thresholdParamKey", thresholdKey);
+                logJson.put("adPlatformType", ctx.getAdPlatformType());
+                logJson.put("abCode", ctx.getAdAbCode());
+                logJson.put("configItem", item);
+                logJson.put("rootSessionId", ctx.getRootSessionId());
+
+                log.info("广告跳出选择 -- userLayerRootSessionId尾号实验结果: {}, 参数: {}",
+                        JSONUtils.toJson(returnMap), logJson.toJSONString());
+
+                return returnMap;
+            }
+        }
+
+
+        return Collections.emptyMap();
+    }
+}

+ 89 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/scorer/PAIScorerV2.java

@@ -0,0 +1,89 @@
+package com.tzld.piaoquan.ad.engine.service.score.scorer;
+
+
+import com.tzld.piaoquan.ad.engine.commons.score.AbstractScorer;
+import com.tzld.piaoquan.ad.engine.commons.score.ScoreParam;
+import com.tzld.piaoquan.ad.engine.commons.score.ScorerConfigInfo;
+import com.tzld.piaoquan.ad.engine.commons.score.model.PAIModelV2;
+import com.tzld.piaoquan.recommend.feature.domain.ad.base.AdRankItem;
+import com.tzld.piaoquan.recommend.feature.domain.ad.base.UserAdFeature;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class PAIScorerV2 extends AbstractScorer {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(PAIScorerV2.class);
+
+
+    public PAIScorerV2(ScorerConfigInfo configInfo) {
+        super(configInfo);
+    }
+
+    @Override
+    public List<AdRankItem> scoring(final ScoreParam param,
+                                    final UserAdFeature userAdFeature,
+                                    final List<AdRankItem> rankItems) {
+        throw new NoSuchMethodError();
+    }
+
+    public List<AdRankItem> scoring(final Map<String, String> sceneFeatureMap,
+                                    final Map<String, String> userFeatureMap,
+                                    final List<AdRankItem> rankItems) {
+        if (CollectionUtils.isEmpty(rankItems)) {
+            return rankItems;
+        }
+
+        long startTime = System.currentTimeMillis();
+
+        List<AdRankItem> result = rankByJava(sceneFeatureMap, userFeatureMap, rankItems);
+
+        LOGGER.debug("ctr ranker time java items size={}, time={} ", result != null ? result.size() : 0,
+                System.currentTimeMillis() - startTime);
+
+        return result;
+    }
+
+    private List<AdRankItem> rankByJava(final Map<String, String> sceneFeatureMap,
+                                        final Map<String, String> userFeatureMap,
+                                        final List<AdRankItem> items) {
+        long startTime = System.currentTimeMillis();
+        PAIModelV2 model = PAIModelV2.getModel();
+        // 所有都参与打分,按照ctr排序
+        multipleCtrScore(items, userFeatureMap, sceneFeatureMap, model);
+
+        // debug log
+        if (LOGGER.isDebugEnabled()) {
+            for (int i = 0; i < items.size(); i++) {
+                LOGGER.debug("before enter feeds model predict ctr score [{}] [{}]", items.get(i), items.get(i));
+            }
+        }
+
+        Collections.sort(items);
+
+        LOGGER.debug("ctr ranker java execute time: [{}]", System.currentTimeMillis() - startTime);
+        LOGGER.debug("[ctr ranker time java] items size={}, cost={} ", items != null ? items.size() : 0,
+                System.currentTimeMillis() - startTime);
+        return items;
+    }
+
+    private void multipleCtrScore(final List<AdRankItem> items,
+                                  final Map<String, String> userFeatureMap,
+                                  final Map<String, String> sceneFeatureMap,
+                                  final PAIModelV2 model) {
+
+        List<Float> score = model.score(items, userFeatureMap, sceneFeatureMap);
+        LOGGER.debug("PAIScorer score={}", score);
+        for (int i = 0; i < items.size(); i++) {
+            Double pro = Double.valueOf(score.get(i));
+            items.get(i).setLrScore(pro);
+            items.get(i).getScoreMap().put("ctcvrScore", pro);
+        }
+    }
+
+
+}

+ 230 - 116
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBasic.java

@@ -1,11 +1,13 @@
 package com.tzld.piaoquan.ad.engine.service.score.strategy;
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.TypeReference;
 import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
 import com.tzld.piaoquan.ad.engine.commons.dto.AdPlatformCreativeDTO;
 import com.tzld.piaoquan.ad.engine.commons.enums.CrowdLayerEnum;
+import com.tzld.piaoquan.ad.engine.commons.enums.FilterTypeEnum;
 import com.tzld.piaoquan.ad.engine.commons.enums.RedisPrefixEnum;
 import com.tzld.piaoquan.ad.engine.commons.param.RankRecommendRequestParam;
 import com.tzld.piaoquan.ad.engine.commons.redis.AdRedisHelper;
@@ -13,10 +15,7 @@ import com.tzld.piaoquan.ad.engine.commons.redis.AlgorithmRedisHelper;
 import com.tzld.piaoquan.ad.engine.commons.score.ScoreParam;
 import com.tzld.piaoquan.ad.engine.commons.util.DateUtils;
 import com.tzld.piaoquan.ad.engine.commons.util.ObjUtil;
-import com.tzld.piaoquan.ad.engine.service.entity.CalibrationModelCtcvrData;
-import com.tzld.piaoquan.ad.engine.service.entity.CorrectCpaParam;
-import com.tzld.piaoquan.ad.engine.service.entity.CorrectCtcvrScoreParam;
-import com.tzld.piaoquan.ad.engine.service.entity.GuaranteeView;
+import com.tzld.piaoquan.ad.engine.service.entity.*;
 import com.tzld.piaoquan.ad.engine.service.feature.Feature;
 import com.tzld.piaoquan.ad.engine.service.feature.FeatureService;
 import com.tzld.piaoquan.recommend.feature.domain.ad.base.AdRankItem;
@@ -76,15 +75,12 @@ public abstract class RankStrategyBasic implements RankStrategy {
     @Value("${correct.cpa.min.ctcvr:0.000001}")
     protected Double minCtcvr;
 
-
     @Value("${exclude.exp:772}")
     protected String excludeExp;
 
     @Value("${target.crowd.exclude.exp:785}")
     protected String targetCrowdExcludeExp;
 
-    @Value("${calibration.coefficient.exp:786}")
-    protected String calibrationCoefficientExp;
 
     @Value("${calibration.view:2000}")
     protected Integer calibrationView;
@@ -102,6 +98,21 @@ public abstract class RankStrategyBasic implements RankStrategy {
     @Value("${guarantee.crowd.weight.coefficient:1.0}")
     protected Double guaranteeCrowdWeightCoefficient;
 
+    @Value("${checkout.ecpm.exp:790}")
+    protected String checkoutEcpmExp;
+
+    @Value("${filter.ecpm:60}")
+    protected String filterEcpm;
+
+    @Value("${calibration.profession.exp:792}")
+    protected String calibrationProfessionExp;
+
+    @Value("${checkout.layer:false}")
+    protected Boolean checkoutLayer;
+
+    @Value("${filter.config.value:[]}")
+    protected String filterConfigValue;
+
     @Autowired
     private FeatureService featureService;
     @Autowired
@@ -113,14 +124,16 @@ public abstract class RankStrategyBasic implements RankStrategy {
 
     String realLayerCtcvrKey = "ad:platform:real:ctcvr:{model}:{layer}:{class}";
 
-    String adCustomerLayerHourKey = "ad:platform:customer:hour:{layer}:{clazz}:{customerId}";
+    String realLayerCtcvrV2Key = "ad:platform:real:ctcvr:v2:{model}:{layer}:{class}";
 
-    String adVerLayerHourKey = "ad:platform:adver:hour:{layer}:{clazz}:{adverId}";
     String adLayerHourKey = "ad:platform:ad:hour:{layer}:{clazz}:{adId}";
 
     String adLayerDayKey = "ad:platform:ad:day:{layer}:{clazz}:{adId}";
 
-    String cidLayerKey = "ad:engine:cid:layer:info:{cid}:{userLayer}";
+    String userLayerDataKey = "ad:platform:{layer}:{class}:{model}:{type}:{value}";
+
+    String userLayerDataV2Key = "ad:platform:v2:{layer}:{class}:{model}:{type}:{value}";
+
 
     private static final double DEFAULT_CORRECTION = 1.0;
 
@@ -206,16 +219,19 @@ public abstract class RankStrategyBasic implements RankStrategy {
                 map.put("root_source_channel", rootSourceChannel);
             }
         }
+
         Map<String, String> userLayer = getUserLayer(request.getMid());
         String layer = userLayer.getOrDefault("layer", "无曝光");
         String clazz = userLayer.getOrDefault("class", "近期未出现");
         if (request.getIsFilterUser()) {
             layer = layer + "-炸";
         }
-        String userLayerClass = layer + "-" + clazz;
-        map.put("user_layer_class", userLayerClass);
         map.put("layer", layer);
         map.put("clazz", clazz);
+        String layerL4 = userLayer.getOrDefault("layer_l4", "无曝光");
+        String clazzL4 = userLayer.getOrDefault("class_l4", "近期未出现");
+        map.put("layer_l4", layerL4);
+        map.put("clazz_l4", clazzL4);
         return map;
     }
 
@@ -270,8 +286,16 @@ public abstract class RankStrategyBasic implements RankStrategy {
 
     protected void filterRequestAdList(RankRecommendRequestParam request, ScoreParam scoreParam) {
         Map<String, String> userLayer = this.getUserLayer(request.getMid());
-        String layer = userLayer.getOrDefault("layer", "无曝光");
-        String clazz = userLayer.getOrDefault("class", "近期未出现");
+        String layer;
+        String clazz;
+        if (!checkoutLayer) {
+            layer = userLayer.getOrDefault("layer", "无曝光");
+            clazz = userLayer.getOrDefault("class", "近期未出现");
+        } else {
+            layer = userLayer.getOrDefault("layer_l4", "无曝光");
+            clazz = userLayer.getOrDefault("class_l4", "近期未出现");
+        }
+
         //有转化中医层,中医和兴趣教育同时存在过滤兴趣教育行业
         if (Objects.equals(layer, "有转化") && Objects.equals(clazz, "中医") && scoreParam.getExpCodeSet().contains(excludeExp)) {
             List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
@@ -285,14 +309,52 @@ public abstract class RankStrategyBasic implements RankStrategy {
             log.info("excludeExp filtered request={}", JSONObject.toJSONString(request));
         }
 
-        // 有曝光无转化-其他 过滤德瑞骅客户 客户id 26
-        if (Objects.equals(layer, "有曝光无转化") && Objects.equals(clazz, "其他") && scoreParam.getExpCodeSet().contains(targetCrowdExcludeExp)) {
-            List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
-            List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getCustomerId() == null || e.getCustomerId() != 26L).collect(Collectors.toList());
-            if (CollectionUtils.isNotEmpty(filteredAdList)) {
-                request.setAdIdList(filteredAdList);
+        List<FilterConfig> filterConfigs = JSONArray.parseArray(filterConfigValue).toJavaList(FilterConfig.class);
+        if (!CollectionUtils.isEmpty(filterConfigs)) {
+            for (FilterConfig filterConfig : filterConfigs) {
+                if (filterConfig.getExpId() == null || !scoreParam.getExpCodeSet().contains(filterConfig.getExpId())) {
+                    continue;
+                }
+                if (!Objects.equals(layer, filterConfig.getLayer()) || !Objects.equals(clazz, filterConfig.getClazz())) {
+                    continue;
+                }
+                if (Objects.equals(FilterTypeEnum.CREATIVE.getCode(), filterConfig.getType())) {
+                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
+                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getCreativeId() == null || !e.getCreativeId().equals(Long.valueOf(filterConfig.getValue()))).collect(Collectors.toList());
+                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
+                        request.setAdIdList(filteredAdList);
+                    }
+                }
+                if (Objects.equals(FilterTypeEnum.AD.getCode(), filterConfig.getType())) {
+                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
+                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getAdId() == null || !e.getAdId().equals(Long.valueOf(filterConfig.getValue()))).collect(Collectors.toList());
+                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
+                        request.setAdIdList(filteredAdList);
+                    }
+                }
+                if (Objects.equals(FilterTypeEnum.ADVERTISER.getCode(), filterConfig.getType())) {
+                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
+                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getAdVerId() == null || !e.getAdVerId().equals(filterConfig.getValue())).collect(Collectors.toList());
+                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
+                        request.setAdIdList(filteredAdList);
+                    }
+                }
+                if (Objects.equals(FilterTypeEnum.PROFESSION.getCode(), filterConfig.getType())) {
+                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
+                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getProfession() == null || !e.getProfession().equals(filterConfig.getValue())).collect(Collectors.toList());
+                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
+                        request.setAdIdList(filteredAdList);
+                    }
+                }
+                if (Objects.equals(FilterTypeEnum.CUSTOMER.getCode(), filterConfig.getType())) {
+                    List<AdPlatformCreativeDTO> adIdList = request.getAdIdList();
+                    List<AdPlatformCreativeDTO> filteredAdList = adIdList.stream().filter(e -> e.getCustomerId() == null || !e.getCustomerId().equals(Long.valueOf(filterConfig.getValue()))).collect(Collectors.toList());
+                    if (CollectionUtils.isNotEmpty(filteredAdList)) {
+                        request.setAdIdList(filteredAdList);
+                    }
+                }
+                log.info("targetCrowdExcludeExp filtered filterConfig={} request={}", filterConfig, JSONObject.toJSONString(request));
             }
-            log.info("targetCrowdExcludeExp filtered request={}", JSONObject.toJSONString(request));
         }
     }
 
@@ -359,71 +421,76 @@ public abstract class RankStrategyBasic implements RankStrategy {
      * 根据人群信息计算保量权重系数(新版本)
      *
      * @param guaranteeView 保量视图对象
-     * @param reqFeature 请求特征信息,包含用户人群layer信息
+     * @param reqFeature    请求特征信息,包含用户人群layer信息
      * @return 保量权重系数
      */
     protected double calculateGuaranteeWeightWithCrowd(GuaranteeView guaranteeView, Map<String, String> reqFeature) {
-        // 空值检查:如果保量视图为空,返回默认权重
-        if (guaranteeView == null) {
-            return 1.0;
-        }
-
-        // 判断是否配置了保量人群代码
-        String guaranteeCrowdCode = guaranteeView.getGuaranteeCrowdCode();
-        if (StringUtils.isEmpty(guaranteeCrowdCode)) {
-            // 保量人群代码无值,走原有逻辑
-            return calculateGuaranteedWeight(guaranteeView);
-        }
-        // 判断是否勾选了全部人群
-        if (isFullCrowd(guaranteeCrowdCode)) {
-            // 勾选了全部人群,走原有逻辑
-            return calculateGuaranteedWeight(guaranteeView);
-        }
+        try {
+            // 空值检查:如果保量视图为空,返回默认权重
+            if (guaranteeView == null) {
+                return 1.0;
+            }
 
-        // 保量人群代码有值,进行人群匹配判断
-        String userLayer = reqFeature.getOrDefault("layer", "");
+            // 判断是否配置了保量人群代码
+            String guaranteeCrowdCode = guaranteeView.getGuaranteeCrowdCode();
+            if (StringUtils.isEmpty(guaranteeCrowdCode)) {
+                // 保量人群代码无值,走原有逻辑
+                return calculateGuaranteedWeight(guaranteeView);
+            }
+            // 判断是否勾选了全部人群
+            if (isFullCrowd(guaranteeCrowdCode)) {
+                // 勾选了全部人群,走原有逻辑
+                return calculateGuaranteedWeight(guaranteeView);
+            }
 
-        // 获取用户人群对应的CrowdLayerEnum code
-        Integer userCrowdCode = getUserCrowdCode(userLayer);
-        if (userCrowdCode == null) {
-            log.error("RankStrategyBasic calculateGuaranteeWeightWithCrowd 无法获取用户人群代码,userLayer={}", userLayer);
-            return 1.0;
-        }
+            // 保量人群代码有值,进行人群匹配判断
+            String userLayer = reqFeature.getOrDefault("layer_l4", "");
 
-        // 判断用户人群是否在保量人群范围内
-        boolean isUserInGuaranteeCrowd = isUserInGuaranteeCrowd(guaranteeCrowdCode, userCrowdCode);
+            // 获取用户人群对应的CrowdLayerEnum code
+            Integer userCrowdCode = getUserCrowdCode(userLayer);
+            if (userCrowdCode == null) {
+                log.error("RankStrategyBasic calculateGuaranteeWeightWithCrowd 无法获取用户人群代码,userLayer={}", userLayer);
+                return 1.0;
+            }
 
-        if (isUserInGuaranteeCrowd) {
-            // 到这里,就是用户在保量人群中,且广告主勾选的不是全部人群
-            // 计算保量权重
-            double baseWeight = calculateGuaranteedWeight(guaranteeView);
-            // 额外加权
-            // 对勾选了保量人群,且不是勾选了全部的保量人群的广告进行额外加权
-            double finalWeight = baseWeight * guaranteeCrowdWeightCoefficient;
-            log.debug("RankStrategyBasic 保量人群加权: userLayer={}, guaranteeCrowdCode={}, baseWeight={}, coefficient={}, finalWeight={}",
-                     userLayer, guaranteeCrowdCode, baseWeight, guaranteeCrowdWeightCoefficient, finalWeight);
-            return finalWeight;
-        } else {
-            // 用户不在保量人群范围内,权重设为1
-            log.debug("RankStrategyBasic 用户不在保量人群范围内: userLayer={}, guaranteeCrowdCode={}", userLayer, guaranteeCrowdCode);
-            return 1.0;
+            // 判断用户人群是否在保量人群范围内
+            boolean isUserInGuaranteeCrowd = isUserInGuaranteeCrowd(guaranteeCrowdCode, userCrowdCode);
+
+            if (isUserInGuaranteeCrowd) {
+                // 到这里,就是用户在保量人群中,且广告主勾选的不是全部人群
+                // 计算保量权重
+                double baseWeight = calculateGuaranteedWeight(guaranteeView);
+                // 额外加权
+                // 对勾选了保量人群,且不是勾选了全部的保量人群的广告进行额外加权
+                double finalWeight = baseWeight * guaranteeCrowdWeightCoefficient;
+                log.debug("RankStrategyBasic 保量人群加权: userLayer={}, guaranteeCrowdCode={}, baseWeight={}, coefficient={}, finalWeight={}",
+                        userLayer, guaranteeCrowdCode, baseWeight, guaranteeCrowdWeightCoefficient, finalWeight);
+                return finalWeight;
+            } else {
+                // 用户不在保量人群范围内,权重设为1
+                log.debug("RankStrategyBasic 用户不在保量人群范围内: userLayer={}, guaranteeCrowdCode={}", userLayer, guaranteeCrowdCode);
+                return 1.0;
+            }
+        } catch (Exception e) {
+            log.error("RankStrategyBasic calculateGuaranteeWeightWithCrowd error", e);
+            return calculateGuaranteedWeight(guaranteeView);
         }
     }
 
     /**
      * 计算保量权重系数(原有逻辑)
-     *
+     * <p>
      * 保量逻辑说明:
      * 1. 根据广告主的保量配置(保量比例、保量上限)和实际曝光情况计算权重
      * 2. 权重用于调整广告排序分数,帮助未达到保量目标的广告主获得更多曝光
      * 3. 已达到保量上限的广告主权重为0,避免过度曝光
      *
      * @param guaranteeView 保量视图对象,包含广告主保量相关数据
-     *                     - adrId: 广告主ID
-     *                     - adrAlgoViewNum: 广告主当天算法曝光数
-     *                     - allAlgoViewNum: 全平台当天算法曝光数
-     *                     - guaranteeNum: 保量上限(广告主最大允许曝光数)
-     *                     - guaranteeRate: 保量比例(广告主期望占全平台曝光的百分比)
+     *                      - adrId: 广告主ID
+     *                      - adrAlgoViewNum: 广告主当天算法曝光数
+     *                      - allAlgoViewNum: 全平台当天算法曝光数
+     *                      - guaranteeNum: 保量上限(广告主最大允许曝光数)
+     *                      - guaranteeRate: 保量比例(广告主期望占全平台曝光的百分比)
      * @return 保量权重系数,范围[0.0, 2.0],默认1.0
      */
     protected double calculateGuaranteedWeight(GuaranteeView guaranteeView) {
@@ -490,8 +557,17 @@ public abstract class RankStrategyBasic implements RankStrategy {
     }
 
     protected Map<Long, CorrectCpaParam> getCorrectCpaParamMap(RankRecommendRequestParam request, ScoreParam scoreParam, Map<String, String> reqFeature) {
-        String layer = reqFeature.get("layer");
-        String clazz = reqFeature.get("clazz");
+        String layer;
+        String clazz;
+        if (!checkoutLayer) {
+            layer = reqFeature.get("layer");
+            clazz = reqFeature.get("clazz");
+        } else {
+            layer = reqFeature.get("layer_l4");
+            clazz = reqFeature.get("clazz_l4");
+        }
+
+
         Map<Long, CorrectCpaParam> resultMap = new HashMap<>();
         try {
             if (CollectionUtils.isEmpty(request.getAdIdList())) {
@@ -631,32 +707,75 @@ public abstract class RankStrategyBasic implements RankStrategy {
             }
         }
 
-        if (scoreParam.getExpCodeSet().contains(calibrationCoefficientExp)) {
+        if (scoreParam.getExpCodeSet().contains(calibrationProfessionExp)) {
             try {
-                calibrationCtcvrScore(items, request, reqFeature);
+                calibrationProfessionCtcvrScore(items, modelName, reqFeature);
             } catch (Exception e) {
-                log.error("calibrationCtcvrScore error", e);
+                log.error("calibrationProfessionCtcvrScore error", e);
             }
         }
     }
 
-    protected void calibrationCtcvrScore(List<AdRankItem> items, RankRecommendRequestParam request, Map<String, String> reqFeature) {
-        String userLayerClass = reqFeature.get("layer") + "-" + reqFeature.get("clazz");
-        String cidLayerClassKey = cidLayerKey.replace("{userLayer}", userLayerClass);
-
-        // 3. 批量查询Redis
-        List<Long> cidList = items.stream().map(AdRankItem::getAdId).collect(Collectors.toList());
-        List<String> redisKeys = cidList.stream()
-                .map(cid -> cidLayerClassKey.replace("{cid}", cid.toString()))
-                .collect(Collectors.toList());
+    private void calibrationProfessionCtcvrScore(List<AdRankItem> items, String modelName, Map<String, String> reqFeature) {
+        if (StringUtils.isEmpty(modelName)) {
+            return;
+        }
+        String layerKeyTemplate;
+        if (!checkoutLayer) {
+            // 构建Key模板
+            layerKeyTemplate = userLayerDataKey
+                    .replace("{model}", modelName)
+                    .replace("{layer}", reqFeature.get("layer"))
+                    .replace("{class}", reqFeature.get("clazz"))
+                    .replace("{type}", "profession");
+        } else {
+            // 构建Key模板
+            layerKeyTemplate = userLayerDataV2Key
+                    .replace("{model}", modelName)
+                    .replace("{layer}", reqFeature.get("layer_l4"))
+                    .replace("{class}", reqFeature.get("clazz_l4"))
+                    .replace("{type}", "profession");
+        }
+        List<String> professions = items.stream().map(AdRankItem::getProfession).filter(StringUtils::isNotEmpty).distinct().collect(Collectors.toList());
+        List<String> professionRedisKeys = professions.stream().map(e -> layerKeyTemplate.replace("{value}", e)).collect(Collectors.toList());
+        List<String> redisValues = adRedisHelper.mget(professionRedisKeys);
+        Map<String, CalibrationModelCtcvrData> map = new HashMap<>();
+        for (int i = 0; i < professions.size(); i++) {
+            String value = redisValues.get(i);
+            if (StringUtils.isEmpty(value)) {
+                continue;
+            }
+            JSONObject json = JSONObject.parseObject(value);
+            CalibrationModelCtcvrData calibrationModelCtcvrData = new CalibrationModelCtcvrData();
+            calibrationModelCtcvrData.setRealCtcvr(json.getDouble("ctcvr"));
+            calibrationModelCtcvrData.setView(json.getInteger("view"));
+            calibrationModelCtcvrData.setPCtcvr(json.getDouble("pCtcvr"));
+            map.put(professions.get(i), calibrationModelCtcvrData);
+        }
 
-        List<String> redisValues = algRedisHelper.mget(redisKeys);
-        Map<Long, CorrectCtcvrScoreParam> calibrationMap = parseRedisValues(cidList, redisValues);
 
-        // 4. 应用校准逻辑
-        applyCalibration(items, calibrationMap);
+        for (AdRankItem item : items) {
+            CalibrationModelCtcvrData calibrationModelCtcvrData = map.get(item.getProfession());
+            item.getExt().put("calibrationModelProfessionCtcvrData", JSONObject.toJSONString(calibrationModelCtcvrData));
+            if (calibrationModelCtcvrData == null
+                    || calibrationModelCtcvrData.getPCtcvr() == null
+                    || calibrationModelCtcvrData.getPCtcvr() == 0.0
+                    || calibrationModelCtcvrData.getRealCtcvr() == null
+                    || calibrationModelCtcvrData.getRealCtcvr() == 0.0) {
+                continue;
+            }
+            double diff = calibrationModelCtcvrData.getRealCtcvr() / calibrationModelCtcvrData.getPCtcvr();
+            if (Math.abs(diff - 1) < 0.1) {
+                continue;
+            }
+            double calibratedCtcvrScore = item.getLrScore() * diff;
+            item.getScoreMap().put("layerModelProfessionCtcvrScore", calibratedCtcvrScore);
+            item.getScoreMap().put("ctcvrScore", calibratedCtcvrScore);
+            item.setLrScore(calibratedCtcvrScore);
+        }
     }
 
+
     // 解析Redis返回值到Map
     private Map<Long, CorrectCtcvrScoreParam> parseRedisValues(List<Long> cidList, List<String> values) {
         Map<Long, CorrectCtcvrScoreParam> map = new HashMap<>();
@@ -681,23 +800,6 @@ public abstract class RankStrategyBasic implements RankStrategy {
         return map;
     }
 
-    // 应用校准到广告项
-    private void applyCalibration(List<AdRankItem> items, Map<Long, CorrectCtcvrScoreParam> calibrationMap) {
-        for (AdRankItem item : items) {
-            CorrectCtcvrScoreParam param = calibrationMap.get(item.getAdId());
-            if (param == null || param.getView() == null || param.getView() < calibrationView) {
-                continue; // 跳过无效数据
-            }
-
-            double realCtcvr = Optional.ofNullable(param.getRealCtcvr()).orElse(0.0);
-            double calibratedScore = item.getLrScore() * calibrationAlpha + (1 - calibrationAlpha) * realCtcvr;
-            item.getExt().put("correctCtcvrScoreParam", JSONObject.toJSONString(param));
-            item.getScoreMap().put("cidCorrectCtcvrScore", calibratedScore);
-            item.getScoreMap().put("ctcvrScore", calibratedScore);
-            item.setLrScore(calibratedScore);
-        }
-    }
-
 
     protected AdRankItem creativeCovertRankItem(AdPlatformCreativeDTO dto, RankRecommendRequestParam request, Set<String> noApiAdVerIds) {
         AdRankItem adRankItem = new AdRankItem();
@@ -772,6 +874,7 @@ public abstract class RankStrategyBasic implements RankStrategy {
             reqFeature.put("adid", String.valueOf(adPlatformCreativeDTO.getAdId()));
             reqFeature.put("adverid", String.valueOf(adPlatformCreativeDTO.getAdVerId()));
             reqFeature.put("profession", adPlatformCreativeDTO.getProfession());
+            reqFeature.put("category_name", adPlatformCreativeDTO.getCategoryName());
         }
         adRankItem.getMetaFeatureMap().put("reqFeature", reqFeature);
         adRankItem.getMetaFeatureMap().put("sceneFeature", sceneFeatureMap);
@@ -787,10 +890,13 @@ public abstract class RankStrategyBasic implements RankStrategy {
             return new HashMap<>();
         }
         try {
-            Map<String, String> map = JSON.parseObject(value, new TypeReference<Map<String, String>>() {
-            });
-            if (map.containsKey("layer") && Objects.equals(map.get("layer"), "已转化")) {
-                map.put("layer", "有转化");
+            Map<String, String> map = new HashMap<>();
+            JSONObject jsonObject = JSONObject.parseObject(value);
+            map.put("layer", jsonObject.getString("layer"));
+            map.put("class", jsonObject.getString("class"));
+            if (jsonObject.containsKey("basic_l4")) {
+                map.put("layer_l4", jsonObject.getJSONObject("basic_l4").getString("level"));
+                map.put("class_l4", jsonObject.getJSONObject("basic_l4").getString("class"));
             }
             return map;
         } catch (Exception e) {
@@ -803,13 +909,20 @@ public abstract class RankStrategyBasic implements RankStrategy {
         if (StringUtils.isEmpty(modelName)) {
             return;
         }
-        // 构建Key模板
-        String layerKeyTemplate = realLayerCtcvrKey
-                .replace("{model}", modelName)
-                .replace("{layer}", reqFeature.get("layer"))
-                .replace("{class}", reqFeature.get("clazz"));
-
-
+        String layerKeyTemplate;
+        if (!checkoutLayer) {
+            // 构建Key模板
+            layerKeyTemplate = realLayerCtcvrKey
+                    .replace("{model}", modelName)
+                    .replace("{layer}", reqFeature.get("layer"))
+                    .replace("{class}", reqFeature.get("clazz"));
+        } else {
+            // 构建Key模板
+            layerKeyTemplate = realLayerCtcvrV2Key
+                    .replace("{model}", modelName)
+                    .replace("{layer}", reqFeature.get("layer_l4"))
+                    .replace("{class}", reqFeature.get("clazz_l4"));
+        }
         String value = adRedisHelper.get(layerKeyTemplate);
         JSONObject json = JSONObject.parseObject(value);
         Integer view = json.getInteger("view");
@@ -873,7 +986,7 @@ public abstract class RankStrategyBasic implements RankStrategy {
      * 判断用户人群是否在保量人群范围内
      *
      * @param guaranteeCrowdCode 保量人群代码字符串,格式如"1,2,3"
-     * @param userCrowdCode 用户人群代码
+     * @param userCrowdCode      用户人群代码
      * @return true表示用户在保量人群范围内
      */
     private boolean isUserInGuaranteeCrowd(String guaranteeCrowdCode, Integer userCrowdCode) {
@@ -898,6 +1011,7 @@ public abstract class RankStrategyBasic implements RankStrategy {
 
     /**
      * 判断是否全部保量人群
+     *
      * @param guaranteeCrowdCode 保量人群代码字符串
      * @return true表示是全部保量人群
      */
@@ -926,7 +1040,7 @@ public abstract class RankStrategyBasic implements RankStrategy {
 
         } catch (Exception e) {
             log.error("判断保量人群加权失败: guaranteeCrowdCode={}",
-                     guaranteeCrowdCode, e);
+                    guaranteeCrowdCode, e);
             return false;
         }
     }

+ 1 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBy680.java

@@ -130,6 +130,7 @@ public class RankStrategyBy680 extends RankStrategyBasic {
                     adRankItem.setCpm(ObjUtil.nullOrDefault(dto.getCpm(), 90).doubleValue());
                     adRankItem.setSkuId(dto.getSkuId());
                     adRankItem.setCustomerId(dto.getCustomerId());
+                    adRankItem.setProfession(dto.getProfession());
                     adRankItem.setRandom(random.nextInt(1000));
                     if (noApiAdVerIds.contains(dto.getAdVerId())) {
                         adRankItem.getExt().put("isApi", "0");

+ 246 - 18
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBy683.java

@@ -2,6 +2,9 @@ package com.tzld.piaoquan.ad.engine.service.score.strategy;
 
 import com.alibaba.fastjson.JSONObject;
 import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.tzld.piaoquan.ad.engine.commons.dto.AdPlatformCreativeDTO;
+import com.tzld.piaoquan.ad.engine.commons.helper.DnnCidDataHelper;
+import com.tzld.piaoquan.ad.engine.commons.param.RankRecommendRequestParam;
 import com.tzld.piaoquan.ad.engine.commons.score.ScoreParam;
 import com.tzld.piaoquan.ad.engine.commons.score.ScorerUtils;
 import com.tzld.piaoquan.ad.engine.commons.thread.ThreadPoolFactory;
@@ -9,8 +12,6 @@ import com.tzld.piaoquan.ad.engine.commons.util.*;
 import com.tzld.piaoquan.ad.engine.service.entity.CorrectCpaParam;
 import com.tzld.piaoquan.ad.engine.service.entity.GuaranteeView;
 import com.tzld.piaoquan.ad.engine.service.feature.Feature;
-import com.tzld.piaoquan.ad.engine.commons.dto.AdPlatformCreativeDTO;
-import com.tzld.piaoquan.ad.engine.commons.param.RankRecommendRequestParam;
 import com.tzld.piaoquan.recommend.feature.domain.ad.base.AdRankItem;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
@@ -45,21 +46,33 @@ public class RankStrategyBy683 extends RankStrategyBasic {
     @Value("${word2vec.exp:694}")
     private String word2vecExp;
 
+
     // FIXME(zhoutian): 可能需要独立配置
     @ApolloJsonValue("${rank.score.weight.680:{}}")
     private Map<String, Double> weightMap;
 
+    /**
+     * 人群分层&创意的权重
+     * 格式:{layer_creativeId: weight}
+     */
+    @ApolloJsonValue("${rank.score.weight.layer.and.creative:{}}")
+    private Map<String, Double> layerAndCreativeWeightMap;
+
     @ApolloJsonValue("${rank.score.neg_sample_rate:0.01}")
     Double negSampleRate;
 
+    Set<String> sparseFeatureSet;
+
+
     @PostConstruct
     public void afterInit() {
         this.readBucketFile();
+        this.initSparseFeatureNames();
     }
 
+
     @Override
     public List<AdRankItem> adItemRank(RankRecommendRequestParam request, ScoreParam scoreParam) {
-
         Map<String, Double> weightParam = ObjUtil.nullOrDefault(weightMap, new HashMap<>());
 
 
@@ -80,6 +93,7 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         // feature1
         Feature feature = this.getFeature(scoreParam, request);
 
+
         Map<String, Map<String, String>> userFeature = feature.getUserFeature();
         Map<String, Map<String, String>> videoFeature = feature.getVideoFeature();
         Map<String, Map<String, Map<String, String>>> allAdVerFeature = feature.getAdVerFeature();
@@ -87,7 +101,6 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         Map<String, Map<String, Map<String, String>>> allSkuFeature = feature.getSkuFeature();
         Map<String, String> reqFeature = this.getReqFeature(scoreParam, request);
 
-
         Map<String, String> userFeatureMap = new HashMap<>();
         Map<String, String> c1Feature = userFeature.getOrDefault("alg_mid_feature_ad_action", new HashMap<>());
         List<TupleMapEntry<Tuple5>> midActionList = this.handleC1Feature(c1Feature, userFeatureMap);
@@ -103,6 +116,35 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         Map<String, String> e1Feature = userFeature.getOrDefault("alg_mid_feature_return_tags", new HashMap<>());
         Map<String, String> e2Feature = userFeature.getOrDefault("alg_mid_feature_share_tags", new HashMap<>());
 
+        Map<String, String> g1Feature = userFeature.getOrDefault("mid_return_video_cate", new HashMap<>());
+        Map<String, String> g2Feature = userFeature.getOrDefault("mid_share_video_cate", new HashMap<>());
+
+
+        userFeatureMap.put("brand", reqFeature.getOrDefault("brand", ""));
+        userFeatureMap.put("region", reqFeature.getOrDefault("region", ""));
+        userFeatureMap.put("city", reqFeature.getOrDefault("city", ""));
+        userFeatureMap.put("vid", reqFeature.getOrDefault("vid", ""));
+        userFeatureMap.put("apptype", reqFeature.getOrDefault("apptype", ""));
+        userFeatureMap.put("is_first_layer", reqFeature.getOrDefault("is_first_layer", ""));
+        userFeatureMap.put("root_source_scene", reqFeature.getOrDefault("root_source_scene", ""));
+        userFeatureMap.put("root_source_channel", reqFeature.getOrDefault("root_source_channel", ""));
+
+
+        userFeatureMap.put("cate1", d3Feature.get("merge_first_level_cate"));
+        userFeatureMap.put("cate2", d3Feature.get("merge_second_level_cate"));
+        userFeatureMap.put("user_vid_return_tags_2h", e1Feature.getOrDefault("tags_2h", null));
+        userFeatureMap.put("user_vid_return_tags_1d", e1Feature.getOrDefault("tags_1d", null));
+        userFeatureMap.put("user_vid_return_tags_3d", e1Feature.getOrDefault("tags_3d", null));
+        userFeatureMap.put("user_vid_return_tags_7d", e1Feature.getOrDefault("tags_7d", null));
+        userFeatureMap.put("user_vid_return_tags_14d", e1Feature.getOrDefault("tags_14d", null));
+        userFeatureMap.put("title_split", d3Feature.getOrDefault("title_split", null));
+        userFeatureMap.put("user_vid_share_tags_1d", e2Feature.getOrDefault("tags_1d", null));
+        userFeatureMap.put("user_vid_share_tags_14d", e2Feature.getOrDefault("tags_14d", null));
+        userFeatureMap.put("user_vid_return_cate1_14d", g1Feature.getOrDefault("cate1_14d", null));
+        userFeatureMap.put("user_vid_return_cate2_14d", g1Feature.getOrDefault("cate2_14d", null));
+        userFeatureMap.put("user_vid_share_cate1_14d", g2Feature.getOrDefault("cate1_14d", null));
+        userFeatureMap.put("user_vid_share_cate2_14d", g2Feature.getOrDefault("cate2_14d", null));
+
         Map<String, String> sceneFeatureMap = this.handleSceneFeature(ts);
         long time1 = System.currentTimeMillis();
 
@@ -127,13 +169,13 @@ public class RankStrategyBy683 extends RankStrategyBasic {
                     adRankItem.setCpm(ObjUtil.nullOrDefault(dto.getCpm(), 90).doubleValue());
                     adRankItem.setSkuId(dto.getSkuId());
                     adRankItem.setCustomerId(dto.getCustomerId());
+                    adRankItem.setProfession(dto.getProfession());
                     adRankItem.setRandom(random.nextInt(1000));
                     if (noApiAdVerIds.contains(dto.getAdVerId())) {
                         adRankItem.getExt().put("isApi", "0");
                     } else {
                         adRankItem.getExt().put("isApi", "1");
                     }
-
                     adRankItem.getExt().put("recallsources", dto.getRecallSources());
                     adRankItem.getExt().put("correctCpaMap", JSONObject.toJSONString(correctCpaMap.get(dto.getAdId())));
                     adRankItem.getExt().put("correctionFactor", correctCpaMap.get(dto.getAdId()).getCorrectionFactor());
@@ -144,7 +186,7 @@ public class RankStrategyBy683 extends RankStrategyBasic {
                     Map<String, String> b1Feature = cidFeature.getOrDefault("alg_cid_feature_basic_info", new HashMap<>());
 
                     Map<String, Map<String, String>> adVerFeature = allAdVerFeature.getOrDefault(dto.getAdVerId(), new HashMap<>());
-
+                    Map<String, Map<String, String>> skuFeature = allSkuFeature.getOrDefault(String.valueOf(dto.getSkuId()), new HashMap<>());
                     Map<String, String> d1Feature = cidFeature.getOrDefault("alg_cid_feature_vid_cf", new HashMap<>());
 
                     this.handleB1Feature(b1Feature, cidFeatureMap, cidStr);
@@ -153,6 +195,18 @@ public class RankStrategyBy683 extends RankStrategyBasic {
                     this.handleC1UIFeature(midTimeDiffMap, actionStaticMap, cidFeatureMap, cidStr);
                     this.handleD1Feature(d1Feature, cidFeatureMap);
                     this.handleD2Feature(vidRankMaps, cidFeatureMap, cidStr);
+                    this.handleH1AndH2Feature(skuFeature, adVerFeature, cidFeatureMap);
+                    cidFeatureMap.put("cid", dto.getCreativeId() != null ? String.valueOf(dto.getCreativeId()) : "");
+                    cidFeatureMap.put("adid", dto.getAdId() != null ? String.valueOf(dto.getAdId()) : "");
+                    cidFeatureMap.put("adverid", dto.getAdVerId() != null ? dto.getAdVerId() : "");
+                    cidFeatureMap.put("profession", dto.getProfession() != null ? dto.getProfession() : "");
+                    cidFeatureMap.put("category_name", dto.getCategoryName() != null ? dto.getCategoryName() : "");
+                    //DNN模型没训练过的cid才不传入广告相关的稀疏特征
+                    if (CollectionUtils.isNotEmpty(DnnCidDataHelper.getCidSetV2()) && !DnnCidDataHelper.getCidSetV2().contains(adRankItem.getAdId())) {
+                        cidFeatureMap.put("cid", "");
+                        cidFeatureMap.put("adid", "");
+                        cidFeatureMap.put("adverid", "");
+                    }
                     return adRankItem;
                 } finally {
                     cdl1.countDown();
@@ -226,27 +280,38 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         long time4 = System.currentTimeMillis();
         // 打分排序
         // getScorerPipeline
-        List<AdRankItem> result = ScorerUtils.getScorerPipeline(ScorerUtils.XGBOOST_SCORE_CONF_683).scoring(sceneFeatureMap, userFeatureMap, adRankItems);
+        List<AdRankItem> result = ScorerUtils.getScorerPipeline(ScorerUtils.PAI_SCORE_CONF_20250804).scoring(sceneFeatureMap, userFeatureMap, adRankItems);
         long time5 = System.currentTimeMillis();
-
-        // calibrate score for negative sampling
+        // calibrate score for negative sampling or cold start
         for (AdRankItem item : result) {
             double originalScore = item.getLrScore();
             double calibratedScore = originalScore / (originalScore + (1 - originalScore) / negSampleRate);
+            // 该创意尚未在模型中训练,打分不可靠
+            if (CollectionUtils.isNotEmpty(DnnCidDataHelper.getCidSetV2()) && !DnnCidDataHelper.getCidSetV2().contains(item.getAdId())) {
+                Map<String, Map<String, String>> cidFeature = allCidFeature.getOrDefault(String.valueOf(item.getAdId()), new HashMap<>());
+                Map<String, String> b3Feature = cidFeature.getOrDefault("alg_cid_feature_cid_action", new HashMap<>());
+                double view = Double.parseDouble(b3Feature.getOrDefault("ad_view_14d", "0"));
+                double conver = Double.parseDouble(b3Feature.getOrDefault("ad_conversion_14d", "0"));
+                double smoothCxr = NumUtil.divSmoothV1(conver, view, 1.64);
+                //模型打分和统计计算取打分更低的
+                calibratedScore = Math.min(smoothCxr, calibratedScore);
+            }
             item.setLrScore(calibratedScore);
             item.getScoreMap().put("originCtcvrScore", originalScore);
             item.getScoreMap().put("modelCtcvrScore", calibratedScore);
             item.getScoreMap().put("ctcvrScore", calibratedScore);
         }
 
-        calculateCtcvrScore(result, request, scoreParam, null, reqFeature);
+        calculateCtcvrScore(result, request, scoreParam, "dnn_v2", reqFeature);
         // loop
         double cpmCoefficient = weightParam.getOrDefault("cpmCoefficient", 0.9);
         boolean isGuaranteeType = false;
+        // 查询人群分层信息
+        String peopleLayer = Optional.of(reqFeature)
+                .map(f -> f.get("layer"))
+                .map(s -> s.replace("-炸", ""))
+                .orElse(null);
         for (AdRankItem item : result) {
-            if (isGuaranteedFlow && item.getExt().get("isGuaranteed") != null && (boolean) item.getExt().get("isGuaranteed")) {
-                isGuaranteeType = true;
-            }
             double bid = item.getCpa();
             if (scoreParam.getExpCodeSet().contains(correctCpaExp1) || scoreParam.getExpCodeSet().contains(correctCpaExp2)) {
                 Double correctionFactor = (Double) item.getExt().get("correctionFactor");
@@ -254,9 +319,16 @@ public class RankStrategyBy683 extends RankStrategyBasic {
                 bid = bid * correctionFactor;
             }
             item.getScoreMap().put("ecpm", item.getLrScore() * bid * 1000);
+            if (isGuaranteedFlow && item.getExt().get("isGuaranteed") != null && (boolean) item.getExt().get("isGuaranteed")) {
+                isGuaranteeType = true;
+            }
+
+            String layerAndCreativeWeightMapKey = getLayerAndCreativeWeightMapKey(peopleLayer, String.valueOf(item.getAdId()));
+            // 人群分层&创意的权重
+            double layerAndCreativeWeight = getLayerAndCreativeWeight(layerAndCreativeWeightMapKey);
             double scoreCoefficient = creativeScoreCoefficient.getOrDefault(item.getAdId(), 1d);
             double guaranteeScoreCoefficient = getGuaranteeScoreCoefficient(isGuaranteedFlow, item.getExt());
-            double score = item.getLrScore() * bid * scoreCoefficient * guaranteeScoreCoefficient;
+            double score = item.getLrScore() * bid * scoreCoefficient * guaranteeScoreCoefficient * layerAndCreativeWeight;
             item.getScoreMap().put("guaranteeScoreCoefficient", guaranteeScoreCoefficient);
             item.getScoreMap().put("cpa", item.getCpa());
             item.getScoreMap().put("cpm", item.getCpm());
@@ -265,6 +337,7 @@ public class RankStrategyBy683 extends RankStrategyBasic {
             item.getScoreMap().put("scoreCoefficient", scoreCoefficient);
             item.getFeatureMap().putAll(userFeatureMap);
             item.getFeatureMap().putAll(sceneFeatureMap);
+
             // 没有转化回传的广告主,使用后台配置的CPM
             if (noApiAdVerIds.contains(item.getAdVerId())) {
                 score = item.getCpm() * cpmCoefficient / 1000;
@@ -277,8 +350,23 @@ public class RankStrategyBy683 extends RankStrategyBasic {
 
         if (CollectionUtils.isNotEmpty(result)) {
             AdRankItem top1Item = result.get(0);
+            List<String> participateCompetitionType = new ArrayList<>();
+            participateCompetitionType.add("engine");
             top1Item.getExt().put("isGuaranteeType", isGuaranteeType);
+            if (isGuaranteeType) {
+                participateCompetitionType.add("guarantee");
+            }
+            top1Item.getExt().put("participateCompetitionType", StringUtils.join(participateCompetitionType, ","));
+            Double modelCtcvrScore = top1Item.getScoreMap().get("modelCtcvrScore");
+            Double ctcvrScore = top1Item.getScoreMap().get("ctcvrScore");
+            if (scoreParam.getExpCodeSet().contains(checkoutEcpmExp)) {
+                top1Item.getExt().put("ecpm", ctcvrScore * top1Item.getCpa() * 1000);
+                top1Item.getExt().put("filterEcpm", filterEcpm);
+            } else {
+                top1Item.getExt().put("ecpm", modelCtcvrScore * top1Item.getCpa() * 1000);
+            }
             putMetaFeature(top1Item, feature, reqFeature, sceneFeatureMap, request);
+            top1Item.getExt().put("model", "dnn_v2");
         }
         long time6 = System.currentTimeMillis();
         log.info("cost={}, getFeature={}, handleFeature={},  similar={}, bucketFeature={}, getScorerPipeline={}, " +
@@ -289,6 +377,34 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         return result;
     }
 
+    /**
+     * 获取人群分层和创意的权重
+     *
+     * @param key
+     * @return
+     */
+    private Double getLayerAndCreativeWeight(String key) {
+        if (StringUtils.isBlank(key)) {
+            return 1d;
+        }
+        return layerAndCreativeWeightMap.getOrDefault(key, 1d);
+    }
+
+    /**
+     * 获取人群分层和创意的权重key
+     *
+     * @param layer
+     * @param creativeId
+     * @return
+     */
+    private String getLayerAndCreativeWeightMapKey(String layer, String creativeId) {
+        if (StringUtils.isBlank(layer) || StringUtils.isBlank(creativeId)) {
+            return null;
+        }
+        return layer + "_" + creativeId;
+    }
+
+
     private void handleB1Feature(Map<String, String> b1Feature, Map<String, String> cidFeatureMap, String cid) {
         cidFeatureMap.put("cid_" + cid, "0.1");
         // if (StringUtils.isNotBlank(b1Feature.get("adid"))) {
@@ -389,6 +505,15 @@ public class RankStrategyBy683 extends RankStrategyBasic {
 
     private List<TupleMapEntry<Tuple5>> handleC1Feature(Map<String, String> c1Feature, Map<String, String> featureMap) {
 
+        //用户近1年内是否有转化
+        if (c1Feature.containsKey("user_has_conver_1y") && c1Feature.get("user_has_conver_1y") != null) {
+            featureMap.put("user_has_conver_1y", c1Feature.get("user_has_conver_1y"));
+        }
+        //用户历史转化过品类
+        if (c1Feature.containsKey("user_conver_ad_class") && c1Feature.get("user_conver_ad_class") != null) {
+            featureMap.put("user_conver_ad_class", c1Feature.get("user_conver_ad_class"));
+        }
+
         // 用户特征
         List<TupleMapEntry<Tuple5>> midActionList = new ArrayList<>();
         if (c1Feature.containsKey("action")) {
@@ -416,7 +541,23 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         featureMap.put("ctcvr_all", String.valueOf(NumUtil.div(converAll, viewAll)));
         featureMap.put("cvr_all", String.valueOf(NumUtil.div(clickAll, converAll)));
         featureMap.put("ecpm_all", String.valueOf(NumUtil.div(incomeAll * 1000, viewAll)));
-
+        if (CollectionUtils.isNotEmpty(midActionList)) {
+            List<String> cidClickList = new ArrayList<>();
+            List<String> cidConverList = new ArrayList<>();
+            for (TupleMapEntry<Tuple5> tupleMapEntry : midActionList) {
+                String cid = tupleMapEntry.key;
+                String click = tupleMapEntry.value.f2;
+                String conver = tupleMapEntry.value.f3;
+                if (Objects.equals(click, "1")) {
+                    cidClickList.add(cid);
+                }
+                if (Objects.equals(conver, "1")) {
+                    cidConverList.add(cid);
+                }
+            }
+            featureMap.put("user_cid_click_list", String.join(",", cidClickList));
+            featureMap.put("user_cid_conver_list", String.join(",", cidConverList));
+        }
         return midActionList;
     }
 
@@ -497,6 +638,33 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         }
     }
 
+    private void handleH1AndH2Feature(Map<String, Map<String, String>> skuFeature,
+                                      Map<String, Map<String, String>> adVerFeature,
+                                      Map<String, String> cidFeatureMap) {
+        Map<String, String> h1Feature = adVerFeature.getOrDefault("alg_mid_feature_adver_action", new HashMap<>());
+        Map<String, String> h2Feature = skuFeature.getOrDefault("alg_mid_feature_sku_action", new HashMap<>());
+        List<String> timeList = Arrays.asList("3d", "7d", "30d");
+        List<Tuple2<Map<String, String>, String>> featureList = Arrays.asList(
+                new Tuple2<>(h1Feature, "adverid"),
+                new Tuple2<>(h2Feature, "skuid")
+        );
+        for (Tuple2<Map<String, String>, String> tuple2 : featureList) {
+            Map<String, String> feature = tuple2.f1;
+            String prefix = tuple2.f2;
+            for (String time : timeList) {
+                String timeValue = feature.getOrDefault(time, "");
+                if (StringUtils.isNotEmpty(timeValue)) {
+                    String[] split = timeValue.split(",");
+                    cidFeatureMap.put("user" + "_" + prefix + "_" + "view" + "_" + time, split[0]);
+                    cidFeatureMap.put("user" + "_" + prefix + "_" + "click" + "_" + time, split[1]);
+                    cidFeatureMap.put("user" + "_" + prefix + "_" + "conver" + "_" + time, split[2]);
+                }
+            }
+        }
+
+
+    }
+
     private void handleD3AndB1Feature(Map<String, String> d3Feature, String cTitle, Map<String, String> featureMap,
                                       ScoreParam scoreParam) {
         if (MapUtils.isEmpty(d3Feature) || !d3Feature.containsKey("title") || StringUtils.isEmpty(cTitle)) {
@@ -608,7 +776,7 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         }
         synchronized (this) {
             String bucketFile = "20250217_ad_bucket_688.txt";
-            InputStream resourceStream = RankStrategyBy683.class.getClassLoader().getResourceAsStream(bucketFile);
+            InputStream resourceStream = this.getClass().getClassLoader().getResourceAsStream(bucketFile);
             if (resourceStream != null) {
                 try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceStream))) {
                     Map<String, double[]> bucketsMap = new HashMap<>();
@@ -640,10 +808,72 @@ public class RankStrategyBy683 extends RankStrategyBasic {
         }
     }
 
+    private void initSparseFeatureNames() {
+        this.sparseFeatureSet = new HashSet<String>() {{
+            add("brand");
+            add("region");
+            add("city");
+            add("vid");
+            add("cate1");
+            add("cate2");
+            add("cid");
+            add("adid");
+            add("adverid");
+            add("user_cid_click_list");
+            add("user_cid_conver_list");
+            add("user_vid_return_tags_2h");
+            add("user_vid_return_tags_1d");
+            add("user_vid_return_tags_3d");
+            add("user_vid_return_tags_7d");
+            add("user_vid_return_tags_14d");
+            add("apptype");
+            add("hour");
+            add("hour_quarter");
+            add("root_source_scene");
+            add("root_source_channel");
+            add("is_first_layer");
+            add("title_split");
+            add("profession");
+            add("user_vid_share_tags_1d");
+            add("user_vid_share_tags_14d");
+            add("user_vid_return_cate1_14d");
+            add("user_vid_return_cate2_14d");
+            add("user_vid_share_cate1_14d");
+            add("user_vid_share_cate2_14d");
+            add("user_has_conver_1y");
+            add("user_adverid_view_3d");
+            add("user_adverid_click_3d");
+            add("user_adverid_conver_3d");
+            add("user_adverid_view_7d");
+            add("user_adverid_click_7d");
+            add("user_adverid_conver_7d");
+            add("user_adverid_view_30d");
+            add("user_adverid_click_30d");
+            add("user_adverid_conver_30d");
+            add("user_skuid_view_3d");
+            add("user_skuid_click_3d");
+            add("user_skuid_conver_3d");
+            add("user_skuid_view_7d");
+            add("user_skuid_click_7d");
+            add("user_skuid_conver_7d");
+            add("user_skuid_view_30d");
+            add("user_skuid_click_30d");
+            add("user_skuid_conver_30d");
+            add("user_conver_ad_class");
+            add("category_name");
+        }};
+    }
+
     private Map<String, String> featureBucket(Map<String, String> featureMap) {
         Map<String, String> newFeatureMap = new ConcurrentHashMap<>(featureMap.size());
         for (Map.Entry<String, String> entry : featureMap.entrySet()) {
             String name = entry.getKey();
+            if (this.sparseFeatureSet.contains(name)) {
+                if (entry.getValue() != null) {
+                    newFeatureMap.put(name, entry.getValue());
+                }
+                continue;
+            }
             double score = Double.parseDouble(entry.getValue());
             // 注意:0值、不在分桶文件中的特征,会被过滤掉。
             if (score > 1E-8) {
@@ -657,8 +887,6 @@ public class RankStrategyBy683 extends RankStrategyBasic {
                 }
             }
         }
-
         return newFeatureMap;
     }
-
 }

+ 2 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBy687.java

@@ -59,6 +59,8 @@ public class RankStrategyBy687 extends RankStrategyBasic {
             return rankStrategyBy688.adItemRank(request, scoreParam);
         }
 
+        filterRequestAdList(request, scoreParam);
+
         Set<String> noApiAdVerIds = getNoApiAdVerIds();
 
         List<AdPlatformCreativeDTO> recallCreativeList = request.getAdIdList();

+ 28 - 4
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/score/strategy/RankStrategyBy688.java

@@ -145,8 +145,6 @@ public class RankStrategyBy688 extends RankStrategyBasic {
         userFeatureMap.put("user_vid_share_cate1_14d", g2Feature.getOrDefault("cate1_14d", null));
         userFeatureMap.put("user_vid_share_cate2_14d", g2Feature.getOrDefault("cate2_14d", null));
 
-        userFeatureMap.put("user_layer_class", reqFeature.getOrDefault("user_layer_class", null));
-
         Map<String, String> sceneFeatureMap = this.handleSceneFeature(ts);
         long time1 = System.currentTimeMillis();
 
@@ -171,6 +169,7 @@ public class RankStrategyBy688 extends RankStrategyBasic {
                     adRankItem.setCpm(ObjUtil.nullOrDefault(dto.getCpm(), 90).doubleValue());
                     adRankItem.setSkuId(dto.getSkuId());
                     adRankItem.setCustomerId(dto.getCustomerId());
+                    adRankItem.setProfession(dto.getProfession());
                     adRankItem.setRandom(random.nextInt(1000));
                     if (noApiAdVerIds.contains(dto.getAdVerId())) {
                         adRankItem.getExt().put("isApi", "0");
@@ -201,6 +200,7 @@ public class RankStrategyBy688 extends RankStrategyBasic {
                     cidFeatureMap.put("adid", dto.getAdId() != null ? String.valueOf(dto.getAdId()) : "");
                     cidFeatureMap.put("adverid", dto.getAdVerId() != null ? dto.getAdVerId() : "");
                     cidFeatureMap.put("profession", dto.getProfession() != null ? dto.getProfession() : "");
+                    cidFeatureMap.put("category_name", dto.getCategoryName() != null ? dto.getCategoryName() : "");
                     //DNN模型没训练过的cid才不传入广告相关的稀疏特征
                     if (CollectionUtils.isNotEmpty(DnnCidDataHelper.getCidSet()) && !DnnCidDataHelper.getCidSet().contains(adRankItem.getAdId())) {
                         cidFeatureMap.put("cid", "");
@@ -280,7 +280,14 @@ public class RankStrategyBy688 extends RankStrategyBasic {
         long time4 = System.currentTimeMillis();
         // 打分排序
         // getScorerPipeline
+
+        if (CollectionUtils.isEmpty(adRankItems)) {
+            log.error("adRankItems is empty");
+        }
         List<AdRankItem> result = ScorerUtils.getScorerPipeline(ScorerUtils.PAI_SCORE_CONF_20250214).scoring(sceneFeatureMap, userFeatureMap, adRankItems);
+        if (CollectionUtils.isEmpty(result)) {
+            log.error("scoring result is empty");
+        }
         long time5 = System.currentTimeMillis();
         // calibrate score for negative sampling or cold start
         for (AdRankItem item : result) {
@@ -303,6 +310,9 @@ public class RankStrategyBy688 extends RankStrategyBasic {
         }
 
         calculateCtcvrScore(result, request, scoreParam, "dnn", reqFeature);
+        if (CollectionUtils.isEmpty(result)) {
+            log.error("calculateCtcvrScore result is empty");
+        }
         // loop
         double cpmCoefficient = weightParam.getOrDefault("cpmCoefficient", 0.9);
         boolean isGuaranteeType = false;
@@ -350,7 +360,21 @@ public class RankStrategyBy688 extends RankStrategyBasic {
 
         if (CollectionUtils.isNotEmpty(result)) {
             AdRankItem top1Item = result.get(0);
+            List<String> participateCompetitionType = new ArrayList<>();
+            participateCompetitionType.add("engine");
             top1Item.getExt().put("isGuaranteeType", isGuaranteeType);
+            if (isGuaranteeType) {
+                participateCompetitionType.add("guarantee");
+            }
+            top1Item.getExt().put("participateCompetitionType", StringUtils.join(participateCompetitionType, ","));
+            Double modelCtcvrScore = top1Item.getScoreMap().get("modelCtcvrScore");
+            Double ctcvrScore = top1Item.getScoreMap().get("ctcvrScore");
+            if (scoreParam.getExpCodeSet().contains(checkoutEcpmExp)) {
+                top1Item.getExt().put("ecpm", ctcvrScore * top1Item.getCpa() * 1000);
+                top1Item.getExt().put("filterEcpm", filterEcpm);
+            } else {
+                top1Item.getExt().put("ecpm", modelCtcvrScore * top1Item.getCpa() * 1000);
+            }
             putMetaFeature(top1Item, feature, reqFeature, sceneFeatureMap, request);
             top1Item.getExt().put("model", "dnn");
         }
@@ -827,7 +851,6 @@ public class RankStrategyBy688 extends RankStrategyBasic {
             add("user_vid_share_cate1_14d");
             add("user_vid_share_cate2_14d");
             add("user_has_conver_1y");
-            add("user_conver_ad_class");
             add("user_adverid_view_3d");
             add("user_adverid_click_3d");
             add("user_adverid_conver_3d");
@@ -846,7 +869,8 @@ public class RankStrategyBy688 extends RankStrategyBasic {
             add("user_skuid_view_30d");
             add("user_skuid_click_30d");
             add("user_skuid_conver_30d");
-            add("user_layer_class");
+            add("user_conver_ad_class");
+            add("category_name");
         }};
     }
 

+ 9 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/user/UserService.java

@@ -0,0 +1,9 @@
+package com.tzld.piaoquan.ad.engine.service.user;
+
+import java.util.Map;
+
+public interface UserService {
+    String getUserLayer4Level(String mid);
+
+    Map<String, String> getUserLayer(String mid);
+}

+ 73 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/user/impl/UserServiceImpl.java

@@ -0,0 +1,73 @@
+package com.tzld.piaoquan.ad.engine.service.user.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.tzld.piaoquan.ad.engine.commons.redis.AlgorithmRedisHelper;
+import com.tzld.piaoquan.ad.engine.service.user.UserService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class UserServiceImpl implements UserService {
+
+    @Autowired
+    protected AlgorithmRedisHelper algRedisHelper;
+
+    @Override
+    public String getUserLayer4Level(String mid) {
+
+        Map<String, String> userLayer = this.getUserLayer(mid);
+        if (MapUtils.isEmpty(userLayer)) {
+            return "无曝光";
+        }
+
+        return userLayer.getOrDefault("layer_l4", "无曝光");
+    }
+
+    @Override
+    public Map<String, String> getUserLayer(String mid) {
+
+        Map<String, String> resultMap = new HashMap<>();
+        resultMap.put("layer", "无曝光");
+        resultMap.put("layer_l4", "无曝光");
+
+        if (StringUtils.isEmpty(mid)) {
+            return resultMap;
+        }
+
+        String key = String.format("ad:engine:mid:layer:%s", mid);
+        String value = algRedisHelper.get(key);
+        if (StringUtils.isEmpty(value)) {
+            return resultMap;
+        }
+
+        try {
+            Map<String, String> layerMap = JSON.parseObject(value, new TypeReference<Map<String, String>>() {
+            });
+            String layer3L = layerMap.getOrDefault("layer", "无曝光");
+            if (StringUtils.equals(layer3L, "已转化")) {
+                layer3L = "有转化";
+            }
+
+            Map<String, String> basic4lMap = JSON.parseObject(layerMap.getOrDefault("basic_l4", "{}"), new TypeReference<Map<String, String>>() {
+            });
+
+            String layer4L = basic4lMap.getOrDefault("level", "无曝光");
+
+            resultMap.put("layer", layer3L);
+            resultMap.put("layer_l4", layer4L);
+            return resultMap;
+        } catch (Exception e) {
+            log.error("UserServiceImpl getUserLayer error: ", e);
+        }
+
+        return resultMap;
+    }
+}