|
|
@@ -0,0 +1,190 @@
|
|
|
+package com.storlead.crm.customer.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.storlead.crm.customer.entity.CustomerAnalysisResultEntity;
|
|
|
+import com.storlead.crm.customer.entity.CustomerCompanyEntity;
|
|
|
+import com.storlead.crm.customer.entity.CustomerEntity;
|
|
|
+import com.storlead.crm.customer.entity.LiaisonEntity;
|
|
|
+import com.storlead.crm.customer.service.*;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class CustomerAiAnalysisServiceImpl implements CustomerAiAnalysisService {
|
|
|
+
|
|
|
+ private static final String AI_ANALYSIS_URL = "http://127.0.0.1:18080/mock/ai/customer/analyze";
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private CustomerEntityService customerEntityService;
|
|
|
+ @Resource
|
|
|
+ private CustomerCompanyEntityService customerCompanyEntityService;
|
|
|
+ @Resource
|
|
|
+ private LiaisonEntityService liaisonEntityService;
|
|
|
+ @Resource
|
|
|
+ private CustomerAnalysisResultEntityService customerAnalysisResultEntityService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CustomerAnalysisResultEntity analyzeSingleCustomer(Long customerId, String analysisScene) {
|
|
|
+ if (customerId == null) {
|
|
|
+ throw new IllegalArgumentException("customerId不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ CustomerEntity customer = customerEntityService.getOne(new LambdaQueryWrapper<CustomerEntity>()
|
|
|
+ .eq(CustomerEntity::getId, customerId)
|
|
|
+ .eq(CustomerEntity::getIsDelete, 0)
|
|
|
+ .last("limit 1"));
|
|
|
+ if (customer == null) {
|
|
|
+ throw new IllegalArgumentException("客户不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ CustomerCompanyEntity company = customerCompanyEntityService.getOne(new LambdaQueryWrapper<CustomerCompanyEntity>()
|
|
|
+ .eq(CustomerCompanyEntity::getCustomerId, customerId)
|
|
|
+ .eq(CustomerCompanyEntity::getIsDelete, 0)
|
|
|
+ .orderByDesc(CustomerCompanyEntity::getId)
|
|
|
+ .last("limit 1"));
|
|
|
+
|
|
|
+ List<LiaisonEntity> liaisons = liaisonEntityService.list(new LambdaQueryWrapper<LiaisonEntity>()
|
|
|
+ .eq(LiaisonEntity::getCustomerId, customerId)
|
|
|
+ .eq(LiaisonEntity::getIsDelete, 0)
|
|
|
+ .orderByDesc(LiaisonEntity::getHasPrimaryLialison)
|
|
|
+ .orderByDesc(LiaisonEntity::getLastFollowUpTime)
|
|
|
+ .last("limit 5"));
|
|
|
+
|
|
|
+ String scene = StringUtils.defaultIfBlank(analysisScene, "potential");
|
|
|
+ Map<String, Object> requestPayload = buildAiRequest(customer, company, liaisons, scene);
|
|
|
+
|
|
|
+ CustomerAnalysisResultEntity entity = new CustomerAnalysisResultEntity();
|
|
|
+ entity.setCustomerId(customer.getId());
|
|
|
+ entity.setTenantId(customer.getTenantId());
|
|
|
+ entity.setOwnerBy(customer.getOwnerBy());
|
|
|
+ entity.setAnalysisBatchNo(UUID.randomUUID().toString().replace("-", ""));
|
|
|
+ entity.setAnalysisScene(scene);
|
|
|
+ entity.setAnalysisTime(new Date());
|
|
|
+
|
|
|
+ try {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ Map<String, Object> aiResult = new RestTemplate().postForObject(AI_ANALYSIS_URL, requestPayload, Map.class);
|
|
|
+ if (aiResult == null || aiResult.isEmpty()) {
|
|
|
+ aiResult = buildFallbackResult(customer, company, liaisons);
|
|
|
+ }
|
|
|
+ applyAiResult(entity, aiResult);
|
|
|
+ entity.setAnalysisStatus(1);
|
|
|
+ } catch (Exception e) {
|
|
|
+ Map<String, Object> fallback = buildFallbackResult(customer, company, liaisons);
|
|
|
+ applyAiResult(entity, fallback);
|
|
|
+ entity.setAnalysisStatus(0);
|
|
|
+ entity.setErrorCode("AI_CALL_FAILED");
|
|
|
+ entity.setErrorMessage(e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ customerAnalysisResultEntityService.save(entity);
|
|
|
+ return entity;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildAiRequest(CustomerEntity customer,
|
|
|
+ CustomerCompanyEntity company,
|
|
|
+ List<LiaisonEntity> liaisons,
|
|
|
+ String scene) {
|
|
|
+ Map<String, Object> payload = new HashMap<>();
|
|
|
+ payload.put("analysisScene", scene);
|
|
|
+ payload.put("customer", customer);
|
|
|
+ payload.put("company", company);
|
|
|
+ payload.put("liaisons", liaisons);
|
|
|
+ return payload;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void applyAiResult(CustomerAnalysisResultEntity entity, Map<String, Object> aiResult) {
|
|
|
+ entity.setPotentialScore(toDouble(aiResult.get("potentialScore"), 50D));
|
|
|
+ entity.setCustomerLevel(String.valueOf(aiResult.getOrDefault("customerLevel", "C")));
|
|
|
+ entity.setConfidence(toDouble(aiResult.get("confidence"), 70D));
|
|
|
+ entity.setPriority(toInteger(aiResult.get("priority"), 3));
|
|
|
+ entity.setCoreTags(String.valueOf(aiResult.getOrDefault("coreTags", "[]")));
|
|
|
+ entity.setReasonSummary(String.valueOf(aiResult.getOrDefault("reasonSummary", "基于客户基础信息自动分析")));
|
|
|
+ entity.setSuggestedActions(String.valueOf(aiResult.getOrDefault("suggestedActions", "[]")));
|
|
|
+ entity.setRiskSignals(String.valueOf(aiResult.getOrDefault("riskSignals", "[]")));
|
|
|
+ entity.setFeatureSnapshot(String.valueOf(aiResult.getOrDefault("featureSnapshot", "{}")));
|
|
|
+ entity.setModelName(String.valueOf(aiResult.getOrDefault("modelName", "mock-ai")));
|
|
|
+ entity.setModelVersion(String.valueOf(aiResult.getOrDefault("modelVersion", "v0")));
|
|
|
+ entity.setPromptVersion(String.valueOf(aiResult.getOrDefault("promptVersion", "p0")));
|
|
|
+ entity.setErrorCode(null);
|
|
|
+ entity.setErrorMessage(null);
|
|
|
+ entity.setNextFollowUpAt(calcNextFollowUpDate());
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildFallbackResult(CustomerEntity customer,
|
|
|
+ CustomerCompanyEntity company,
|
|
|
+ List<LiaisonEntity> liaisons) {
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ int score = 40;
|
|
|
+ if (customer.getHasOrderForm() != null && customer.getHasOrderForm() == 1) {
|
|
|
+ score += 20;
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(customer.getIntendedProductsIds())) {
|
|
|
+ score += 15;
|
|
|
+ }
|
|
|
+ if (company != null && StringUtils.isNotBlank(company.getIndustry())) {
|
|
|
+ score += 10;
|
|
|
+ }
|
|
|
+ if (liaisons != null && !liaisons.isEmpty()) {
|
|
|
+ score += 10;
|
|
|
+ }
|
|
|
+ score = Math.min(score, 95);
|
|
|
+
|
|
|
+ String level;
|
|
|
+ if (score >= 80) {
|
|
|
+ level = "A";
|
|
|
+ } else if (score >= 65) {
|
|
|
+ level = "B";
|
|
|
+ } else if (score >= 50) {
|
|
|
+ level = "C";
|
|
|
+ } else {
|
|
|
+ level = "D";
|
|
|
+ }
|
|
|
+
|
|
|
+ result.put("potentialScore", score);
|
|
|
+ result.put("customerLevel", level);
|
|
|
+ result.put("confidence", 65);
|
|
|
+ result.put("priority", "A".equals(level) ? 1 : ("B".equals(level) ? 2 : 3));
|
|
|
+ result.put("coreTags", "[\"fallback-rule\",\"single-customer\"]");
|
|
|
+ result.put("reasonSummary", "AI接口不可用,已使用本地规则完成潜力分析");
|
|
|
+ result.put("suggestedActions", "[\"建议3日内首次触达\",\"建议补全关键联系人信息\"]");
|
|
|
+ result.put("riskSignals", score < 60 ? "[\"客户潜力偏低\"]" : "[]");
|
|
|
+ result.put("featureSnapshot", "{\"customerId\":" + customer.getId() + "}");
|
|
|
+ result.put("modelName", "fallback-rule-engine");
|
|
|
+ result.put("modelVersion", "rule-v1");
|
|
|
+ result.put("promptVersion", "local");
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Date calcNextFollowUpDate() {
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.add(Calendar.DAY_OF_MONTH, 3);
|
|
|
+ return calendar.getTime();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Double toDouble(Object val, Double defaultValue) {
|
|
|
+ if (val == null) {
|
|
|
+ return defaultValue;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return Double.parseDouble(String.valueOf(val));
|
|
|
+ } catch (Exception ignored) {
|
|
|
+ return defaultValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer toInteger(Object val, Integer defaultValue) {
|
|
|
+ if (val == null) {
|
|
|
+ return defaultValue;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return Integer.parseInt(String.valueOf(val));
|
|
|
+ } catch (Exception ignored) {
|
|
|
+ return defaultValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|