Explorar el Código

添加企业等级检查和企业健康检查v2

ximinghao hace 1 mes
padre
commit
c98833c3b5

+ 6 - 0
.idea/JPASupport-project.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DatabaseSettingsState">
+    <option name="language" value="zh-CN" />
+  </component>
+</project>

+ 6 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="SameParameterValue" enabled="false" level="WARNING" enabled_by_default="false" />
+  </profile>
+</component>

+ 0 - 7
application.yml

@@ -1,7 +0,0 @@
-#
-app:
-  dms:
-    column:
-      data: '{"INDUSTRIAL_PARK":{"id":"1580","modelId":"1525"},"ENTERPRISE":{"id":"1593","modelId":"1537"},"ENTERPRISE_ECONOMIC":{"id":"1594","modelId":"1538"},"ORDER":{"id":"1587","modelId":"1531"},"LEASE_DETAIL":{"id":"1574","modelId":"1520"},"INVESTMENT_TARGET":{"id":"1578","modelId":"1523"},"CLUE":{"id":"1576","modelId":"1521"},"CLUE_FOLLOW":{"id":"1577","modelId":"1522"},"ENTERPRISE_HEALTH":{"id":"1643","modelId":"1643"},"WECHAT_ARTICLE":{"id":"1599","modelId":"1544"},"MESSAGE":{"id":"1646","modelId":"1649"},"USER":{"id":"1579","modelId":"1524"},"MESSAGE_TARGET":{"id":"1661","modelId":"1671"},"LCXM":{"id":"1660","modelId":"1669"},"INSPECTION_TASK":{"id":"1616","modelId":"1576"},"ENTERPRISE_PRODUCT":{"id":"1592","modelId":"1536"},"ENTERPRISE_PURCHASE":{"id":"1591","modelId":"1535"},"INDUSTRIAL_MANAGE":{"id":"1645","modelId":"1647"},"P18_ENTERPRISE_ECONOMIC":{"id":"1745","modelId":"1796"}}'
-  oauth:
-    role-config: '{"ENTERPRISE_ROLE":[{"roleId":"40","serviceId":"11","comment":"企业用户权限,pc端"},{"roleId":"44","serviceId":"12","comment":"企业用户权限,wx小程序端"}],"INIT_ROLE":[{"roleId":"41","serviceId":"11","comment":"普通游客权限,pc端"},{"roleId":"45","serviceId":"12","comment":"普通游客权限,wx小程序端"},{"roleId":"48","serviceId":"2","comment":"徐泾一般用户,dms"}]}'

+ 5 - 5
pom.xml

@@ -32,6 +32,11 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-tomcat</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.httpcomponents.client5</groupId>
             <artifactId>httpclient5</artifactId>
@@ -54,11 +59,6 @@
             <artifactId>lombok</artifactId>
             <optional>true</optional>
         </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-tomcat</artifactId>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>

+ 47 - 8
src/main/java/com/skyversation/xjcy/Timer.java

@@ -3,7 +3,10 @@ package com.skyversation.xjcy;
 import com.alibaba.fastjson.JSONObject;
 import com.skyversation.xjcy.bean.*;
 import com.skyversation.xjcy.computer.EnterpriseHealthComputer;
+import com.skyversation.xjcy.computer.EnterpriseHealthComputerV2;
+import com.skyversation.xjcy.computer.EnterpriseLevelComputer;
 import com.skyversation.xjcy.dms.DMSColumn;
+import com.skyversation.xjcy.dms.DMSColumnConfig;
 import com.skyversation.xjcy.dms.DMSQuery;
 import com.skyversation.xjcy.dms.DMSService;
 import com.skyversation.xjcy.service.AuthService;
@@ -31,17 +34,15 @@ public class Timer {
     private final InvestmentPreCountService investmentPreCountService;
     private final DMSService dMSService;
     private final AuthService authService;
-    private final MessageService messageService;
-    
+
     @Value("${app.execute-startup-tasks:true}")
     private boolean executeStartupTasks;
 
-    public Timer(ParkPreCountService parkPreCountService, InvestmentPreCountService investmentPreCountService, DMSService dMSService, AuthService authService, MessageService messageService) {
+    public Timer(ParkPreCountService parkPreCountService, InvestmentPreCountService investmentPreCountService, DMSService dMSService, AuthService authService, MessageService messageService, DMSColumnConfig dMSColumnConfig) {
         this.parkPreCountService = parkPreCountService;
         this.investmentPreCountService = investmentPreCountService;
         this.dMSService = dMSService;
         this.authService = authService;
-        this.messageService = messageService;
     }
 
     @PostConstruct
@@ -84,19 +85,29 @@ public class Timer {
             System.err.println("招商预统计执行失败: " + e.getMessage());
             e.printStackTrace();
         }
-        
+        // 计算企业等级评估
+        try {
+            countEnterpriseLevel();
+            System.out.println("企业等级评估完成");
+        }catch (Exception e){
+            System.err.println("企业等级评估执行失败: " + e.getMessage());
+            e.printStackTrace();
+        }
+
         // 执行企业健康度检查
         try {
-            checkEnterpriseHealth();
+//            checkEnterpriseHealth();
+            checkEnterpriseHealthV2();
             System.out.println("企业健康度检查完成");
         } catch (Exception e) {
             System.err.println("企业健康度检查执行失败: " + e.getMessage());
             e.printStackTrace();
         }
-        
+
         System.out.println("启动任务序列执行完毕");
     }
 
+
     /**
      * 每日执行序列
      */
@@ -120,10 +131,19 @@ public class Timer {
             System.err.println("招商预统计执行失败: " + e.getMessage());
             e.printStackTrace();
         }
+        // 计算企业等级评估
+        try {
+            countEnterpriseLevel();
+            System.out.println("企业等级评估完成");
+        }catch (Exception e){
+            System.err.println("企业等级评估执行失败: " + e.getMessage());
+            e.printStackTrace();
+        }
         
         // 执行企业健康度检查
         try {
-            checkEnterpriseHealth();
+//            checkEnterpriseHealth();
+            checkEnterpriseHealthV2();
             System.out.println("企业健康度检查完成");
         } catch (Exception e) {
             System.err.println("企业健康度检查执行失败: " + e.getMessage());
@@ -198,4 +218,23 @@ public class Timer {
 //            }
 //        });
     }
+
+
+    private void checkEnterpriseHealthV2() {
+        List<Enterprise> enterpriseList = dMSService.query(authService.getTokenOfServiceAccount(), DMSQuery.ENTERPRISE,Enterprise.class,LocalDate.now());
+        List<LeaseDetail> leaseDetailList = dMSService.query(authService.getTokenOfServiceAccount(), DMSQuery.LAST_LEASE_DETAIL,LeaseDetail.class,LocalDate.now());
+        EnterpriseHealthComputerV2 computer = new EnterpriseHealthComputerV2(enterpriseList,leaseDetailList);
+        computer.compute();
+        List<JSONObject> updateJSON = computer.getUpdateJSON();
+        dMSService.updateToDms(updateJSON,authService.getTokenOfServiceAccount(), DMSColumn.ENTERPRISE);
+    }
+
+    private void countEnterpriseLevel(){
+        EnterpriseLevelComputer enterpriseLevelComputer = new EnterpriseLevelComputer(
+                dMSService.query(authService.getTokenOfServiceAccount(), DMSQuery.ENTERPRISE, Enterprise.class, LocalDate.now()),
+                dMSService.query(authService.getTokenOfServiceAccount(), DMSQuery.ENTERPRISE_HONOR, EnterpriseHonor.class, LocalDate.now()));
+        enterpriseLevelComputer.compute();
+        dMSService.updateToDms(enterpriseLevelComputer.getAllNeedUpdate(), authService.getTokenOfServiceAccount(), DMSColumn.ENTERPRISE);
+
+    }
 }

+ 11 - 1
src/main/java/com/skyversation/xjcy/bean/Enterprise.java

@@ -12,7 +12,7 @@ import java.util.stream.Stream;
 
 @Data
 public class Enterprise implements FromJSON ,DateInterval {
-
+    private String id;
     private String cUnifiedSocialCreditCode;
     private String cEnterpriseName;
     private String regType;
@@ -37,6 +37,16 @@ public class Enterprise implements FromJSON ,DateInterval {
     private LocalDate gsqyTime;
     private String cNationalIndustryCode;
     private String cNationalIndustry;
+    private String tags;
+    private String registeredAddress;
+    private String businessScope;
+    private String cZjzdcybq;
+    private String cEmployeeCount;
+    private String cInsuredPeople;
+    private int cPatentNum;
+    private String cQyfx;
+    private String cLawsuits;
+    private String cOperationalRisks;
     @Override
     public LocalDate getStartDate() {
         return Optional.ofNullable(cCertificateDate).orElse(LocalDate.MIN);

+ 26 - 0
src/main/java/com/skyversation/xjcy/bean/EnterpriseBranch.java

@@ -0,0 +1,26 @@
+package com.skyversation.xjcy.bean;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.skyversation.xjcy.config.LocalDateFromTimestampDeserializer;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class EnterpriseBranch implements FromJSON {
+    
+    private String id;
+    
+    private String cEnterpriseCode;
+    
+    private String cRegion;
+    
+    private String cCompanyName;
+    
+    private String cPrincipal;
+    
+    private String cStatus;
+    
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cEstablishedDate;
+}

+ 28 - 0
src/main/java/com/skyversation/xjcy/bean/EnterpriseControlledCompany.java

@@ -0,0 +1,28 @@
+package com.skyversation.xjcy.bean;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.skyversation.xjcy.config.LocalDateFromTimestampDeserializer;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class EnterpriseControlledCompany implements FromJSON {
+    
+    private String id;
+    
+    private String cEnterpriseCode;
+    
+    private String cControlledCompanyName;
+    
+    private String cStatus;
+    
+    private String cRegion;
+    
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cEstablishedDate;
+    
+    private String cIndustry;
+    
+    private String cInvestmentRatio;
+}

+ 30 - 0
src/main/java/com/skyversation/xjcy/bean/EnterpriseHonor.java

@@ -0,0 +1,30 @@
+package com.skyversation.xjcy.bean;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.skyversation.xjcy.config.LocalDateFromTimestampDeserializer;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class EnterpriseHonor implements FromJSON {
+    
+    private String id;
+    
+    private String cEnterpriseCode;
+    
+    private String cHonorType;
+    
+    private String cSource;
+    
+    private String cName;
+    
+    private String cLevel;
+    
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cPublishDate;
+    
+    private String cPublisher;
+    
+    private String cValidityPeriod;
+}

+ 30 - 0
src/main/java/com/skyversation/xjcy/bean/EnterpriseInvestment.java

@@ -0,0 +1,30 @@
+package com.skyversation.xjcy.bean;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.skyversation.xjcy.config.LocalDateFromTimestampDeserializer;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class EnterpriseInvestment implements FromJSON {
+    
+    private String id;
+    
+    private String cEnterpriseCode;
+    
+    private String cStatus;
+    
+    private String cShareholdingRatio;
+    
+    private String cRegion;
+    
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cEstablishedDate;
+    
+    private String cSubscribedCapitalOrShares;
+    
+    private String cIndustry;
+    
+    private String cInvesteeCompanyName;
+}

+ 319 - 0
src/main/java/com/skyversation/xjcy/computer/EnterpriseHealthComputerV2.java

@@ -0,0 +1,319 @@
+package com.skyversation.xjcy.computer;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.JSONObject;
+import com.skyversation.xjcy.bean.Enterprise;
+import com.skyversation.xjcy.bean.EnterpriseHealth;
+import com.skyversation.xjcy.bean.LeaseDetail;
+import com.skyversation.xjcy.enums.EnterpriseLevel;
+import lombok.Getter;
+
+import java.time.LocalDate;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class EnterpriseHealthComputerV2 {
+    private final List<Enterprise> enterprises;
+    private final List<LeaseDetail> leases;
+
+    @Getter
+    private static final class Result {
+        private final Enterprise enterprise;
+        private final List<LeaseDetail> leases;
+        public Set<HealthItem> healthItems;
+
+        private Result(Enterprise enterprise, List<LeaseDetail> leases) {
+            this.enterprise = enterprise;
+            this.leases = leases;
+        }
+    }
+
+    @Getter
+    public enum HealthItem {
+        RED_HIGH_RISK_COURT_ENFORCEMENT(1, "红色高风险", "存在法院强制执行案件"),
+        RED_HIGH_RISK_LARGE_LAWSUIT(2, "红色高风险", "近1年存在大额涉诉"),
+        ORANGE_MEDIUM_RISK_ADDRESS_MISMATCH(3, "橙色中风险", "注册地与实际经营地不一致"),
+        ORANGE_MEDIUM_RISK_ABNORMAL_OPERATION(4, "橙色中风险", "经营异常名录未移除"),
+        ORANGE_MEDIUM_RISK_MIDDLE_LAWSUIT(5, "橙色中风险", "近1年存在中额涉诉"),
+        ORANGE_MEDIUM_RISK_EQUITY_FREEZE(6, "橙色中风险", "股权冻结/大额股权质押"),
+        ORANGE_MEDIUM_RISK_LEASE_EXPIRED(7, "橙色中风险", "经营场地租约到期"),
+        YELLOW_GENERAL_RISK_SMALL_LAWSUIT(8, "黄色一般风险", "近1年存在小额涉诉"),
+        YELLOW_GENERAL_RISK_NOT_OPERATING_LOCALLY(9, "黄色一般风险", "未在徐泾辖区实地经营");
+
+        private final String code;
+        private final String riskLevel;
+        private final String description;
+
+        HealthItem(int code, String riskLevel, String description) {
+            this.code = code + "";
+            this.riskLevel = riskLevel;
+            this.description = description;
+        }
+
+        public static HealthItem codeOf(String code) {
+            for (HealthItem item : values()) {
+                if (item.code.equals(code)) return item;
+            }
+            return null;
+        }
+
+        public static String toDmsData(Collection<HealthItem> healthItems) {
+            if (healthItems == null || healthItems.isEmpty()) return "";
+            return healthItems.stream()
+                    .map(HealthItem::getCode)
+                    .collect(Collectors.joining(","));
+        }
+
+        public static Set<HealthItem> fromDmsData(String dmsData) {
+            if (dmsData == null || dmsData.isEmpty()) return new HashSet<>();
+            return Arrays.stream(dmsData.split(","))
+                    .map(HealthItem::codeOf)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+        }
+    }
+
+    @Getter
+    private final List<Result> results = new ArrayList<>();
+
+    public EnterpriseHealthComputerV2(List<Enterprise> enterprises, List<LeaseDetail> leases) {
+        this.enterprises = enterprises;
+        this.leases = leases;
+    }
+
+    public void compute() {
+        readData();
+        for (Result result : results) {
+            result.healthItems = checkHealthItems(result);
+        }
+    }
+    public List<JSONObject> getUpdateJSON() {
+        if (results.isEmpty())compute();
+        List<JSONObject> updateJSON = new ArrayList<>();
+        for (Result result : results){
+            String sourceCQyfx = result.enterprise.getCQyfx();
+            Set<HealthItem> sourceHealth =  HealthItem.fromDmsData(sourceCQyfx);
+            Set<HealthItem> targetHealth = result.healthItems;
+            if (!sourceHealth.equals(targetHealth)){
+                JSONObject jsonObject = new JSONObject();
+                jsonObject.put("id", result.enterprise.getId());
+                jsonObject.put("cQyfx", HealthItem.toDmsData(targetHealth));
+                updateJSON.add(jsonObject);
+            }
+        }
+        return updateJSON;
+    }
+
+    private Set<HealthItem> checkHealthItems(Result result) {
+        Set<HealthItem> healthItems = new HashSet<>();
+        healthItems.addAll(checkTags(result));
+        healthItems.addAll(checkLeases(result));
+        healthItems.addAll(checkLawsuit(result));
+        healthItems.addAll(checkOperationalRisks(result));
+        healthItems.addAll(checkRegType(result));
+        return healthItems;
+    }
+
+    private void readData() {
+        Map<String, List<LeaseDetail>> leaseMap = leases.stream().collect(Collectors.groupingBy(LeaseDetail::getCEnterpriseCode));
+        for (Enterprise enterprise : enterprises) {
+            Result result = new Result(enterprise, leaseMap.get(enterprise.getCUnifiedSocialCreditCode()));
+            results.add(result);
+        }
+    }
+
+    private Set<HealthItem> checkTags(Result result) {
+        Set<String> tags;
+        String tagStr = result.getEnterprise().getTags();
+        if (tagStr != null && !tagStr.isEmpty()) {
+            tags = Arrays.stream(tagStr.split(","))
+                    .map(String::trim)
+                    .collect(Collectors.toSet());
+        } else {
+            tags = null;
+        }
+        Set<HealthItem> healthItems = new HashSet<>();
+        if (anyMatch(tags, Pattern.compile("^(失信)?被执行人$"))) {
+            healthItems.add(HealthItem.RED_HIGH_RISK_COURT_ENFORCEMENT);
+        }
+        if (anyMatch(tags, Pattern.compile("^经营异常$"))) {
+            healthItems.add(HealthItem.ORANGE_MEDIUM_RISK_ABNORMAL_OPERATION);
+        }
+        return healthItems;
+    }
+
+    private Set<HealthItem> checkLeases(Result result) {
+        List<LeaseDetail> leases = result.getLeases();
+        Set<HealthItem> healthItems = new HashSet<>();
+        if (leases == null || leases.isEmpty()) {
+            return healthItems;
+        }
+        boolean isLevelBetterThanB = Arrays.asList(
+                        EnterpriseLevel.LEVEL_A_PLUS,
+                        EnterpriseLevel.LEVEL_A,
+                        EnterpriseLevel.LEVEL_B)
+                .contains(EnterpriseLevel.codeOf(result.getEnterprise().getCompanyLevel()));
+
+        if (isLevelBetterThanB) {
+            Map<String, LocalDate> endTimes = leases.stream()
+                    .filter(lease -> lease.getCEndDate() != null)
+                    .collect(Collectors.groupingBy(
+                            LeaseDetail::getCRoomCode,
+                            Collectors.collectingAndThen(
+                                    Collectors.maxBy(Comparator.comparing(LeaseDetail::getCEndDate)),
+                                    optionalLease -> optionalLease.map(LeaseDetail::getCEndDate).orElse(null)
+                            )
+                    ));
+            //租约过期或即将过期(30天)
+            if (endTimes.values()
+                    .stream()
+                    .anyMatch(endTime -> endTime.isBefore(LocalDate.now().minusDays(30)))
+            ) {
+                healthItems.add(HealthItem.ORANGE_MEDIUM_RISK_LEASE_EXPIRED);
+            }
+        }
+        return healthItems;
+    }
+
+    private Set<HealthItem> checkLawsuit(Result result) {
+        Set<HealthItem> healthItems = new HashSet<>();
+        String lawsuitStr = result.getEnterprise().getCLawsuits();
+        JSONObject lawsuit = null;
+        if (lawsuitStr == null || lawsuitStr.isEmpty()) return healthItems;
+        try {
+            lawsuit = JSONObject.parseObject(lawsuitStr);
+            try {
+                JSONArray justiciaryCases = lawsuit.getJSONObject("司法案件").getJSONArray("table");
+                List<JSONObject> justiciaryCaseList = new ArrayList<>();
+                for (int i = 0; i < justiciaryCases.size(); i++) {
+                    JSONObject justiciaryCase = justiciaryCases.getJSONObject(i);
+                    justiciaryCaseList.add(justiciaryCase);
+                }
+                List<JSONObject> passiveCases = justiciaryCaseList.stream()
+                        .filter(justiciaryCase -> Optional.ofNullable(justiciaryCase.getString("案件身份")).orElse("").contains("被告"))
+                        .collect(Collectors.toList());
+                List<JSONObject> passiveCaseCountLastYear = passiveCases.stream()
+                        .filter(passiveCase -> {
+                            String date = passiveCase.getString("进程日期");
+                            if (date != null && date.length() >= 10) {
+                                LocalDate processDate = LocalDate.parse(date.substring(0, 10));
+                                return processDate.isAfter(LocalDate.now().minusYears(1));
+                            } else {
+                                return false;
+                            }
+                        }).collect(Collectors.toList());
+                double maxAmount = passiveCaseCountLastYear.stream()
+                        .mapToDouble(passiveCase -> parseAmount(passiveCase.getString("案件金额(元)")))
+                        .max().orElse(0);
+                if (maxAmount > 2000000) {
+                    healthItems.add(HealthItem.RED_HIGH_RISK_LARGE_LAWSUIT);
+                } else if (maxAmount > 500000) {
+                    healthItems.add(HealthItem.ORANGE_MEDIUM_RISK_MIDDLE_LAWSUIT);
+                } else {
+                    healthItems.add(HealthItem.YELLOW_GENERAL_RISK_SMALL_LAWSUIT);
+                }
+            } catch (JSONException ignore) {
+            }
+            //股权冻结
+            try {
+                JSONArray equityFreeze = lawsuit.getJSONObject("股权冻结").getJSONArray("table");
+                if (!equityFreeze.isEmpty()) {
+                    healthItems.add(HealthItem.ORANGE_MEDIUM_RISK_EQUITY_FREEZE);
+                }
+            } catch (JSONException ignore) {
+
+            }
+        } catch (JSONException ignore) {
+        }
+        return healthItems;
+    }
+
+    private Set<HealthItem> checkOperationalRisks(Result result) {
+        Set<HealthItem> healthItems = new HashSet<>();
+        String operationalRiskStr = result.getEnterprise().getCOperationalRisks();
+        if (operationalRiskStr == null || operationalRiskStr.isEmpty()) return healthItems;
+        try {
+            JSONObject operationalRisk = JSONObject.parseObject(operationalRiskStr);
+            try {
+                //股权质押
+                JSONArray stockFreeze = operationalRisk.getJSONObject("司法案件").getJSONArray("table");
+                double stockFreezeAmount = 0;
+                for (int i = 0; i < stockFreeze.size(); i++) {
+                    JSONObject stockFreezeItem = stockFreeze.getJSONObject(i);
+                    String amountStr = stockFreezeItem.getString("质押股份市值(元)");
+                    stockFreezeAmount += parseMonetaryValue(amountStr);
+                }
+                if (stockFreezeAmount >= 30000000){
+                    healthItems.add(HealthItem.ORANGE_MEDIUM_RISK_EQUITY_FREEZE);
+                }
+
+            } catch (JSONException ignore) {
+
+            }
+        } catch (JSONException ignore) {
+        }
+        return healthItems;
+    }
+
+    private Set<HealthItem> checkRegType(Result result) {
+        String regType = result.getEnterprise().getRegType();
+        Set<HealthItem> healthItems = new HashSet<>();
+        if ("2".equals(regType)) {
+            healthItems.add(HealthItem.YELLOW_GENERAL_RISK_NOT_OPERATING_LOCALLY);
+        } else if ("3".equals(regType)) {
+            healthItems.add(HealthItem.ORANGE_MEDIUM_RISK_ADDRESS_MISMATCH);
+        }
+        return healthItems;
+    }
+
+    public static boolean anyMatch(Collection<String> strings, Pattern pattern) {
+        if (strings == null || strings.isEmpty() || pattern == null) {
+            return false;
+        }
+        return strings.stream().anyMatch(s -> s != null && pattern.matcher(s).matches());
+    }
+
+    public static double parseAmount(String amount) {
+        if (amount == null || amount.isEmpty()) return 0;
+        return Double.parseDouble(amount.replaceAll("[^0-9.]", ""));
+    }
+
+    /**
+     * 解析金额字符串,支持中文单位(亿、万、亿万)
+     * 例如:87.52亿 -> 8752000000, 123.45万 -> 1234500, 1.5亿万 -> 150000000
+     */
+    public static double parseMonetaryValue(String amount) {
+        if (amount == null || amount.isEmpty()) {
+            return 0;
+        }
+
+        String cleaned = amount.trim();
+
+        // 提取数字部分
+        String numberPart = cleaned.replaceAll("[^0-9.]", "");
+        if (numberPart.isEmpty()) {
+            return 0;
+        }
+
+        double value;
+        try {
+            value = Double.parseDouble(numberPart);
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+
+        // 按优先级检查单位:先检查"亿万",再检查"亿",最后检查"万"
+        if (cleaned.contains("亿万")) {
+            return value * 100000000 * 10000;
+        } else if (cleaned.contains("亿")) {
+            return value * 100000000;
+        } else if (cleaned.contains("万")) {
+            return value * 10000;
+        }
+
+        return value;
+    }
+
+}

+ 241 - 0
src/main/java/com/skyversation/xjcy/computer/EnterpriseLevelComputer.java

@@ -0,0 +1,241 @@
+package com.skyversation.xjcy.computer;
+
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.skyversation.xjcy.bean.Enterprise;
+import com.skyversation.xjcy.bean.EnterpriseHonor;
+import com.skyversation.xjcy.dms.DMSColumn;
+import com.skyversation.xjcy.dms.DMSService;
+import com.skyversation.xjcy.enums.EnterpriseLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.apache.commons.lang3.math.NumberUtils;
+
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class EnterpriseLevelComputer {
+    private static final List<Pattern> FORCE_A_PLUS_HONOR_PATTERNS = Arrays.asList(
+            Pattern.compile(".*高新技术企业.*"),
+            Pattern.compile(".*专精特新.?小巨人.?企业.*"),
+            Pattern.compile(".*服务型制造示范(企业|平台).*"),
+            Pattern.compile(".*科技企业孵化器.*"),
+            Pattern.compile(".*重点实验室.*"),
+            Pattern.compile(".*工程技术研究中心.*"),
+            Pattern.compile(".*独角兽企业.*"),
+            Pattern.compile(".*专精特新中小企业.*"),
+            Pattern.compile(".*科技型中小企业.*"),
+            Pattern.compile(".*科技小巨人企业.*"),
+            Pattern.compile(".*培育企业.*"),
+            Pattern.compile(".*技术先进型服务企业.*"),
+            Pattern.compile(".*先进级智能工厂.*"),
+            Pattern.compile(".*(院士|专家)工作站.*"),
+            Pattern.compile(".*创新型中小企业.*"),
+            Pattern.compile(".*留创园入驻优质企业.*"),
+            Pattern.compile(".*灯塔奖.*企业.*"),
+            Pattern.compile(".*纳税百强企业.*"),
+            Pattern.compile(".*发展增速十强企业.*"),
+            Pattern.compile(".*制造业十强企业.*"),
+            Pattern.compile(".*服务业十强企业.*"),
+            Pattern.compile(".*高新技术十强企业.*")
+            );
+
+    private static final List<Pattern> FORCE_A_PLUS_TAG_PATTERNS = Arrays.asList(
+            Pattern.compile(".*高新技术企业.*"),
+            Pattern.compile(".*专精特新.?小巨人.?企业.*"),
+            Pattern.compile(".*服务型制造示范(企业|平台).*"),
+            Pattern.compile(".*科技企业孵化器.*"),
+            Pattern.compile(".*重点实验室.*"),
+            Pattern.compile(".*工程技术研究中心.*"),
+            Pattern.compile(".*独角兽企业.*"),
+            Pattern.compile(".*专精特新中小企业.*"),
+            Pattern.compile(".*科技型中小企业.*"),
+            Pattern.compile(".*科技小巨人企业.*"),
+            Pattern.compile(".*培育企业.*"),
+            Pattern.compile(".*技术先进型服务企业.*"),
+            Pattern.compile(".*先进级智能工厂.*"),
+            Pattern.compile(".*(院士|专家)工作站.*"),
+            Pattern.compile(".*(民营|贸易型|跨国公司地区)总部.*"),
+            Pattern.compile(".*创新型中小企业.*"),
+            Pattern.compile(".*留创园入驻优质企业.*"),
+            Pattern.compile(".*灯塔奖.*企业.*"),
+            Pattern.compile(".*纳税百强企业.*"),
+            Pattern.compile(".*发展增速十强企业.*"),
+            Pattern.compile(".*十强企业.*(制造业|服务业|高新技术).*")
+    );
+
+    public final List<Enterprise> enterprises;
+    public final List<EnterpriseHonor> enterpriseHonors;
+    private final Map<String, List<EnterpriseHonor>> honorsByEnterpriseCode;
+
+    @AllArgsConstructor
+    public static final class Result {
+        public final String level;
+        public final int point;
+        public final int locationPoint;
+        public final int directionPoint;
+        public final int employeePoint;
+        public final int honorPoint;
+    }
+
+    public EnterpriseLevelComputer(List<Enterprise> enterprises, List<EnterpriseHonor> enterpriseHonors) {
+        this.enterprises = enterprises;
+        this.enterpriseHonors = enterpriseHonors;
+        this.honorsByEnterpriseCode = new HashMap<>();
+        if (enterpriseHonors != null) {
+            for (EnterpriseHonor honor : enterpriseHonors) {
+                String enterpriseCode = honor.getCEnterpriseCode();
+                if (enterpriseCode != null && !enterpriseCode.isEmpty()) {
+                    honorsByEnterpriseCode.computeIfAbsent(enterpriseCode, k -> new ArrayList<>()).add(honor);
+                }
+            }
+        }
+    }
+
+    @Getter
+    private Map<Enterprise,Result> results = null;
+
+
+    private boolean matchesAnyPattern(String text, List<Pattern> patterns) {
+        if (text == null || text.isEmpty()) {
+            return false;
+        }
+        return patterns.stream().anyMatch(pattern -> pattern.matcher(text).matches());
+    }
+
+    public int locationPoint(Enterprise enterprise) {
+        String regAddress = enterprise.getRegisteredAddress();
+        int regPoint;
+        if (regAddress != null){
+            if (regAddress.contains("徐泾")){
+                regPoint = 20;
+            }else if (regAddress.contains("青浦")){
+                regPoint = 5;
+            }else {
+                regPoint = 0;
+            }
+        }else {
+            regPoint = 0;
+        }
+        int workPoint;
+        if ("2".equals(enterprise.getIsGsqy())){
+            workPoint = 10;
+        }else if ("1".equals(enterprise.getRegType())||"3".equals(enterprise.getRegType())){
+            workPoint = 10;
+        }else {
+            workPoint = 0;
+        }
+
+        return regPoint + workPoint;
+    }
+    public int directionPoint(Enterprise enterprise) {
+        String zjzdcybq = enterprise.getCZjzdcybq();
+        int point = 0;
+        if (zjzdcybq != null&&zjzdcybq.equals("新一代信息技术类")|zjzdcybq.equals("时尚消费类"))point +=30;
+        String businessScope = enterprise.getBusinessScope();
+        if (businessScope != null){
+            Pattern pattern = Pattern.compile(".*(进口|出口|进出口|跨境|出海).*");
+            if (pattern.matcher(businessScope).matches())point +=5;
+            if (point>30) point = 30;
+        }
+        return point;
+    }
+    public int employeePoint(Enterprise enterprise) {
+        String employeeCountStr = enterprise.getCEmployeeCount();
+        String insuredPeopleStr  = enterprise.getCInsuredPeople();
+        int employeeCount = parseNumberWithSuffix(employeeCountStr);
+        int insuredPeople = parseNumberWithSuffix(insuredPeopleStr);
+        
+        if (employeeCount==0 && insuredPeople==0)return 0;
+        double insuranceRatio = insuredPeople / (double) employeeCount;
+        return insuranceRatio > 0.5 ? 20 : insuranceRatio>0.1 ? 16 : 6;
+    }
+    
+    private int parseNumberWithSuffix(String str) {
+        if (str == null || str.isEmpty()) {
+            return 0;
+        }
+        String cleaned = str.replaceAll("[^0-9]", "");
+        return NumberUtils.toInt(cleaned, 0);
+    }
+    public int honorPoint(Enterprise enterprise, List<EnterpriseHonor> honors) {
+        int honorNum = honors.size();
+        int patentNum = enterprise.getCPatentNum();
+        int point = honorNum*3+patentNum*3;
+        if (point>20) point = 20;
+        return point;
+    }
+
+    public boolean checkForceAPlus(Enterprise enterprise, List<EnterpriseHonor> honors) {
+        //规上企业
+        if ("2".equals(enterprise.getIsGsqy())) return true;
+        //检查荣誉
+        for (EnterpriseHonor honor : honors) {
+            if (matchesAnyPattern(honor.getCName(), FORCE_A_PLUS_HONOR_PATTERNS)) {
+                return true;
+            }
+        }
+        //检查标签
+        String tags = enterprise.getTags();
+        if (matchesAnyPattern(tags, FORCE_A_PLUS_TAG_PATTERNS)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public Result getResult(Enterprise enterprise, List<EnterpriseHonor> honors) {
+        int locationPoint = locationPoint(enterprise);
+        int directionPoint = directionPoint(enterprise);
+        int employeePoint = employeePoint(enterprise);
+        int honorPoint = honorPoint(enterprise, honors);
+        int point = locationPoint + directionPoint + employeePoint + honorPoint;
+        String level = point >= 80 ? "A" : point >= 60 ? "B" : point >= 40 ? "C" : "D";
+        if (checkForceAPlus(enterprise, honors))level = "A+";
+        return new Result(level, point, locationPoint, directionPoint, employeePoint, honorPoint);
+    }
+
+    public List<EnterpriseHonor> getHonorsByEnterpriseCode(String enterpriseCode) {
+        return honorsByEnterpriseCode.getOrDefault(enterpriseCode, Collections.emptyList());
+    }
+
+    public void compute() {
+        Map<Enterprise, Result> results = new HashMap<>();
+        for (Enterprise enterprise : enterprises) {
+            List<EnterpriseHonor> honors = getHonorsByEnterpriseCode(enterprise.getCUnifiedSocialCreditCode());
+            Result result = getResult(enterprise, honors);
+            results.put(enterprise, result);
+        }
+        this.results = results;
+    }
+
+    public List<JSONObject> getAllNeedUpdate() {
+        if (results == null) {
+            compute();
+        }
+        
+        List<JSONObject> needUpdates = new ArrayList<>();
+        
+        for (Map.Entry<Enterprise, Result> entry : results.entrySet()) {
+            Enterprise enterprise = entry.getKey();
+            Result result = entry.getValue();
+            
+            String persistedLevel = enterprise.getCompanyLevel();
+            String calculatedLevel = result.level;
+            
+            String persistedLevelConverted = EnterpriseLevel.toDisplayLevel(persistedLevel);
+            
+            if (!Objects.equals(persistedLevelConverted, calculatedLevel)) {
+                JSONObject updateData = new JSONObject();
+                updateData.put("id", enterprise.getId());
+                updateData.put("level", EnterpriseLevel.toPersistedCode(calculatedLevel));
+                needUpdates.add(updateData);
+            }
+        }
+        
+        return needUpdates;
+    }
+
+}

+ 4 - 0
src/main/java/com/skyversation/xjcy/dms/DMSColumn.java

@@ -22,6 +22,10 @@ public class DMSColumn {
     public static String ENTERPRISE_PURCHASE = "ENTERPRISE_PURCHASE";
     public static String INDUSTRIAL_MANAGE = "INDUSTRIAL_MANAGE";
     public static String P18_ENTERPRISE_ECONOMIC = "P18_ENTERPRISE_ECONOMIC";
+    public static String ENTERPRISE_HONOR = "ENTERPRISE_HONOR";
+    public static String ENTERPRISE_BRANCH = "ENTERPRISE_BRANCH";
+    public static String ENTERPRISE_CONTROLLED_COMPANY = "ENTERPRISE_CONTROLLED_COMPANY";
+    public static String ENTERPRISE_EXTERNAL_INVESTMENT = "ENTERPRISE_EXTERNAL_INVESTMENT";
     
     @Getter
     private String id;

+ 6 - 0
src/main/java/com/skyversation/xjcy/dms/DMSQuery.java

@@ -102,6 +102,12 @@ public enum DMSQuery {
             request.addWhere("c_year_month", "2", "%"+year);
             return request;
         }
+    },
+    ENTERPRISE_HONOR(DMSColumn.ENTERPRISE_HONOR,EnterpriseHonor.class) {
+        @Override
+        DMSQueryRequest preRequest(LocalDate now) {
+            return new DMSQueryRequest();
+        }
     };
 
     private final String column;

+ 61 - 15
src/main/java/com/skyversation/xjcy/enums/EnterpriseLevel.java

@@ -1,24 +1,70 @@
 package com.skyversation.xjcy.enums;
 
-import java.util.Optional;
+import lombok.Getter;
 
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 企业等级枚举
+ * 用于管理企业内部存储等级与展示等级之间的转换
+ */
+@Getter
 public enum EnterpriseLevel {
-    A,B,C;
+
+    LEVEL_A_PLUS("0", "A+"),
+    LEVEL_A("1", "A"),
+    LEVEL_B("2", "B"),
+    LEVEL_C("3", "C"),
+    LEVEL_D("4", "D");
+
+    private static final Map<String, EnterpriseLevel> BY_PERSISTED_CODE = new HashMap<>();
+    private static final Map<String, EnterpriseLevel> BY_DISPLAY_LEVEL = new HashMap<>();
+
+    static {
+        for (EnterpriseLevel level : values()) {
+            BY_PERSISTED_CODE.put(level.persistedCode, level);
+            BY_DISPLAY_LEVEL.put(level.displayLevel, level);
+        }
+    }
+
+    private final String persistedCode;
+    private final String displayLevel;
+
+    EnterpriseLevel(String persistedCode, String displayLevel) {
+        this.persistedCode = persistedCode;
+        this.displayLevel = displayLevel;
+    }
+
+    public static EnterpriseLevel codeOf(String code){
+        return BY_PERSISTED_CODE.get(code);
+    }
+
     /**
-     * 把 JSON 中的值 1/2/3 转成枚举。
-     * @throws IllegalArgumentException 如果不是 1、2、3
+     * 根据内部存储码获取对应的展示等级
+     * @param persistedCode 内部存储码 ("0", "1", "2", "3", "4")
+     * @return 对应的展示等级 ("A+", "A", "B", "C", "D"),如果未找到则返回原值
      */
-    public static Optional<EnterpriseLevel> ofJsonValue(String jsonValue) {
-        if (jsonValue==null)return Optional.empty();
-        switch (jsonValue) {
-            case "1":
-                return Optional.of(A);
-            case "2":
-                return Optional.of(B);
-            case "3":
-                return Optional.of(C);
-            default:
-                return Optional.empty();
+    public static String toDisplayLevel(String persistedCode) {
+        if (persistedCode == null || persistedCode.isEmpty()) {
+            return null;
         }
+
+        EnterpriseLevel level = BY_PERSISTED_CODE.get(persistedCode);
+        return level != null ? level.getDisplayLevel() : persistedCode;
+    }
+
+    /**
+     * 根据展示等级获取对应的内部存储码
+     * @param displayLevel 展示等级 ("A+", "A", "B", "C", "D")
+     * @return 对应的内部存储码 ("0", "1", "2", "3", "4"),如果未找到则返回原值
+     */
+    public static String toPersistedCode(String displayLevel) {
+        if (displayLevel == null || displayLevel.isEmpty()) {
+            return null;
+        }
+
+        EnterpriseLevel level = BY_DISPLAY_LEVEL.get(displayLevel);
+        return level != null ? level.getPersistedCode() : displayLevel;
     }
 }

+ 1 - 1
src/main/resources/application.yml

@@ -28,7 +28,7 @@ app:
     #    path: http://127.0.0.1:10081/dms
     path: ${DMS_PATH:http://121.43.55.7:10081/dms}
     column:
-      data: '{"INDUSTRIAL_PARK":{"id":"1580","modelId":"1525"},"ENTERPRISE":{"id":"1593","modelId":"1537"},"ENTERPRISE_ECONOMIC":{"id":"1594","modelId":"1538"},"ORDER":{"id":"1587","modelId":"1531"},"LEASE_DETAIL":{"id":"1574","modelId":"1520"},"INVESTMENT_TARGET":{"id":"1578","modelId":"1523"},"CLUE":{"id":"1576","modelId":"1521"},"CLUE_FOLLOW":{"id":"1577","modelId":"1522"},"ENTERPRISE_HEALTH":{"id":"1643","modelId":"1643"},"WECHAT_ARTICLE":{"id":"1599","modelId":"1544"},"MESSAGE":{"id":"1646","modelId":"1649"},"USER":{"id":"1579","modelId":"1524"},"MESSAGE_TARGET":{"id":"1661","modelId":"1671"},"LCXM":{"id":"1660","modelId":"1669"},"INSPECTION_TASK":{"id":"1616","modelId":"1576"},"ENTERPRISE_PRODUCT":{"id":"1592","modelId":"1536"},"ENTERPRISE_PURCHASE":{"id":"1591","modelId":"1535"},"INDUSTRIAL_MANAGE":{"id":"1645","modelId":"1647"},"P18_ENTERPRISE_ECONOMIC":{"id":"1745","modelId":"1796"}}'
+      data: '{"INDUSTRIAL_PARK":{"id":"1580","modelId":"1525"},"ENTERPRISE":{"id":"1593","modelId":"1537"},"ENTERPRISE_ECONOMIC":{"id":"1594","modelId":"1538"},"ORDER":{"id":"1587","modelId":"1531"},"LEASE_DETAIL":{"id":"1574","modelId":"1520"},"INVESTMENT_TARGET":{"id":"1578","modelId":"1523"},"CLUE":{"id":"1576","modelId":"1521"},"CLUE_FOLLOW":{"id":"1577","modelId":"1522"},"ENTERPRISE_HEALTH":{"id":"1643","modelId":"1643"},"WECHAT_ARTICLE":{"id":"1599","modelId":"1544"},"MESSAGE":{"id":"1646","modelId":"1649"},"USER":{"id":"1579","modelId":"1524"},"MESSAGE_TARGET":{"id":"1661","modelId":"1671"},"LCXM":{"id":"1660","modelId":"1669"},"INSPECTION_TASK":{"id":"1616","modelId":"1576"},"ENTERPRISE_PRODUCT":{"id":"1592","modelId":"1536"},"ENTERPRISE_PURCHASE":{"id":"1591","modelId":"1535"},"INDUSTRIAL_MANAGE":{"id":"1645","modelId":"1647"},"P18_ENTERPRISE_ECONOMIC":{"id":"1745","modelId":"1796"},"ENTERPRISE_HONOR":{"id":"1720","modelId":"1758"},"ENTERPRISE_BRANCH":{"id":"1721","modelId":"1759"},"ENTERPRISE_CONTROLLED_COMPANY":{"id":"1722","modelId":"1760"},"ENTERPRISE_EXTERNAL_INVESTMENT":{"id":"1723","modelId":"1761"}}'
   oauth:
     login-name: ${DMS_LOGIN_NAME:user_hj}
     password: ${DMS_PASSWORD:Hj@123456}