Browse Source

feat(recommend): 推荐增强(读生效批次过滤 + 命中带价值 + 补查 POP,VO 扩展)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jintao.geng 1 month ago
parent
commit
d107419886

+ 28 - 0
mango-common/src/main/java/com/mangoo/rating/recommend/response/recommend/CardPopVO.java

@@ -0,0 +1,28 @@
+package com.mangoo.rating.recommend.response.recommend;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 卡片 POP 明细 VO(单机构)
+ *
+ * @author gengjintao
+ * @date 2026/06/25
+ */
+@Data
+@ApiModel("卡片POP明细")
+public class CardPopVO {
+    @ApiModelProperty(value = "评级机构")
+    private String agency;
+    @ApiModelProperty(value = "总评级数")
+    private Integer all;
+    @ApiModelProperty(value = "10分数量")
+    private Integer g10;
+    @ApiModelProperty(value = "9分数量")
+    private Integer g9;
+    @ApiModelProperty(value = "8分数量")
+    private Integer g8;
+    @ApiModelProperty(value = "7分数量")
+    private Integer g7;
+}

+ 21 - 0
mango-common/src/main/java/com/mangoo/rating/recommend/response/recommend/RecommendCardVO.java

@@ -45,4 +45,25 @@ public class RecommendCardVO {
 
     @ApiModelProperty(value = "反面图URL(透传)")
     private String backImageUrl;
+
+    @ApiModelProperty(value = "数仓卡片唯一ID")
+    private String cardId;
+
+    @ApiModelProperty(value = "卡号")
+    private String cardNo;
+
+    @ApiModelProperty(value = "当前价")
+    private java.math.BigDecimal currentValue;
+
+    @ApiModelProperty(value = "涨跌幅(百分数)")
+    private java.math.BigDecimal valueChangePct;
+
+    @ApiModelProperty(value = "价格区间下限")
+    private java.math.BigDecimal valueMin;
+
+    @ApiModelProperty(value = "价格区间上限")
+    private java.math.BigDecimal valueMax;
+
+    @ApiModelProperty(value = "POP明细(三机构)")
+    private java.util.List<CardPopVO> pops;
 }

+ 55 - 25
mango-domain/src/main/java/com/mangoo/rating/recommend/service/impl/RecommendServiceImpl.java

@@ -4,10 +4,14 @@ import com.mangoo.rating.recommend.AjaxResult;
 import com.mangoo.rating.recommend.config.RecommendProperties;
 import com.mangoo.rating.recommend.enums.EvaluateEfficiencyEnum;
 import com.mangoo.rating.recommend.enums.MatchLevelEnum;
+import com.mangoo.rating.recommend.manager.ActiveBatchManager;
+import com.mangoo.rating.recommend.manager.CardPopManager;
 import com.mangoo.rating.recommend.manager.RatingRecommendRuleManager;
+import com.mangoo.rating.recommend.po.CardPopPO;
 import com.mangoo.rating.recommend.po.RatingRecommendRulePO;
 import com.mangoo.rating.recommend.request.recommend.RecommendCardDTO;
 import com.mangoo.rating.recommend.request.recommend.RecommendRequest;
+import com.mangoo.rating.recommend.response.recommend.CardPopVO;
 import com.mangoo.rating.recommend.response.recommend.OrderSuggestionVO;
 import com.mangoo.rating.recommend.response.recommend.RecommendCardVO;
 import com.mangoo.rating.recommend.response.recommend.RecommendResultVO;
@@ -27,7 +31,8 @@ import java.util.Map;
  * 评级时效推荐服务实现
  *
  * 流程:
- *   1) 逐卡按"全等→L2→L3→兜底"降级匹配,得到 (recommendEfficiency, matchLevel)
+ *   0) 读当前生效批次号(无则后续查询走兜底)
+ *   1) 逐卡按"全等→L2→L3→兜底"降级匹配(带 batchNo 过滤),命中带出价值并补查 POP
  *   2) 用纯类 OrderGrouper 按时效精确分组
  *   3) 加载时效价格(取不到则全部降级"待计算")
  *   4) 回填单价/总计/合计,组装响应
@@ -48,9 +53,14 @@ public class RecommendServiceImpl implements RecommendService {
     @Resource
     private RecommendProperties recommendProperties;
 
+    @Resource
+    private ActiveBatchManager activeBatchManager;
+
+    @Resource
+    private CardPopManager cardPopManager;
+
     @Override
     public AjaxResult recommendEfficiency(RecommendRequest request) {
-        // 入参校验
         if (request == null || request.getCards() == null || request.getCards().isEmpty()) {
             return AjaxResult.error("卡列表不能为空");
         }
@@ -58,16 +68,19 @@ public class RecommendServiceImpl implements RecommendService {
             return AjaxResult.error("单次卡数量超过上限:" + recommendProperties.getBatchMax());
         }
 
-        // 1) 逐卡推荐 → 转 VO
+        // 0) 当前生效批次号(为空说明尚未同步过规则数据 → 全部走兜底)
+        String activeBatchNo = activeBatchManager.selectActiveBatchNo();
+
+        // 1) 逐卡推荐
         List<RecommendCardVO> cardVOs = new ArrayList<>(request.getCards().size());
         for (RecommendCardDTO dto : request.getCards()) {
-            cardVOs.add(recommendOneCard(dto));
+            cardVOs.add(recommendOneCard(dto, activeBatchNo));
         }
 
         // 2) 按时效精确分组
         List<OrderSuggestionVO> orders = OrderGrouper.group(cardVOs);
 
-        // 3) 报价(取不到则全部 null=待计算,不阻断)
+        // 3) 报价
         Map<Integer, BigDecimal> priceMap = efficiencyPriceProvider.loadEfficiencyPrices();
         BigDecimal grandTotal = applyPricing(orders, priceMap);
 
@@ -81,9 +94,9 @@ public class RecommendServiceImpl implements RecommendService {
     }
 
     /**
-     * 单卡降级匹配:EXACT → L2 → L3 → DEFAULT
+     * 单卡降级匹配:EXACT → L2 → L3 → DEFAULT;命中后带出价值并补查 POP
      */
-    private RecommendCardVO recommendOneCard(RecommendCardDTO dto) {
+    private RecommendCardVO recommendOneCard(RecommendCardDTO dto, String activeBatchNo) {
         RecommendCardVO vo = new RecommendCardVO();
         vo.setClientCardId(dto.getClientCardId());
         vo.setPlayer(dto.getPlayer());
@@ -93,26 +106,20 @@ public class RecommendServiceImpl implements RecommendService {
         vo.setFrontImageUrl(dto.getFrontImageUrl());
         vo.setBackImageUrl(dto.getBackImageUrl());
 
-        // L1:四字段全等命中
         RatingRecommendRulePO rule = ratingRecommendRuleManager.selectByExact(
-                dto.getPlayer(), dto.getYear(), dto.getSeries(), dto.getCardSet());
+                dto.getPlayer(), dto.getYear(), dto.getSeries(), dto.getCardSet(), activeBatchNo);
         if (isLegalRule(rule)) {
-            vo.setRecommendEfficiency(rule.getRecommendEfficiency());
-            vo.setMatchLevel(MatchLevelEnum.EXACT.getCode());
+            fillFromRule(vo, rule, MatchLevelEnum.EXACT, activeBatchNo);
             return vo;
         }
-        // L2:series + cardSet + year
-        rule = ratingRecommendRuleManager.selectBySeriesSetYear(dto.getSeries(), dto.getCardSet(), dto.getYear());
+        rule = ratingRecommendRuleManager.selectBySeriesSetYear(dto.getSeries(), dto.getCardSet(), dto.getYear(), activeBatchNo);
         if (isLegalRule(rule)) {
-            vo.setRecommendEfficiency(rule.getRecommendEfficiency());
-            vo.setMatchLevel(MatchLevelEnum.L2.getCode());
+            fillFromRule(vo, rule, MatchLevelEnum.L2, activeBatchNo);
             return vo;
         }
-        // L3:series + cardSet
-        rule = ratingRecommendRuleManager.selectBySeriesSet(dto.getSeries(), dto.getCardSet());
+        rule = ratingRecommendRuleManager.selectBySeriesSet(dto.getSeries(), dto.getCardSet(), activeBatchNo);
         if (isLegalRule(rule)) {
-            vo.setRecommendEfficiency(rule.getRecommendEfficiency());
-            vo.setMatchLevel(MatchLevelEnum.L3.getCode());
+            fillFromRule(vo, rule, MatchLevelEnum.L3, activeBatchNo);
             return vo;
         }
         // 兜底
@@ -122,8 +129,35 @@ public class RecommendServiceImpl implements RecommendService {
     }
 
     /**
-     * 校验规则及其时效值是否合法(非空 + 时效在 EvaluateEfficiencyEnum 范围内)
+     * 命中规则后填充:时效、命中层级、卡片ID/卡号/价值,并补查 POP 明细
      */
+    private void fillFromRule(RecommendCardVO vo, RatingRecommendRulePO rule, MatchLevelEnum level, String activeBatchNo) {
+        vo.setRecommendEfficiency(rule.getRecommendEfficiency());
+        vo.setMatchLevel(level.getCode());
+        vo.setCardId(rule.getCardId());
+        vo.setCardNo(rule.getCardNo());
+        vo.setCurrentValue(rule.getCurrentValue());
+        vo.setValueChangePct(rule.getValueChangePct());
+        vo.setValueMin(rule.getValueMin());
+        vo.setValueMax(rule.getValueMax());
+        // 补查 POP(三机构)
+        if (rule.getCardId() != null && activeBatchNo != null) {
+            List<CardPopPO> pops = cardPopManager.selectByCardId(rule.getCardId(), activeBatchNo);
+            List<CardPopVO> popVOs = new ArrayList<>();
+            for (CardPopPO p : pops) {
+                CardPopVO pv = new CardPopVO();
+                pv.setAgency(p.getAgency());
+                pv.setAll(p.getGradeAll());
+                pv.setG10(p.getGrade10());
+                pv.setG9(p.getGrade9());
+                pv.setG8(p.getGrade8());
+                pv.setG7(p.getGrade7());
+                popVOs.add(pv);
+            }
+            vo.setPops(popVOs);
+        }
+    }
+
     private boolean isLegalRule(RatingRecommendRulePO rule) {
         return rule != null
                 && rule.getRecommendEfficiency() != null
@@ -131,8 +165,7 @@ public class RecommendServiceImpl implements RecommendService {
     }
 
     /**
-     * 给订单列表回填单价/总计,并返回所有订单合计。
-     * 价格 Map 为空或某档无价 → 该订单 unitPrice/totalAmount = null("待计算"),并将 grandTotal 置 null。
+     * 回填单价/总计并返回合计;某档无价 → 该订单 null("待计算"),合计置 null
      */
     private BigDecimal applyPricing(List<OrderSuggestionVO> orders, Map<Integer, BigDecimal> priceMap) {
         if (orders == null || orders.isEmpty()) {
@@ -140,15 +173,12 @@ public class RecommendServiceImpl implements RecommendService {
         }
         BigDecimal grand = BigDecimal.ZERO;
         boolean grandNull = false;
-
         for (OrderSuggestionVO order : orders) {
             BigDecimal unit = (priceMap == null) ? null : priceMap.get(order.getEfficiency());
             if (unit == null) {
-                // 该档价格缺失 → 订单 unitPrice/totalAmount = null,整体合计同步置 null
                 order.setUnitPrice(null);
                 order.setTotalAmount(null);
                 grandNull = true;
-                // 卡级单价同步 null
                 if (order.getCards() != null) {
                     for (RecommendCardVO c : order.getCards()) {
                         c.setUnitPrice(null);