package com.trafficaudit.auditengine.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.trafficaudit.auditengine.entity.AuditResult;
|
import com.trafficaudit.auditengine.entity.AuditRun;
|
import com.trafficaudit.auditengine.mapper.AuditResultMapper;
|
import com.trafficaudit.auditengine.mapper.AuditRunMapper;
|
import com.trafficaudit.dataimport.entity.EnergyAuthVehicle;
|
import com.trafficaudit.dataimport.entity.EnergyVehicleQuarterly;
|
import com.trafficaudit.dataimport.entity.H2032EnterpriseMonthly;
|
import com.trafficaudit.dataimport.entity.InvestmentMonthly;
|
import com.trafficaudit.dataimport.entity.InvestmentProject;
|
import com.trafficaudit.dataimport.entity.InvestmentSystem;
|
import com.trafficaudit.dataimport.entity.CityBusMonthly;
|
import com.trafficaudit.dataimport.entity.CityTaxiAuth;
|
import com.trafficaudit.dataimport.entity.CityTaxiMonthly;
|
import com.trafficaudit.dataimport.entity.PassengerAuthVehicle;
|
import com.trafficaudit.dataimport.entity.PassengerEnterpriseMonthly;
|
import com.trafficaudit.dataimport.entity.TransportAuthVehicle;
|
import com.trafficaudit.dataimport.entity.VehicleTrackMileage;
|
import com.trafficaudit.dataimport.mapper.EnergyAuthVehicleMapper;
|
import com.trafficaudit.dataimport.mapper.EnergyVehicleQuarterlyMapper;
|
import com.trafficaudit.dataimport.mapper.H2032EnterpriseMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.InvestmentMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.InvestmentProjectMapper;
|
import com.trafficaudit.dataimport.mapper.InvestmentSystemMapper;
|
import com.trafficaudit.dataimport.mapper.CityBusMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.CityTaxiAuthMapper;
|
import com.trafficaudit.dataimport.mapper.CityTaxiMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.PassengerAuthVehicleMapper;
|
import com.trafficaudit.dataimport.mapper.PassengerEnterpriseMonthlyMapper;
|
import com.trafficaudit.dataimport.mapper.TransportAuthVehicleMapper;
|
import com.trafficaudit.dataimport.mapper.VehicleTrackMileageMapper;
|
import com.trafficaudit.rulemanage.entity.AuditRule;
|
import com.trafficaudit.rulemanage.mapper.AuditRuleMapper;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
|
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Row;
|
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
import javax.annotation.Resource;
|
import java.io.ByteArrayOutputStream;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* 审核规则引擎(Demo 6条规则)
|
*/
|
@Slf4j
|
@Service
|
public class AuditEngineService {
|
|
/** 牵引车标准吨位 */
|
private static final double TRACTOR_STD_TONS = 31.0;
|
|
/** 货运类型字段 → 中文名称 */
|
private static final Map<String, String> FREIGHT_TYPE_NAMES = new HashMap<>();
|
|
static {
|
FREIGHT_TYPE_NAMES.put("freightContainer", "集装箱");
|
FREIGHT_TYPE_NAMES.put("freightCoal", "煤炭及制品");
|
FREIGHT_TYPE_NAMES.put("freightOilGas", "石油天然气及制品");
|
FREIGHT_TYPE_NAMES.put("freightCrudeOil", "原油");
|
FREIGHT_TYPE_NAMES.put("freightMetalOre", "金属矿石");
|
FREIGHT_TYPE_NAMES.put("freightIronOre", "铁矿石");
|
FREIGHT_TYPE_NAMES.put("freightBuilding", "矿物性建筑材料");
|
FREIGHT_TYPE_NAMES.put("freightGrain", "粮食");
|
}
|
|
/** H204 车辆类型码 -> 允许的运政车辆类型(已去掉重/中/轻型前缀) */
|
private static final Map<String, java.util.Set<String>> ENERGY_VEHICLE_TYPE_ALLOWED = new HashMap<>();
|
|
/** H204 燃料类型码 -> 允许的运政燃料类型 */
|
private static final Map<String, java.util.Set<String>> ENERGY_FUEL_TYPE_ALLOWED = new HashMap<>();
|
|
static {
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("01", new java.util.HashSet<>(java.util.Arrays.asList("普通货车", "栏板货车", "自卸货车")));
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("02", new java.util.HashSet<>(java.util.Arrays.asList("平板货车", "栏板货车")));
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("03", new java.util.HashSet<>(java.util.Arrays.asList("仓栅式货车")));
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("04", new java.util.HashSet<>(java.util.Arrays.asList("厢式货车")));
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("06", new java.util.HashSet<>(java.util.Arrays.asList("罐式货车")));
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("07", new java.util.HashSet<>(java.util.Arrays.asList("特殊结构货车", "车辆运输车")));
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("08", new java.util.HashSet<>(java.util.Arrays.asList("自卸货车", "栏板货车")));
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("09", new java.util.HashSet<>(java.util.Arrays.asList("车辆运输车", "中置轴车辆运输车")));
|
ENERGY_VEHICLE_TYPE_ALLOWED.put("11", new java.util.HashSet<>(java.util.Arrays.asList("半挂牵引车")));
|
|
ENERGY_FUEL_TYPE_ALLOWED.put("01", new java.util.HashSet<>(java.util.Arrays.asList("汽油")));
|
ENERGY_FUEL_TYPE_ALLOWED.put("02", new java.util.HashSet<>(java.util.Arrays.asList("柴油")));
|
ENERGY_FUEL_TYPE_ALLOWED.put("03", new java.util.HashSet<>(java.util.Arrays.asList("压缩天然气")));
|
ENERGY_FUEL_TYPE_ALLOWED.put("04", new java.util.HashSet<>(java.util.Arrays.asList("液态天然气", "液化天然气", "双燃料")));
|
ENERGY_FUEL_TYPE_ALLOWED.put("07", new java.util.HashSet<>(java.util.Arrays.asList("电动")));
|
ENERGY_FUEL_TYPE_ALLOWED.put("08", new java.util.HashSet<>(java.util.Arrays.asList("氢气")));
|
}
|
|
@Resource
|
private AuditRuleMapper ruleMapper;
|
@Resource
|
private AuditResultMapper resultMapper;
|
@Resource
|
private AuditRunMapper auditRunMapper;
|
@Resource
|
private H2032EnterpriseMonthlyMapper h2032Mapper;
|
@Resource
|
private TransportAuthVehicleMapper transportAuthMapper;
|
@Resource
|
private VehicleTrackMileageMapper trackMileageMapper;
|
@Resource
|
private PassengerEnterpriseMonthlyMapper passengerMapper;
|
@Resource
|
private PassengerAuthVehicleMapper passengerAuthMapper;
|
@Resource
|
private EnergyVehicleQuarterlyMapper energyMapper;
|
@Resource
|
private EnergyAuthVehicleMapper energyAuthMapper;
|
@Resource
|
private InvestmentProjectMapper investProjectMapper;
|
@Resource
|
private InvestmentMonthlyMapper investMonthlyMapper;
|
@Resource
|
private InvestmentSystemMapper investSystemMapper;
|
@Resource
|
private CityBusMonthlyMapper cityBusMapper;
|
@Resource
|
private CityTaxiMonthlyMapper cityTaxiMapper;
|
@Resource
|
private CityTaxiAuthMapper cityTaxiAuthMapper;
|
|
/** 记录「该期该类型已完成审核」标记(先审核再出表依据;无规则或无数据不标记,重复执行幂等) */
|
public void markAudited(String reportType, String reportPeriod, List<AuditRule> rules, boolean hasData) {
|
if (rules == null || rules.isEmpty() || !hasData) return;
|
Long cnt = auditRunMapper.selectCount(new LambdaQueryWrapper<AuditRun>()
|
.eq(AuditRun::getReportType, reportType)
|
.eq(AuditRun::getReportPeriod, reportPeriod));
|
if (cnt != null && cnt > 0) return;
|
AuditRun run = new AuditRun();
|
run.setReportType(reportType);
|
run.setReportPeriod(reportPeriod);
|
auditRunMapper.insert(run);
|
}
|
|
/** 2 参数重载:按报表类型自动取启用规则 + 校验该期有数据后再写标记(线下审核通过数据手动标记 / Controller 调用) */
|
public boolean markAudited(String reportType, String reportPeriod) {
|
List<AuditRule> rules = rulesOf(reportType);
|
if (rules.isEmpty()) {
|
log.info("markAudited skip: no enabled rules for {}", reportType);
|
return false;
|
}
|
long dataCount;
|
if ("H2031".equals(reportType)) {
|
dataCount = passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
|
.eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod));
|
} else if ("H204".equals(reportType)) {
|
dataCount = energyMapper.selectCount(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
|
.eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod));
|
} else if ("INVEST".equals(reportType)) {
|
dataCount = investMonthlyMapper.selectCount(new LambdaQueryWrapper<InvestmentMonthly>()
|
.eq(InvestmentMonthly::getReportPeriod, reportPeriod));
|
} else if ("CITY_BUS".equals(reportType)) {
|
dataCount = cityBusMapper.selectCount(new LambdaQueryWrapper<CityBusMonthly>()
|
.eq(CityBusMonthly::getReportPeriod, reportPeriod));
|
} else if ("CITY_TAXI".equals(reportType)) {
|
dataCount = cityTaxiMapper.selectCount(new LambdaQueryWrapper<CityTaxiMonthly>()
|
.eq(CityTaxiMonthly::getReportPeriod, reportPeriod));
|
} else {
|
dataCount = h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
|
.eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod));
|
}
|
if (dataCount <= 0) {
|
log.info("markAudited skip: no data for {} / {}", reportType, reportPeriod);
|
return false;
|
}
|
markAudited(reportType, reportPeriod, rules, true);
|
return true;
|
}
|
|
public List<AuditResult> executeAudit(String reportPeriod) {
|
List<AuditRule> rules = rulesOf("H2032");
|
List<H2032EnterpriseMonthly> reports = h2032Mapper.selectList(
|
new LambdaQueryWrapper<H2032EnterpriseMonthly>()
|
.eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod));
|
|
// 加载运政/轨迹数据,按企业名索引
|
Map<String, TransportAuthVehicle> authMap = loadAuthMap(reportPeriod);
|
Map<String, VehicleTrackMileage> trackMap = loadTrackMap(reportPeriod);
|
Map<String, H2032EnterpriseMonthly> lastMonthMap = loadLastMonthMap(reportPeriod);
|
|
// 幂等:同报表期先清除旧审核结果
|
resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
|
.eq(AuditResult::getReportPeriod, reportPeriod));
|
|
Map<Long, AuditRule> ruleMap = new HashMap<>();
|
for (AuditRule rule : rules) ruleMap.put(rule.getId(), rule);
|
|
List<AuditResult> results = new ArrayList<>();
|
for (H2032EnterpriseMonthly report : reports) {
|
for (AuditRule rule : rules) {
|
AuditResult result = checkRule(rule, report, authMap, trackMap, lastMonthMap);
|
if (result != null) {
|
result.setRuleName(rule.getRuleName());
|
result.setRuleCode(rule.getRuleCode());
|
result.setAlertLevel(rule.getAlertLevel());
|
result.setEnterpriseName(report.getEnterpriseName());
|
result.setVerifyExplanation(report.getVerifyExplanation());
|
resultMapper.insert(result);
|
results.add(result);
|
}
|
}
|
}
|
log.info("Audit complete: {} reports, {} issues for {}", reports.size(), results.size(), reportPeriod);
|
if (!rules.isEmpty() && !reports.isEmpty()) markAudited("H2032", reportPeriod);
|
return results;
|
}
|
|
public List<AuditResult> executePassengerAudit(String reportPeriod) {
|
List<AuditRule> rules = rulesOf("H2031");
|
List<PassengerEnterpriseMonthly> reports = passengerMapper.selectList(
|
new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
|
.eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod));
|
|
Map<String, PassengerAuthVehicle> authMap = loadPassengerAuthMap(reportPeriod);
|
Map<String, PassengerEnterpriseMonthly> lastMonthMap = loadPassengerLastMonthMap(reportPeriod);
|
|
// 幂等:只清除 H2031 规则的旧审核结果
|
List<Long> ruleIds = new ArrayList<>();
|
for (AuditRule rule : rules) ruleIds.add(rule.getId());
|
if (!ruleIds.isEmpty()) {
|
resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
|
.eq(AuditResult::getReportPeriod, reportPeriod)
|
.in(AuditResult::getRuleId, ruleIds));
|
}
|
|
Map<Long, AuditRule> ruleMap = new HashMap<>();
|
for (AuditRule rule : rules) ruleMap.put(rule.getId(), rule);
|
|
List<AuditResult> results = new ArrayList<>();
|
for (PassengerEnterpriseMonthly report : reports) {
|
for (AuditRule rule : rules) {
|
AuditResult result = checkPassengerRule(rule, report, authMap, lastMonthMap);
|
if (result != null) {
|
result.setRuleName(rule.getRuleName());
|
result.setRuleCode(rule.getRuleCode());
|
result.setAlertLevel(rule.getAlertLevel());
|
result.setEnterpriseName(report.getEnterpriseName());
|
result.setVerifyExplanation(report.getVerifyExplanation());
|
resultMapper.insert(result);
|
results.add(result);
|
}
|
}
|
}
|
log.info("Passenger audit complete: {} reports, {} issues for {}", reports.size(), results.size(), reportPeriod);
|
if (!rules.isEmpty() && !reports.isEmpty()) markAudited("H2031", reportPeriod);
|
return results;
|
}
|
|
|
// ========== H204 能耗审核(车辆粒度,与运政车辆信息跨表对比) ==========
|
|
public List<AuditResult> executeEnergyAudit(String reportPeriod) {
|
List<AuditRule> rules = rulesOf("H204");
|
List<EnergyVehicleQuarterly> vehicles = energyMapper.selectList(
|
new LambdaQueryWrapper<EnergyVehicleQuarterly>()
|
.eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod));
|
|
Map<String, EnergyAuthVehicle> authMap = new HashMap<>();
|
List<EnergyAuthVehicle> authList = energyAuthMapper.selectList(
|
new LambdaQueryWrapper<EnergyAuthVehicle>()
|
.eq(EnergyAuthVehicle::getReportPeriod, reportPeriod));
|
for (EnergyAuthVehicle v : authList) {
|
authMap.putIfAbsent(key(v.getPlateNo()), v);
|
}
|
|
// E007 环比基准:上一季度同车牌百公里单耗
|
String lastPeriod = prevQuarter(reportPeriod);
|
Map<String, EnergyVehicleQuarterly> lastQuarterMap = new HashMap<>();
|
if (lastPeriod != null) {
|
for (EnergyVehicleQuarterly v : energyMapper.selectList(
|
new LambdaQueryWrapper<EnergyVehicleQuarterly>()
|
.eq(EnergyVehicleQuarterly::getReportPeriod, lastPeriod))) {
|
lastQuarterMap.putIfAbsent(key(v.getPlateNo()), v);
|
}
|
}
|
|
// E006 超范围基准:本期各燃料类型百公里单耗中位数
|
Map<String, Double> fuelMedianMap = fuelPer100kmMedian(vehicles);
|
|
// E008/E009 对比基准:同期 H2032 企业月报(按企业名索引)
|
Map<String, H2032EnterpriseMonthly> h2032Map = new HashMap<>();
|
for (H2032EnterpriseMonthly m : h2032Mapper.selectList(
|
new LambdaQueryWrapper<H2032EnterpriseMonthly>()
|
.eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod))) {
|
String ent = m.getEnterpriseName();
|
if (ent != null && !ent.trim().isEmpty()) h2032Map.putIfAbsent(ent.trim(), m);
|
}
|
|
// 幂等:只清除 H204 规则的旧审核结果
|
List<Long> ruleIds = new ArrayList<>();
|
for (AuditRule rule : rules) ruleIds.add(rule.getId());
|
if (!ruleIds.isEmpty()) {
|
resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
|
.eq(AuditResult::getReportPeriod, reportPeriod)
|
.in(AuditResult::getRuleId, ruleIds));
|
}
|
|
List<AuditResult> results = new ArrayList<>();
|
for (EnergyVehicleQuarterly vehicle : vehicles) {
|
for (AuditRule rule : rules) {
|
AuditResult result = checkEnergyRule(rule, vehicle, authMap, fuelMedianMap, lastQuarterMap, h2032Map);
|
if (result != null) {
|
result.setRuleName(rule.getRuleName());
|
result.setRuleCode(rule.getRuleCode());
|
result.setAlertLevel(rule.getAlertLevel());
|
result.setEnterpriseName(vehicle.getEnterpriseName());
|
result.setVerifyExplanation(null);
|
resultMapper.insert(result);
|
results.add(result);
|
}
|
}
|
}
|
log.info("Energy audit complete: {} vehicles, {} issues for {}", vehicles.size(), results.size(), reportPeriod);
|
if (!rules.isEmpty() && !vehicles.isEmpty()) markAudited("H204", reportPeriod);
|
return results;
|
}
|
|
private AuditResult checkEnergyRule(AuditRule rule, EnergyVehicleQuarterly vehicle,
|
Map<String, EnergyAuthVehicle> authMap,
|
Map<String, Double> fuelMedianMap,
|
Map<String, EnergyVehicleQuarterly> lastQuarterMap,
|
Map<String, H2032EnterpriseMonthly> h2032Map) {
|
try {
|
switch (rule.getCompareType()) {
|
case "CROSS_DIFF":
|
return checkEnergyCrossDiff(rule, vehicle, authMap);
|
case "EXISTENCE_AUTH":
|
return checkEnergyExistence(rule, vehicle, authMap);
|
case "LOADED_RATIO_DIFF":
|
return checkEnergyLoadedRatio(rule, vehicle);
|
case "FUEL_RANGE":
|
return checkEnergyFuelRange(rule, vehicle, fuelMedianMap);
|
case "FUEL_MOM":
|
return checkEnergyFuelMom(rule, vehicle, lastQuarterMap);
|
case "FREIGHT_VS_H2032":
|
return checkEnergyVsFreightReport(rule, vehicle, h2032Map);
|
case "AVG_DIST_VS_H2032":
|
return checkEnergyAvgDistVsFreight(rule, vehicle, h2032Map);
|
case "LOADED_TON_RATIO":
|
return checkEnergyLoadedTonnageRatio(rule, vehicle);
|
default:
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("Energy rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
|
return null;
|
}
|
}
|
|
/** 跨表字段对比:车辆类型/燃料类型按码表比对,标记吨位(牵引车用准牵引质量)按阈值比对 */
|
private AuditResult checkEnergyCrossDiff(AuditRule rule, EnergyVehicleQuarterly vehicle,
|
Map<String, EnergyAuthVehicle> authMap) {
|
EnergyAuthVehicle auth = authMap.get(key(vehicle.getPlateNo()));
|
if (auth == null) return null; // 运政缺失由 EXISTENCE_AUTH 规则提示
|
|
String field = rule.getCheckField();
|
if ("vehicleType".equals(field)) {
|
java.util.Set<String> allowed = ENERGY_VEHICLE_TYPE_ALLOWED.get(vehicle.getVehicleTypeCode());
|
if (allowed == null || !allowed.contains(normalizeVehicleType(auth.getVehicleType()))) {
|
return buildEnergyResult(rule, vehicle,
|
"能耗=" + valueOf(vehicle.getVehicleType()) + "(码" + valueOf(vehicle.getVehicleTypeCode())
|
+ "), 运政=" + valueOf(auth.getVehicleType()),
|
"车辆类型一致", "车辆类型不一致");
|
}
|
return null;
|
}
|
if ("fuelType".equals(field)) {
|
java.util.Set<String> allowed = ENERGY_FUEL_TYPE_ALLOWED.get(vehicle.getFuelTypeCode());
|
if (allowed == null || !allowed.contains(key(auth.getFuelType()))) {
|
return buildEnergyResult(rule, vehicle,
|
"能耗=" + valueOf(vehicle.getFuelType()) + "(码" + valueOf(vehicle.getFuelTypeCode())
|
+ "), 运政=" + valueOf(auth.getFuelType()),
|
"燃料类型一致", "燃料类型不一致");
|
}
|
return null;
|
}
|
if ("markedTonnage".equals(field)) {
|
double actual = nvl(vehicle.getMarkedTonnage());
|
double reference = "11".equals(vehicle.getVehicleTypeCode())
|
? nvl(auth.getTractionQuality())
|
: nvl(auth.getMarkedTonnage());
|
double threshold = Double.parseDouble(rule.getThreshold());
|
double diff = Math.abs(actual - reference);
|
if (diff > threshold) {
|
String refName = "11".equals(vehicle.getVehicleTypeCode()) ? "准牵引质量" : "标记吨位";
|
return buildEnergyResult(rule, vehicle,
|
"能耗标记吨位=" + fmt(actual) + ", 运政" + refName + "=" + fmt(reference),
|
"差距≤" + fmt(threshold), "差距=" + fmt(diff));
|
}
|
return null;
|
}
|
return null;
|
}
|
|
/** 能耗明细车牌在运政车辆信息中不存在时提示 */
|
private AuditResult checkEnergyExistence(AuditRule rule, EnergyVehicleQuarterly vehicle,
|
Map<String, EnergyAuthVehicle> authMap) {
|
EnergyAuthVehicle auth = authMap.get(key(vehicle.getPlateNo()));
|
if (auth == null) {
|
return buildEnergyResult(rule, vehicle,
|
"车牌号=" + vehicle.getPlateNo(), "运政车辆信息存在", "运政车辆信息缺失");
|
}
|
return null;
|
}
|
|
/** E005 载货里程占比 vs 平均运距/单趟平均里程 相差±10% */
|
private AuditResult checkEnergyLoadedRatio(AuditRule rule, EnergyVehicleQuarterly vehicle) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.1);
|
double total = nvl(vehicle.getTotalMileage());
|
double loaded = nvl(vehicle.getLoadedMileage());
|
double avgDist = nvl(vehicle.getAvgDistance());
|
double avgTrip = nvl(vehicle.getAvgTripMileage());
|
if (total <= 0 || avgTrip <= 0 || avgDist <= 0) return null;
|
double r1 = loaded / total;
|
double r2 = avgDist / avgTrip;
|
if (r2 <= 0) return null;
|
double diff = Math.abs(r1 - r2) / r2;
|
if (diff <= threshold) return null;
|
return buildEnergyResult(rule, vehicle,
|
"载货里程/总行程=" + fmt4(r1) + ",平均运距/单趟平均里程=" + fmt4(r2),
|
"两者相差≤" + fmt(threshold * 100) + "%",
|
"相差=" + fmt(diff * 100) + "%");
|
}
|
|
/** E006 同燃料类型百公里单耗偏离中位数超阈值(默认 50%) */
|
private AuditResult checkEnergyFuelRange(AuditRule rule, EnergyVehicleQuarterly vehicle,
|
Map<String, Double> fuelMedianMap) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.5);
|
Double v = vehicle.getFuelPer100km();
|
if (v == null || v <= 0 || vehicle.getFuelTypeCode() == null) return null;
|
Double median = fuelMedianMap.get(vehicle.getFuelTypeCode().trim());
|
if (median == null || median <= 0) return null;
|
double diff = Math.abs(v - median) / median;
|
if (diff <= threshold) return null;
|
return buildEnergyResult(rule, vehicle,
|
"百公里单耗=" + fmt(v) + "(" + valueOf(vehicle.getFuelType()) + "),同燃料中位数=" + fmt(median),
|
"偏离中位数≤" + fmt(threshold * 100) + "%",
|
"偏离=" + fmt(diff * 100) + "%");
|
}
|
|
/** E007 同一辆车百公里单耗环比变化超阈值(默认 ±50%) */
|
private AuditResult checkEnergyFuelMom(AuditRule rule, EnergyVehicleQuarterly vehicle,
|
Map<String, EnergyVehicleQuarterly> lastQuarterMap) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.5);
|
Double cur = vehicle.getFuelPer100km();
|
if (cur == null || cur <= 0) return null;
|
EnergyVehicleQuarterly last = lastQuarterMap.get(key(vehicle.getPlateNo()));
|
if (last == null || last.getFuelPer100km() == null || last.getFuelPer100km() <= 0) return null;
|
double diff = Math.abs(cur - last.getFuelPer100km()) / last.getFuelPer100km();
|
if (diff <= threshold) return null;
|
return buildEnergyResult(rule, vehicle,
|
"本期百公里单耗=" + fmt(cur) + ",上季度=" + fmt(last.getFuelPer100km()),
|
"环比变化≤" + fmt(threshold * 100) + "%",
|
"环比变化=" + fmt(diff * 100) + "%");
|
}
|
|
/** E008 能耗货运量/周转量与 H2032 月报对比超阈值(默认 50%) */
|
private AuditResult checkEnergyVsFreightReport(AuditRule rule, EnergyVehicleQuarterly vehicle,
|
Map<String, H2032EnterpriseMonthly> h2032Map) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.5);
|
String ent = vehicle.getEnterpriseName();
|
if (ent == null || ent.trim().isEmpty()) return null;
|
H2032EnterpriseMonthly m = h2032Map.get(ent.trim());
|
if (m == null) return null;
|
List<String> msgs = new ArrayList<>();
|
if (m.getFreightTotal() != null && m.getFreightTotal() > 0 && vehicle.getFreight() != null) {
|
double r = vehicle.getFreight() / m.getFreightTotal();
|
if (r > threshold) {
|
msgs.add("能耗货运量=" + fmt(vehicle.getFreight()) + ",月报=" + fmt(m.getFreightTotal())
|
+ ",占比=" + fmt4(r));
|
}
|
}
|
if (m.getTurnoverTotal() != null && m.getTurnoverTotal() > 0 && vehicle.getTurnover() != null) {
|
double r = vehicle.getTurnover() / m.getTurnoverTotal();
|
if (r > threshold) {
|
msgs.add("能耗周转量=" + fmt(vehicle.getTurnover()) + ",月报=" + fmt(m.getTurnoverTotal())
|
+ ",占比=" + fmt4(r));
|
}
|
}
|
if (msgs.isEmpty()) return null;
|
return buildEnergyResult(rule, vehicle, String.join(";", msgs),
|
"能耗量≤月报" + fmt(threshold * 100) + "%", "超过月报" + fmt(threshold * 100) + "%");
|
}
|
|
/** E009 车辆平均运距与 H2032 月报平均运距相差超阈值(默认 ±60%) */
|
private AuditResult checkEnergyAvgDistVsFreight(AuditRule rule, EnergyVehicleQuarterly vehicle,
|
Map<String, H2032EnterpriseMonthly> h2032Map) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.6);
|
String ent = vehicle.getEnterpriseName();
|
if (ent == null || ent.trim().isEmpty()) return null;
|
H2032EnterpriseMonthly m = h2032Map.get(ent.trim());
|
if (m == null || m.getAvgDistance() == null || m.getAvgDistance() <= 0) return null;
|
double cur = nvl(vehicle.getAvgDistance());
|
if (cur <= 0) return null;
|
double diff = Math.abs(cur - m.getAvgDistance()) / m.getAvgDistance();
|
if (diff <= threshold) return null;
|
return buildEnergyResult(rule, vehicle,
|
"能耗平均运距=" + fmt(cur) + ",月报=" + fmt(m.getAvgDistance()),
|
"相差≤" + fmt(threshold * 100) + "%",
|
"相差=" + fmt(diff * 100) + "%");
|
}
|
|
/** E010 平均载货吨位/标记吨位 比值超出 [0.5, 1] */
|
private AuditResult checkEnergyLoadedTonnageRatio(AuditRule rule, EnergyVehicleQuarterly vehicle) {
|
double lo = 0.5, hi = 1.0;
|
String t = rule.getThreshold();
|
if (t != null && t.contains(",")) {
|
String[] parts = t.split(",");
|
try {
|
lo = Double.parseDouble(parts[0].trim());
|
hi = Double.parseDouble(parts[1].trim());
|
} catch (Exception ignore) {
|
}
|
}
|
double marked = nvl(vehicle.getMarkedTonnage());
|
double loaded = nvl(vehicle.getAvgLoadedTonnage());
|
if (marked <= 0 || loaded <= 0) return null;
|
double r = loaded / marked;
|
if (r >= lo && r <= hi) return null;
|
return buildEnergyResult(rule, vehicle,
|
"平均载货吨位=" + fmt(loaded) + ",标记吨位=" + fmt(marked) + ",比值=" + fmt4(r),
|
"比值在[" + fmt(lo) + "," + fmt(hi) + "]",
|
"比值=" + fmt4(r) + " 超出范围");
|
}
|
|
/** 上一季度报表期:2026-06 → 2026-03 */
|
private String prevQuarter(String period) {
|
if (period == null || period.length() < 7) return null;
|
try {
|
int year = Integer.parseInt(period.substring(0, 4));
|
int month = Integer.parseInt(period.substring(5, 7));
|
int pm = month - 3;
|
int py = year;
|
if (pm <= 0) {
|
pm += 12;
|
py -= 1;
|
}
|
return String.format("%04d-%02d", py, pm);
|
} catch (Exception e) {
|
return null;
|
}
|
}
|
|
/** 本期各燃料类型百公里单耗中位数 */
|
private Map<String, Double> fuelPer100kmMedian(List<EnergyVehicleQuarterly> vehicles) {
|
Map<String, List<Double>> byFuel = new HashMap<>();
|
for (EnergyVehicleQuarterly v : vehicles) {
|
if (v.getFuelTypeCode() == null || v.getFuelPer100km() == null || v.getFuelPer100km() <= 0) continue;
|
byFuel.computeIfAbsent(v.getFuelTypeCode().trim(), k -> new ArrayList<>()).add(v.getFuelPer100km());
|
}
|
Map<String, Double> med = new HashMap<>();
|
for (Map.Entry<String, List<Double>> e : byFuel.entrySet()) {
|
List<Double> list = e.getValue();
|
list.sort(null);
|
double m = list.size() % 2 == 1 ? list.get(list.size() / 2)
|
: (list.get(list.size() / 2 - 1) + list.get(list.size() / 2)) / 2.0;
|
med.put(e.getKey(), m);
|
}
|
return med;
|
}
|
|
private Map<String, EnergyAuthVehicle> loadEnergyAuthMap(String reportPeriod) {
|
Map<String, EnergyAuthVehicle> map = new HashMap<>();
|
List<EnergyAuthVehicle> list = energyAuthMapper.selectList(
|
new LambdaQueryWrapper<EnergyAuthVehicle>()
|
.eq(EnergyAuthVehicle::getReportPeriod, reportPeriod));
|
for (EnergyAuthVehicle v : list) {
|
map.putIfAbsent(key(v.getPlateNo()), v);
|
}
|
return map;
|
}
|
|
/** 运政车辆类型去掉 重型/中型/轻型 前缀后比对 */
|
private String normalizeVehicleType(String type) {
|
if (type == null) return "";
|
String t = type.trim();
|
for (String prefix : new String[]{"重型", "中型", "轻型"}) {
|
if (t.startsWith(prefix)) {
|
t = t.substring(prefix.length());
|
break;
|
}
|
}
|
return t;
|
}
|
|
private String valueOf(String v) {
|
return v == null ? "空" : v.trim();
|
}
|
|
private AuditResult buildEnergyResult(AuditRule rule, EnergyVehicleQuarterly vehicle,
|
String actual, String threshold, String deviation) {
|
AuditResult result = new AuditResult();
|
result.setRuleId(rule.getId());
|
result.setReportId(vehicle.getId());
|
result.setEnterpriseCode(vehicle.getEnterpriseCode());
|
result.setReportPeriod(vehicle.getReportPeriod());
|
result.setActualValue(actual);
|
result.setThresholdValue(threshold);
|
result.setDeviation(deviation);
|
result.setStatus("PENDING");
|
return result;
|
}
|
|
|
// ========== INVEST 投资审核(汇总大表 与 投资系统导出 跨表对比) ==========
|
|
public List<AuditResult> executeInvestmentAudit(String reportPeriod) {
|
List<AuditRule> rules = rulesOf("INVEST");
|
List<InvestmentMonthly> monthlies = investMonthlyMapper.selectList(
|
new LambdaQueryWrapper<InvestmentMonthly>()
|
.eq(InvestmentMonthly::getReportPeriod, reportPeriod));
|
Map<Long, InvestmentProject> projectMap = new HashMap<>();
|
for (InvestmentProject p : investProjectMapper.selectList(null)) {
|
projectMap.put(p.getId(), p);
|
}
|
Map<String, List<InvestmentSystem>> sysMap = new HashMap<>();
|
for (InvestmentSystem s : investSystemMapper.selectList(
|
new LambdaQueryWrapper<InvestmentSystem>()
|
.eq(InvestmentSystem::getReportPeriod, reportPeriod))) {
|
sysMap.computeIfAbsent(normInvestName(s.getProjectName()), k -> new ArrayList<>()).add(s);
|
}
|
|
// 幂等:只清除 INVEST 规则的旧审核结果
|
List<Long> ruleIds = new ArrayList<>();
|
for (AuditRule rule : rules) ruleIds.add(rule.getId());
|
if (!ruleIds.isEmpty()) {
|
resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
|
.eq(AuditResult::getReportPeriod, reportPeriod)
|
.in(AuditResult::getRuleId, ruleIds));
|
}
|
|
List<AuditResult> results = new ArrayList<>();
|
for (InvestmentMonthly monthly : monthlies) {
|
InvestmentProject project = projectMap.get(monthly.getProjectId());
|
String projectName = project == null ? "未知项目" : project.getProjectName();
|
InvestmentSystem sys = findInvestSystem(sysMap, projectName);
|
for (AuditRule rule : rules) {
|
AuditResult result = checkInvestRule(rule, monthly, projectName, sys);
|
if (result != null) {
|
result.setRuleName(rule.getRuleName());
|
result.setRuleCode(rule.getRuleCode());
|
result.setAlertLevel(rule.getAlertLevel());
|
result.setEnterpriseName(projectName);
|
result.setVerifyExplanation(null);
|
resultMapper.insert(result);
|
results.add(result);
|
}
|
}
|
}
|
log.info("Investment audit complete: {} projects, {} issues for {}", monthlies.size(), results.size(), reportPeriod);
|
if (!rules.isEmpty() && !monthlies.isEmpty()) markAudited("INVEST", reportPeriod);
|
return results;
|
}
|
|
private AuditResult checkInvestRule(AuditRule rule, InvestmentMonthly monthly,
|
String projectName, InvestmentSystem sys) {
|
try {
|
switch (rule.getCompareType()) {
|
case "CROSS_DIFF":
|
return checkInvestCrossDiff(rule, monthly, projectName, sys);
|
case "EXISTENCE_SYS":
|
return checkInvestExistence(rule, monthly, projectName, sys);
|
default:
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("Investment rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
|
return null;
|
}
|
}
|
|
/** 与投资系统导出数值对比:阈值格式 "绝对差|相对差%" */
|
private AuditResult checkInvestCrossDiff(AuditRule rule, InvestmentMonthly monthly,
|
String projectName, InvestmentSystem sys) {
|
if (sys == null) return null; // 缺失由 EXISTENCE_SYS 规则提示
|
String field = rule.getCheckField();
|
double actual;
|
double reference;
|
String fieldName;
|
if ("startCum".equals(field)) {
|
actual = nvl(monthly.getStartCum());
|
reference = nvl(sys.getStartCum());
|
fieldName = "自开始建设累计";
|
} else if ("yearCum".equals(field)) {
|
actual = nvl(monthly.getYearCum());
|
reference = nvl(sys.getYearCum());
|
fieldName = "自年初累计";
|
} else if ("monthDone".equals(field)) {
|
actual = nvl(monthly.getMonthDone());
|
reference = nvl(sys.getMonthDone());
|
fieldName = "本月完成";
|
} else {
|
return null;
|
}
|
String[] parts = rule.getThreshold().split("\\|");
|
double absThreshold = parts.length > 0 ? parseDoubleSafe(parts[0], 50) : 50;
|
double pctThreshold = parts.length > 1 ? parseDoubleSafe(parts[1], 5) : 5;
|
double diff = Math.abs(actual - reference);
|
double base = Math.max(Math.abs(reference), 1.0);
|
double pct = diff / base * 100;
|
boolean hit = diff > absThreshold && pct > pctThreshold;
|
if (!hit && reference == 0 && actual > absThreshold) {
|
hit = true;
|
}
|
if (hit) {
|
return buildInvestResult(rule, monthly,
|
"汇总大表" + fieldName + "=" + fmt(actual) + ", 投资系统=" + fmt(reference),
|
"绝对差≤" + fmt(absThreshold) + "万 且 相对差≤" + fmt(pctThreshold) + "%",
|
"绝对差=" + fmt(diff) + "万, 相对差=" + fmt(Math.round(pct * 10) / 10.0) + "%");
|
}
|
return null;
|
}
|
|
/** 汇总大表项目在投资系统导出中不存在 */
|
private AuditResult checkInvestExistence(AuditRule rule, InvestmentMonthly monthly,
|
String projectName, InvestmentSystem sys) {
|
if (sys == null) {
|
return buildInvestResult(rule, monthly,
|
"项目名称=" + projectName, "投资系统导出中存在", "投资系统导出中不存在");
|
}
|
return null;
|
}
|
|
private AuditResult buildInvestResult(AuditRule rule, InvestmentMonthly monthly,
|
String actual, String threshold, String deviation) {
|
AuditResult result = new AuditResult();
|
result.setRuleId(rule.getId());
|
result.setReportId(monthly.getId());
|
result.setReportPeriod(monthly.getReportPeriod());
|
result.setActualValue(actual);
|
result.setThresholdValue(threshold);
|
result.setDeviation(deviation);
|
result.setStatus("PENDING");
|
return result;
|
}
|
|
// ========== CITY_BUS 城市公交审核(4 组规则,企业级) ==========
|
|
public List<AuditResult> executeCityBusAudit(String reportPeriod) {
|
List<AuditRule> rules = rulesOf("CITY_BUS");
|
List<CityBusMonthly> rows = cityBusMapper.selectList(
|
new LambdaQueryWrapper<CityBusMonthly>()
|
.eq(CityBusMonthly::getReportPeriod, reportPeriod));
|
|
// 幂等:只清除 CITY_BUS 规则的旧审核结果
|
List<Long> ruleIds = new ArrayList<>();
|
for (AuditRule rule : rules) ruleIds.add(rule.getId());
|
if (!ruleIds.isEmpty()) {
|
resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
|
.eq(AuditResult::getReportPeriod, reportPeriod)
|
.in(AuditResult::getRuleId, ruleIds));
|
}
|
|
List<AuditResult> results = new ArrayList<>();
|
for (CityBusMonthly row : rows) {
|
// 无公交经营活动企业(运营车数/客运量/周转量均 0)跳过
|
if (nvl(row.getOpVehicles()) <= 0 && nvl(row.getPassengerVolume()) <= 0
|
&& nvl(row.getTurnover()) <= 0) {
|
continue;
|
}
|
for (AuditRule rule : rules) {
|
AuditResult result = checkCityBusRule(rule, row);
|
if (result != null) {
|
result.setRuleName(rule.getRuleName());
|
result.setRuleCode(rule.getRuleCode());
|
result.setAlertLevel(rule.getAlertLevel());
|
result.setEnterpriseName(row.getEnterpriseName());
|
result.setVerifyExplanation(row.getVerifyExplanation());
|
resultMapper.insert(result);
|
results.add(result);
|
}
|
}
|
}
|
log.info("CityBus audit complete: {} enterprises, {} issues for {}", rows.size(), results.size(), reportPeriod);
|
if (!rules.isEmpty() && !rows.isEmpty()) markAudited("CITY_BUS", reportPeriod);
|
return results;
|
}
|
|
private AuditResult checkCityBusRule(AuditRule rule, CityBusMonthly row) {
|
try {
|
switch (rule.getCompareType()) {
|
case "TREND_CONSISTENCY":
|
return checkCityBusTrend(rule, row);
|
case "RATIO_CHANGE":
|
return checkCityBusRatioChange(rule, row);
|
case "AVG_DIST_CHANGE":
|
return checkCityBusAvgDistChange(rule, row);
|
default:
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("CityBus rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
|
return null;
|
}
|
}
|
|
/** 趋势一致:check_field 中成对字段(环比/同比)趋势不一致且增速超阈值需核实 */
|
private AuditResult checkCityBusTrend(AuditRule rule, CityBusMonthly row) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.02);
|
String field = rule.getCheckField() == null ? "" : rule.getCheckField();
|
List<String> msgs = new ArrayList<>();
|
if (field.contains("passengerCityMom")) {
|
checkTrendPair(msgs, "城市内客运量环比", row.getRatePassengerCityMom(),
|
"城际城乡客运量环比", row.getRateChengxiangMom(), threshold);
|
}
|
if (field.contains("passengerCityYoy")) {
|
checkTrendPair(msgs, "城市内客运量同比", row.getRatePassengerCityYoy(),
|
"城际城乡客运量同比", row.getRateChengxiangYoy(), threshold);
|
}
|
if (field.contains("passengerMom")) {
|
checkTrendPair(msgs, "客运量环比", row.getRatePassengerMom(),
|
"旅客周转量环比", row.getRateTurnoverMom(), threshold);
|
}
|
if (field.contains("passengerYoy")) {
|
checkTrendPair(msgs, "客运量同比", row.getRatePassengerYoy(),
|
"旅客周转量同比", row.getRateTurnoverYoy(), threshold);
|
}
|
if (msgs.isEmpty()) return null;
|
return buildCityBusResult(rule, row, String.join(";", msgs),
|
"两指标环比/同比趋势一致", "趋势不一致且增速超阈值");
|
}
|
|
private void checkTrendPair(List<String> msgs, String aName, Double aVal,
|
String bName, Double bVal, double threshold) {
|
double a = nvl(aVal);
|
double b = nvl(bVal);
|
boolean aUp = a > 0, bUp = b > 0;
|
boolean aDown = a < 0, bDown = b < 0;
|
if ((aUp && bDown) || (aDown && bUp)) {
|
double maxAbs = Math.max(Math.abs(a), Math.abs(b));
|
if (maxAbs > threshold) {
|
msgs.add(aName + "=" + fmt(a) + ", " + bName + "=" + fmt(b) + "(趋势不一致)");
|
}
|
}
|
}
|
|
/** 占比变化:本月占比 vs 由环比增速反推的上月占比,变化>1% 需核实 */
|
private AuditResult checkCityBusRatioChange(AuditRule rule, CityBusMonthly row) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.01);
|
String field = rule.getCheckField() == null ? "" : rule.getCheckField();
|
List<String> msgs = new ArrayList<>();
|
if (field.contains("passengerCityRatio")) {
|
Double change = cityBusRatioChange(row.getPassengerCityRatio(),
|
row.getPassengerCity(), row.getRatePassengerCityMom(),
|
row.getPassengerVolume(), row.getRatePassengerMom());
|
if (change != null && change > threshold) {
|
msgs.add("城市内客运量占比变化=" + fmt4(change) + "(>1%)");
|
}
|
}
|
if (field.contains("chengxiangRatio")) {
|
Double change = cityBusRatioChange(row.getChengxiangRatio(),
|
row.getPassengerChengxiang(), row.getRateChengxiangMom(),
|
row.getPassengerVolume(), row.getRatePassengerMom());
|
if (change != null && change > threshold) {
|
msgs.add("城际城乡客运量占比变化=" + fmt4(change) + "(>1%)");
|
}
|
}
|
if (msgs.isEmpty()) return null;
|
return buildCityBusResult(rule, row, String.join(";", msgs),
|
"占比变化≤1%", "占比变化>1%");
|
}
|
|
/** 占比环比变化:本月占比 vs 上月占比(由环比增速反推);无法推算返回 null */
|
private Double cityBusRatioChange(Double curRatio, Double curValue, Double curRate,
|
Double curTotal, Double totalRate) {
|
if (curRatio == null) return null;
|
double r = nvl(curRate);
|
double tr = nvl(totalRate);
|
if (r <= -1.0 || tr <= -1.0) return null;
|
double lastRatio = (nvl(curValue) / (1 + r)) / (nvl(curTotal) / (1 + tr));
|
if (Double.isNaN(lastRatio) || Double.isInfinite(lastRatio)) return null;
|
return Math.abs(curRatio - lastRatio);
|
}
|
|
/** 平均运距变化:环比增减(公里)或城际城乡由 周转量/客运量 环比反推,超过 3 公里需核实 */
|
private AuditResult checkCityBusAvgDistChange(AuditRule rule, CityBusMonthly row) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 3.0);
|
String field = rule.getCheckField() == null ? "" : rule.getCheckField();
|
List<String> msgs = new ArrayList<>();
|
if (field.contains("avgDistanceMomChange")) {
|
double chg = Math.abs(nvl(row.getAvgDistanceMomChange()));
|
if (chg > threshold) {
|
msgs.add("平均运距环比增减=" + fmt(nvl(row.getAvgDistanceMomChange())) + "公里");
|
}
|
}
|
if (field.contains("avgDistanceCityMomChange")) {
|
double chg = Math.abs(nvl(row.getAvgDistanceCityMomChange()));
|
if (chg > threshold) {
|
msgs.add("城市内平均运距环比增减=" + fmt(nvl(row.getAvgDistanceCityMomChange())) + "公里");
|
}
|
}
|
if (field.contains("avgDistanceChengxiang")) {
|
double cur = nvl(row.getAvgDistanceChengxiang());
|
double km = nvl(row.getRateChengxiangMom());
|
double kt = nvl(row.getRateChengxiangTurnoverMom());
|
if (cur > 0 && km > -1.0 && kt > -1.0) {
|
double last = cur * (1 + km) / (1 + kt);
|
double chg = Math.abs(cur - last);
|
if (chg > threshold) {
|
msgs.add("城际城乡平均运距变化=" + fmt(chg) + "公里(本月" + fmt(cur)
|
+ " vs 上月" + fmt(last) + ")");
|
}
|
}
|
}
|
if (msgs.isEmpty()) return null;
|
return buildCityBusResult(rule, row, String.join(";", msgs),
|
"平均运距变化≤" + fmt(threshold) + "公里", "平均运距变化>" + fmt(threshold) + "公里");
|
}
|
|
private AuditResult buildCityBusResult(AuditRule rule, CityBusMonthly row,
|
String actual, String threshold, String deviation) {
|
AuditResult result = new AuditResult();
|
result.setRuleId(rule.getId());
|
result.setReportId(row.getId());
|
result.setReportPeriod(row.getReportPeriod());
|
result.setActualValue(actual);
|
result.setThresholdValue(threshold);
|
result.setDeviation(deviation);
|
result.setStatus("PENDING");
|
return result;
|
}
|
|
// ========== CITY_TAXI 巡游出租审核(7 组规则,市州级) ==========
|
|
public List<AuditResult> executeCityTaxiAudit(String reportPeriod) {
|
List<AuditRule> rules = rulesOf("CITY_TAXI");
|
List<CityTaxiMonthly> rows = cityTaxiMapper.selectList(
|
new LambdaQueryWrapper<CityTaxiMonthly>()
|
.eq(CityTaxiMonthly::getReportPeriod, reportPeriod));
|
|
// 幂等:只清除 CITY_TAXI 规则的旧审核结果
|
List<Long> ruleIds = new ArrayList<>();
|
for (AuditRule rule : rules) ruleIds.add(rule.getId());
|
if (!ruleIds.isEmpty()) {
|
resultMapper.delete(new LambdaQueryWrapper<AuditResult>()
|
.eq(AuditResult::getReportPeriod, reportPeriod)
|
.in(AuditResult::getRuleId, ruleIds));
|
}
|
|
// 运政车辆按市州计数(T001 对比基准)
|
Map<String, Long> authCount = loadTaxiAuthCount(reportPeriod);
|
|
List<AuditResult> results = new ArrayList<>();
|
for (CityTaxiMonthly row : rows) {
|
if (row.getCity() == null || row.getCity().trim().isEmpty()) continue;
|
// 无经营活动市州(运营车数/客运量/周转量均 0)跳过
|
if (nvl(row.getOpVehicles()) <= 0 && nvl(row.getPassengerVolume()) <= 0
|
&& nvl(row.getTurnover()) <= 0) {
|
continue;
|
}
|
for (AuditRule rule : rules) {
|
AuditResult result = checkCityTaxiRule(rule, row, authCount);
|
if (result != null) {
|
result.setRuleName(rule.getRuleName());
|
result.setRuleCode(rule.getRuleCode());
|
result.setAlertLevel(rule.getAlertLevel());
|
result.setEnterpriseName(row.getCity());
|
result.setVerifyExplanation(row.getVerifyExplanation());
|
resultMapper.insert(result);
|
results.add(result);
|
}
|
}
|
}
|
log.info("CityTaxi audit complete: {} cities, {} issues for {}", rows.size(), results.size(), reportPeriod);
|
if (!rules.isEmpty() && !rows.isEmpty()) markAudited("CITY_TAXI", reportPeriod);
|
return results;
|
}
|
|
private Map<String, Long> loadTaxiAuthCount(String reportPeriod) {
|
Map<String, Long> map = new HashMap<>();
|
List<CityTaxiAuth> auths = cityTaxiAuthMapper.selectList(
|
new LambdaQueryWrapper<CityTaxiAuth>()
|
.eq(CityTaxiAuth::getReportPeriod, reportPeriod));
|
for (CityTaxiAuth a : auths) {
|
if (a.getCity() == null || a.getCity().trim().isEmpty()) continue;
|
map.merge(a.getCity(), 1L, Long::sum);
|
}
|
return map;
|
}
|
|
private AuditResult checkCityTaxiRule(AuditRule rule, CityTaxiMonthly row, Map<String, Long> authCount) {
|
try {
|
switch (rule.getCompareType()) {
|
case "CROSS_DIFF_PCT":
|
return checkTaxiAuthDiff(rule, row, authCount);
|
case "TREND_CONSISTENCY":
|
return checkCityTaxiTrend(rule, row);
|
case "RATIO_CHANGE":
|
return checkCityTaxiRatioChange(rule, row);
|
case "AVG_DIST_CHANGE":
|
return checkCityTaxiAvgDistChange(rule, row);
|
case "PAX_PER_TRIP_CHANGE":
|
return checkTaxiPaxPerTripChange(rule, row);
|
default:
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("CityTaxi rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
|
return null;
|
}
|
}
|
|
/** T001 运营车数 vs 运政在营车辆数:差异率>3% 需核实(RED) */
|
private AuditResult checkTaxiAuthDiff(AuditRule rule, CityTaxiMonthly row, Map<String, Long> authCount) {
|
// 整期未导入运政数据时跳过对比(避免把“运政=0”误当基准报差异)
|
if (authCount == null || authCount.isEmpty()) return null;
|
double threshold = parseDoubleSafe(rule.getThreshold(), 3.0);
|
double monthly = nvl(row.getOpVehicles());
|
Long auth = authCount.get(row.getCity());
|
long authV = auth == null ? 0 : auth;
|
if (monthly <= 0 && authV <= 0) return null;
|
double diffPct = authV <= 0 ? 100.0 : Math.abs(monthly - authV) / authV * 100.0;
|
if (diffPct <= threshold) return null;
|
return buildTaxiResult(rule, row,
|
"月报运营车辆数=" + fmt(monthly) + ",运政在营车辆数=" + authV + ",差异率=" + fmt(diffPct) + "%",
|
"差异率≤" + fmt(threshold) + "%",
|
"差异率=" + fmt(diffPct) + "%>" + fmt(threshold) + "%");
|
}
|
|
/** T002/T003/T007 趋势一致:check_field 中成对字段(环比/同比)趋势不一致且增速超阈值需核实 */
|
private AuditResult checkCityTaxiTrend(AuditRule rule, CityTaxiMonthly row) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.02);
|
String field = rule.getCheckField() == null ? "" : rule.getCheckField();
|
List<String> msgs = new ArrayList<>();
|
if (field.contains("passengerCityMom")) {
|
checkTrendPair(msgs, "城市内客运量环比", row.getRatePassengerCityMom(),
|
"城际城乡客运量环比", row.getRatePassengerChengxiangMom(), threshold);
|
}
|
if (field.contains("turnoverMomDerived")) {
|
// 月报无直接周转量环比列:周转量=客运量×平均运距,由两者环比推导
|
Double rateTurnover = taxiTurnoverMomDerived(row);
|
if (rateTurnover != null) {
|
checkTrendPair(msgs, "客运量环比", row.getRatePassengerMom(),
|
"旅客周转量环比(推导)", rateTurnover, threshold);
|
}
|
}
|
if (field.contains("tripMom")) {
|
checkTrendPair(msgs, "载客车次总数环比", row.getRateTripMom(),
|
"客运量环比", row.getRatePassengerMom(), threshold);
|
}
|
if (field.contains("tripYoy")) {
|
checkTrendPair(msgs, "载客车次同比", row.getRateTripYoy(),
|
"客运量同比", row.getRatePassengerYoy(), threshold);
|
}
|
if (msgs.isEmpty()) return null;
|
return buildTaxiResult(rule, row, String.join(";", msgs),
|
"两指标环比/同比趋势一致", "趋势不一致且增速超阈值");
|
}
|
|
/** 推导周转量环比:rate≈(1+客运量环比)×(1+平均运距环比)-1,平均运距环比由上期运距=本期−增减推算 */
|
private Double taxiTurnoverMomDerived(CityTaxiMonthly row) {
|
double dist = nvl(row.getAvgDistance());
|
double chg = nvl(row.getAvgDistanceMomChange());
|
double rp = nvl(row.getRatePassengerMom());
|
if (dist <= 0 || dist - chg <= 0 || rp <= -1.0) return null;
|
double rd = chg / (dist - chg);
|
return (1 + rp) * (1 + rd) - 1;
|
}
|
|
/** T004 占比变化:城市内客运量占比变化>1% 需核实 */
|
private AuditResult checkCityTaxiRatioChange(AuditRule rule, CityTaxiMonthly row) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.01);
|
String field = rule.getCheckField() == null ? "" : rule.getCheckField();
|
List<String> msgs = new ArrayList<>();
|
if (field.contains("passengerCityRatio")) {
|
Double change = cityBusRatioChange(row.getPassengerCityRatio(),
|
row.getPassengerCity(), row.getRatePassengerCityMom(),
|
row.getPassengerVolume(), row.getRatePassengerMom());
|
if (change != null && change > threshold) {
|
msgs.add("城市内客运量占比变化=" + fmt4(change) + "(>1%)");
|
}
|
}
|
if (msgs.isEmpty()) return null;
|
return buildTaxiResult(rule, row, String.join(";", msgs),
|
"占比变化≤1%", "占比变化>1%");
|
}
|
|
/** T005 平均运距变化:环比增减(公里)或城际城乡由平均运距增速反推,超过 3 公里需核实 */
|
private AuditResult checkCityTaxiAvgDistChange(AuditRule rule, CityTaxiMonthly row) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 3.0);
|
String field = rule.getCheckField() == null ? "" : rule.getCheckField();
|
List<String> msgs = new ArrayList<>();
|
if (field.contains("avgDistanceMomChange")) {
|
double chg = Math.abs(nvl(row.getAvgDistanceMomChange()));
|
if (chg > threshold) {
|
msgs.add("平均运距环比增减=" + fmt(nvl(row.getAvgDistanceMomChange())) + "公里");
|
}
|
}
|
if (field.contains("avgDistanceCityMomChange")) {
|
double chg = Math.abs(nvl(row.getAvgDistanceCityMomChange()));
|
if (chg > threshold) {
|
msgs.add("城市内平均运距环比增减=" + fmt(nvl(row.getAvgDistanceCityMomChange())) + "公里");
|
}
|
}
|
if (field.contains("avgDistanceChengxiang")) {
|
double cur = nvl(row.getAvgDistanceChengxiang());
|
double dist = nvl(row.getAvgDistance());
|
double chg = nvl(row.getAvgDistanceMomChange());
|
if (cur > 0 && dist > 0 && dist > chg) {
|
double rd = chg / (dist - chg);
|
double last = cur / (1 + rd);
|
double diff = Math.abs(cur - last);
|
if (diff > threshold) {
|
msgs.add("城际城乡平均运距变化=" + fmt(diff) + "公里(本月" + fmt(cur)
|
+ " vs 上月" + fmt(last) + ")");
|
}
|
}
|
}
|
if (msgs.isEmpty()) return null;
|
return buildTaxiResult(rule, row, String.join(";", msgs),
|
"平均运距变化≤" + fmt(threshold) + "公里", "平均运距变化>" + fmt(threshold) + "公里");
|
}
|
|
/** T006 单车次载客人数变化:由上期(载客车次/客运量环比)反推,变化>0.2人 需核实 */
|
private AuditResult checkTaxiPaxPerTripChange(AuditRule rule, CityTaxiMonthly row) {
|
double threshold = parseDoubleSafe(rule.getThreshold(), 0.2);
|
String field = rule.getCheckField() == null ? "" : rule.getCheckField();
|
List<String> msgs = new ArrayList<>();
|
if (field.contains("passengersPerTrip")) {
|
double cur = nvl(row.getPassengersPerTrip());
|
double rp = nvl(row.getRatePassengerMom());
|
double rt = nvl(row.getRateTripMom());
|
if (cur > 0 && rp > -1.0 && rt > -1.0) {
|
double last = cur * (1 + rt) / (1 + rp);
|
double diff = Math.abs(cur - last);
|
if (diff > threshold) {
|
msgs.add("单车次载客人数变化=" + fmt(diff) + "人(本月" + fmt(cur)
|
+ " vs 上月" + fmt(last) + ")");
|
}
|
}
|
}
|
if (msgs.isEmpty()) return null;
|
return buildTaxiResult(rule, row, String.join(";", msgs),
|
"单车次载客人数变化≤" + fmt(threshold) + "人", "单车次载客人数变化>" + fmt(threshold) + "人");
|
}
|
|
private AuditResult buildTaxiResult(AuditRule rule, CityTaxiMonthly row,
|
String actual, String threshold, String deviation) {
|
AuditResult result = new AuditResult();
|
result.setRuleId(rule.getId());
|
result.setReportId(row.getId());
|
result.setReportPeriod(row.getReportPeriod());
|
result.setActualValue(actual);
|
result.setThresholdValue(threshold);
|
result.setDeviation(deviation);
|
result.setStatus("PENDING");
|
return result;
|
}
|
|
/** 项目名归一化:去括号字符、连接符,罗马数字统一,去省/市/县/区前缀与空白 */
|
private String normInvestName(String name) {
|
if (name == null) return "";
|
String s = name.trim();
|
s = s.replaceAll("[((]", "").replaceAll("[))]", "");
|
s = s.replaceAll("[·•—-_\\-]", "");
|
s = s.replaceAll("[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ]", "I");
|
s = s.replaceAll("^(湖北省|武汉市|黄石市|十堰市|宜昌市|襄阳市|鄂州市|荆门市|孝感市|荆州市|黄冈市|咸宁市|随州市|恩施州|仙桃市|潜江市|天门市|神农架林区)", "");
|
s = s.replaceAll("^(?:[\\u4e00-\\u9fa5]{2,4}(?:县|市|区))", "");
|
s = s.replaceAll("\\s+", "");
|
return s;
|
}
|
|
/** 名称归一化后精确匹配(同名多候选按县区消歧),失败再按评分做前缀/包含/子序列模糊匹配 */
|
private InvestmentSystem findInvestSystem(Map<String, List<InvestmentSystem>> sysMap, String projectName) {
|
String norm = normInvestName(projectName);
|
if (norm.isEmpty()) return null;
|
List<InvestmentSystem> exact = sysMap.get(norm);
|
if (exact != null && !exact.isEmpty()) {
|
return disambiguateInvest(exact, projectName);
|
}
|
InvestmentSystem best = null;
|
int bestScore = Integer.MIN_VALUE;
|
for (Map.Entry<String, List<InvestmentSystem>> e : sysMap.entrySet()) {
|
String key = e.getKey();
|
if (key.length() < 5 || norm.length() < 5) continue;
|
String shortSide = key.length() <= norm.length() ? key : norm;
|
String longSide = key.length() > norm.length() ? key : norm;
|
int idx = longSide.indexOf(shortSide);
|
int lenDiff = Math.abs(key.length() - norm.length());
|
int score;
|
if (idx == 0) {
|
// 前缀式包含(短名=长名开头)最可信
|
score = 1000 - lenDiff * 2;
|
} else if (idx > 0 && shortSide.length() >= 6) {
|
// 中后部包含:短名足够长才可信
|
score = 500 - idx * 5 - lenDiff * 2 + shortSide.length();
|
} else if (idx > 0 && shortSide.length() == 5 && lenDiff <= 5) {
|
// 5字短名中后部:差异部分不含 县/市/州 才匹配(排除 罗田县综合物流园 这类)
|
String diff = longSide.substring(0, idx) + longSide.substring(idx + shortSide.length());
|
if (diff.matches(".*(县|市|州).*")) continue;
|
score = 400 - idx * 5 - lenDiff;
|
} else if (isInvestSubsequence(shortSide, longSide) && lenDiff <= 8) {
|
// 子序列兜底:短名为长名去掉插入词后的子序列(如 综合枢纽 vs 综合客运枢纽、物流中心 vs 物流园中心)
|
score = 300 - lenDiff * 3;
|
} else {
|
continue;
|
}
|
if (score > bestScore) {
|
bestScore = score;
|
best = disambiguateInvest(e.getValue(), projectName);
|
}
|
}
|
return best;
|
}
|
|
/** 多个系统项目归一化后同名(如 咸丰县综合物流园/罗田县综合物流园 → 综合物流园),按县区前缀区分 */
|
private InvestmentSystem disambiguateInvest(List<InvestmentSystem> candidates, String projectName) {
|
if (candidates == null || candidates.isEmpty()) return null;
|
if (candidates.size() == 1) return candidates.get(0);
|
String county = extractCounty(projectName);
|
if (!county.isEmpty()) {
|
for (InvestmentSystem c : candidates) {
|
String raw = c.getProjectName() == null ? "" : c.getProjectName();
|
if (raw.contains(county)) return c;
|
}
|
}
|
return candidates.get(0);
|
}
|
|
/** 提取名称中的 县/市/区 前缀(如 咸丰县) */
|
private String extractCounty(String name) {
|
if (name == null) return "";
|
java.util.regex.Matcher m = java.util.regex.Pattern.compile("([\\u4e00-\\u9fa5]{2,4}(?:县|市|区))").matcher(name);
|
if (m.find()) return m.group(1);
|
return "";
|
}
|
|
/** 判断短名是否为长名的字符子序列(按顺序出现即可,允许中间插入词) */
|
private boolean isInvestSubsequence(String shortSide, String longSide) {
|
int i = 0;
|
for (int j = 0; i < shortSide.length() && j < longSide.length(); j++) {
|
if (shortSide.charAt(i) == longSide.charAt(j)) i++;
|
}
|
return i == shortSide.length();
|
}
|
|
private double parseDoubleSafe(String v, double def) {
|
try {
|
return Double.parseDouble(v.trim());
|
} catch (Exception e) {
|
return def;
|
}
|
}
|
|
public List<AuditResult> queryResults(String reportPeriod, String reportType) {
|
String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim();
|
List<AuditResult> results = resultMapper.selectList(new LambdaQueryWrapper<AuditResult>()
|
.eq(AuditResult::getReportPeriod, reportPeriod)
|
.orderByAsc(AuditResult::getId));
|
List<Long> ruleIds = new ArrayList<>();
|
for (AuditRule rule : ruleMapper.selectList(null)) {
|
if (type.equals(rule.getReportType())) ruleIds.add(rule.getId());
|
}
|
List<AuditResult> filtered = new ArrayList<>();
|
for (AuditResult result : results) {
|
if (ruleIds.contains(result.getRuleId())) filtered.add(result);
|
}
|
attachInfo(filtered);
|
return filtered;
|
}
|
|
public void reviewResult(Long id, String status, String comment) {
|
AuditResult result = resultMapper.selectById(id);
|
if (result == null) return;
|
result.setStatus(status);
|
result.setReviewComment(comment);
|
result.setReviewedAt(LocalDateTime.now());
|
resultMapper.updateById(result);
|
}
|
|
private void attachInfo(List<AuditResult> results) {
|
if (results.isEmpty()) return;
|
Map<Long, AuditRule> ruleMap = new HashMap<>();
|
for (AuditRule rule : ruleMapper.selectList(null)) ruleMap.put(rule.getId(), rule);
|
Map<Long, String> h2032Name = new HashMap<>();
|
Map<Long, String> h2032Explain = new HashMap<>();
|
Map<Long, String> passengerName = new HashMap<>();
|
Map<Long, String> passengerExplain = new HashMap<>();
|
Map<Long, String> energyName = new HashMap<>();
|
Map<Long, String> investName = new HashMap<>();
|
Map<Long, String> busName = new HashMap<>();
|
Map<Long, String> busExplain = new HashMap<>();
|
Map<Long, String> taxiName = new HashMap<>();
|
Map<Long, String> taxiExplain = new HashMap<>();
|
for (H2032EnterpriseMonthly r : h2032Mapper.selectList(null)) {
|
h2032Name.putIfAbsent(r.getId(), r.getEnterpriseName());
|
h2032Explain.putIfAbsent(r.getId(), r.getVerifyExplanation());
|
}
|
for (PassengerEnterpriseMonthly r : passengerMapper.selectList(null)) {
|
passengerName.putIfAbsent(r.getId(), r.getEnterpriseName());
|
passengerExplain.putIfAbsent(r.getId(), r.getVerifyExplanation());
|
}
|
for (EnergyVehicleQuarterly r : energyMapper.selectList(null)) {
|
energyName.putIfAbsent(r.getId(), r.getEnterpriseName());
|
}
|
for (CityBusMonthly r : cityBusMapper.selectList(null)) {
|
busName.putIfAbsent(r.getId(), r.getEnterpriseName());
|
busExplain.putIfAbsent(r.getId(), r.getVerifyExplanation());
|
}
|
for (CityTaxiMonthly r : cityTaxiMapper.selectList(null)) {
|
taxiName.putIfAbsent(r.getId(), r.getCity());
|
taxiExplain.putIfAbsent(r.getId(), r.getVerifyExplanation());
|
}
|
Map<Long, Long> investProjectId = new HashMap<>();
|
for (InvestmentMonthly r : investMonthlyMapper.selectList(null)) {
|
investProjectId.putIfAbsent(r.getId(), r.getProjectId());
|
}
|
for (InvestmentProject p : investProjectMapper.selectList(null)) {
|
for (Map.Entry<Long, Long> e : investProjectId.entrySet()) {
|
if (e.getValue().equals(p.getId())) investName.putIfAbsent(e.getKey(), p.getProjectName());
|
}
|
}
|
for (AuditResult result : results) {
|
AuditRule rule = ruleMap.get(result.getRuleId());
|
if (rule != null) {
|
result.setRuleName(rule.getRuleName());
|
result.setRuleCode(rule.getRuleCode());
|
result.setAlertLevel(rule.getAlertLevel());
|
}
|
// 各报表表 id 各自独立自增,需按规则所属报表类型分表回填企业名称
|
String type = rule == null ? null : rule.getReportType();
|
Map<Long, String> nameMap = "INVEST".equals(type) ? investName
|
: "H204".equals(type) ? energyName
|
: "H2031".equals(type) ? passengerName
|
: "CITY_BUS".equals(type) ? busName
|
: "CITY_TAXI".equals(type) ? taxiName : h2032Name;
|
Map<Long, String> explainMap = "H2031".equals(type) ? passengerExplain
|
: "CITY_BUS".equals(type) ? busExplain
|
: "CITY_TAXI".equals(type) ? taxiExplain : h2032Explain;
|
String enterpriseName = nameMap.get(result.getReportId());
|
if (enterpriseName == null) {
|
// 上报数据被重新导入后旧审核结果已失效,给出提示而非空白
|
enterpriseName = "\uFF08\u539F\u6570\u636E\u5DF2\u91CD\u65B0\u5BFC\u5165\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C\u5BA1\u6838\uFF09";
|
}
|
result.setEnterpriseName(enterpriseName);
|
result.setVerifyExplanation(explainMap.get(result.getReportId()));
|
}
|
}
|
|
/** 最近有数据的报表期(用于前端默认选中) */
|
public String latestPeriod(String reportType) {
|
String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim();
|
String max = null;
|
switch (type) {
|
case "H2031":
|
for (PassengerEnterpriseMonthly m : passengerMapper.selectList(
|
new LambdaQueryWrapper<PassengerEnterpriseMonthly>().select(PassengerEnterpriseMonthly::getReportPeriod))) {
|
max = maxPeriod(max, m.getReportPeriod());
|
}
|
break;
|
case "H204":
|
for (EnergyVehicleQuarterly m : energyMapper.selectList(
|
new LambdaQueryWrapper<EnergyVehicleQuarterly>().select(EnergyVehicleQuarterly::getReportPeriod))) {
|
max = maxPeriod(max, m.getReportPeriod());
|
}
|
break;
|
case "INVEST":
|
for (InvestmentMonthly m : investMonthlyMapper.selectList(
|
new LambdaQueryWrapper<InvestmentMonthly>().select(InvestmentMonthly::getReportPeriod))) {
|
max = maxPeriod(max, m.getReportPeriod());
|
}
|
break;
|
case "CITY_BUS":
|
for (CityBusMonthly m : cityBusMapper.selectList(
|
new LambdaQueryWrapper<CityBusMonthly>().select(CityBusMonthly::getReportPeriod))) {
|
max = maxPeriod(max, m.getReportPeriod());
|
}
|
break;
|
case "CITY_TAXI":
|
for (CityTaxiMonthly m : cityTaxiMapper.selectList(
|
new LambdaQueryWrapper<CityTaxiMonthly>().select(CityTaxiMonthly::getReportPeriod))) {
|
max = maxPeriod(max, m.getReportPeriod());
|
}
|
break;
|
default:
|
for (H2032EnterpriseMonthly m : h2032Mapper.selectList(
|
new LambdaQueryWrapper<H2032EnterpriseMonthly>().select(H2032EnterpriseMonthly::getReportPeriod))) {
|
max = maxPeriod(max, m.getReportPeriod());
|
}
|
break;
|
}
|
return max;
|
}
|
|
private String maxPeriod(String a, String b) {
|
if (b == null || b.trim().isEmpty()) return a;
|
if (a == null || a.compareTo(b) < 0) return b;
|
return a;
|
}
|
|
/** 审核结果导出 Excel(含企业名/规则名/状态/审核意见) */
|
public byte[] exportResults(String reportPeriod, String reportType) throws Exception {
|
List<AuditResult> results = queryResults(reportPeriod, reportType);
|
try (XSSFWorkbook wb = new XSSFWorkbook()) {
|
Sheet sheet = wb.createSheet("审核结果");
|
String[] headers = {"报表期", "企业/市州", "规则编码", "审核规则", "等级", "实际值", "阈值/基准", "偏差说明", "状态", "审核意见"};
|
Row head = sheet.createRow(0);
|
for (int i = 0; i < headers.length; i++) {
|
Cell cell = head.createCell(i);
|
cell.setCellValue(headers[i]);
|
org.apache.poi.ss.usermodel.CellStyle style = wb.createCellStyle();
|
org.apache.poi.ss.usermodel.Font font = wb.createFont();
|
font.setBold(true);
|
style.setFont(font);
|
cell.setCellStyle(style);
|
}
|
int r = 1;
|
for (AuditResult res : results) {
|
Row row = sheet.createRow(r++);
|
row.createCell(0).setCellValue(valueOf(res.getReportPeriod()));
|
row.createCell(1).setCellValue(valueOf(res.getEnterpriseName()));
|
row.createCell(2).setCellValue(valueOf(res.getRuleCode()));
|
row.createCell(3).setCellValue(valueOf(res.getRuleName()));
|
row.createCell(4).setCellValue(valueOf(res.getAlertLevel()));
|
row.createCell(5).setCellValue(valueOf(res.getActualValue()));
|
row.createCell(6).setCellValue(valueOf(res.getThresholdValue()));
|
row.createCell(7).setCellValue(valueOf(res.getDeviation()));
|
row.createCell(8).setCellValue(auditStatusCn(res.getStatus()));
|
row.createCell(9).setCellValue(valueOf(res.getReviewComment()));
|
}
|
int[] widths = {12, 28, 12, 28, 8, 36, 22, 36, 10, 28};
|
for (int i = 0; i < widths.length; i++) {
|
sheet.setColumnWidth(i, widths[i] * 256);
|
}
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
wb.write(out);
|
return out.toByteArray();
|
}
|
}
|
|
private String auditStatusCn(String status) {
|
if ("CONFIRMED".equals(status)) return "已确认";
|
if ("IGNORED".equals(status)) return "已忽略";
|
return "待处理";
|
}
|
|
/** 规则执行统计:每条启用规则的检查对象数与命中数 */
|
public List<Map<String, Object>> ruleStats(String reportPeriod, String reportType) {
|
String type = reportType == null || reportType.trim().isEmpty() ? "H2032" : reportType.trim();
|
List<AuditRule> rules = rulesOf(type);
|
long checked;
|
switch (type) {
|
case "H2031":
|
checked = passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
|
.eq(PassengerEnterpriseMonthly::getReportPeriod, reportPeriod));
|
break;
|
case "H204":
|
checked = energyMapper.selectCount(new LambdaQueryWrapper<EnergyVehicleQuarterly>()
|
.eq(EnergyVehicleQuarterly::getReportPeriod, reportPeriod));
|
break;
|
case "INVEST":
|
checked = investMonthlyMapper.selectCount(new LambdaQueryWrapper<InvestmentMonthly>()
|
.eq(InvestmentMonthly::getReportPeriod, reportPeriod));
|
break;
|
case "CITY_BUS":
|
checked = cityBusMapper.selectCount(new LambdaQueryWrapper<CityBusMonthly>()
|
.eq(CityBusMonthly::getReportPeriod, reportPeriod));
|
break;
|
case "CITY_TAXI":
|
checked = cityTaxiMapper.selectCount(new LambdaQueryWrapper<CityTaxiMonthly>()
|
.eq(CityTaxiMonthly::getReportPeriod, reportPeriod));
|
break;
|
default:
|
checked = h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
|
.eq(H2032EnterpriseMonthly::getReportPeriod, reportPeriod));
|
break;
|
}
|
List<AuditResult> results = resultMapper.selectList(new LambdaQueryWrapper<AuditResult>()
|
.eq(AuditResult::getReportPeriod, reportPeriod));
|
Map<Long, Long> hitMap = new HashMap<>();
|
for (AuditResult r : results) {
|
hitMap.merge(r.getRuleId(), 1L, Long::sum);
|
}
|
List<Map<String, Object>> out = new ArrayList<>();
|
for (AuditRule rule : rules) {
|
Map<String, Object> m = new HashMap<>();
|
m.put("ruleId", rule.getId());
|
m.put("ruleCode", rule.getRuleCode());
|
m.put("ruleName", rule.getRuleName());
|
m.put("alertLevel", rule.getAlertLevel());
|
m.put("checked", checked);
|
m.put("hit", hitMap.getOrDefault(rule.getId(), 0L));
|
m.put("skipped", ruleSkipped(rule, reportPeriod));
|
out.add(m);
|
}
|
return out;
|
}
|
|
/** 跨表/环比基准数据整期缺失时,规则标记为跳过(避免误报) */
|
private boolean ruleSkipped(AuditRule rule, String period) {
|
String code = rule.getRuleCode();
|
if (code == null) return false;
|
switch (code) {
|
case "RULE_001":
|
case "RULE_002":
|
return transportAuthMapper.selectCount(new LambdaQueryWrapper<TransportAuthVehicle>()
|
.eq(TransportAuthVehicle::getReportPeriod, period)) == 0;
|
case "RULE_003":
|
return trackMileageMapper.selectCount(new LambdaQueryWrapper<VehicleTrackMileage>()
|
.eq(VehicleTrackMileage::getReportPeriod, period)) == 0;
|
case "RULE_004":
|
return h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
|
.eq(H2032EnterpriseMonthly::getReportPeriod, getLastPeriod(period))) == 0;
|
case "RULE_P001":
|
case "RULE_P002":
|
return passengerAuthMapper.selectCount(new LambdaQueryWrapper<PassengerAuthVehicle>()
|
.eq(PassengerAuthVehicle::getReportPeriod, period)) == 0;
|
case "RULE_P003":
|
case "RULE_P004":
|
case "RULE_P005":
|
case "RULE_P006":
|
return passengerMapper.selectCount(new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
|
.eq(PassengerEnterpriseMonthly::getReportPeriod, getLastPeriod(period))) == 0;
|
case "RULE_E001":
|
case "RULE_E002":
|
case "RULE_E003":
|
return energyAuthMapper.selectCount(new LambdaQueryWrapper<EnergyAuthVehicle>()
|
.eq(EnergyAuthVehicle::getReportPeriod, period)) == 0;
|
case "RULE_E008":
|
case "RULE_E009":
|
return h2032Mapper.selectCount(new LambdaQueryWrapper<H2032EnterpriseMonthly>()
|
.eq(H2032EnterpriseMonthly::getReportPeriod, period)) == 0;
|
case "RULE_T001":
|
return cityTaxiAuthMapper.selectCount(new LambdaQueryWrapper<CityTaxiAuth>()
|
.eq(CityTaxiAuth::getReportPeriod, period)) == 0;
|
case "RULE_I001":
|
case "RULE_I002":
|
case "RULE_I003":
|
return investSystemMapper.selectCount(new LambdaQueryWrapper<InvestmentSystem>()
|
.eq(InvestmentSystem::getReportPeriod, period)) == 0;
|
default:
|
return false;
|
}
|
}
|
|
private List<AuditRule> rulesOf(String reportType) {
|
List<AuditRule> all = ruleMapper.selectEnabledRules();
|
List<AuditRule> matched = new ArrayList<>();
|
for (AuditRule rule : all) {
|
String type = rule.getReportType();
|
if (reportType.equals(type) || (type == null && "H2032".equals(reportType))) {
|
matched.add(rule);
|
}
|
}
|
return matched;
|
}
|
|
private AuditResult checkRule(AuditRule rule, H2032EnterpriseMonthly report,
|
Map<String, TransportAuthVehicle> authMap,
|
Map<String, VehicleTrackMileage> trackMap,
|
Map<String, H2032EnterpriseMonthly> lastMonthMap) {
|
try {
|
switch (rule.getCompareType()) {
|
case "DIFF":
|
return checkDiff(rule, report, authMap);
|
case "RATIO":
|
return checkRatio(rule, report, authMap, trackMap);
|
case "MOM":
|
return checkMom(rule, report, lastMonthMap);
|
case "NULL_CHECK":
|
return checkNull(rule, report);
|
case "OUT_RANGE":
|
return checkOutRange(rule, report);
|
default:
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("Rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
|
return null;
|
}
|
}
|
|
/** 车辆数/吨位 与运政数据对比,差距超阈值核实 */
|
private AuditResult checkDiff(AuditRule rule, H2032EnterpriseMonthly report,
|
Map<String, TransportAuthVehicle> authMap) {
|
TransportAuthVehicle auth = findAuth(authMap, report.getEnterpriseName());
|
if (auth == null) return null;
|
|
double actual;
|
double reference;
|
if (rule.getCheckField().contains("tons")) {
|
actual = nvl(report.getTonsTotal());
|
reference = nvl(auth.getTrailerTons()) + nvl(auth.getOtherTons());
|
} else {
|
actual = num(report.getVehicleTotal());
|
reference = num(auth.getTractorCount()) + num(auth.getTrailerCount()) + num(auth.getOtherCount());
|
}
|
double threshold = Double.parseDouble(rule.getThreshold());
|
double diff = Math.abs(actual - reference);
|
|
if (diff > threshold) {
|
return buildResult(rule, report, "上报=" + fmt(actual) + ", 运政=" + fmt(reference),
|
"差距≤" + fmt(threshold), "差距=" + fmt(diff));
|
}
|
return null;
|
}
|
|
/** 上报周转量与轨迹测算周转量对比,高出阈值比例核实 */
|
private AuditResult checkRatio(AuditRule rule, H2032EnterpriseMonthly report,
|
Map<String, TransportAuthVehicle> authMap,
|
Map<String, VehicleTrackMileage> trackMap) {
|
TransportAuthVehicle auth = findAuth(authMap, report.getEnterpriseName());
|
VehicleTrackMileage track = trackMap.get(key(report.getEnterpriseName()));
|
if (auth == null || track == null) return null;
|
|
double trackVehicles = num(track.getTrackedVehicles());
|
if (trackVehicles == 0.0) return null;
|
if (num(auth.getOtherCount()) == 0) return null;
|
|
// 整车吨位 = 其它车辆总吨位 / 其它车辆数
|
double wholeTonnage = nvl(auth.getOtherTons()) / num(auth.getOtherCount());
|
|
// 轨迹测算周转量 = 轨迹里程/轨迹车辆 × (牵引车数×31 + 整车吨位)
|
double avgMileagePerVehicle = nvl(track.getMonthlyMileage()) / trackVehicles;
|
double totalTonnage = num(auth.getTractorCount()) * TRACTOR_STD_TONS + wholeTonnage;
|
double estimatedTurnover = avgMileagePerVehicle * totalTonnage;
|
|
if (estimatedTurnover == 0.0) return null;
|
|
double actual = nvl(report.getTurnoverTotal());
|
double ratio = actual / estimatedTurnover;
|
double threshold = Double.parseDouble(rule.getThreshold());
|
|
if (ratio > threshold) {
|
return buildResult(rule, report,
|
"上报周转量=" + fmt(actual) + ", 轨迹测算=" + fmt(estimatedTurnover),
|
"≤轨迹测算×" + fmt(threshold), "=轨迹测算×" + fmt(ratio));
|
}
|
return null;
|
}
|
|
/** 货运类型环比:本月新增(上月为0、本月>0)需核实,少的不管 */
|
private AuditResult checkMom(AuditRule rule, H2032EnterpriseMonthly report,
|
Map<String, H2032EnterpriseMonthly> lastMonthMap) {
|
H2032EnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
|
if (lastMonth == null) return null;
|
List<String> hits = new ArrayList<>();
|
for (String field : rule.getCheckField().split(",")) {
|
double current = getH2032FieldValue(report, field);
|
double last = getH2032FieldValue(lastMonth, field);
|
if (current > 0 && last == 0) {
|
hits.add(h2032FieldLabel(field) + "=" + fmt(current));
|
}
|
}
|
if (!hits.isEmpty()) {
|
return buildResult(rule, report, "本月新增: " + String.join("、", hits),
|
"上月该类型为0", "本月新增货运类型(上月无)");
|
}
|
return null;
|
}
|
|
/** 有货运量但对应车辆为0 需核实(如 有集装箱货运量无集装箱车辆) */
|
private AuditResult checkNull(AuditRule rule, H2032EnterpriseMonthly report) {
|
String[] fields = rule.getCheckField().split(",");
|
if (fields.length < 2) return null;
|
double volume = getH2032FieldValue(report, fields[0]);
|
double vehicle = getH2032FieldValue(report, fields[1]);
|
if (volume > 0 && vehicle == 0) {
|
return buildResult(rule, report,
|
h2032FieldLabel(fields[0]) + "=" + fmt(volume) + ", " + h2032FieldLabel(fields[1]) + "=0",
|
"有" + h2032FieldLabel(fields[0]) + "应有对应车辆", "有" + h2032FieldLabel(fields[0]) + "但无" + h2032FieldLabel(fields[1]));
|
}
|
return null;
|
}
|
|
/** 数值范围检查(如 整车吨位 4≤且≤40) */
|
private AuditResult checkOutRange(AuditRule rule, H2032EnterpriseMonthly report) {
|
String[] bounds = rule.getThreshold().split(",");
|
if (bounds.length < 2) return null;
|
double value = getH2032FieldValue(report, rule.getCheckField());
|
double low = Double.parseDouble(bounds[0].trim());
|
double high = Double.parseDouble(bounds[1].trim());
|
if (value > 0 && (value < low || value > high)) {
|
return buildResult(rule, report, h2032FieldLabel(rule.getCheckField()) + "=" + fmt(value),
|
"范围 " + fmt(low) + "~" + fmt(high), "超出范围");
|
}
|
return null;
|
}
|
|
private AuditResult buildResult(AuditRule rule, H2032EnterpriseMonthly report,
|
String actual, String threshold, String deviation) {
|
AuditResult result = new AuditResult();
|
result.setRuleId(rule.getId());
|
result.setReportId(report.getId());
|
result.setEnterpriseCode(report.getEnterpriseCode());
|
result.setReportPeriod(report.getReportPeriod());
|
result.setActualValue(actual);
|
result.setThresholdValue(threshold);
|
result.setDeviation(deviation);
|
result.setStatus("PENDING");
|
return result;
|
}
|
|
private TransportAuthVehicle findAuth(Map<String, TransportAuthVehicle> authMap, String name) {
|
return authMap.get(key(name));
|
}
|
|
private Map<String, TransportAuthVehicle> loadAuthMap(String reportPeriod) {
|
Map<String, TransportAuthVehicle> map = new HashMap<>();
|
List<TransportAuthVehicle> list = transportAuthMapper.selectList(
|
new LambdaQueryWrapper<TransportAuthVehicle>()
|
.eq(TransportAuthVehicle::getReportPeriod, reportPeriod));
|
for (TransportAuthVehicle v : list) {
|
map.putIfAbsent(key(v.getEnterpriseName()), v);
|
}
|
return map;
|
}
|
|
private Map<String, VehicleTrackMileage> loadTrackMap(String reportPeriod) {
|
Map<String, VehicleTrackMileage> map = new HashMap<>();
|
List<VehicleTrackMileage> list = trackMileageMapper.selectList(
|
new LambdaQueryWrapper<VehicleTrackMileage>()
|
.eq(VehicleTrackMileage::getReportPeriod, reportPeriod));
|
for (VehicleTrackMileage v : list) {
|
map.putIfAbsent(key(v.getEnterpriseName()), v);
|
}
|
return map;
|
}
|
|
private Map<String, H2032EnterpriseMonthly> loadLastMonthMap(String period) {
|
Map<String, H2032EnterpriseMonthly> map = new HashMap<>();
|
String lastPeriod = getLastPeriod(period);
|
List<H2032EnterpriseMonthly> list = h2032Mapper.selectList(
|
new LambdaQueryWrapper<H2032EnterpriseMonthly>()
|
.eq(H2032EnterpriseMonthly::getReportPeriod, lastPeriod));
|
for (H2032EnterpriseMonthly v : list) {
|
map.putIfAbsent(key(v.getEnterpriseCode()), v);
|
}
|
return map;
|
}
|
|
private String getLastPeriod(String period) {
|
String[] parts = period.split("-");
|
int year = Integer.parseInt(parts[0]);
|
int month = Integer.parseInt(parts[1]);
|
if (month == 1) {
|
year--;
|
month = 12;
|
} else {
|
month--;
|
}
|
return year + "-" + (month < 10 ? "0" + month : String.valueOf(month));
|
}
|
|
private double getH2032FieldValue(H2032EnterpriseMonthly report, String fieldName) {
|
try {
|
java.lang.reflect.Field field = H2032EnterpriseMonthly.class.getDeclaredField(fieldName);
|
field.setAccessible(true);
|
Object value = field.get(report);
|
if (value == null) return 0.0;
|
if (value instanceof Double) return (Double) value;
|
if (value instanceof Integer) return ((Integer) value).doubleValue();
|
return Double.parseDouble(value.toString());
|
} catch (Exception e) {
|
return 0.0;
|
}
|
}
|
|
private String h2032FieldLabel(String field) {
|
String label = FREIGHT_TYPE_NAMES.get(field);
|
if (label != null) return label;
|
java.util.Map<String, String> labels = new HashMap<>();
|
labels.put("vehicleTotal", "车辆数");
|
labels.put("tonsTotal", "总吨位");
|
labels.put("vehicleContainer", "集装箱车辆数");
|
labels.put("tonsWhole", "整车吨位");
|
labels.put("turnoverTotal", "周转量");
|
labels.put("vehicleWhole", "整车车辆数");
|
String v = labels.get(field);
|
return v == null ? field : v;
|
}
|
|
private boolean nearlyEqual(double a, double b) {
|
if (a == b) return true;
|
return Math.abs(a - b) <= 1e-6 * Math.max(1.0, Math.max(Math.abs(a), Math.abs(b)));
|
}
|
|
private String key(String s) {
|
if (s == null) return "";
|
return s.trim().replaceAll("\\s+", "");
|
}
|
|
private double nvl(Double v) {
|
return v == null ? 0.0 : v;
|
}
|
|
private double num(Integer v) {
|
return v == null ? 0.0 : v.doubleValue();
|
}
|
|
private String fmt(double v) {
|
if (v == Math.floor(v) && !Double.isInfinite(v)) {
|
return String.valueOf((long) v);
|
}
|
return String.valueOf(Math.round(v * 100.0) / 100.0);
|
}
|
|
/** 占比/小数值:保留 4 位小数 */
|
private String fmt4(double v) {
|
if (v == Math.floor(v) && !Double.isInfinite(v)) {
|
return String.valueOf((long) v);
|
}
|
return String.valueOf(Math.round(v * 10000.0) / 10000.0);
|
}
|
|
// ========== H2031 旅客审核 ==========
|
|
private AuditResult checkPassengerRule(AuditRule rule, PassengerEnterpriseMonthly report,
|
Map<String, PassengerAuthVehicle> authMap,
|
Map<String, PassengerEnterpriseMonthly> lastMonthMap) {
|
try {
|
switch (rule.getCompareType()) {
|
case "DIFF":
|
return checkPassengerDiff(rule, report, authMap);
|
case "MOM_EQ_ZERO":
|
return checkPassengerMomEqZero(rule, report, lastMonthMap);
|
case "MOM_PCT":
|
return checkPassengerMomPct(rule, report, lastMonthMap);
|
case "MOM_EQ":
|
return checkPassengerMomEq(rule, report, lastMonthMap);
|
case "EXISTENCE":
|
return checkPassengerExistence(rule, report);
|
default:
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("Passenger rule check error: {} - {}", rule.getRuleCode(), e.getMessage());
|
return null;
|
}
|
}
|
|
/** 车辆数/载客位数 与运政对比,差距超阈值核实 */
|
private AuditResult checkPassengerDiff(AuditRule rule, PassengerEnterpriseMonthly report,
|
Map<String, PassengerAuthVehicle> authMap) {
|
PassengerAuthVehicle auth = authMap.get(key(report.getEnterpriseName()));
|
if (auth == null) return null;
|
double actual;
|
double reference;
|
if (rule.getCheckField().contains("seat")) {
|
actual = num(report.getSeatTotal());
|
reference = num(auth.getSeatCount());
|
} else {
|
actual = num(report.getVehicleTotal());
|
reference = num(auth.getVehicleCount());
|
}
|
double threshold = Double.parseDouble(rule.getThreshold());
|
double diff = Math.abs(actual - reference);
|
if (diff > threshold) {
|
return buildPassengerResult(rule, report, "上报=" + fmt(actual) + ", 运政=" + fmt(reference),
|
"差距≤" + fmt(threshold), "差距=" + fmt(diff));
|
}
|
return null;
|
}
|
|
/** 环比上个月等于0:值与上月相同需核实(上月为0不核实) */
|
private AuditResult checkPassengerMomEqZero(AuditRule rule, PassengerEnterpriseMonthly report,
|
Map<String, PassengerEnterpriseMonthly> lastMonthMap) {
|
PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
|
if (lastMonth == null) return null;
|
List<String> hits = new ArrayList<>();
|
for (String field : rule.getCheckField().split(",")) {
|
double current = getPassengerFieldValue(report, field);
|
double last = getPassengerFieldValue(lastMonth, field);
|
if (last > 0 && nearlyEqual(current, last)) {
|
hits.add(fieldLabel(field) + "=" + fmt(current));
|
}
|
}
|
if (!hits.isEmpty()) {
|
return buildPassengerResult(rule, report, "本月与上月相同: " + String.join("、", hits),
|
"环比≠0", "环比=0");
|
}
|
return null;
|
}
|
|
/** 平均运距环比超 ±threshold% 核实 */
|
private AuditResult checkPassengerMomPct(AuditRule rule, PassengerEnterpriseMonthly report,
|
Map<String, PassengerEnterpriseMonthly> lastMonthMap) {
|
PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
|
if (lastMonth == null) return null;
|
double threshold = Double.parseDouble(rule.getThreshold());
|
List<String> hits = new ArrayList<>();
|
for (String field : rule.getCheckField().split(",")) {
|
double current = getPassengerFieldValue(report, field);
|
double last = getPassengerFieldValue(lastMonth, field);
|
if (last > 0) {
|
double pct = Math.abs((current - last) / last) * 100.0;
|
if (pct > threshold) {
|
hits.add(fieldLabel(field) + "=" + fmt(current) + ",上月=" + fmt(last) + ",环比" + fmt(pct) + "%");
|
}
|
}
|
}
|
if (!hits.isEmpty()) {
|
return buildPassengerResult(rule, report, String.join(";", hits),
|
"环比≤±" + fmt(threshold) + "%", "环比超限");
|
}
|
return null;
|
}
|
|
/** 包车平均运距等于上月核实 */
|
private AuditResult checkPassengerMomEq(AuditRule rule, PassengerEnterpriseMonthly report,
|
Map<String, PassengerEnterpriseMonthly> lastMonthMap) {
|
PassengerEnterpriseMonthly lastMonth = lastMonthMap.get(key(report.getEnterpriseCode()));
|
if (lastMonth == null) return null;
|
List<String> hits = new ArrayList<>();
|
for (String field : rule.getCheckField().split(",")) {
|
double current = getPassengerFieldValue(report, field);
|
double last = getPassengerFieldValue(lastMonth, field);
|
if (last > 0 && nearlyEqual(current, last)) {
|
hits.add(fieldLabel(field) + "=" + fmt(current));
|
}
|
}
|
if (!hits.isEmpty()) {
|
return buildPassengerResult(rule, report, "等于上月: " + String.join("、", hits),
|
"与上月不同", "与上月相同");
|
}
|
return null;
|
}
|
|
/** 无班线/无包车却有对应客运量:check_field 首个为车辆字段,其余为客运量字段 */
|
private AuditResult checkPassengerExistence(AuditRule rule, PassengerEnterpriseMonthly report) {
|
String[] fields = rule.getCheckField().split(",");
|
if (fields.length < 2) return null;
|
String vehicleField = fields[0];
|
double vehicleCount = getPassengerFieldValue(report, vehicleField);
|
if (vehicleCount > 0) return null;
|
List<String> hits = new ArrayList<>();
|
for (int i = 1; i < fields.length; i++) {
|
double value = getPassengerFieldValue(report, fields[i]);
|
if (value > 0) {
|
hits.add(fieldLabel(fields[i]) + "=" + fmt(value));
|
}
|
}
|
if (!hits.isEmpty()) {
|
String label = "schedule".equals(vehicleField) || "vehicleSchedule".equals(vehicleField) ? "班线" : "包车";
|
return buildPassengerResult(rule, report, String.join("、", hits),
|
"有" + label + "车辆", "无" + label + "车辆但有客运量");
|
}
|
return null;
|
}
|
|
private Map<String, PassengerAuthVehicle> loadPassengerAuthMap(String reportPeriod) {
|
Map<String, PassengerAuthVehicle> map = new HashMap<>();
|
List<PassengerAuthVehicle> list = passengerAuthMapper.selectList(
|
new LambdaQueryWrapper<PassengerAuthVehicle>()
|
.eq(PassengerAuthVehicle::getReportPeriod, reportPeriod));
|
for (PassengerAuthVehicle v : list) {
|
map.putIfAbsent(key(v.getEnterpriseName()), v);
|
}
|
return map;
|
}
|
|
private Map<String, PassengerEnterpriseMonthly> loadPassengerLastMonthMap(String period) {
|
Map<String, PassengerEnterpriseMonthly> map = new HashMap<>();
|
String lastPeriod = getLastPeriod(period);
|
List<PassengerEnterpriseMonthly> list = passengerMapper.selectList(
|
new LambdaQueryWrapper<PassengerEnterpriseMonthly>()
|
.eq(PassengerEnterpriseMonthly::getReportPeriod, lastPeriod));
|
for (PassengerEnterpriseMonthly v : list) {
|
map.putIfAbsent(key(v.getEnterpriseCode()), v);
|
}
|
return map;
|
}
|
|
private double getPassengerFieldValue(PassengerEnterpriseMonthly report, String fieldName) {
|
try {
|
java.lang.reflect.Field field = PassengerEnterpriseMonthly.class.getDeclaredField(fieldName);
|
field.setAccessible(true);
|
Object value = field.get(report);
|
if (value == null) return 0.0;
|
if (value instanceof Double) return (Double) value;
|
if (value instanceof Integer) return ((Integer) value).doubleValue();
|
return Double.parseDouble(value.toString());
|
} catch (Exception e) {
|
return 0.0;
|
}
|
}
|
|
private String fieldLabel(String field) {
|
java.util.Map<String, String> labels = new HashMap<>();
|
labels.put("passengerClass1", "一类班线客运量");
|
labels.put("passengerClass2", "二类班线客运量");
|
labels.put("passengerClass3", "三类班线客运量");
|
labels.put("passengerClass4", "四类班线客运量");
|
labels.put("passengerCharter", "包车客运量");
|
labels.put("turnoverClass1", "一类班线周转量");
|
labels.put("turnoverClass2", "二类班线周转量");
|
labels.put("turnoverClass3", "三类班线周转量");
|
labels.put("turnoverClass4", "四类班线周转量");
|
labels.put("turnoverCharter", "包车周转量");
|
labels.put("avgDistanceClass1", "一类班线平均运距");
|
labels.put("avgDistanceClass2", "二类班线平均运距");
|
labels.put("avgDistanceClass3", "三类班线平均运距");
|
labels.put("avgDistanceClass4", "四类班线平均运距");
|
labels.put("avgDistanceCharter", "包车平均运距");
|
String label = labels.get(field);
|
return label == null ? field : label;
|
}
|
|
private AuditResult buildPassengerResult(AuditRule rule, PassengerEnterpriseMonthly report,
|
String actual, String threshold, String deviation) {
|
AuditResult result = new AuditResult();
|
result.setRuleId(rule.getId());
|
result.setReportId(report.getId());
|
result.setEnterpriseCode(report.getEnterpriseCode());
|
result.setReportPeriod(report.getReportPeriod());
|
result.setActualValue(actual);
|
result.setThresholdValue(threshold);
|
result.setDeviation(deviation);
|
result.setStatus("PENDING");
|
return result;
|
}
|
}
|