fix: 增加年度导出
This commit is contained in:
parent
29105cf1f5
commit
b19db9e8d2
@ -47,20 +47,29 @@ public class QgcExportController {
|
||||
@RequestParam(required = false) String sysId,
|
||||
@RequestParam(defaultValue = "true") boolean isDailyDimension,
|
||||
@RequestParam(defaultValue = "false") boolean isCalcQec,
|
||||
@RequestParam(required = false) String yearStatType,
|
||||
@RequestParam(required = false) String year,
|
||||
@RequestParam(required = false) String baseId,
|
||||
HttpServletResponse response) {
|
||||
|
||||
// 解析逗号分隔参数
|
||||
List<String> dataFields = parseCsv(dataField);
|
||||
List<String> monthList = parseCsv(months);
|
||||
List<String> stationList = parseCsv(stcd);
|
||||
List<String> baseIdList = parseCsv(baseId);
|
||||
|
||||
// if (dataFields.isEmpty()) {
|
||||
// dataFields = Arrays.asList("v", "q", "z");
|
||||
// }
|
||||
if (tmDimension == null || tmDimension.isEmpty()) {
|
||||
tmDimension = "month";
|
||||
}
|
||||
|
||||
// 年度导出分支
|
||||
if ("year".equals(tmDimension)) {
|
||||
List<String> yearStatTypeList = parseCsv(yearStatType);
|
||||
List<String> yearList = parseCsv(year);
|
||||
qgcExportService.exportYearData(yearStatTypeList, yearList, stationList, baseIdList, response);
|
||||
return;
|
||||
}
|
||||
|
||||
qgcExportService.exportData(dataFields, monthList, stationList,
|
||||
isDailyDimension, tmDimension, response);
|
||||
}
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
package com.yfd.platform.qgc_export.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* V_MS_STBPRP_T 视图扩展映射 — 用于年度导出时获取流域、基地等信息
|
||||
*/
|
||||
@Data
|
||||
@TableName("V_MS_STBPRP_T")
|
||||
public class StationInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableField("STCD")
|
||||
private String stcd;
|
||||
|
||||
@TableField("STNM")
|
||||
private String stnm;
|
||||
|
||||
@TableField("ENNM")
|
||||
private String ennm;
|
||||
|
||||
@TableField("STTP")
|
||||
private String sttp;
|
||||
|
||||
@TableField("RSTCD")
|
||||
private String rstcd;
|
||||
|
||||
@TableField("BASE_ID")
|
||||
private String baseId;
|
||||
|
||||
@TableField("BASE_NAME")
|
||||
private String baseName;
|
||||
|
||||
@TableField("HBRVCD_NAME")
|
||||
private String hbrvcdName;
|
||||
|
||||
@TableField("RVCD_NAME")
|
||||
private String rvcdName;
|
||||
|
||||
@TableField("REACHCD")
|
||||
private String reachcd;
|
||||
|
||||
@TableField("REACHCD_NAME")
|
||||
private String reachcdName;
|
||||
|
||||
@TableField("HYNM")
|
||||
private String hynm;
|
||||
|
||||
@TableField("MWAY")
|
||||
private Integer mway;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.yfd.platform.qgc_export.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.yfd.platform.qgc_export.domain.StationInfo;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 年度统计导出 Mapper
|
||||
*/
|
||||
public interface YearStatMapper extends BaseMapper<StationInfo> {
|
||||
|
||||
/**
|
||||
* 查询生态流量月度达标数据(SD_QECDRTP_S DRTP='MON')
|
||||
* 返回 STCD、三部分达标数
|
||||
*/
|
||||
List<Map<String, Object>> selectQecMonthlyCompliance(@Param("stcdList") List<String> stcdList,
|
||||
@Param("year") int year);
|
||||
|
||||
/**
|
||||
* 查询生态流量月度达标数据(按 BASE_ID 分组)
|
||||
*/
|
||||
List<Map<String, Object>> selectQecMonthlyComplianceByBase(@Param("baseIdList") List<String> baseIdList,
|
||||
@Param("year") int year);
|
||||
}
|
||||
@ -24,4 +24,19 @@ public interface IQgcExportService {
|
||||
boolean isDailyDimension,
|
||||
String tmDimension,
|
||||
HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 年度统计导出(水质达标率 + 生态流量达标率)
|
||||
*
|
||||
* @param yearStatType 统计指标类型(wq, qec)
|
||||
* @param years 年份列表(如 ["2026","2025"])
|
||||
* @param stationCodes 电站编码列表(RSTCD)
|
||||
* @param baseIds 水电基地编码列表(BASE_ID)
|
||||
* @param response HttpServletResponse
|
||||
*/
|
||||
void exportYearData(List<String> yearStatType,
|
||||
List<String> years,
|
||||
List<String> stationCodes,
|
||||
List<String> baseIds,
|
||||
HttpServletResponse response);
|
||||
}
|
||||
|
||||
@ -14,6 +14,8 @@ import com.yfd.platform.qgc_export.mapper.SdQecRMapper;
|
||||
import com.yfd.platform.qgc_export.mapper.SdQecdaySMapper;
|
||||
import com.yfd.platform.qgc_export.mapper.StationMappingMapper;
|
||||
import com.yfd.platform.qgc_export.mapper.MsAlongdetBMapper;
|
||||
import com.yfd.platform.qgc_export.mapper.YearStatMapper;
|
||||
import com.yfd.platform.qgc_export.domain.StationInfo;
|
||||
import com.yfd.platform.qgc_env.wt.mapper.SdWtvtRMapper;
|
||||
import com.yfd.platform.qgc_export.mapper.SdWtvtdaySMapper;
|
||||
import com.yfd.platform.qgc_export.processor.*;
|
||||
@ -156,13 +158,20 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
@Resource
|
||||
private RwtProcessor rwtProcessor;
|
||||
|
||||
@Resource
|
||||
private YearStatMapper yearStatMapper;
|
||||
|
||||
private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时");
|
||||
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/** 基础汇总指标(7个) */
|
||||
/**
|
||||
* 基础汇总指标(7个)
|
||||
*/
|
||||
private static final List<String> BASE_LABELS = Collections.unmodifiableList(
|
||||
Arrays.asList("日均最低", "日均最高", "月内最低", "月内最低时间", "月内最高", "月内最高时间", "月均"));
|
||||
/** 小时达标率标签 */
|
||||
/**
|
||||
* 小时达标率标签
|
||||
*/
|
||||
private static final String LABEL_COMPLIANCE = "小时达标率";
|
||||
|
||||
@Override
|
||||
@ -233,10 +242,14 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
headers = new ArrayList<>();
|
||||
headers.add("测站名称");
|
||||
headers.add("水深(m)");
|
||||
headers.add("5日"); headers.add("");
|
||||
headers.add("15日"); headers.add("");
|
||||
headers.add("25日"); headers.add("");
|
||||
headers.add("月均"); headers.add("");
|
||||
headers.add("5日");
|
||||
headers.add("");
|
||||
headers.add("15日");
|
||||
headers.add("");
|
||||
headers.add("25日");
|
||||
headers.add("");
|
||||
headers.add("月均");
|
||||
headers.add("");
|
||||
} else {
|
||||
// 仅包含当前处理器 STTP 类型有对应测站的电站
|
||||
headers = new ArrayList<>();
|
||||
@ -400,16 +413,32 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
for (String rstcd : rstcdToStcd.keySet()) {
|
||||
Aggregation agg = aggMap.get(rstcd);
|
||||
switch (i) {
|
||||
case 0: row.add(formatOrEmpty(agg != null ? agg.getDailyMin() : null, decimals)); break;
|
||||
case 1: row.add(formatOrEmpty(agg != null ? agg.getDailyMax() : null, decimals)); break;
|
||||
case 2: row.add(formatOrEmpty(agg != null ? agg.getExtremeMin() : null, decimals)); break;
|
||||
case 3: row.add(agg != null && agg.getExtremeMinTime() != null
|
||||
? SDF_TIME.format(agg.getExtremeMinTime()) : ""); break;
|
||||
case 4: row.add(formatOrEmpty(agg != null ? agg.getExtremeMax() : null, decimals)); break;
|
||||
case 5: row.add(agg != null && agg.getExtremeMaxTime() != null
|
||||
? SDF_TIME.format(agg.getExtremeMaxTime()) : ""); break;
|
||||
case 6: row.add(formatOrEmpty(agg != null ? agg.getMonthlyAvg() : null, decimals)); break;
|
||||
case 7: row.add(formatComplianceRate(agg != null ? agg.getComplianceRate() : null)); break;
|
||||
case 0:
|
||||
row.add(formatOrEmpty(agg != null ? agg.getDailyMin() : null, decimals));
|
||||
break;
|
||||
case 1:
|
||||
row.add(formatOrEmpty(agg != null ? agg.getDailyMax() : null, decimals));
|
||||
break;
|
||||
case 2:
|
||||
row.add(formatOrEmpty(agg != null ? agg.getExtremeMin() : null, decimals));
|
||||
break;
|
||||
case 3:
|
||||
row.add(agg != null && agg.getExtremeMinTime() != null
|
||||
? SDF_TIME.format(agg.getExtremeMinTime()) : "");
|
||||
break;
|
||||
case 4:
|
||||
row.add(formatOrEmpty(agg != null ? agg.getExtremeMax() : null, decimals));
|
||||
break;
|
||||
case 5:
|
||||
row.add(agg != null && agg.getExtremeMaxTime() != null
|
||||
? SDF_TIME.format(agg.getExtremeMaxTime()) : "");
|
||||
break;
|
||||
case 6:
|
||||
row.add(formatOrEmpty(agg != null ? agg.getMonthlyAvg() : null, decimals));
|
||||
break;
|
||||
case 7:
|
||||
row.add(formatComplianceRate(agg != null ? agg.getComplianceRate() : null));
|
||||
break;
|
||||
}
|
||||
}
|
||||
rows.add(row);
|
||||
@ -540,7 +569,11 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
|
||||
private int toInt(Object v) {
|
||||
if (v instanceof Number) return ((Number) v).intValue();
|
||||
try { return Integer.parseInt(v.toString()); } catch (Exception e) { return -1; }
|
||||
try {
|
||||
return Integer.parseInt(v.toString());
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== WQ 水质达标率 Sheet(双表头) ========================
|
||||
@ -651,11 +684,16 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
List<List<Object>> rows = new ArrayList<>();
|
||||
// 子标题行(Row 0)
|
||||
List<Object> subHeaderRow = new ArrayList<>();
|
||||
subHeaderRow.add(""); subHeaderRow.add("");
|
||||
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)");
|
||||
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)");
|
||||
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)");
|
||||
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)");
|
||||
subHeaderRow.add("");
|
||||
subHeaderRow.add("");
|
||||
subHeaderRow.add("水温(℃)");
|
||||
subHeaderRow.add("相应高程(m)");
|
||||
subHeaderRow.add("水温(℃)");
|
||||
subHeaderRow.add("相应高程(m)");
|
||||
subHeaderRow.add("水温(℃)");
|
||||
subHeaderRow.add("相应高程(m)");
|
||||
subHeaderRow.add("水温(℃)");
|
||||
subHeaderRow.add("相应高程(m)");
|
||||
rows.add(subHeaderRow);
|
||||
|
||||
for (String key : sortedKeys) {
|
||||
@ -855,7 +893,9 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
}
|
||||
}
|
||||
|
||||
/** 分部达标统计 */
|
||||
/**
|
||||
* 分部达标统计
|
||||
*/
|
||||
private static class ComplianceStats {
|
||||
String dailyHb, dailySl, dailyDn;
|
||||
String hourlyHb, hourlySl, hourlyDn;
|
||||
@ -896,7 +936,7 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
for (String field : dataFields) {
|
||||
String lower = field.trim().toLowerCase();
|
||||
if ("wq".equals(lower)) {
|
||||
expanded.addAll(List.of("wtmp","codcr", "ph", "dox", "cond", "tu"));
|
||||
expanded.addAll(List.of("wtmp", "codcr", "ph", "dox", "cond", "tu"));
|
||||
} else {
|
||||
expanded.add(lower);
|
||||
}
|
||||
@ -1011,7 +1051,10 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
}
|
||||
if (defaultDisplayName == null) {
|
||||
for (StationMapping m : rows) {
|
||||
if (m.getStnm() != null) { defaultDisplayName = m.getStnm(); break; }
|
||||
if (m.getStnm() != null) {
|
||||
defaultDisplayName = m.getStnm();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defaultDisplayName == null) {
|
||||
@ -1157,7 +1200,9 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
|
||||
// ======================== 日变幅格式(出入库水温日变幅) ========================
|
||||
|
||||
/** 日变幅汇总标签 */
|
||||
/**
|
||||
* 日变幅汇总标签
|
||||
*/
|
||||
private static final List<String> VARIATION_LABELS = List.of("日变幅均值", "日变幅最大值");
|
||||
|
||||
/**
|
||||
@ -1270,16 +1315,26 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
// ======================== 内部数据类 ========================
|
||||
|
||||
private static class StationResolution {
|
||||
/** 默认测站名称(fallback),RSTCD → displayName */
|
||||
/**
|
||||
* 默认测站名称(fallback),RSTCD → displayName
|
||||
*/
|
||||
final Map<String, String> displayNames = new LinkedHashMap<>();
|
||||
/** 按 STTP 分组的测站名称,RSTCD → {STTP → displayName} */
|
||||
/**
|
||||
* 按 STTP 分组的测站名称,RSTCD → {STTP → displayName}
|
||||
*/
|
||||
final Map<String, Map<String, String>> sttpDisplayNames = new LinkedHashMap<>();
|
||||
/** STTP → STCD 映射,RSTCD → {STTP → STCD} */
|
||||
/**
|
||||
* STTP → STCD 映射,RSTCD → {STTP → STCD}
|
||||
*/
|
||||
final Map<String, Map<String, String>> sttpStcdMap = new LinkedHashMap<>();
|
||||
/** 所属水电基地名称,RSTCD → {STTP → BASE_NAME} */
|
||||
/**
|
||||
* 所属水电基地名称,RSTCD → {STTP → BASE_NAME}
|
||||
*/
|
||||
final Map<String, Map<String, String>> baseNameMap = new LinkedHashMap<>();
|
||||
|
||||
/** 获取默认测站名称 */
|
||||
/**
|
||||
* 获取默认测站名称
|
||||
*/
|
||||
String getDisplayName(String rstcd) {
|
||||
return displayNames.getOrDefault(rstcd, rstcd);
|
||||
}
|
||||
@ -1307,7 +1362,9 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
return inner.get(sttp);
|
||||
}
|
||||
|
||||
/** 获取指定 STTP 的水电基地名称 */
|
||||
/**
|
||||
* 获取指定 STTP 的水电基地名称
|
||||
*/
|
||||
String getBaseName(String rstcd, String sttp) {
|
||||
Map<String, String> inner = baseNameMap.get(rstcd);
|
||||
if (inner != null && sttp != null) {
|
||||
@ -1334,10 +1391,510 @@ public class QgcExportServiceImpl implements IQgcExportService {
|
||||
private BigDecimal extremeMax;
|
||||
private Date extremeMaxTime;
|
||||
private BigDecimal monthlyAvg;
|
||||
/** 小时达标率(百分比,仅水质指标使用) */
|
||||
/**
|
||||
* 小时达标率(百分比,仅水质指标使用)
|
||||
*/
|
||||
private BigDecimal complianceRate;
|
||||
}
|
||||
|
||||
// ======================== 年度统计导出 ========================
|
||||
|
||||
@Override
|
||||
public void exportYearData(List<String> yearStatType, List<String> years,
|
||||
List<String> stationCodes, List<String> baseIds,
|
||||
HttpServletResponse response) {
|
||||
if ((years == null || years.isEmpty()) || (stationCodes.isEmpty() && baseIds.isEmpty())) {
|
||||
log.warn("年度导出需要传入年份和电站/基地编码");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasWq = yearStatType.contains("wq");
|
||||
boolean hasQec = yearStatType.contains("qec");
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
|
||||
// 预查询测站信息(年维度无关,一次查询即可)
|
||||
List<StationInfo> wqStations = null;
|
||||
List<StationInfo> engStations = null;
|
||||
if (hasWq && !stationCodes.isEmpty()) {
|
||||
wqStations = yearStatMapper.selectList(
|
||||
new LambdaQueryWrapper<StationInfo>()
|
||||
.eq(StationInfo::getSttp, "WQ")
|
||||
.eq(StationInfo::getMway, 2)
|
||||
.and(w -> w.in(StationInfo::getRstcd, stationCodes)
|
||||
.or()
|
||||
.in(StationInfo::getStcd, stationCodes))
|
||||
);
|
||||
if (!wqStations.isEmpty()) {
|
||||
wqStations = sortStationsByOrder(wqStations);
|
||||
}
|
||||
}
|
||||
if (hasQec && !stationCodes.isEmpty()) {
|
||||
engStations = yearStatMapper.selectList(
|
||||
new LambdaQueryWrapper<StationInfo>()
|
||||
.eq(StationInfo::getSttp, "ENG")
|
||||
.and(w -> w.in(StationInfo::getRstcd, stationCodes)
|
||||
.or()
|
||||
.in(StationInfo::getStcd, stationCodes))
|
||||
);
|
||||
if (!engStations.isEmpty()) {
|
||||
engStations = sortStationsByOrder(engStations);
|
||||
}
|
||||
}
|
||||
|
||||
// 多年份:每年一个 Excel 文件
|
||||
List<ExportZipUtil.ExcelData> excelList = new ArrayList<>();
|
||||
for (String yearStr : years) {
|
||||
int year = Integer.parseInt(yearStr);
|
||||
List<ExportZipUtil.SheetData> sheets = new ArrayList<>();
|
||||
|
||||
// WQ
|
||||
if (wqStations != null && !wqStations.isEmpty()) {
|
||||
ExportZipUtil.SheetData wqSheet = buildYearWqSheet(wqStations, year);
|
||||
if (wqSheet != null) sheets.add(wqSheet);
|
||||
}
|
||||
|
||||
// QEC 电站维度
|
||||
if (engStations != null && !engStations.isEmpty()) {
|
||||
ExportZipUtil.SheetData qecEpaSheet = buildYearQecStationSheet(engStations, year, "QEC");
|
||||
ExportZipUtil.SheetData qecMwrSheet = buildYearQecStationSheet(engStations, year, "MWR");
|
||||
ExportZipUtil.SheetData qecAvqSheet = buildYearQecStationSheet(engStations, year, "AVQ");
|
||||
if (qecEpaSheet != null) sheets.add(qecEpaSheet);
|
||||
if (qecMwrSheet != null) sheets.add(qecMwrSheet);
|
||||
if (qecAvqSheet != null) sheets.add(qecAvqSheet);
|
||||
}
|
||||
|
||||
// QEC 水电基地维度
|
||||
if (!baseIds.isEmpty()) {
|
||||
ExportZipUtil.SheetData baseEpaSheet = buildYearQecBaseSheet(baseIds, year, "QEC");
|
||||
ExportZipUtil.SheetData baseMwrSheet = buildYearQecBaseSheet(baseIds, year, "MWR");
|
||||
ExportZipUtil.SheetData baseAvqSheet = buildYearQecBaseSheet(baseIds, year, "AVQ");
|
||||
if (baseEpaSheet != null) sheets.add(baseEpaSheet);
|
||||
if (baseMwrSheet != null) sheets.add(baseMwrSheet);
|
||||
if (baseAvqSheet != null) sheets.add(baseAvqSheet);
|
||||
}
|
||||
|
||||
if (!sheets.isEmpty()) {
|
||||
excelList.add(new ExportZipUtil.ExcelData(year + "年度报表_" + timestamp, sheets));
|
||||
}
|
||||
}
|
||||
|
||||
if (excelList.isEmpty()) {
|
||||
log.warn("年度导出没有可用的数据");
|
||||
return;
|
||||
}
|
||||
|
||||
String zipFileName = "年度报表.zip";
|
||||
ExportZipUtil.exportToResponse(response, zipFileName, excelList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建年度水质达标率 Sheet
|
||||
* 格式:电站 | 所在流域 | 小时尺度(%) | 日均尺度(%) | 主要污染物
|
||||
*/
|
||||
private ExportZipUtil.SheetData buildYearWqSheet(List<StationInfo> wqStations, int year) {
|
||||
List<String> stcdList = wqStations.stream().map(StationInfo::getStcd).distinct().toList();
|
||||
if (stcdList.isEmpty()) return null;
|
||||
|
||||
String startTime = year + "-01-01 00:00:00";
|
||||
String endTime = (year + 1) + "-01-01 00:00:00";
|
||||
|
||||
// 查询小时/日均达标数据
|
||||
List<Map<String, Object>> hourlyData = sdWqRMapper.selectComplianceData(stcdList, startTime, endTime);
|
||||
List<Map<String, Object>> dailyData = sdWqdaySMapper.selectComplianceData(stcdList, startTime, endTime);
|
||||
// 查询主要污染物
|
||||
List<Map<String, Object>> pollutantData = sdWqRMapper.selectPollutants(stcdList, startTime, endTime);
|
||||
// 查询要素中文名
|
||||
Map<String, String> colCommentMap = new LinkedHashMap<>();
|
||||
List<Map<String, Object>> colComments = sdWqRMapper.selectColumnComments();
|
||||
for (Map<String, Object> cm : colComments) {
|
||||
String colName = (String) cm.get("COLUMN_NAME");
|
||||
String comment = (String) cm.get("COMMENTS");
|
||||
if (colName != null && comment != null && !comment.isEmpty()) {
|
||||
colCommentMap.put(colName, comment);
|
||||
}
|
||||
}
|
||||
|
||||
// 按 STCD 聚合污染物
|
||||
Map<String, Set<String>> pollutantSetMap = new LinkedHashMap<>();
|
||||
for (Map<String, Object> m : pollutantData) {
|
||||
String stcd = (String) m.get("STCD");
|
||||
String pol = (String) m.get("POLLUTANT");
|
||||
if (pol != null && !pol.isEmpty()) {
|
||||
Set<String> set = pollutantSetMap.computeIfAbsent(stcd, k -> new LinkedHashSet<>());
|
||||
for (String field : pol.split(",")) {
|
||||
String f = field.trim();
|
||||
if (!f.isEmpty()) {
|
||||
String desc = colCommentMap.getOrDefault(f, f);
|
||||
set.add(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 索引达标统计
|
||||
Map<String, Map<String, Object>> hourlyMap = indexByStcd(hourlyData);
|
||||
Map<String, Map<String, Object>> dailyMap = indexByStcd(dailyData);
|
||||
|
||||
// 表头
|
||||
List<String> headers = Arrays.asList("电站", "所在流域", "小时尺度(%)", "日均尺度(%)", "主要污染物");
|
||||
|
||||
// 数据行
|
||||
List<List<Object>> rows = new ArrayList<>();
|
||||
for (StationInfo station : wqStations) {
|
||||
List<Object> row = new ArrayList<>();
|
||||
row.add(station.getEnnm() != null ? station.getEnnm() : station.getStnm());
|
||||
row.add(buildBasinPath(station));
|
||||
// 小时达标率
|
||||
row.add(calcComplianceRate(hourlyMap.get(station.getStcd())));
|
||||
// 日均达标率
|
||||
row.add(calcComplianceRate(dailyMap.get(station.getStcd())));
|
||||
// 主要污染物
|
||||
Set<String> pollutants = pollutantSetMap.get(station.getStcd());
|
||||
row.add(pollutants != null && !pollutants.isEmpty() ? String.join(", ", pollutants) : "");
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
return new ExportZipUtil.SheetData("电站-自动站水质达标率", headers, rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建年度生态流量电站维度 Sheet(环保部/水利部/多年平均)
|
||||
* 格式:电站 | 所在流域 | 所在水电基地 | 小时尺度(%) | 日尺度(%) | 月尺度(%)
|
||||
*/
|
||||
private ExportZipUtil.SheetData buildYearQecStationSheet(List<StationInfo> engStations, int year, String dept) {
|
||||
List<String> stcdList = engStations.stream().map(StationInfo::getStcd).distinct().toList();
|
||||
if (stcdList.isEmpty()) return null;
|
||||
|
||||
String startTime = year + "-01-01 00:00:00";
|
||||
String endTime = (year + 1) + "-01-01 00:00:00";
|
||||
|
||||
// 查询三种维度达标数据
|
||||
List<Map<String, Object>> hourlyData = sdQecRMapper.selectComplianceData(stcdList, startTime, endTime);
|
||||
List<Map<String, Object>> dailyData = sdQecdaySMapper.selectComplianceData(stcdList, startTime, endTime);
|
||||
List<Map<String, Object>> monthlyData = yearStatMapper.selectQecMonthlyCompliance(stcdList, year);
|
||||
|
||||
// 按 STCD 聚合小时/日数据(原始数据是逐行明细,需聚合)
|
||||
Map<String, long[]> hourlyAgg = aggregateQecCompliance(hourlyData, dept);
|
||||
Map<String, long[]> dailyAgg = aggregateQecCompliance(dailyData, dept);
|
||||
Map<String, Map<String, Object>> monthlyMap = indexByStcd(monthlyData);
|
||||
|
||||
// 部门名称和对应的 SFDB 字段
|
||||
String deptName;
|
||||
String sfdbField;
|
||||
String compliantField;
|
||||
String totalField;
|
||||
switch (dept) {
|
||||
case "MWR":
|
||||
deptName = "水利部";
|
||||
sfdbField = "MWR_SFDB";
|
||||
compliantField = "MWR_COMPLIANT";
|
||||
totalField = "MWR_TOTAL";
|
||||
break;
|
||||
case "AVQ":
|
||||
deptName = "多年平均";
|
||||
sfdbField = "AVQ_SFDB";
|
||||
compliantField = "AVQ_COMPLIANT";
|
||||
totalField = "AVQ_TOTAL";
|
||||
break;
|
||||
default: // QEC 环保部
|
||||
deptName = "环保部";
|
||||
sfdbField = "SFDB";
|
||||
compliantField = "QEC_COMPLIANT";
|
||||
totalField = "QEC_TOTAL";
|
||||
break;
|
||||
}
|
||||
|
||||
List<String> headers = Arrays.asList("电站", "所在流域", "所在水电基地", "小时尺度(%)", "日尺度(%)", "月尺度(%)");
|
||||
List<List<Object>> rows = new ArrayList<>();
|
||||
|
||||
for (StationInfo station : engStations) {
|
||||
List<Object> row = new ArrayList<>();
|
||||
row.add(station.getEnnm() != null ? station.getEnnm() : station.getStnm());
|
||||
row.add(buildBasinPath(station));
|
||||
row.add(station.getBaseName() != null ? station.getBaseName() : "");
|
||||
// 小时达标率
|
||||
long[] hAgg = hourlyAgg.get(station.getStcd());
|
||||
row.add(formatRate(hAgg != null ? hAgg[0] : 0, hAgg != null ? hAgg[1] : 0));
|
||||
// 日达标率
|
||||
long[] dAgg = dailyAgg.get(station.getStcd());
|
||||
row.add(formatRate(dAgg != null ? dAgg[0] : 0, dAgg != null ? dAgg[1] : 0));
|
||||
// 月度达标率
|
||||
row.add(calcMonthlyComplianceRate(monthlyMap.get(station.getStcd()), compliantField, totalField));
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
return new ExportZipUtil.SheetData("电站-生态流量达标率(" + deptName + ")", headers, rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建年度生态流量水电基地维度 Sheet
|
||||
* 格式:水电基地 | 小时尺度(%) | 日尺度(%) | 月尺度(%)
|
||||
*/
|
||||
private ExportZipUtil.SheetData buildYearQecBaseSheet(List<String> baseIds, int year, String dept) {
|
||||
if (baseIds.isEmpty()) return null;
|
||||
|
||||
// 查询该 BASE_ID 下的所有 ENG 测站
|
||||
List<StationInfo> baseStations = yearStatMapper.selectList(
|
||||
new LambdaQueryWrapper<StationInfo>()
|
||||
.eq(StationInfo::getSttp, "ENG")
|
||||
.in(StationInfo::getBaseId, baseIds)
|
||||
);
|
||||
if (baseStations.isEmpty()) return null;
|
||||
|
||||
// 按 BASE_ID 分组
|
||||
Map<String, List<StationInfo>> byBase = new LinkedHashMap<>();
|
||||
Map<String, String> baseNameMap = new LinkedHashMap<>();
|
||||
for (StationInfo s : baseStations) {
|
||||
String bid = s.getBaseId();
|
||||
if (bid == null) continue;
|
||||
byBase.computeIfAbsent(bid, k -> new ArrayList<>()).add(s);
|
||||
if (s.getBaseName() != null) baseNameMap.put(bid, s.getBaseName());
|
||||
}
|
||||
|
||||
// 收集所有 STCD
|
||||
List<String> allStcds = baseStations.stream().map(StationInfo::getStcd).distinct().toList();
|
||||
String startTime = year + "-01-01 00:00:00";
|
||||
String endTime = (year + 1) + "-01-01 00:00:00";
|
||||
|
||||
// 查询达标数据
|
||||
List<Map<String, Object>> hourlyData = sdQecRMapper.selectComplianceData(allStcds, startTime, endTime);
|
||||
List<Map<String, Object>> dailyData = sdQecdaySMapper.selectComplianceData(allStcds, startTime, endTime);
|
||||
List<Map<String, Object>> monthlyData = yearStatMapper.selectQecMonthlyComplianceByBase(baseIds, year);
|
||||
|
||||
Map<String, Map<String, Object>> monthlyMap = new LinkedHashMap<>();
|
||||
for (Map<String, Object> m : monthlyData) {
|
||||
String bid = (String) m.get("BASE_ID");
|
||||
if (bid != null) monthlyMap.put(bid, m);
|
||||
}
|
||||
|
||||
String deptName;
|
||||
String compliantField;
|
||||
String totalField;
|
||||
switch (dept) {
|
||||
case "MWR":
|
||||
deptName = "水利部";
|
||||
compliantField = "MWR_COMPLIANT";
|
||||
totalField = "MWR_TOTAL";
|
||||
break;
|
||||
case "AVQ":
|
||||
deptName = "多年平均";
|
||||
compliantField = "AVQ_COMPLIANT";
|
||||
totalField = "AVQ_TOTAL";
|
||||
break;
|
||||
default:
|
||||
deptName = "环保部";
|
||||
compliantField = "QEC_COMPLIANT";
|
||||
totalField = "QEC_TOTAL";
|
||||
break;
|
||||
}
|
||||
|
||||
List<String> headers = Arrays.asList("水电基地", "小时尺度(%)", "日尺度(%)", "月尺度(%)");
|
||||
List<List<Object>> rows = new ArrayList<>();
|
||||
|
||||
for (String baseId : baseIds) {
|
||||
List<StationInfo> sts = byBase.get(baseId);
|
||||
if (sts == null || sts.isEmpty()) continue;
|
||||
|
||||
List<String> baseStcds = sts.stream().map(StationInfo::getStcd).distinct().toList();
|
||||
|
||||
// 按 BASE 聚合达标率
|
||||
long hourCompliant = 0, hourTotal = 0;
|
||||
long dayCompliant = 0, dayTotal = 0;
|
||||
|
||||
for (Map<String, Object> m : hourlyData) {
|
||||
if (baseStcds.contains(m.get("STCD"))) {
|
||||
hourCompliant += countCompliant(m, "SFDB", "MWR_SFDB", "AVQ_SFDB", dept);
|
||||
hourTotal += countTotal(m, "SFDB", "MWR_SFDB", "AVQ_SFDB", dept);
|
||||
}
|
||||
}
|
||||
for (Map<String, Object> m : dailyData) {
|
||||
if (baseStcds.contains(m.get("STCD"))) {
|
||||
dayCompliant += countCompliant(m, "SFDB", "MWR_SFDB", "AVQ_SFDB", dept);
|
||||
dayTotal += countTotal(m, "SFDB", "MWR_SFDB", "AVQ_SFDB", dept);
|
||||
}
|
||||
}
|
||||
|
||||
List<Object> row = new ArrayList<>();
|
||||
row.add(baseNameMap.getOrDefault(baseId, baseId));
|
||||
row.add(formatRate(hourCompliant, hourTotal));
|
||||
row.add(formatRate(dayCompliant, dayTotal));
|
||||
|
||||
// 月度达标率(直接从 SQL 聚合结果获取)
|
||||
Map<String, Object> mStat = monthlyMap.get(baseId);
|
||||
if (mStat != null) {
|
||||
Object cObj = mStat.get(compliantField);
|
||||
Object tObj = mStat.get(totalField);
|
||||
long c = cObj instanceof Number ? ((Number) cObj).longValue() : 0;
|
||||
long t = tObj instanceof Number ? ((Number) tObj).longValue() : 0;
|
||||
row.add(formatRate(c, t));
|
||||
} else {
|
||||
row.add("");
|
||||
}
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
return new ExportZipUtil.SheetData("水电基地-生态流量达标率(" + deptName + ")", headers, rows);
|
||||
}
|
||||
|
||||
// ======================== 年度导出辅助方法 ========================
|
||||
|
||||
/**
|
||||
* 按 STCD 索引 Map 列表
|
||||
*/
|
||||
private Map<String, Map<String, Object>> indexByStcd(List<Map<String, Object>> data) {
|
||||
Map<String, Map<String, Object>> result = new LinkedHashMap<>();
|
||||
for (Map<String, Object> m : data) {
|
||||
String stcd = (String) m.get("STCD");
|
||||
if (stcd != null) result.put(stcd, m);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建流域路径:一级流域-二级流域-水系
|
||||
*/
|
||||
private String buildBasinPath(StationInfo station) {
|
||||
// StringBuilder sb = new StringBuilder();
|
||||
// if (station.getHbrvcdName() != null) sb.append(station.getHbrvcdName());
|
||||
// if (station.getRvcdName() != null) {
|
||||
// if (sb.length() > 0) sb.append("-");
|
||||
// sb.append(station.getRvcdName());
|
||||
// }
|
||||
// if (station.getHynm() != null) {
|
||||
// if (sb.length() > 0) sb.append("-");
|
||||
// sb.append(station.getHynm());
|
||||
// }
|
||||
// return sb.toString();
|
||||
return station.getReachcdName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算水质达标率(%, 保留2位小数)
|
||||
*/
|
||||
private String calcComplianceRate(Map<String, Object> stat) {
|
||||
if (stat == null) return "";
|
||||
Object cObj = stat.get("COMPLIANT_CNT");
|
||||
Object tObj = stat.get("TOTAL_CNT");
|
||||
long compliant = cObj instanceof Number ? ((Number) cObj).longValue() : 0;
|
||||
long total = tObj instanceof Number ? ((Number) tObj).longValue() : 0;
|
||||
return formatRate(compliant, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算 QEC 部门达标率(从明细数据 SFDB/MWR_SFDB/AVQ_SFDB)
|
||||
*/
|
||||
private String calcQecComplianceRate(Map<String, Object> stat, String dept) {
|
||||
if (stat == null) return "";
|
||||
long compliant = countCompliant(stat, "SFDB", "MWR_SFDB", "AVQ_SFDB", dept);
|
||||
long total = countTotal(stat, "SFDB", "MWR_SFDB", "AVQ_SFDB", dept);
|
||||
return formatRate(compliant, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合 QEC 达标明细数据:按 STCD 汇总达标数/总数
|
||||
*
|
||||
* @return Map<STCD, long [ 2 ]> 其中 [0]=compliant, [1]=total
|
||||
*/
|
||||
private Map<String, long[]> aggregateQecCompliance(List<Map<String, Object>> data, String dept) {
|
||||
Map<String, long[]> result = new LinkedHashMap<>();
|
||||
for (Map<String, Object> m : data) {
|
||||
String stcd = (String) m.get("STCD");
|
||||
if (stcd == null) continue;
|
||||
result.computeIfAbsent(stcd, k -> new long[2]);
|
||||
long[] arr = result.get(stcd);
|
||||
arr[0] += countCompliant(m, "SFDB", "MWR_SFDB", "AVQ_SFDB", dept);
|
||||
arr[1] += countTotal(m, "SFDB", "MWR_SFDB", "AVQ_SFDB", dept);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private long countCompliant(Map<String, Object> map, String qecField, String mwrField, String avqField, String dept) {
|
||||
String field;
|
||||
switch (dept) {
|
||||
case "MWR":
|
||||
field = mwrField;
|
||||
break;
|
||||
case "AVQ":
|
||||
field = avqField;
|
||||
break;
|
||||
default:
|
||||
field = qecField;
|
||||
}
|
||||
Object val = map.get(field);
|
||||
if (val instanceof Number && ((Number) val).intValue() == 1) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private long countTotal(Map<String, Object> map, String qecField, String mwrField, String avqField, String dept) {
|
||||
String field;
|
||||
switch (dept) {
|
||||
case "MWR":
|
||||
field = mwrField;
|
||||
break;
|
||||
case "AVQ":
|
||||
field = avqField;
|
||||
break;
|
||||
default:
|
||||
field = qecField;
|
||||
}
|
||||
Object val = map.get(field);
|
||||
if (val instanceof Number) {
|
||||
int v = ((Number) val).intValue();
|
||||
if (v == 0 || v == 1) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算月度达标率(从聚合结果 COMPLIANT/TOTAL 字段)
|
||||
*/
|
||||
private String calcMonthlyComplianceRate(Map<String, Object> stat, String compliantField, String totalField) {
|
||||
if (stat == null) return "";
|
||||
Object cObj = stat.get(compliantField);
|
||||
Object tObj = stat.get(totalField);
|
||||
long c = cObj instanceof Number ? ((Number) cObj).longValue() : 0;
|
||||
long t = tObj instanceof Number ? ((Number) tObj).longValue() : 0;
|
||||
return formatRate(c, t);
|
||||
}
|
||||
|
||||
private String formatRate(long compliant, long total) {
|
||||
if (total > 0) {
|
||||
BigDecimal rate = BigDecimal.valueOf(compliant * 100.0 / total);
|
||||
return rate.setScale(2, RoundingMode.HALF_UP).toPlainString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 按沿程排序测站
|
||||
*/
|
||||
private List<StationInfo> sortStationsByOrder(List<StationInfo> stations) {
|
||||
// 按 RSTCD 收集沿程排序
|
||||
List<String> rstcds = stations.stream().map(StationInfo::getRstcd).filter(Objects::nonNull).distinct().toList();
|
||||
if (rstcds.isEmpty()) return stations;
|
||||
|
||||
List<Map<String, Object>> orderList = msAlongdetBMapper.selectAlongSortOrder(rstcds);
|
||||
Map<String, Integer> orderMap = new LinkedHashMap<>();
|
||||
for (Map<String, Object> m : orderList) {
|
||||
String rstcd = (String) m.get("RSTCD");
|
||||
Object sortObj = m.get("SORT");
|
||||
if (rstcd != null && sortObj instanceof Number) {
|
||||
orderMap.put(rstcd, ((Number) sortObj).intValue());
|
||||
}
|
||||
}
|
||||
|
||||
List<StationInfo> sorted = new ArrayList<>(stations);
|
||||
sorted.sort((a, b) -> {
|
||||
Integer oa = orderMap.get(a.getRstcd());
|
||||
Integer ob = orderMap.get(b.getRstcd());
|
||||
if (oa != null && ob != null) return oa.compareTo(ob);
|
||||
if (oa != null) return -1;
|
||||
if (ob != null) return 1;
|
||||
return 0;
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
|
||||
// ======================== hour/day 原始数据导出 ========================
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.yfd.platform.qgc_export.mapper.YearStatMapper">
|
||||
|
||||
<!-- 生态流量月度达标统计:按 STCD 聚合三种 SFDB -->
|
||||
<select id="selectQecMonthlyCompliance" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
STCD,
|
||||
SUM(CASE WHEN SFDB = 1 THEN 1 ELSE 0 END) AS QEC_COMPLIANT,
|
||||
SUM(CASE WHEN SFDB IN (0, 1) THEN 1 ELSE 0 END) AS QEC_TOTAL,
|
||||
SUM(CASE WHEN MWR_SFDB = 1 THEN 1 ELSE 0 END) AS MWR_COMPLIANT,
|
||||
SUM(CASE WHEN MWR_SFDB IN (0, 1) THEN 1 ELSE 0 END) AS MWR_TOTAL,
|
||||
SUM(CASE WHEN AVQ_SFDB = 1 THEN 1 ELSE 0 END) AS AVQ_COMPLIANT,
|
||||
SUM(CASE WHEN AVQ_SFDB IN (0, 1) THEN 1 ELSE 0 END) AS AVQ_TOTAL
|
||||
FROM SD_QECDRTP_S
|
||||
WHERE IS_DELETED = 0
|
||||
AND DRTP = 'MON'
|
||||
AND STCD IN
|
||||
<foreach collection="stcdList" item="stcd" open="(" separator="," close=")">
|
||||
#{stcd}
|
||||
</foreach>
|
||||
AND YEAR = #{year}
|
||||
GROUP BY STCD
|
||||
</select>
|
||||
|
||||
<!-- 生态流量月度达标统计:按 BASE_ID 聚合 -->
|
||||
<select id="selectQecMonthlyComplianceByBase" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
v.BASE_ID,
|
||||
SUM(CASE WHEN d.SFDB = 1 THEN 1 ELSE 0 END) AS QEC_COMPLIANT,
|
||||
SUM(CASE WHEN d.SFDB IN (0, 1) THEN 1 ELSE 0 END) AS QEC_TOTAL,
|
||||
SUM(CASE WHEN d.MWR_SFDB = 1 THEN 1 ELSE 0 END) AS MWR_COMPLIANT,
|
||||
SUM(CASE WHEN d.MWR_SFDB IN (0, 1) THEN 1 ELSE 0 END) AS MWR_TOTAL,
|
||||
SUM(CASE WHEN d.AVQ_SFDB = 1 THEN 1 ELSE 0 END) AS AVQ_COMPLIANT,
|
||||
SUM(CASE WHEN d.AVQ_SFDB IN (0, 1) THEN 1 ELSE 0 END) AS AVQ_TOTAL
|
||||
FROM SD_QECDRTP_S d
|
||||
INNER JOIN V_MS_STBPRP_T v ON v.STCD = d.STCD AND v.STTP = 'ENG'
|
||||
WHERE d.IS_DELETED = 0
|
||||
AND d.DRTP = 'MON'
|
||||
AND v.BASE_ID IN
|
||||
<foreach collection="baseIdList" item="bid" open="(" separator="," close=")">
|
||||
#{bid}
|
||||
</foreach>
|
||||
AND d.YEAR = #{year}
|
||||
GROUP BY v.BASE_ID
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
Loading…
Reference in New Issue
Block a user