fix: 增加年度导出

This commit is contained in:
tangwei 2026-08-07 09:46:02 +08:00
parent 29105cf1f5
commit b19db9e8d2
6 changed files with 758 additions and 45 deletions

View File

@ -47,20 +47,29 @@ public class QgcExportController {
@RequestParam(required = false) String sysId, @RequestParam(required = false) String sysId,
@RequestParam(defaultValue = "true") boolean isDailyDimension, @RequestParam(defaultValue = "true") boolean isDailyDimension,
@RequestParam(defaultValue = "false") boolean isCalcQec, @RequestParam(defaultValue = "false") boolean isCalcQec,
@RequestParam(required = false) String yearStatType,
@RequestParam(required = false) String year,
@RequestParam(required = false) String baseId,
HttpServletResponse response) { HttpServletResponse response) {
// 解析逗号分隔参数 // 解析逗号分隔参数
List<String> dataFields = parseCsv(dataField); List<String> dataFields = parseCsv(dataField);
List<String> monthList = parseCsv(months); List<String> monthList = parseCsv(months);
List<String> stationList = parseCsv(stcd); List<String> stationList = parseCsv(stcd);
List<String> baseIdList = parseCsv(baseId);
// if (dataFields.isEmpty()) {
// dataFields = Arrays.asList("v", "q", "z");
// }
if (tmDimension == null || tmDimension.isEmpty()) { if (tmDimension == null || tmDimension.isEmpty()) {
tmDimension = "month"; 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, qgcExportService.exportData(dataFields, monthList, stationList,
isDailyDimension, tmDimension, response); isDailyDimension, tmDimension, response);
} }

View File

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

View File

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

View File

@ -24,4 +24,19 @@ public interface IQgcExportService {
boolean isDailyDimension, boolean isDailyDimension,
String tmDimension, String tmDimension,
HttpServletResponse response); 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);
} }

View File

@ -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.SdQecdaySMapper;
import com.yfd.platform.qgc_export.mapper.StationMappingMapper; import com.yfd.platform.qgc_export.mapper.StationMappingMapper;
import com.yfd.platform.qgc_export.mapper.MsAlongdetBMapper; 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_env.wt.mapper.SdWtvtRMapper;
import com.yfd.platform.qgc_export.mapper.SdWtvtdaySMapper; import com.yfd.platform.qgc_export.mapper.SdWtvtdaySMapper;
import com.yfd.platform.qgc_export.processor.*; import com.yfd.platform.qgc_export.processor.*;
@ -156,13 +158,20 @@ public class QgcExportServiceImpl implements IQgcExportService {
@Resource @Resource
private RwtProcessor rwtProcessor; private RwtProcessor rwtProcessor;
@Resource
private YearStatMapper yearStatMapper;
private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时"); private static final SimpleDateFormat SDF_TIME = new SimpleDateFormat("dd日 HH时");
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/** 基础汇总指标7个 */ /**
* 基础汇总指标7个
*/
private static final List<String> BASE_LABELS = Collections.unmodifiableList( private static final List<String> BASE_LABELS = Collections.unmodifiableList(
Arrays.asList("日均最低", "日均最高", "月内最低", "月内最低时间", "月内最高", "月内最高时间", "月均")); Arrays.asList("日均最低", "日均最高", "月内最低", "月内最低时间", "月内最高", "月内最高时间", "月均"));
/** 小时达标率标签 */ /**
* 小时达标率标签
*/
private static final String LABEL_COMPLIANCE = "小时达标率"; private static final String LABEL_COMPLIANCE = "小时达标率";
@Override @Override
@ -233,10 +242,14 @@ public class QgcExportServiceImpl implements IQgcExportService {
headers = new ArrayList<>(); headers = new ArrayList<>();
headers.add("测站名称"); headers.add("测站名称");
headers.add("水深(m)"); headers.add("水深(m)");
headers.add("5日"); headers.add(""); headers.add("5日");
headers.add("15日"); headers.add(""); headers.add("");
headers.add("25日"); headers.add(""); headers.add("15日");
headers.add("月均"); headers.add(""); headers.add("");
headers.add("25日");
headers.add("");
headers.add("月均");
headers.add("");
} else { } else {
// 仅包含当前处理器 STTP 类型有对应测站的电站 // 仅包含当前处理器 STTP 类型有对应测站的电站
headers = new ArrayList<>(); headers = new ArrayList<>();
@ -400,16 +413,32 @@ public class QgcExportServiceImpl implements IQgcExportService {
for (String rstcd : rstcdToStcd.keySet()) { for (String rstcd : rstcdToStcd.keySet()) {
Aggregation agg = aggMap.get(rstcd); Aggregation agg = aggMap.get(rstcd);
switch (i) { switch (i) {
case 0: row.add(formatOrEmpty(agg != null ? agg.getDailyMin() : null, decimals)); break; case 0:
case 1: row.add(formatOrEmpty(agg != null ? agg.getDailyMax() : null, decimals)); break; row.add(formatOrEmpty(agg != null ? agg.getDailyMin() : null, decimals));
case 2: row.add(formatOrEmpty(agg != null ? agg.getExtremeMin() : null, decimals)); break; break;
case 3: row.add(agg != null && agg.getExtremeMinTime() != null case 1:
? SDF_TIME.format(agg.getExtremeMinTime()) : ""); break; row.add(formatOrEmpty(agg != null ? agg.getDailyMax() : null, decimals));
case 4: row.add(formatOrEmpty(agg != null ? agg.getExtremeMax() : null, decimals)); break; break;
case 5: row.add(agg != null && agg.getExtremeMaxTime() != null case 2:
? SDF_TIME.format(agg.getExtremeMaxTime()) : ""); break; row.add(formatOrEmpty(agg != null ? agg.getExtremeMin() : null, decimals));
case 6: row.add(formatOrEmpty(agg != null ? agg.getMonthlyAvg() : null, decimals)); break; break;
case 7: row.add(formatComplianceRate(agg != null ? agg.getComplianceRate() : null)); 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); rows.add(row);
@ -540,7 +569,11 @@ public class QgcExportServiceImpl implements IQgcExportService {
private int toInt(Object v) { private int toInt(Object v) {
if (v instanceof Number) return ((Number) v).intValue(); 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双表头 ======================== // ======================== WQ 水质达标率 Sheet双表头 ========================
@ -651,11 +684,16 @@ public class QgcExportServiceImpl implements IQgcExportService {
List<List<Object>> rows = new ArrayList<>(); List<List<Object>> rows = new ArrayList<>();
// 子标题行Row 0 // 子标题行Row 0
List<Object> subHeaderRow = new ArrayList<>(); List<Object> subHeaderRow = new ArrayList<>();
subHeaderRow.add(""); subHeaderRow.add(""); subHeaderRow.add("");
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)"); subHeaderRow.add("");
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)"); subHeaderRow.add("水温(℃)");
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)"); subHeaderRow.add("相应高程(m)");
subHeaderRow.add("水温(℃)"); subHeaderRow.add("相应高程(m)"); subHeaderRow.add("水温(℃)");
subHeaderRow.add("相应高程(m)");
subHeaderRow.add("水温(℃)");
subHeaderRow.add("相应高程(m)");
subHeaderRow.add("水温(℃)");
subHeaderRow.add("相应高程(m)");
rows.add(subHeaderRow); rows.add(subHeaderRow);
for (String key : sortedKeys) { for (String key : sortedKeys) {
@ -855,7 +893,9 @@ public class QgcExportServiceImpl implements IQgcExportService {
} }
} }
/** 分部达标统计 */ /**
* 分部达标统计
*/
private static class ComplianceStats { private static class ComplianceStats {
String dailyHb, dailySl, dailyDn; String dailyHb, dailySl, dailyDn;
String hourlyHb, hourlySl, hourlyDn; String hourlyHb, hourlySl, hourlyDn;
@ -1011,7 +1051,10 @@ public class QgcExportServiceImpl implements IQgcExportService {
} }
if (defaultDisplayName == null) { if (defaultDisplayName == null) {
for (StationMapping m : rows) { for (StationMapping m : rows) {
if (m.getStnm() != null) { defaultDisplayName = m.getStnm(); break; } if (m.getStnm() != null) {
defaultDisplayName = m.getStnm();
break;
}
} }
} }
if (defaultDisplayName == null) { if (defaultDisplayName == null) {
@ -1157,7 +1200,9 @@ public class QgcExportServiceImpl implements IQgcExportService {
// ======================== 日变幅格式出入库水温日变幅 ======================== // ======================== 日变幅格式出入库水温日变幅 ========================
/** 日变幅汇总标签 */ /**
* 日变幅汇总标签
*/
private static final List<String> VARIATION_LABELS = List.of("日变幅均值", "日变幅最大值"); private static final List<String> VARIATION_LABELS = List.of("日变幅均值", "日变幅最大值");
/** /**
@ -1270,16 +1315,26 @@ public class QgcExportServiceImpl implements IQgcExportService {
// ======================== 内部数据类 ======================== // ======================== 内部数据类 ========================
private static class StationResolution { private static class StationResolution {
/** 默认测站名称fallbackRSTCD → displayName */ /**
* 默认测站名称fallbackRSTCD displayName
*/
final Map<String, String> displayNames = new LinkedHashMap<>(); final Map<String, String> displayNames = new LinkedHashMap<>();
/** 按 STTP 分组的测站名称RSTCD → {STTP → displayName} */ /**
* STTP 分组的测站名称RSTCD {STTP displayName}
*/
final Map<String, Map<String, String>> sttpDisplayNames = new LinkedHashMap<>(); 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<>(); 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<>(); final Map<String, Map<String, String>> baseNameMap = new LinkedHashMap<>();
/** 获取默认测站名称 */ /**
* 获取默认测站名称
*/
String getDisplayName(String rstcd) { String getDisplayName(String rstcd) {
return displayNames.getOrDefault(rstcd, rstcd); return displayNames.getOrDefault(rstcd, rstcd);
} }
@ -1307,7 +1362,9 @@ public class QgcExportServiceImpl implements IQgcExportService {
return inner.get(sttp); return inner.get(sttp);
} }
/** 获取指定 STTP 的水电基地名称 */ /**
* 获取指定 STTP 的水电基地名称
*/
String getBaseName(String rstcd, String sttp) { String getBaseName(String rstcd, String sttp) {
Map<String, String> inner = baseNameMap.get(rstcd); Map<String, String> inner = baseNameMap.get(rstcd);
if (inner != null && sttp != null) { if (inner != null && sttp != null) {
@ -1334,10 +1391,510 @@ public class QgcExportServiceImpl implements IQgcExportService {
private BigDecimal extremeMax; private BigDecimal extremeMax;
private Date extremeMaxTime; private Date extremeMaxTime;
private BigDecimal monthlyAvg; private BigDecimal monthlyAvg;
/** 小时达标率(百分比,仅水质指标使用) */ /**
* 小时达标率百分比仅水质指标使用
*/
private BigDecimal complianceRate; 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 原始数据导出 ======================== // ======================== hour/day 原始数据导出 ========================
/** /**

View File

@ -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>