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