reportEngine:dcp报告支持
This commit is contained in:
parent
5d8ae0e2f0
commit
694f48acd1
@ -232,3 +232,13 @@ map:
|
|||||||
gaode_ak: 3af85be39e2f28334097781349336157
|
gaode_ak: 3af85be39e2f28334097781349336157
|
||||||
gaode_secret: d0561ad20142e74f8c59edc5deafdbe4
|
gaode_secret: d0561ad20142e74f8c59edc5deafdbe4
|
||||||
tianditu_ak: b920e6076a06aa279c974f5f76cf746e
|
tianditu_ak: b920e6076a06aa279c974f5f76cf746e
|
||||||
|
|
||||||
|
# 设备监测报告导出配置(全局一刀切,test/prod 环境共用)
|
||||||
|
report:
|
||||||
|
# 模板编码(取值即 templates/ 下的模板名,V2 为按分类取默认的版本标记):
|
||||||
|
# 留空 或 V2 → 完整版,按监测分类走 DEVICE_{CATEGORY}_V2(共 10 节)
|
||||||
|
# DCPReport → DCP 平台专用三节版式(项目概况 / 测点数据 / 时程曲线),
|
||||||
|
# 测点表只输出 ThingsThreshold.is_primary=1 的主监测指标
|
||||||
|
# 默认保持完整版(V2):切换版式是交付格式变更,需现场确认后由运维显式改为 DCPReport
|
||||||
|
template-code: V2
|
||||||
|
|
||||||
|
|||||||
@ -313,6 +313,10 @@ public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
|||||||
? null : primaryIdentifiers.get(0);
|
? null : primaryIdentifiers.get(0);
|
||||||
result.setPrimaryIdentifier(primaryIdentity);
|
result.setPrimaryIdentifier(primaryIdentity);
|
||||||
|
|
||||||
|
// 4.1 用户显式勾选的主监测指标(is_primary=1):与上面的推断结果是两个口径,原样透传,
|
||||||
|
// 供精简版报告的测点表严格按用户配置出列(没勾就是空,不做关键词兜底)
|
||||||
|
result.setExplicitPrimaryIdentifiers(resolveExplicitPrimary(context, primaryIdentifiers));
|
||||||
|
|
||||||
// 5. 组装测点数据(主指标排第一,辅助量作为影响量保留)
|
// 5. 组装测点数据(主指标排第一,辅助量作为影响量保留)
|
||||||
result.setMetricPoints(buildMetricPointsFiltered(result, context, dataByIdentity, statisticsDurationSeconds));
|
result.setMetricPoints(buildMetricPointsFiltered(result, context, dataByIdentity, statisticsDurationSeconds));
|
||||||
|
|
||||||
@ -491,6 +495,41 @@ public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
|||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析「用户显式勾选的主监测指标」({@code is_primary=1}),供精简版报告出表用
|
||||||
|
*
|
||||||
|
* <p>严格取 {@link ReportContext#getExplicitPrimaryIdentifiers()},<b>不做任何推断兜底</b>:
|
||||||
|
* 用户没勾就是空列表,精简版测点表据此输出"未配置主监测指标"提示行,而不是偷偷用
|
||||||
|
* 关键词推断的指标顶替 —— 否则报告里会出现用户从未配置、也没预期看到的列。</p>
|
||||||
|
*
|
||||||
|
* <p>顺序:沿用 {@code primaryIdentifiers} 的物模型顺序(Resolver 收集显式项时即按此序),
|
||||||
|
* 保证与完整版报告的列顺序一致,便于两份报告横向对照。</p>
|
||||||
|
*
|
||||||
|
* <p>不过滤「统计期内无数据」的显式项:该列保留并显示 {@code -},让缺测可见;
|
||||||
|
* 若在此处剔除,报告会把「配了但没上报」伪装成「没配」。</p>
|
||||||
|
*/
|
||||||
|
private List<String> resolveExplicitPrimary(ReportContext context, List<String> primaryIdentifiers) {
|
||||||
|
Set<String> explicit = context.getExplicitPrimaryIdentifiers();
|
||||||
|
if (explicit == null || explicit.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
List<String> ordered = new ArrayList<>();
|
||||||
|
if (primaryIdentifiers != null) {
|
||||||
|
for (String id : primaryIdentifiers) {
|
||||||
|
if (explicit.contains(id) && !ordered.contains(id)) {
|
||||||
|
ordered.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 理论不会走到这里(Resolver 显式分支必然收齐全部勾选项),保底避免静默丢列
|
||||||
|
for (String id : explicit) {
|
||||||
|
if (!ordered.contains(id)) {
|
||||||
|
ordered.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断统计值是否全为 0(或无效)
|
* 判断统计值是否全为 0(或无效)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -47,9 +47,6 @@ public class ReportRequest implements Serializable {
|
|||||||
@ApiModelProperty("周期锚点日期 yyyy-MM-dd(默认今天,仅周期预设时使用)")
|
@ApiModelProperty("周期锚点日期 yyyy-MM-dd(默认今天,仅周期预设时使用)")
|
||||||
private String anchorDate;
|
private String anchorDate;
|
||||||
|
|
||||||
@ApiModelProperty("模板编码,如 DEVICE_CRACK_METER_V2")
|
|
||||||
private String templateCode;
|
|
||||||
|
|
||||||
@ApiModelProperty("导出格式(目前仅支持 WORD)")
|
@ApiModelProperty("导出格式(目前仅支持 WORD)")
|
||||||
private String format;
|
private String format;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,73 @@
|
|||||||
|
package com.fastbee.report.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报告导出配置
|
||||||
|
*
|
||||||
|
* <p>只控制「用哪一套模板」,全局一刀切。</p>
|
||||||
|
*
|
||||||
|
* <p><b>模板版式</b>(2026-10-04 起只有两种,不要按字面值硬判断):</p>
|
||||||
|
* <ul>
|
||||||
|
* <li><b>V2</b>:完整版,按监测分类取 {@code DEVICE_{CATEGORY}_V2},共 10 节;</li>
|
||||||
|
* <li><b>DCPReport</b>:DCP 平台专用的三节版式(项目概况 / 测点数据 / 时程曲线),
|
||||||
|
* 测点表只输出 {@code iot_things_threshold.is_primary=1} 的主监测指标。
|
||||||
|
* 全分类共用一份 {@code templates/DCPReport.docx},首行标题用
|
||||||
|
* {@code {{device.categoryDesc}}} 占位,因此不再按分类拆分。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p><b>为什么叫 DCPReport</b>:这套三节版式是 DCP 平台(平台实时数据推送,见
|
||||||
|
* {@code com.fastbee.iot.platform.adapter.dcp})要求交付的报告格式,命名与用途对齐,
|
||||||
|
* 便于日后回溯「为什么报告有两套版式」。注意与推送侧的 DCP 适配器区分:本常量属
|
||||||
|
* <b>报告导出</b>,彼处属<b>数据推送</b>,两者是不同业务线。</p>
|
||||||
|
*
|
||||||
|
* <p><b>为什么不按分类/产品白名单</b>:报告模板是现场交付物,格式要求由业主一次性约定,
|
||||||
|
* 不会一部分产品要三节版、一部分要完整版。全局开关让「改配置即全量生效」,
|
||||||
|
* 也避免白名单长期无人维护、导致部分设备悄悄导出错版本。</p>
|
||||||
|
*
|
||||||
|
* @author FastBee
|
||||||
|
* @date 2026-10-04
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "report")
|
||||||
|
public class ReportProperties {
|
||||||
|
|
||||||
|
/** 完整版模板的版本标记:配置值等于它(或为空)时按监测分类取默认模板 */
|
||||||
|
public static final String FULL_VERSION_CODE = "V2";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCP 平台三节版式的模板编码,与 {@code templates/DCPReport.docx} 文件名严格一致
|
||||||
|
*/
|
||||||
|
public static final String DCP_REPORT_TEMPLATE_CODE = "DCPReport";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 报告模板编码
|
||||||
|
*
|
||||||
|
* <p>取值:空 / {@code V2} → 完整版;{@code DCPReport} → DCP 三节版式。</p>
|
||||||
|
*/
|
||||||
|
private String templateCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用 DCP 三节版式(只出项目概况、isPrimary 测点表、时程曲线)
|
||||||
|
*
|
||||||
|
* <p>唯一判定入口。装配器与渲染层都只依赖这个方法,不各自比对模板编码字符串 ——
|
||||||
|
* 早期版本把「是否只出主指标」硬绑在 {@code templateCode == "DEVICE_SUMMARY"} 上,
|
||||||
|
* 一旦模板改名,判定会静默失效(不报错,辅助量列又冒出来),是必须避免的耦合。</p>
|
||||||
|
*/
|
||||||
|
public boolean isDcpReportMode() {
|
||||||
|
return DCP_REPORT_TEMPLATE_CODE.equalsIgnoreCase(trimmed());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际生效的模板编码
|
||||||
|
*
|
||||||
|
* <p>用于 {@code ReportModel.templateCode} 落值与日志;非 DCP 版式返回 {@code V2},
|
||||||
|
* 由调用方拼分类前缀得到具体文件名。</p>
|
||||||
|
*/
|
||||||
|
public String trimmed() {
|
||||||
|
return templateCode == null ? "" : templateCode.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -107,6 +107,9 @@ public class MultiDeviceAnalysisMerger {
|
|||||||
merged.setPrimaryIdentifier(base.getPrimaryIdentifier());
|
merged.setPrimaryIdentifier(base.getPrimaryIdentifier());
|
||||||
// 多主指标:沿用最不利测点的主指标集合(同一产品各测点物模型一致,主指标集合也应一致)
|
// 多主指标:沿用最不利测点的主指标集合(同一产品各测点物模型一致,主指标集合也应一致)
|
||||||
merged.setPrimaryIdentifiers(base.getPrimaryIdentifiers());
|
merged.setPrimaryIdentifiers(base.getPrimaryIdentifiers());
|
||||||
|
// 用户显式勾选的主监测指标(is_primary=1):同样取代表测点,
|
||||||
|
// 漏传会让精简版多测点报告误判为"未配置主监测指标"而出提示行
|
||||||
|
merged.setExplicitPrimaryIdentifiers(base.getExplicitPrimaryIdentifiers());
|
||||||
merged.setSummary(base.getSummary());
|
merged.setSummary(base.getSummary());
|
||||||
merged.setProfessionalAdvice(base.getProfessionalAdvice());
|
merged.setProfessionalAdvice(base.getProfessionalAdvice());
|
||||||
merged.setSafetyLevel(base.getSafetyLevel());
|
merged.setSafetyLevel(base.getSafetyLevel());
|
||||||
|
|||||||
@ -3,11 +3,14 @@ package com.fastbee.report.core;
|
|||||||
import com.fastbee.common.enums.ReportPeriod;
|
import com.fastbee.common.enums.ReportPeriod;
|
||||||
import com.fastbee.common.utils.DateUtils;
|
import com.fastbee.common.utils.DateUtils;
|
||||||
import com.fastbee.common.utils.StringUtils;
|
import com.fastbee.common.utils.StringUtils;
|
||||||
|
import com.fastbee.report.config.ReportProperties;
|
||||||
import com.fastbee.report.model.AnalysisResult;
|
import com.fastbee.report.model.AnalysisResult;
|
||||||
import com.fastbee.report.model.ReportModel;
|
import com.fastbee.report.model.ReportModel;
|
||||||
import com.fastbee.report.model.Statistics;
|
import com.fastbee.report.model.Statistics;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,6 +22,9 @@ import java.util.UUID;
|
|||||||
@Component
|
@Component
|
||||||
public class ReportAssembler {
|
public class ReportAssembler {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ReportProperties reportProperties;
|
||||||
|
|
||||||
public ReportModel assemble(ReportContext context, AnalysisResult analysis) {
|
public ReportModel assemble(ReportContext context, AnalysisResult analysis) {
|
||||||
return assemble(context, analysis, 1);
|
return assemble(context, analysis, 1);
|
||||||
}
|
}
|
||||||
@ -27,8 +33,8 @@ public class ReportAssembler {
|
|||||||
* @param pointCount 参与分析的测点数(>1 时为多测点报告,设备信息展示测点规模)
|
* @param pointCount 参与分析的测点数(>1 时为多测点报告,设备信息展示测点规模)
|
||||||
*/
|
*/
|
||||||
public ReportModel assemble(ReportContext context, AnalysisResult analysis, int pointCount) {
|
public ReportModel assemble(ReportContext context, AnalysisResult analysis, int pointCount) {
|
||||||
// 默认使用专业模版,如未配置 templateCode 则根据监测分类自动选择
|
// 模板选择:DCP 版式为全局配置(report.template-code),不拼分类后缀,全分类共用一份模板
|
||||||
String templateCode = "DEVICE_" + context.getCategory().name() + "_V2";
|
String templateCode = resolveTemplateCode(context);
|
||||||
|
|
||||||
ReportPeriod period = context.getTimeWindow().getPeriod();
|
ReportPeriod period = context.getTimeWindow().getPeriod();
|
||||||
String serialNumber = context.getDevice().getSerialNumber();
|
String serialNumber = context.getDevice().getSerialNumber();
|
||||||
@ -55,7 +61,7 @@ public class ReportAssembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 多主指标:收集全部主指标名称(水位、水温…),供头部「监测指标」字段并列展示
|
// 多主指标:收集全部主指标名称(水位、水温…),供头部「监测指标」字段并列展示
|
||||||
java.util.List<String> metricNames = collectMetricNames(analysis, primary);
|
List<String> metricNames = collectMetricNames(analysis, primary);
|
||||||
|
|
||||||
// 添加监测分类描述
|
// 添加监测分类描述
|
||||||
String categoryDesc = context.getCategory().getDesc() + "监测分析报告";
|
String categoryDesc = context.getCategory().getDesc() + "监测分析报告";
|
||||||
@ -76,18 +82,42 @@ public class ReportAssembler {
|
|||||||
.metricName(primary == null ? null : primary.getModelName())
|
.metricName(primary == null ? null : primary.getModelName())
|
||||||
.metricNames(metricNames)
|
.metricNames(metricNames)
|
||||||
.metricUnit(primary == null ? null : primary.getUnit())
|
.metricUnit(primary == null ? null : primary.getUnit())
|
||||||
|
// DCP 版式:测点表只出 is_primary=1 的主监测指标(未勾选时由渲染层输出提示行)
|
||||||
|
.primaryOnly(ReportProperties.DCP_REPORT_TEMPLATE_CODE.equals(templateCode))
|
||||||
.analysis(analysis)
|
.analysis(analysis)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析报告模板编码
|
||||||
|
*
|
||||||
|
* <p>取值优先级:</p>
|
||||||
|
* <ol>
|
||||||
|
* <li><b>配置 {@code report.template-code=DCPReport}</b> → DCP 平台三节版式,
|
||||||
|
* 全分类共用一份 {@code templates/DCPReport.docx},不拼分类后缀;</li>
|
||||||
|
* <li>配空或配成 {@code V2} → 按监测分类取完整版 {@code DEVICE_{CATEGORY}_V2}。</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p><b>为什么不读 {@code request.templateCode}</b>:那是历史字段,从未被真正使用过
|
||||||
|
* (此前本行是硬编码),前端也从未传值。放开它等于给外部一个可任意指定 classpath
|
||||||
|
* 模板文件名的入口,而报告模板与渲染策略强耦合(动态表列、图表位、poi-tl 策略绑定),
|
||||||
|
* 传错编码会直接渲染失败或产出错版式。格式是现场交付物,由运维改配置决定即可。</p>
|
||||||
|
*/
|
||||||
|
private String resolveTemplateCode(ReportContext context) {
|
||||||
|
if (reportProperties != null && reportProperties.isDcpReportMode()) {
|
||||||
|
return ReportProperties.DCP_REPORT_TEMPLATE_CODE;
|
||||||
|
}
|
||||||
|
return "DEVICE_" + context.getCategory().name() + "_" + ReportProperties.FULL_VERSION_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收集全部主监测指标名称(有序)
|
* 收集全部主监测指标名称(有序)
|
||||||
*
|
*
|
||||||
* <p>多主指标(primaryIdentifiers 非空且 >1)时返回全部名称;否则退化为单值 metricName。</p>
|
* <p>多主指标(primaryIdentifiers 非空且 >1)时返回全部名称;否则退化为单值 metricName。</p>
|
||||||
*/
|
*/
|
||||||
private java.util.List<String> collectMetricNames(AnalysisResult analysis, Statistics primary) {
|
private List<String> collectMetricNames(AnalysisResult analysis, Statistics primary) {
|
||||||
java.util.List<String> names = new java.util.ArrayList<>();
|
List<String> names = new java.util.ArrayList<>();
|
||||||
java.util.List<String> ids = analysis.getPrimaryIdentifiers();
|
List<String> ids = analysis.getPrimaryIdentifiers();
|
||||||
if (ids != null && !ids.isEmpty()) {
|
if (ids != null && !ids.isEmpty()) {
|
||||||
for (String id : ids) {
|
for (String id : ids) {
|
||||||
Statistics s = analysis.getStatistics().get(id);
|
Statistics s = analysis.getStatistics().get(id);
|
||||||
|
|||||||
@ -37,6 +37,24 @@ public class AnalysisResult {
|
|||||||
*/
|
*/
|
||||||
private List<String> primaryIdentifiers;
|
private List<String> primaryIdentifiers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户显式勾选的主监测指标标识符(有序,来自 {@code iot_things_threshold.is_primary})
|
||||||
|
*
|
||||||
|
* <p><b>与 {@link #primaryIdentifiers} 的区别,这是两个不同口径,不可互相替代:</b></p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code primaryIdentifiers} 是三级漏斗的产物(isPrimary → 关键词推断 → 排序兜底),
|
||||||
|
* 零配置也能给出一个值;</li>
|
||||||
|
* <li>{@code explicitPrimaryIdentifiers} 只包含用户在阈值配置页真真切切勾选为
|
||||||
|
* {@code is_primary=1} 的物模型,<b>没配就是空,不做任何推断</b>。</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>精简版报告({@code report.template-code=DCPReport})的测点表只输出本集合中的物模型,
|
||||||
|
* 因此必须与用户配置严格一致 —— 用关键词推断的结果冒充实配会输出用户没勾的指标。</p>
|
||||||
|
*
|
||||||
|
* <p>多测点归并时取代表测点(最不利测点)的集合,与 {@link #primaryIdentifiers} 同源同口径。</p>
|
||||||
|
*/
|
||||||
|
private List<String> explicitPrimaryIdentifiers;
|
||||||
|
|
||||||
/** 项目概况总结(开头段落,概括关键数据) */
|
/** 项目概况总结(开头段落,概括关键数据) */
|
||||||
private String summary;
|
private String summary;
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,18 @@ public class ReportModel {
|
|||||||
/** 监测分类描述(如"裂缝计监测分析报告") */
|
/** 监测分类描述(如"裂缝计监测分析报告") */
|
||||||
private String categoryDesc;
|
private String categoryDesc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测点表是否只输出主监测指标(DCP 三节版式)
|
||||||
|
*
|
||||||
|
* <p>为 true 时,测点表列只由 {@link AnalysisResult#getExplicitPrimaryIdentifiers()}
|
||||||
|
* ({@code iot_things_threshold.is_primary=1})生成,非主监测指标(温度、电压、GPS…)
|
||||||
|
* <b>整列不出现</b>;该集合为空时表体输出"未配置主监测指标"提示行。</p>
|
||||||
|
*
|
||||||
|
* <p>为 false(完整版 V2)时沿用原口径:主指标出完整四列,其余物模型合并为
|
||||||
|
* 一列「本次值(变化量)」保留影响量,用于分析辅助物理量的影响。</p>
|
||||||
|
*/
|
||||||
|
private boolean primaryOnly;
|
||||||
|
|
||||||
/** 控制值(安全参考基准) */
|
/** 控制值(安全参考基准) */
|
||||||
private BigDecimal controlValue;
|
private BigDecimal controlValue;
|
||||||
|
|
||||||
|
|||||||
@ -254,6 +254,8 @@ public class WordRenderer implements ReportRenderer {
|
|||||||
device.put("productName", model.getProductName());
|
device.put("productName", model.getProductName());
|
||||||
device.put("metricName", metricNameText(model));
|
device.put("metricName", metricNameText(model));
|
||||||
device.put("metricUnit", model.getMetricUnit() == null ? "" : model.getMetricUnit());
|
device.put("metricUnit", model.getMetricUnit() == null ? "" : model.getMetricUnit());
|
||||||
|
// 精简版模板首行标题用占位符(单份模板服务全部分类,不硬编码分类名)
|
||||||
|
device.put("categoryDesc", model.getCategoryDesc() == null ? "" : model.getCategoryDesc());
|
||||||
|
|
||||||
Map<String, Object> statistics = new HashMap<>();
|
Map<String, Object> statistics = new HashMap<>();
|
||||||
if (primary != null) {
|
if (primary != null) {
|
||||||
@ -354,7 +356,8 @@ public class WordRenderer implements ReportRenderer {
|
|||||||
}
|
}
|
||||||
data.put("metricPoints", metricPointsList);
|
data.put("metricPoints", metricPointsList);
|
||||||
// 动态列测点表:列由导出设备的物模型(物理量)决定,一行一个测点(iot_device)
|
// 动态列测点表:列由导出设备的物模型(物理量)决定,一行一个测点(iot_device)
|
||||||
data.put("metricTable", buildMetricTable(analysis, model.getCategory()));
|
// 精简版(primaryOnly)只出 is_primary=1 的主监测指标列,其余物模型整列不生成
|
||||||
|
data.put("metricTable", buildMetricTable(analysis, model.getCategory(), model.isPrimaryOnly()));
|
||||||
|
|
||||||
// 图表:兼容 timeSeriesChart(主指标)/ chart2 / chart3(分组指标)/ crackTrend(旧模板)
|
// 图表:兼容 timeSeriesChart(主指标)/ chart2 / chart3(分组指标)/ crackTrend(旧模板)
|
||||||
Map<String, Object> charts = new HashMap<>();
|
Map<String, Object> charts = new HashMap<>();
|
||||||
@ -487,8 +490,13 @@ public class WordRenderer implements ReportRenderer {
|
|||||||
*
|
*
|
||||||
* <p>数值精度与正文一致,按监测类别取 {@link ValueFormatter} 配置(静力水准 2 位),
|
* <p>数值精度与正文一致,按监测类别取 {@link ValueFormatter} 配置(静力水准 2 位),
|
||||||
* 避免 6 位小数把列撑宽导致内容折行。</p>
|
* 避免 6 位小数把列撑宽导致内容折行。</p>
|
||||||
|
*
|
||||||
|
* @param primaryOnly 精简版模式:列只由 {@code explicitPrimaryIdentifiers}(is_primary=1)生成,
|
||||||
|
* 非主监测指标整列不出现;为空时输出未配置提示行。
|
||||||
|
* 完整版为 false,沿用「主指标 + 辅助量合并列」原口径。
|
||||||
*/
|
*/
|
||||||
private TableRenderData buildMetricTable(AnalysisResult analysis, MonitorCategory category) {
|
private TableRenderData buildMetricTable(AnalysisResult analysis, MonitorCategory category,
|
||||||
|
boolean primaryOnly) {
|
||||||
List<MetricPointData> points = analysis.getMetricPoints() == null
|
List<MetricPointData> points = analysis.getMetricPoints() == null
|
||||||
? new ArrayList<>() : analysis.getMetricPoints();
|
? new ArrayList<>() : analysis.getMetricPoints();
|
||||||
if (points.isEmpty()) {
|
if (points.isEmpty()) {
|
||||||
@ -506,8 +514,25 @@ public class WordRenderer implements ReportRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 列顺序:主指标优先,其余按首次出现顺序(全测点统一,便于横向对比)
|
// 2. 列顺序:主指标优先,其余按首次出现顺序(全测点统一,便于横向对比)
|
||||||
|
// 精简版:只保留用户显式勾选(is_primary=1)的主监测指标,口径与配置严格一致
|
||||||
List<String> orderedIdentifiers = new ArrayList<>();
|
List<String> orderedIdentifiers = new ArrayList<>();
|
||||||
Set<String> primarySet = new java.util.LinkedHashSet<>();
|
Set<String> primarySet = new java.util.LinkedHashSet<>();
|
||||||
|
if (primaryOnly) {
|
||||||
|
List<String> explicit = analysis.getExplicitPrimaryIdentifiers();
|
||||||
|
if (explicit == null || explicit.isEmpty()) {
|
||||||
|
// 没有勾选主监测指标:明确提示,不回退到关键词推断结果
|
||||||
|
return Tables.ofA4Width()
|
||||||
|
.addRow(Rows.of("未配置主监测指标(请在阈值配置页勾选「主监测指标」后重新导出)")
|
||||||
|
.center().textFontSize(9).create())
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
for (String id : explicit) {
|
||||||
|
if (id != null && !orderedIdentifiers.contains(id)) {
|
||||||
|
orderedIdentifiers.add(id);
|
||||||
|
primarySet.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (analysis.getPrimaryIdentifiers() != null && !analysis.getPrimaryIdentifiers().isEmpty()) {
|
if (analysis.getPrimaryIdentifiers() != null && !analysis.getPrimaryIdentifiers().isEmpty()) {
|
||||||
for (String id : analysis.getPrimaryIdentifiers()) {
|
for (String id : analysis.getPrimaryIdentifiers()) {
|
||||||
if (id != null && !orderedIdentifiers.contains(id)) {
|
if (id != null && !orderedIdentifiers.contains(id)) {
|
||||||
@ -526,6 +551,15 @@ public class WordRenderer implements ReportRenderer {
|
|||||||
orderedIdentifiers.add(point.getIdentifier());
|
orderedIdentifiers.add(point.getIdentifier());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2b. 表格代表指标:决定尾部「初始观测时间」「监测结论」两列以哪个物模型为准
|
||||||
|
// 精简版必须用 isPrimary 集合的首个(用户配置口径),
|
||||||
|
// 不能用 analysis.primaryIdentifier(那是关键词推断值,可能根本不在 isPrimary 集合里,
|
||||||
|
// 会导致这两列指向用户没勾的物模型)
|
||||||
|
String primaryIdentifier = primaryOnly
|
||||||
|
? orderedIdentifiers.get(0)
|
||||||
|
: analysis.getPrimaryIdentifier();
|
||||||
|
|
||||||
boolean hasSection = points.stream()
|
boolean hasSection = points.stream()
|
||||||
.anyMatch(p -> p.getSectionName() != null && !p.getSectionName().trim().isEmpty());
|
.anyMatch(p -> p.getSectionName() != null && !p.getSectionName().trim().isEmpty());
|
||||||
|
|||||||
BIN
fastbee-report/src/main/resources/templates/DCPReport.docx
Normal file
BIN
fastbee-report/src/main/resources/templates/DCPReport.docx
Normal file
Binary file not shown.
@ -0,0 +1,395 @@
|
|||||||
|
package com.fastbee.report.renderer;
|
||||||
|
|
||||||
|
import com.fastbee.common.enums.MonitorCategory;
|
||||||
|
import com.fastbee.common.enums.ReportPeriod;
|
||||||
|
import com.fastbee.report.chart.ChartGenerator;
|
||||||
|
import com.fastbee.report.config.ReportProperties;
|
||||||
|
import com.fastbee.report.core.ReportAssembler;
|
||||||
|
import com.fastbee.report.data.MetricData;
|
||||||
|
import com.fastbee.report.model.AnalysisResult;
|
||||||
|
import com.fastbee.report.model.MetricPointData;
|
||||||
|
import com.fastbee.report.model.QualityAnalysis;
|
||||||
|
import com.fastbee.report.model.ReportModel;
|
||||||
|
import com.fastbee.report.model.Statistics;
|
||||||
|
import com.fastbee.report.model.TrendAnalysis;
|
||||||
|
import com.fastbee.report.model.WarningAnalysis;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCP 平台三节版式报告({@code DCPReport})渲染测试
|
||||||
|
*
|
||||||
|
* <p>覆盖三件事:① DCP 模板可渲染且只含三节;② 测点表只出 {@code is_primary=1} 的列,
|
||||||
|
* 辅助量整列不出现;③ 未勾选主监测指标时输出提示行而非回退到关键词推断结果。</p>
|
||||||
|
*
|
||||||
|
* @author FastBee
|
||||||
|
* @date 2026-10-04
|
||||||
|
*/
|
||||||
|
public class WordRendererDcpReportTemplateTest {
|
||||||
|
|
||||||
|
/** 固定模板解析器:按编码加载 templates/ 下的真实模板 */
|
||||||
|
private WordRenderer newRenderer() {
|
||||||
|
WordRenderer renderer = new WordRenderer();
|
||||||
|
ReflectionTestUtils.setField(renderer, "templateResolver", new WordTemplateResolver() {
|
||||||
|
@Override
|
||||||
|
public byte[] resolve(String templateCode) {
|
||||||
|
try (InputStream in = getClass().getClassLoader()
|
||||||
|
.getResourceAsStream("templates/" + templateCode + ".docx")) {
|
||||||
|
if (in == null) {
|
||||||
|
throw new IllegalStateException("模板不存在: " + templateCode);
|
||||||
|
}
|
||||||
|
return cn.hutool.core.io.IoUtil.readBytes(in);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRenderDcpReportTemplate() {
|
||||||
|
ReportModel model = buildModel(Arrays.asList("crackWidth"));
|
||||||
|
byte[] docx = newRenderer().render(model);
|
||||||
|
assertNotNull(docx);
|
||||||
|
assertTrue(docx.length > 0);
|
||||||
|
String magic = new String(new byte[]{docx[0], docx[1]}, StandardCharsets.ISO_8859_1);
|
||||||
|
assertTrue("PK".equals(magic), "渲染结果应为有效 docx(zip)");
|
||||||
|
|
||||||
|
try {
|
||||||
|
java.io.File out = new java.io.File("target/test-output-dcp-report.docx");
|
||||||
|
java.nio.file.Files.write(out.toPath(), docx);
|
||||||
|
System.out.println("DCP 版式渲染结果已输出: " + out.getAbsolutePath());
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 输出失败不影响断言
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DCP 模板只需一份,服务全部监测分类(首行标题用 {{device.categoryDesc}} 占位) */
|
||||||
|
@Test
|
||||||
|
public void testDcpReportTemplateExists() {
|
||||||
|
// 常量必须与 templates/ 下的文件名严格一致:解析器按 templateCode 拼文件名找资源,
|
||||||
|
// 常量与文件任一侧改名都会在运行时抛「报告模板不存在」,这里提前挡住
|
||||||
|
assertEquals("DCPReport.docx",
|
||||||
|
ReportProperties.DCP_REPORT_TEMPLATE_CODE + ".docx",
|
||||||
|
"常量与模板文件名不一致,改名时需同步");
|
||||||
|
try (InputStream in = getClass().getClassLoader()
|
||||||
|
.getResourceAsStream("templates/DCPReport.docx")) {
|
||||||
|
assertNotNull(in, "DCP 版式模板缺失: DCPReport.docx");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 配置切到 DCPReport 时,装配出的模板编码与 primaryOnly 标记都正确 */
|
||||||
|
@Test
|
||||||
|
public void testAssemblerResolvesDcpReportTemplate() {
|
||||||
|
ReportAssembler assembler = new ReportAssembler();
|
||||||
|
ReflectionTestUtils.setField(assembler, "reportProperties", dcpReportProperties());
|
||||||
|
|
||||||
|
com.fastbee.report.core.ReportContext context = new com.fastbee.report.core.ReportContext();
|
||||||
|
context.setCategory(MonitorCategory.CRACK_METER);
|
||||||
|
com.fastbee.iot.domain.Device device = new com.fastbee.iot.domain.Device();
|
||||||
|
device.setDeviceId(1L);
|
||||||
|
device.setDeviceName("裂缝计1");
|
||||||
|
device.setSerialNumber("SN001");
|
||||||
|
context.setDevice(device);
|
||||||
|
com.fastbee.iot.domain.Product product = new com.fastbee.iot.domain.Product();
|
||||||
|
product.setProductId(1L);
|
||||||
|
product.setProductName("裂缝计");
|
||||||
|
context.setProduct(product);
|
||||||
|
com.fastbee.report.period.TimeWindow window = new com.fastbee.report.period.TimeWindow();
|
||||||
|
window.setPeriod(ReportPeriod.CUSTOM);
|
||||||
|
context.setTimeWindow(window);
|
||||||
|
context.setReportNo("REPORT-TEST-0001");
|
||||||
|
|
||||||
|
ReportModel model = assembler.assemble(context, buildAnalysis(Arrays.asList("crackWidth")), 1);
|
||||||
|
assertEquals(ReportProperties.DCP_REPORT_TEMPLATE_CODE, model.getTemplateCode());
|
||||||
|
assertTrue(model.isPrimaryOnly(), "DCP 版式必须置 primaryOnly,测点表才只出主监测指标");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 配置切回 V2 时,仍按监测分类取完整版模板,且 primaryOnly=false(原行为不变) */
|
||||||
|
@Test
|
||||||
|
public void testAssemblerFallsBackToFullTemplate() {
|
||||||
|
ReportAssembler assembler = new ReportAssembler();
|
||||||
|
ReportProperties properties = new ReportProperties();
|
||||||
|
properties.setTemplateCode(ReportProperties.FULL_VERSION_CODE);
|
||||||
|
ReflectionTestUtils.setField(assembler, "reportProperties", properties);
|
||||||
|
|
||||||
|
com.fastbee.report.core.ReportContext context = new com.fastbee.report.core.ReportContext();
|
||||||
|
context.setCategory(MonitorCategory.CRACK_METER);
|
||||||
|
com.fastbee.iot.domain.Device device = new com.fastbee.iot.domain.Device();
|
||||||
|
device.setDeviceId(1L);
|
||||||
|
device.setDeviceName("裂缝计1");
|
||||||
|
context.setDevice(device);
|
||||||
|
com.fastbee.iot.domain.Product product = new com.fastbee.iot.domain.Product();
|
||||||
|
product.setProductId(1L);
|
||||||
|
product.setProductName("裂缝计");
|
||||||
|
context.setProduct(product);
|
||||||
|
com.fastbee.report.period.TimeWindow window = new com.fastbee.report.period.TimeWindow();
|
||||||
|
window.setPeriod(ReportPeriod.CUSTOM);
|
||||||
|
context.setTimeWindow(window);
|
||||||
|
context.setReportNo("REPORT-TEST-0002");
|
||||||
|
|
||||||
|
ReportModel model = assembler.assemble(context, buildAnalysis(Arrays.asList("crackWidth")), 1);
|
||||||
|
assertEquals("DEVICE_CRACK_METER_V2", model.getTemplateCode());
|
||||||
|
assertTrue(!model.isPrimaryOnly(), "完整版必须保持原有口径:主指标 + 辅助量合并列");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** isPrimary 未勾选时,DCP 版式必须渲染出提示行而不是回退到关键词推断的主指标 */
|
||||||
|
@Test
|
||||||
|
public void testDcpReportWithoutExplicitPrimaryShowsHint() {
|
||||||
|
ReportModel model = buildModel(new ArrayList<>());
|
||||||
|
model.setTemplateCode(ReportProperties.DCP_REPORT_TEMPLATE_CODE);
|
||||||
|
byte[] docx = newRenderer().render(model);
|
||||||
|
assertTrue(docx.length > 0, "未配置主监测指标时也应产出可读文档");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整版回归:{@code primaryOnly=false} 时辅助量仍按「本次值(变化量)」合并列出现
|
||||||
|
*
|
||||||
|
* <p>这是本次改造最危险的回归点 —— DCP 版式过滤逻辑不能误伤完整版原有行为。</p>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testFullVersionKeepsAuxiliaryColumns() throws Exception {
|
||||||
|
ReportModel model = buildModel(Arrays.asList("crackWidth"));
|
||||||
|
model.setTemplateCode("DEVICE_CRACK_METER_V2");
|
||||||
|
model.setPrimaryOnly(false);
|
||||||
|
byte[] docx = newRenderer().render(model);
|
||||||
|
assertTrue(docx.length > 0);
|
||||||
|
|
||||||
|
String xml = readDocumentXml(docx);
|
||||||
|
// 主指标出完整四列
|
||||||
|
assertTrue(xml.contains("裂缝宽度·初始值(mm)"), "完整版主指标应出四列");
|
||||||
|
// 辅助量仍在(合并列),未被DCP 版式逻辑误删
|
||||||
|
assertTrue(xml.contains("裂缝温度·本次值(变化量)"), "完整版辅助量合并列必须保留");
|
||||||
|
assertTrue(xml.contains("设备电压·本次值(变化量)"), "完整版辅助量合并列必须保留");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DCP 版式回归:辅助量列不得出现 */
|
||||||
|
@Test
|
||||||
|
public void testDcpReportDropsAuxiliaryColumns() throws Exception {
|
||||||
|
ReportModel model = buildModel(Arrays.asList("crackWidth"));
|
||||||
|
byte[] docx = newRenderer().render(model);
|
||||||
|
String xml = readDocumentXml(docx);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("裂缝宽度·初始值(mm)"), "主指标列应保留");
|
||||||
|
assertTrue(!xml.contains("裂缝温度·本次值(变化量)"), "DCP 版式不得输出辅助量列");
|
||||||
|
assertTrue(!xml.contains("设备电压·本次值(变化量)"), "DCP 版式不得输出辅助量列");
|
||||||
|
// 被裁掉的章节不得残留
|
||||||
|
assertTrue(!xml.contains("安全等级"), "DCP 版式不应有安全评估章节");
|
||||||
|
assertTrue(!xml.contains("结论内容"), "DCP 版式不应有结论表格");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从 docx 字节中读出 document.xml 文本 */
|
||||||
|
private String readDocumentXml(byte[] docx) throws Exception {
|
||||||
|
try (java.util.zip.ZipInputStream zin = new java.util.zip.ZipInputStream(
|
||||||
|
new java.io.ByteArrayInputStream(docx))) {
|
||||||
|
java.util.zip.ZipEntry entry;
|
||||||
|
while ((entry = zin.getNextEntry()) != null) {
|
||||||
|
if ("word/document.xml".equals(entry.getName())) {
|
||||||
|
return new String(cn.hutool.core.io.IoUtil.readBytes(zin), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("docx 中未找到 word/document.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReportProperties dcpReportProperties() {
|
||||||
|
ReportProperties properties = new ReportProperties();
|
||||||
|
properties.setTemplateCode(ReportProperties.DCP_REPORT_TEMPLATE_CODE);
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造模型:产品配了 3 个物模型(裂缝宽度=主监测指标、裂缝温度、设备电压),
|
||||||
|
* 但 {@code explicitPrimaryIdentifiers} 只含用户实际勾选的项。
|
||||||
|
*/
|
||||||
|
private ReportModel buildModel(List<String> explicitPrimary) {
|
||||||
|
AnalysisResult analysis = buildAnalysis(explicitPrimary);
|
||||||
|
return ReportModel.builder()
|
||||||
|
.reportNo("REPORT-20261004120000-0001")
|
||||||
|
.templateCode(ReportProperties.DCP_REPORT_TEMPLATE_CODE)
|
||||||
|
.fileName("裂缝计-监测分析报告-abc123.docx")
|
||||||
|
.periodDesc("2026-10-01 00:00:00 ~ 2026-10-04 00:00:00")
|
||||||
|
.period(ReportPeriod.CUSTOM)
|
||||||
|
.generatedAt(new Date())
|
||||||
|
.deviceName("裂缝计_1")
|
||||||
|
.serialNumber("SN20261004001")
|
||||||
|
.productName("裂缝计")
|
||||||
|
.deviceId(20001L)
|
||||||
|
.category(MonitorCategory.CRACK_METER)
|
||||||
|
.categoryDesc("裂缝计监测分析报告")
|
||||||
|
.metricName("裂缝宽度")
|
||||||
|
.metricNames(Arrays.asList("裂缝宽度"))
|
||||||
|
.metricUnit("mm")
|
||||||
|
.primaryOnly(true)
|
||||||
|
.analysis(analysis)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnalysisResult buildAnalysis(List<String> explicitPrimary) {
|
||||||
|
Statistics crack = new Statistics();
|
||||||
|
crack.setIdentifier("crackWidth");
|
||||||
|
crack.setModelName("裂缝宽度");
|
||||||
|
crack.setUnit("mm");
|
||||||
|
crack.setCount(144);
|
||||||
|
crack.setCurrent(new BigDecimal("0.382"));
|
||||||
|
crack.setInitial(new BigDecimal("0.350"));
|
||||||
|
crack.setMax(new BigDecimal("0.405"));
|
||||||
|
crack.setMin(new BigDecimal("0.348"));
|
||||||
|
crack.setAvg(new BigDecimal("0.371"));
|
||||||
|
crack.setMedian(new BigDecimal("0.370"));
|
||||||
|
crack.setDelta(new BigDecimal("0.032"));
|
||||||
|
crack.setTotalDelta(new BigDecimal("0.057"));
|
||||||
|
|
||||||
|
// 辅助量:非主监测指标,DCP 版式下整列不出现
|
||||||
|
Statistics temp = new Statistics();
|
||||||
|
temp.setIdentifier("crackTemp");
|
||||||
|
temp.setModelName("裂缝温度");
|
||||||
|
temp.setUnit("℃");
|
||||||
|
temp.setCount(144);
|
||||||
|
temp.setCurrent(new BigDecimal("18.5"));
|
||||||
|
temp.setInitial(new BigDecimal("18.1"));
|
||||||
|
temp.setDelta(new BigDecimal("0.4"));
|
||||||
|
|
||||||
|
Statistics voltage = new Statistics();
|
||||||
|
voltage.setIdentifier("voltage");
|
||||||
|
voltage.setModelName("设备电压");
|
||||||
|
voltage.setUnit("V");
|
||||||
|
voltage.setCount(144);
|
||||||
|
voltage.setCurrent(new BigDecimal("12.6"));
|
||||||
|
voltage.setInitial(new BigDecimal("12.5"));
|
||||||
|
voltage.setDelta(new BigDecimal("0.1"));
|
||||||
|
|
||||||
|
AnalysisResult analysis = new AnalysisResult();
|
||||||
|
analysis.setPrimaryIdentifier("crackWidth");
|
||||||
|
analysis.setPrimaryIdentifiers(Arrays.asList("crackWidth"));
|
||||||
|
analysis.setExplicitPrimaryIdentifiers(explicitPrimary);
|
||||||
|
analysis.getStatistics().put("crackWidth", crack);
|
||||||
|
analysis.getStatistics().put("crackTemp", temp);
|
||||||
|
analysis.getStatistics().put("voltage", voltage);
|
||||||
|
|
||||||
|
TrendAnalysis trend = new TrendAnalysis();
|
||||||
|
trend.setIdentifier("crackWidth");
|
||||||
|
trend.setModelName("裂缝宽度");
|
||||||
|
trend.setDirection(TrendAnalysis.DIRECTION_UP);
|
||||||
|
trend.setDescription("整体呈增大趋势,日变化速率 0.0008mm");
|
||||||
|
analysis.getTrends().put("crackWidth", trend);
|
||||||
|
|
||||||
|
QualityAnalysis quality = new QualityAnalysis();
|
||||||
|
quality.setTotal(144);
|
||||||
|
quality.setMissing(0);
|
||||||
|
quality.setCompletenessRate(new BigDecimal("1.0"));
|
||||||
|
quality.setDescription("统计期内共 144 条数据,完整率 100.00%");
|
||||||
|
analysis.getQualities().put("crackWidth", quality);
|
||||||
|
|
||||||
|
WarningAnalysis warning = new WarningAnalysis();
|
||||||
|
warning.setIdentifier("crackWidth");
|
||||||
|
warning.setStatus(WarningAnalysis.STATUS_NORMAL);
|
||||||
|
warning.setDescription("未超过预警值");
|
||||||
|
analysis.getWarnings().put("crackWidth", warning);
|
||||||
|
|
||||||
|
analysis.setSummary("本期裂缝计监测数据显示:当前裂缝宽度为0.382mm,较初始值变化0.032mm,呈增大趋势。");
|
||||||
|
analysis.setProfessionalAdvice("建议加强巡视检查。");
|
||||||
|
analysis.setSafetyLevel("SAFE");
|
||||||
|
analysis.setSafetyDescription("监测指标处于正常范围。");
|
||||||
|
analysis.setControlValue(new BigDecimal("1.000"));
|
||||||
|
analysis.setControlValueUnit("mm");
|
||||||
|
|
||||||
|
analysis.getMetricPoints().add(MetricPointData.builder()
|
||||||
|
.identifier("crackWidth")
|
||||||
|
.modelName("裂缝宽度")
|
||||||
|
.deviceId(20001L)
|
||||||
|
.pointNo("SN20261004001")
|
||||||
|
.pointName("裂缝计_1")
|
||||||
|
.initialValue(new BigDecimal("0.350"))
|
||||||
|
.initialUnit("mm")
|
||||||
|
.currentValue(new BigDecimal("0.382"))
|
||||||
|
.currentUnit("mm")
|
||||||
|
.changeValue(new BigDecimal("0.032"))
|
||||||
|
.changeUnit("mm")
|
||||||
|
.changeRate(new BigDecimal("0.0008"))
|
||||||
|
.rateUnit("mm/d")
|
||||||
|
.maxValue(new BigDecimal("0.405"))
|
||||||
|
.minValue(new BigDecimal("0.348"))
|
||||||
|
.avgValue(new BigDecimal("0.371"))
|
||||||
|
.primary(true)
|
||||||
|
.warningStatus(WarningAnalysis.STATUS_NORMAL)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
analysis.getMetricPoints().add(MetricPointData.builder()
|
||||||
|
.identifier("crackTemp")
|
||||||
|
.modelName("裂缝温度")
|
||||||
|
.deviceId(20001L)
|
||||||
|
.pointNo("SN20261004001")
|
||||||
|
.pointName("裂缝计_1")
|
||||||
|
.initialValue(new BigDecimal("18.1"))
|
||||||
|
.initialUnit("℃")
|
||||||
|
.currentValue(new BigDecimal("18.5"))
|
||||||
|
.currentUnit("℃")
|
||||||
|
.changeValue(new BigDecimal("0.4"))
|
||||||
|
.changeUnit("℃")
|
||||||
|
.changeRate(new BigDecimal("0.0133"))
|
||||||
|
.rateUnit("℃/d")
|
||||||
|
.primary(false)
|
||||||
|
.warningStatus(WarningAnalysis.STATUS_NORMAL)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
analysis.getMetricPoints().add(MetricPointData.builder()
|
||||||
|
.identifier("voltage")
|
||||||
|
.modelName("设备电压")
|
||||||
|
.deviceId(20001L)
|
||||||
|
.pointNo("SN20261004001")
|
||||||
|
.pointName("裂缝计_1")
|
||||||
|
.initialValue(new BigDecimal("12.5"))
|
||||||
|
.initialUnit("V")
|
||||||
|
.currentValue(new BigDecimal("12.6"))
|
||||||
|
.currentUnit("V")
|
||||||
|
.changeValue(new BigDecimal("0.1"))
|
||||||
|
.changeUnit("V")
|
||||||
|
.changeRate(new BigDecimal("0.0003"))
|
||||||
|
.rateUnit("V/d")
|
||||||
|
.primary(false)
|
||||||
|
.warningStatus(WarningAnalysis.STATUS_NORMAL)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
List<MetricData> chartData = new ArrayList<>();
|
||||||
|
long base = System.currentTimeMillis();
|
||||||
|
for (int i = 0; i < 24; i++) {
|
||||||
|
MetricData point = new MetricData();
|
||||||
|
point.setIdentity("crackWidth");
|
||||||
|
point.setModelName("裂缝宽度");
|
||||||
|
point.setUnit("mm");
|
||||||
|
point.setTime(new Date(base + i * 3600_000L));
|
||||||
|
point.setNumericValue(new BigDecimal("0.350").add(new BigDecimal("0.0013").multiply(new BigDecimal(i))));
|
||||||
|
point.setValue(point.getNumericValue().toPlainString());
|
||||||
|
chartData.add(point);
|
||||||
|
}
|
||||||
|
byte[] png = new ChartGenerator().timeSeriesChart("裂缝宽度时程曲线", "裂缝宽度", "mm", chartData);
|
||||||
|
analysis.getCharts().put("timeSeriesChart", png);
|
||||||
|
|
||||||
|
com.fastbee.report.model.MetricChartGroup group = new com.fastbee.report.model.MetricChartGroup();
|
||||||
|
group.setIdentifier("crackWidth");
|
||||||
|
group.setName("裂缝宽度");
|
||||||
|
group.setUnit("mm");
|
||||||
|
group.setTimeSeries(png);
|
||||||
|
group.setCumulative(png);
|
||||||
|
group.setRate(png);
|
||||||
|
analysis.getMetricChartGroups().add(group);
|
||||||
|
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user