|
|
|
|
@ -18,15 +18,15 @@ import java.util.Map;
|
|
|
|
|
@Service
|
|
|
|
|
public class ProductDiagnosisIterationStepOneServiceImpl implements ProductDiagnosisIterationStepOneService {
|
|
|
|
|
private static final List<String> LINK_NODES = Arrays.asList("曝光-点击", "点击-加购", "加购-下单", "下单-好评", "好评-复购");
|
|
|
|
|
private static final List<String> CHANNELS = Arrays.asList("天猫", "京东", "抖音", "拼多多");
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public ProductDiagnosisIterationStepOneValidationResult validate(ProductDiagnosisIterationStepOneValidationRequest request, JwtUser user) {
|
|
|
|
|
requireStudent(user);
|
|
|
|
|
if (request == null) throw badRequest("请完成全链路转化数据填写");
|
|
|
|
|
Map<String, ProductDiagnosisIterationStepOneValidationRequest.ConversionRate> rates = validateConversionRates(request.getConversionRates());
|
|
|
|
|
Map<String, String> channels = validateChannels(request.getChannels());
|
|
|
|
|
Map<String, ProductDiagnosisIterationStepOneValidationRequest.ConversionRate> rates = validateConversionRates(request.getConversionRates(), channels);
|
|
|
|
|
if (StringUtils.isBlank(request.getFunnelChartUrl())) throw badRequest("请上传节点转化漏斗对比图");
|
|
|
|
|
validateAnomalyRows(request.getAnomalyRows(), rates);
|
|
|
|
|
validateAnomalyRows(request.getAnomalyRows(), rates, channels);
|
|
|
|
|
|
|
|
|
|
ProductDiagnosisIterationStepOneValidationResult result = new ProductDiagnosisIterationStepOneValidationResult();
|
|
|
|
|
result.setValid(true);
|
|
|
|
|
@ -34,17 +34,36 @@ public class ProductDiagnosisIterationStepOneServiceImpl implements ProductDiagn
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, String> validateChannels(List<ProductDiagnosisIterationStepOneValidationRequest.Channel> rows) {
|
|
|
|
|
Map<String, String> channels = new LinkedHashMap<>();
|
|
|
|
|
if (rows == null) {
|
|
|
|
|
channels.put("tmall", "天猫");
|
|
|
|
|
channels.put("jd", "京东");
|
|
|
|
|
channels.put("douyin", "抖音");
|
|
|
|
|
channels.put("pinduoduo", "拼多多");
|
|
|
|
|
return channels;
|
|
|
|
|
}
|
|
|
|
|
if (rows.isEmpty()) throw badRequest("请至少添加 1 个渠道");
|
|
|
|
|
for (ProductDiagnosisIterationStepOneValidationRequest.Channel row : rows) {
|
|
|
|
|
String id = row == null ? "" : StringUtils.trimToEmpty(row.getId());
|
|
|
|
|
String name = row == null ? "" : StringUtils.trimToEmpty(row.getName());
|
|
|
|
|
if (StringUtils.isBlank(id) || StringUtils.isBlank(name)) throw badRequest("渠道名称不能为空");
|
|
|
|
|
if (channels.containsKey(id) || channels.containsValue(name)) throw badRequest("渠道名称不能重复");
|
|
|
|
|
channels.put(id, name);
|
|
|
|
|
}
|
|
|
|
|
return channels;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Map<String, ProductDiagnosisIterationStepOneValidationRequest.ConversionRate> validateConversionRates(
|
|
|
|
|
List<ProductDiagnosisIterationStepOneValidationRequest.ConversionRate> rows) {
|
|
|
|
|
List<ProductDiagnosisIterationStepOneValidationRequest.ConversionRate> rows, Map<String, String> channels) {
|
|
|
|
|
if (rows == null || rows.size() != LINK_NODES.size()) throw badRequest("请完整填写 5 个链路节点的转化率");
|
|
|
|
|
Map<String, ProductDiagnosisIterationStepOneValidationRequest.ConversionRate> byNode = new LinkedHashMap<>();
|
|
|
|
|
for (ProductDiagnosisIterationStepOneValidationRequest.ConversionRate row : rows) {
|
|
|
|
|
String node = row == null ? "" : StringUtils.trimToEmpty(row.getLinkNode());
|
|
|
|
|
if (!LINK_NODES.contains(node) || byNode.put(node, row) != null) throw badRequest("链路节点必须与题目中的 5 个节点一致且不能重复");
|
|
|
|
|
validateRate(row.getTmall(), node + "的天猫转化率");
|
|
|
|
|
validateRate(row.getJd(), node + "的京东转化率");
|
|
|
|
|
validateRate(row.getDouyin(), node + "的抖音转化率");
|
|
|
|
|
validateRate(row.getPinduoduo(), node + "的拼多多转化率");
|
|
|
|
|
for (Map.Entry<String, String> channel : channels.entrySet()) {
|
|
|
|
|
validateRate(rateForChannel(row, channel.getKey()), node + "的" + channel.getValue() + "转化率");
|
|
|
|
|
}
|
|
|
|
|
validateRate(row.getIndustryAverage(), node + "的行业均值转化率");
|
|
|
|
|
}
|
|
|
|
|
if (byNode.size() != LINK_NODES.size()) throw badRequest("请完整填写 5 个链路节点的转化率");
|
|
|
|
|
@ -52,18 +71,20 @@ public class ProductDiagnosisIterationStepOneServiceImpl implements ProductDiagn
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void validateAnomalyRows(List<ProductDiagnosisIterationStepOneValidationRequest.AnomalyRow> rows,
|
|
|
|
|
Map<String, ProductDiagnosisIterationStepOneValidationRequest.ConversionRate> rates) {
|
|
|
|
|
Map<String, ProductDiagnosisIterationStepOneValidationRequest.ConversionRate> rates,
|
|
|
|
|
Map<String, String> channels) {
|
|
|
|
|
if (rows == null || rows.isEmpty() || rows.size() > 5) throw badRequest("异常节点分析最少填写 1 行,最多填写 5 行");
|
|
|
|
|
for (int index = 0; index < rows.size(); index++) {
|
|
|
|
|
ProductDiagnosisIterationStepOneValidationRequest.AnomalyRow row = rows.get(index);
|
|
|
|
|
if (row == null) throw badRequest("请完整填写第 " + (index + 1) + " 行异常节点分析");
|
|
|
|
|
String channel = row == null ? "" : StringUtils.trimToEmpty(row.getChannel());
|
|
|
|
|
String channelId = StringUtils.trimToEmpty(row.getChannelId());
|
|
|
|
|
if (StringUtils.isBlank(channelId)) channelId = findChannelId(channels, row.getChannel());
|
|
|
|
|
String node = row == null ? "" : StringUtils.trimToEmpty(row.getLinkNode());
|
|
|
|
|
if (!CHANNELS.contains(channel)) throw badRequest("第 " + (index + 1) + " 行渠道必须来源于第一个表格");
|
|
|
|
|
if (!channels.containsKey(channelId)) throw badRequest("第 " + (index + 1) + " 行渠道必须来源于第一个表格");
|
|
|
|
|
ProductDiagnosisIterationStepOneValidationRequest.ConversionRate source = rates.get(node);
|
|
|
|
|
if (source == null) throw badRequest("第 " + (index + 1) + " 行链路节点必须来源于第一个表格");
|
|
|
|
|
if (row.getStoreConversionRate() == null || row.getIndustryAverageConversionRate() == null
|
|
|
|
|
|| row.getStoreConversionRate().compareTo(rateForChannel(source, channel)) != 0
|
|
|
|
|
|| row.getStoreConversionRate().compareTo(rateForChannel(source, channelId)) != 0
|
|
|
|
|
|| row.getIndustryAverageConversionRate().compareTo(source.getIndustryAverage()) != 0) {
|
|
|
|
|
throw badRequest("第 " + (index + 1) + " 行的店铺和行业转化率必须与第一个表格对应数据一致");
|
|
|
|
|
}
|
|
|
|
|
@ -71,11 +92,21 @@ public class ProductDiagnosisIterationStepOneServiceImpl implements ProductDiagn
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private BigDecimal rateForChannel(ProductDiagnosisIterationStepOneValidationRequest.ConversionRate rate, String channel) {
|
|
|
|
|
if ("天猫".equals(channel)) return rate.getTmall();
|
|
|
|
|
if ("京东".equals(channel)) return rate.getJd();
|
|
|
|
|
if ("抖音".equals(channel)) return rate.getDouyin();
|
|
|
|
|
return rate.getPinduoduo();
|
|
|
|
|
private String findChannelId(Map<String, String> channels, String channelName) {
|
|
|
|
|
String name = StringUtils.trimToEmpty(channelName);
|
|
|
|
|
for (Map.Entry<String, String> channel : channels.entrySet()) {
|
|
|
|
|
if (channel.getValue().equals(name)) return channel.getKey();
|
|
|
|
|
}
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private BigDecimal rateForChannel(ProductDiagnosisIterationStepOneValidationRequest.ConversionRate rate, String channelId) {
|
|
|
|
|
if (rate.getChannelRates() != null && rate.getChannelRates().containsKey(channelId)) return rate.getChannelRates().get(channelId);
|
|
|
|
|
if ("tmall".equals(channelId)) return rate.getTmall();
|
|
|
|
|
if ("jd".equals(channelId)) return rate.getJd();
|
|
|
|
|
if ("douyin".equals(channelId)) return rate.getDouyin();
|
|
|
|
|
if ("pinduoduo".equals(channelId)) return rate.getPinduoduo();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void validateRate(BigDecimal rate, String label) {
|
|
|
|
|
|