|
@@ -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;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|