Browse Source

Merge branch '20250729_feature_fjy_guarantee'
merge 20250729_feature_fjy_guarantee

yaodaoseng 6 days ago
parent
commit
81c7f448ad

+ 53 - 0
ad-engine-commons/src/main/java/com/tzld/piaoquan/ad/engine/commons/enums/CrowdLayerEnum.java

@@ -0,0 +1,53 @@
+package com.tzld.piaoquan.ad.engine.commons.enums;
+
+/**
+ * 人群分层枚举
+ */
+public enum CrowdLayerEnum {
+
+
+    NO_EXPOSURE(1, "无曝光"),
+    WITH_CONVERSION(2, "有转化"),
+    WITH_EXPOSURE_NO_CONVERSION(3, "有曝光无转化"),
+    ;
+    private Integer code;
+    private String desc;
+
+    CrowdLayerEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public static CrowdLayerEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (CrowdLayerEnum constants : values()) {
+            if (constants.getCode().equals(code)) {
+                return constants;
+            }
+        }
+        return null;
+    }
+
+    public static CrowdLayerEnum getBytDesc( String desc) {
+        if (desc == null) {
+            return null;
+        }
+        for (CrowdLayerEnum constants : values()) {
+            if (constants.getDesc().equals(desc)) {
+                return constants;
+            }
+        }
+        return null;
+    }
+
+}

+ 6 - 0
ad-engine-service/src/main/java/com/tzld/piaoquan/ad/engine/service/entity/GuaranteeView.java

@@ -16,4 +16,10 @@ public class GuaranteeView {
     private Integer guaranteeNum;
 
     private Double guaranteeRate;
+
+    /**
+     * @see com.tzld.piaoquan.ad.engine.commons.enums.CrowdLayerEnum
+     * CrowdLayerEnum.code,保量人群code 码,(1,2.。。)中间用英文逗号隔开
+      */
+    private String guaranteeCrowdCode;
 }

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

@@ -5,14 +5,13 @@ 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.RedisPrefixEnum;
 import com.tzld.piaoquan.ad.engine.commons.param.RankRecommendRequestParam;
-import com.tzld.piaoquan.ad.engine.commons.param.RecommendRequestParam;
 import com.tzld.piaoquan.ad.engine.commons.redis.AdRedisHelper;
 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.NumUtil;
 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;
@@ -99,6 +98,10 @@ public abstract class RankStrategyBasic implements RankStrategy {
     @Value("${guarantee.switching.time:1754236800000}")
     protected Long guaranteeSwitchingTime;
 
+    // 保量人群加权系数配置
+    @Value("${guarantee.crowd.weight.coefficient:1.0}")
+    protected Double guaranteeCrowdWeightCoefficient;
+
     @Autowired
     private FeatureService featureService;
     @Autowired
@@ -330,11 +333,11 @@ public abstract class RankStrategyBasic implements RankStrategy {
         return map;
     }
 
-    protected void setGuaranteeWeight(Map<String, GuaranteeView> map, String adVerId, Map<String, Object> ext, boolean isGuaranteedFlow) {
+    protected void setGuaranteeWeight(Map<String, GuaranteeView> map, String adVerId, Map<String, Object> ext, boolean isGuaranteedFlow, Map<String, String> reqFeature) {
         if (isGuaranteedFlow && MapUtils.isNotEmpty(map)) {
             GuaranteeView guaranteeView = map.get(adVerId);
             if (guaranteeView != null) {
-                double guaranteeWeight = calculateGuaranteedWeight(guaranteeView);
+                double guaranteeWeight = calculateGuaranteeWeightWithCrowd(guaranteeView, reqFeature);
                 boolean isGuaranteed = isGuaranteed(guaranteeView);
                 ext.put("guaranteeView", guaranteeView.toString());
                 ext.put("guaranteeWeight", guaranteeWeight);
@@ -344,30 +347,127 @@ public abstract class RankStrategyBasic implements RankStrategy {
         }
     }
 
+    /**
+     * 兼容旧版本的setGuaranteeWeight方法(不带reqFeature参数)
+     * 为了保持向后兼容性,保留原有方法签名
+     */
+    protected void setGuaranteeWeight(Map<String, GuaranteeView> map, String adVerId, Map<String, Object> ext, boolean isGuaranteedFlow) {
+        setGuaranteeWeight(map, adVerId, ext, isGuaranteedFlow, new HashMap<>());
+    }
+
+    /**
+     * 根据人群信息计算保量权重系数(新版本)
+     *
+     * @param guaranteeView 保量视图对象
+     * @param reqFeature 请求特征信息,包含用户人群layer信息
+     * @return 保量权重系数
+     */
+    protected double calculateGuaranteeWeightWithCrowd(GuaranteeView guaranteeView, Map<String, String> reqFeature) {
+        try {
+            // 空值检查:如果保量视图为空,返回默认权重
+            if (guaranteeView == null) {
+                return 1.0;
+            }
+
+            // 判断是否配置了保量人群代码
+            String guaranteeCrowdCode = guaranteeView.getGuaranteeCrowdCode();
+            if (StringUtils.isEmpty(guaranteeCrowdCode)) {
+                // 保量人群代码无值,走原有逻辑
+                return calculateGuaranteedWeight(guaranteeView);
+            }
+            // 判断是否勾选了全部人群
+            if (isFullCrowd(guaranteeCrowdCode)) {
+                // 勾选了全部人群,走原有逻辑
+                return calculateGuaranteedWeight(guaranteeView);
+            }
+
+            // 保量人群代码有值,进行人群匹配判断
+            String userLayer = reqFeature.getOrDefault("layer", "");
+
+            // 获取用户人群对应的CrowdLayerEnum code
+            Integer userCrowdCode = getUserCrowdCode(userLayer);
+            if (userCrowdCode == null) {
+                log.error("RankStrategyBasic calculateGuaranteeWeightWithCrowd 无法获取用户人群代码,userLayer={}", userLayer);
+                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);
+        }
+    }
+
+    /**
+     * 计算保量权重系数(原有逻辑)
+     *
+     * 保量逻辑说明:
+     * 1. 根据广告主的保量配置(保量比例、保量上限)和实际曝光情况计算权重
+     * 2. 权重用于调整广告排序分数,帮助未达到保量目标的广告主获得更多曝光
+     * 3. 已达到保量上限的广告主权重为0,避免过度曝光
+     *
+     * @param guaranteeView 保量视图对象,包含广告主保量相关数据
+     *                     - adrId: 广告主ID
+     *                     - adrAlgoViewNum: 广告主当天算法曝光数
+     *                     - allAlgoViewNum: 全平台当天算法曝光数
+     *                     - guaranteeNum: 保量上限(广告主最大允许曝光数)
+     *                     - guaranteeRate: 保量比例(广告主期望占全平台曝光的百分比)
+     * @return 保量权重系数,范围[0.0, 2.0],默认1.0
+     */
     protected double calculateGuaranteedWeight(GuaranteeView guaranteeView) {
+        // 1. 空值检查:如果保量视图为空,返回默认权重
         if (guaranteeView == null) {
             return 1.0;
         }
+
         double guaranteeWeight;
+
+        // 2. 保量配置检查:如果保量上限或保量比例未配置,返回默认权重
         if (guaranteeView.getGuaranteeNum() == null || guaranteeView.getGuaranteeNum() == 0
                 || guaranteeView.getGuaranteeRate() == null || guaranteeView.getGuaranteeRate() == 0.0) {
             guaranteeWeight = 1.0;
         } else {
+            // 3. 保量达标检查:如果广告主曝光数已达到或超过保量上限,停止加权
             if (guaranteeView.getAdrAlgoViewNum() != null &&
                     guaranteeView.getGuaranteeNum() <= guaranteeView.getAdrAlgoViewNum()) {
+                // 已达到保量目标,权重设为0,避免过度曝光
                 guaranteeWeight = 0.0;
             } else {
+                // 4. 计算保量权重
+                // 防止除零错误:如果曝光数为空或0,设为1
                 int allViewNum = guaranteeView.getAllAlgoViewNum() == null || guaranteeView.getAllAlgoViewNum() == 0 ?
                         1 : guaranteeView.getAllAlgoViewNum();
                 int adrAlogViewNum = guaranteeView.getAdrAlgoViewNum() == null || guaranteeView.getAdrAlgoViewNum() == 0 ?
                         1 : guaranteeView.getAdrAlgoViewNum();
-                // guaranteeView.getGuaranteeRate() 是百分之几  要乘0.01
+
+                // 核心计算公式:权重 = 保量比例 × 0.01 × 全平台曝光数 / 广告主曝光数
+                // guaranteeView.getGuaranteeRate() 是百分之几,需要乘0.01转换为小数
+                // 逻辑:如果广告主当前曝光占比低于目标保量比例,给予更高权重提升排序
                 guaranteeWeight = guaranteeView.getGuaranteeRate() * 0.01 * allViewNum / adrAlogViewNum;
+
+                // 5. 权重边界限制:避免权重过小或过大影响系统稳定性
                 if (guaranteeWeight < 0.5) {
-                    guaranteeWeight = 0.5;
+                    guaranteeWeight = 0.5;  // 最小权重限制
                 }
                 if (guaranteeWeight > 2) {
-                    guaranteeWeight = 2;
+                    guaranteeWeight = 2;    // 最大权重限制
                 }
             }
         }
@@ -749,4 +849,90 @@ public abstract class RankStrategyBasic implements RankStrategy {
     private boolean isValidViews(Integer views, int threshold) {
         return views != null && views >= threshold;
     }
+
+    /**
+     * 根据用户人群layer获取对应的CrowdLayerEnum code
+     *
+     * @param userLayer 用户人群layer,如"无曝光"、"有转化"、"有曝光无转化"
+     * @return 对应的CrowdLayerEnum code,如果无法匹配则返回null
+     */
+    private Integer getUserCrowdCode(String userLayer) {
+        if (StringUtils.isEmpty(userLayer)) {
+            return null;
+        }
+
+        // 处理可能的"-炸"后缀
+        String cleanLayer = userLayer.replace("-炸", "");
+
+        // 使用CrowdLayerEnum提供的方法根据描述获取枚举对象
+        CrowdLayerEnum crowdLayerEnum = CrowdLayerEnum.getBytDesc(cleanLayer);
+        if (crowdLayerEnum != null) {
+            return crowdLayerEnum.getCode();
+        }
+
+        log.warn("无法匹配用户人群layer到CrowdLayerEnum: {}", userLayer);
+        return null;
+    }
+
+    /**
+     * 判断用户人群是否在保量人群范围内
+     *
+     * @param guaranteeCrowdCode 保量人群代码字符串,格式如"1,2,3"
+     * @param userCrowdCode 用户人群代码
+     * @return true表示用户在保量人群范围内
+     */
+    private boolean isUserInGuaranteeCrowd(String guaranteeCrowdCode, Integer userCrowdCode) {
+        if (StringUtils.isEmpty(guaranteeCrowdCode) || userCrowdCode == null) {
+            return false;
+        }
+
+        try {
+            // 将保量人群代码字符串按逗号分割
+            String[] crowdCodes = guaranteeCrowdCode.split(",");
+            for (String code : crowdCodes) {
+                if (String.valueOf(userCrowdCode).equals(code.trim())) {
+                    return true;
+                }
+            }
+        } catch (Exception e) {
+            log.error("解析保量人群代码失败: guaranteeCrowdCode={}", guaranteeCrowdCode, e);
+        }
+
+        return false;
+    }
+
+    /**
+     * 判断是否全部保量人群
+     * @param guaranteeCrowdCode 保量人群代码字符串
+     * @return true表示是全部保量人群
+     */
+    private boolean isFullCrowd(String guaranteeCrowdCode) {
+        if (StringUtils.isEmpty(guaranteeCrowdCode)) {
+            return false;
+        }
+
+        try {
+            // 获取CrowdLayerEnum中的所有code值
+            Set<String> allCrowdCodes = new HashSet<>();
+            for (CrowdLayerEnum crowdLayer : CrowdLayerEnum.values()) {
+                allCrowdCodes.add(String.valueOf(crowdLayer.getCode()));
+            }
+
+            // 解析保量人群代码
+            Set<String> guaranteeCodes = new HashSet<>();
+            String[] codes = guaranteeCrowdCode.split(",");
+            for (String code : codes) {
+                guaranteeCodes.add(code.trim());
+            }
+
+            // 判断保量人群代码是否为全量值(包含所有CrowdLayerEnum的code)
+            boolean isFullCrowd = guaranteeCodes.containsAll(allCrowdCodes);
+            return isFullCrowd;
+
+        } catch (Exception e) {
+            log.error("判断保量人群加权失败: guaranteeCrowdCode={}",
+                     guaranteeCrowdCode, e);
+            return false;
+        }
+    }
 }

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

@@ -128,7 +128,7 @@ public class RankStrategyBy679 extends RankStrategyBasic {
                     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());
-                    setGuaranteeWeight(map, dto.getAdVerId(), adRankItem.getExt(), isGuaranteedFlow);
+                    setGuaranteeWeight(map, dto.getAdVerId(), adRankItem.getExt(), isGuaranteedFlow, reqFeature);
                     String cidStr = dto.getCreativeId().toString();
                     Map<String, String> cidFeatureMap = adRankItem.getFeatureMap();
                     Map<String, Map<String, String>> cidFeature = allCidFeature.getOrDefault(cidStr, new HashMap<>());

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

@@ -140,7 +140,7 @@ public class RankStrategyBy680 extends RankStrategyBasic {
                     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());
-                    setGuaranteeWeight(map, dto.getAdVerId(), adRankItem.getExt(), isGuaranteedFlow);
+                    setGuaranteeWeight(map, dto.getAdVerId(), adRankItem.getExt(), isGuaranteedFlow, reqFeature);
                     String cidStr = dto.getCreativeId().toString();
                     Map<String, String> cidFeatureMap = adRankItem.getFeatureMap();
                     Map<String, Map<String, String>> cidFeature = allCidFeature.getOrDefault(cidStr, new HashMap<>());

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

@@ -137,7 +137,7 @@ public class RankStrategyBy683 extends RankStrategyBasic {
                     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());
-                    setGuaranteeWeight(map, dto.getAdVerId(), adRankItem.getExt(), isGuaranteedFlow);
+                    setGuaranteeWeight(map, dto.getAdVerId(), adRankItem.getExt(), isGuaranteedFlow, reqFeature);
                     String cidStr = dto.getCreativeId().toString();
                     Map<String, String> cidFeatureMap = adRankItem.getFeatureMap();
                     Map<String, Map<String, String>> cidFeature = allCidFeature.getOrDefault(cidStr, new HashMap<>());

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

@@ -180,7 +180,7 @@ public class RankStrategyBy688 extends RankStrategyBasic {
                     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());
-                    setGuaranteeWeight(map, dto.getAdVerId(), adRankItem.getExt(), isGuaranteedFlow);
+                    setGuaranteeWeight(map, dto.getAdVerId(), adRankItem.getExt(), isGuaranteedFlow, reqFeature);
                     String cidStr = dto.getCreativeId().toString();
                     Map<String, String> cidFeatureMap = adRankItem.getFeatureMap();
                     Map<String, Map<String, String>> cidFeature = allCidFeature.getOrDefault(cidStr, new HashMap<>());