fastbee/docs/fastbee-device-report-engine.md
2026-08-16 07:29:07 +08:00

1377 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FastBee IoT 设备监测报告引擎开发 Skill
> **领域背景**:公司主营业务为桥梁、建筑、边坡、隧道等结构老化与安全监测。核心监测对象为裂缝、深部位移、轴力/应力、沉降、倾角、应变、振动等工程物理量。报告引擎的目标是:基于已落库的 IoT 物模型监测数据,为单个监测设备生成专业分析报告,并支持**日报 / 周报 / 半月报 / 月报**四种周期,最终通过 Word 模板导出 `.docx`。
>
> **第一阶段范围**:根据所选设备 + 报告周期(或自定义时间范围)生成单设备分析报告。
>
> **不在第一阶段实现**:项目综合报告、多设备联合分析、AI 自动工程安全判定、在线 Word 模板编辑器。
---
# 0. 现状盘点(先读后写,禁止重复造轮子)
执行本 Skill 前,必须先认清 FastBee 当前已有什么、缺什么。下列结论已通过代码调研确认。
## 0.1 已有可复用基础设施
| 能力 | 位置 | 复用方式 |
|:---|:---|:---|
| 设备实体 | `iot_device` / `Device.java` | `Device` 含 `deviceId, serialNumber, productName, projectId, thingsModelValue` |
| 产品实体 | `iot_product` / `Product.java` | `Product` 含 `categoryId, protocolCode, thingsModelsJson` |
| 物模型 | `iot_things_model` / `ThingsModel.java` | 字段:`identifier, unit, datatype, type(1属性/2功能/3事件), isMonitor, isHistory, isChart, isCalculated(0上报/1横向计算/2纵向计算), formula, specs` |
| 项目实体 | `iot_project` / `Project.java` | `projectId, projectCode, projectName, address, startTime, endTime, projectStatus` |
| 设备日志(时序数据) | `iot_device_log` / `DeviceLog.java` | 字段:`ts, logType(1属性上报/7运算数据), logValue, deviceId, serialNumber, identity, isMonitor, mode` |
| 日志查询(MySQL) | `DeviceLogMapper.java` | `listHistory(DeviceLog)` / `selectHistoryList` / `selectMonitorList` / `selectLastReport` |
| 日志查询(TDengine) | `TDDeviceLogMapper.java` | TDengine 启用时走此 Mapper |
| 告警定义 | `iot_alert` / `Alert.java` | `alertLevel(1提醒/2轻微/3严重), status`,阈值规则在 `iot_scene`(LiteFlow) |
| OSS 上传 | `fastbee-plugs/oss` / `OssFactory.java` | `OssFactory.instance(redis).upload(byte[], path, contentType)` → `UploadResult{url, filename}` |
| Word/POI | `fastbee-common/pom.xml` | 已含 `poi-ooxml 4.1.2`;**未含** poi-tl / docx4j |
| Excel | `easyexcel-core 3.3.1` | 已含 |
| MQ | `fastbee-mq` | RocketMQ,未来异步报告任务复用 |
| Maven 父 POM | `pom.xml` | `revision=3.8.5`, Java 1.8, Spring Boot 2.5.14, `poi.version=4.1.2` |
## 0.2 关键缺失(本 Skill 要补齐的)
1. **设备分类(最重要)**:`iot_device.device_type` / `DeviceType.java` 枚举 = 连接拓扑 `{UNKNOWN, DIRECT_DEVICE, GATEWAY, CAMERA, SUB_GATEWAY}`,**不区分裂缝计/深部位移/轴力计/静力水准/振弦**。`iot_category` 是租户私有的产品分类树,只有 `categoryName` 文本、无稳定机器码,不能作为 Analyzer 路由键。
2. **报告周期**:当前无日报/周报/半月报/月报概念。
3. **报告引擎**:无 `fastbee-report` 模块、无 `ReportEngine`、无 `ReportAnalyzer`、无 `ReportRecord` 表。
4. **监测点阈值**:`iot_alert` 无阈值数值列,阈值藏在 LiteFlow `iot_scene` 规则里,报告难以直接读取结构化阈值。
5. **Java 端图表**:仅前端有 echarts;后端无 JFreeChart/XChart,无法生成 PNG 嵌入 Word。
6. **Word 模板引擎**:仅有裸 POI,无 `{{var}}` 占位符语法支持(需引入 poi-tl)。
---
# 1. Skill 目标
补齐报告能力链路:
```text
IoT 数据
↓
ReportRequest(设备 + 周期/时间范围 + 模板)
↓
ReportContext(设备/产品/物模型/监测数据/阈值)
↓
ReportAnalyzer(按 MonitorCategory 路由)
↓
AnalysisResult(统计/趋势/质量/预警/结论)
↓
ReportModel
↓
WordRenderer(poi-tl + JFreeChart)
↓
DOCX → OSS → ReportRecord
```
最终形成:
```text
IoT 数据 → Report Engine → 专业监测分析报告 (.docx)
```
---
# 2. 核心设计原则
## 2.1 Report Engine 不直接理解数据库
禁止 `ReportEngine → 直接操作 Mapper → 查询数据库 → 分析 → Word`。
推荐:
```text
数据访问层(DeviceLogMapper / ThingsModelMapper / OssFactory)
↓
ReportContext(已装配好的上下文)
↓
Analyzer(纯计算)
↓
AnalysisResult
↓
ReportModel
↓
WordRenderer
```
Report Engine 只负责报告流程编排,Analyzer 不允许自己 `@Autowired DeviceService/ProjectService` 到处查询。
## 2.2 复用优先
- OSS → 复用 `OssFactory`,禁止新写 `MinIOService/S3Service`
- 数据查询 → 复用 `DeviceLogMapper.listHistory`,禁止报告模块自己写 TDengine SQL
- Word → 基于 `poi-ooxml`(已含)+ 引入 `poi-tl`,禁止再引入 docx4j
- 图表 → 引入 `JFreeChart`(成熟、纯 Java、生成 PNG)
- 物模型/设备/产品领域对象 → 复用 `fastbee-iot-service` 现有实体,禁止新建 `ReportDevice/ReportThingsModel` 重复 DTO(除非报告场景确实需要独立 DTO)
## 2.3 单一职责与可扩展
新增设备分类只新增一个 Analyzer,不修改 `ReportEngine`、不写 `if(category==xxx)` 巨型判断。
---
# 3. 设备分类深度设计(MonitorCategory)
> 这是本 Skill 最核心的设计。**公司做桥梁/建筑老化检测,设备分类是报告分析的入口**——不同传感器(裂缝计 vs 深部位移 vs 静力水准)的分析维度、图表、结论模板完全不同,必须有稳定、机器可读的分类路由键。
## 3.1 为什么不能复用现有分类
| 现有分类 | 为什么不行 |
|:---|:---|
| `iot_device.device_type`(DeviceType 枚举) | 是连接拓扑(直连/网关/摄像头/子设备),与传感器物理量无关 |
| `iot_product.category_id` → `iot_category` | 租户私有的产品目录树,仅 `categoryName` 文本;同一类传感器在不同租户下分类名/层级可能不同;无稳定 code,不能做路由键 |
| ThingsModel `identifier` 启发式推断 | 命名不规范时误判,专业工程报告不能依赖猜测 |
## 3.2 方案:iot_product 加列 + MonitorCategoryEnum
### 3.2.1 数据库改动
```sql
-- iot_product 新增监测分类编码列(可空,兼容存量数据)
ALTER TABLE iot_product
ADD COLUMN monitor_category_code VARCHAR(64) NULL COMMENT '监测分类编码(报告引擎路由键,如 CRACK_METER)'
AFTER category_id;
-- 存量产品由后台运维或 SQL 一次性回填
-- 例:UPDATE iot_product SET monitor_category_code='CRACK_METER' WHERE product_name LIKE '%裂缝%';
```
> 不在 `iot_device` 落库:设备分类随产品走,避免冗余与不一致。设备查询时 JOIN `iot_product` 取 `monitor_category_code` 即可(DeviceMapper 已有产品关联)。
### 3.2.2 MonitorCategoryEnum
位置:`fastbee-common/src/main/java/com/fastbee/common/enums/MonitorCategory.java`
```java
@Getter
@AllArgsConstructor
public enum MonitorCategory {
CRACK_METER("CRACK_METER", "裂缝计"),
DEEP_DISPLACEMENT("DEEP_DISPLACEMENT", "深部位移"),
AXIAL_FORCE("AXIAL_FORCE", "轴力/压力计"),
STATIC_LEVEL("STATIC_LEVEL", "静力水准仪"),
VIBRATING_WIRE("VIBRATING_WIRE", "振弦式采集仪"),
TILT_METER("TILT_METER", "倾角仪"),
STRAIN_GAUGE("STRAIN_GAUGE", "应变计"),
TEMPERATURE_HUMIDITY("TEMPERATURE_HUMIDITY", "温湿度"),
VIBRATION("VIBRATION", "振动/加速度"),
CUSTOM("CUSTOM", "自定义"),
UNKNOWN("UNKNOWN", "未知");
private final String code;
private final String desc;
public static MonitorCategory of(String code) {
for (MonitorCategory v : values()) {
if (v.code.equalsIgnoreCase(code)) return v;
}
return MonitorCategory.UNKNOWN;
}
}
```
### 3.2.3 Product 实体加字段
`Product.java` 增加:
```java
@ApiModelProperty("监测分类编码(报告引擎路由键)")
private String monitorCategoryCode;
```
### 3.2.4 MonitorCategoryResolver
位置:`fastbee-report/src/main/java/com/fastbee/report/category/MonitorCategoryResolver.java`
```java
public interface MonitorCategoryResolver {
MonitorCategory resolve(Long productId);
MonitorCategory resolveByDeviceId(Long deviceId);
}
```
实现:先查 `iot_product.monitor_category_code`;为空时返回 `UNKNOWN`(第一阶段不实现启发式回退,避免误判;后台需维护分类)。
### 3.2.5 AnalyzerFactory 按 MonitorCategory 路由
```java
public interface ReportAnalyzerFactory {
ReportAnalyzer get(MonitorCategory category);
}
```
```java
@Component
public class DefaultReportAnalyzerFactory implements ReportAnalyzerFactory {
private final List<ReportAnalyzer> analyzers;
@Override
public ReportAnalyzer get(MonitorCategory category) {
return analyzers.stream()
.filter(a -> a.supports(category))
.findFirst()
.orElseThrow(() -> new ReportException("不支持的监测分类: " + category));
}
}
```
## 3.3 与现有 iot_category 的关系
两者**正交**,互不替代:
```text
iot_category → 产品商务分类(客户买什么、目录树展示)
iot_product.monitor_category_code → 监测语义分类(如何分析数据、走哪个 Analyzer)
```
同一个产品可以:`category_id` = "桥梁监测传感器/裂缝计"(商务树)+ `monitor_category_code` = `CRACK_METER`(分析路由键)。
## 3.4 命名冲突说明
**不要**复用 `DeviceType` 这个类名——项目中已存在 `com.fastbee.iot.enums.DeviceType`(连接拓扑)。本 Skill 的分类统一叫 `MonitorCategory`,避免歧义。
---
# 4. 报告周期设计(ReportPeriod)
支持:**日报 / 周报 / 半月报 / 月报**,统一使用**自然日历窗口**(符合工程监测报告习惯)。
## 4.1 ReportPeriod 枚举
位置:`fastbee-common/src/main/java/com/fastbee/common/enums/ReportPeriod.java`
```java
@Getter
@AllArgsConstructor
public enum ReportPeriod {
DAY("DAY", "日报"),
WEEK("WEEK", "周报"),
HALF_MONTH("HALF_MONTH", "半月报"),
MONTH("MONTH", "月报"),
CUSTOM("CUSTOM", "自定义");
private final String code;
private final String desc;
}
```
## 4.2 自然日历窗口规则
锚点日期 = `anchorDate`(默认为报告生成当天)。
| 周期 | startTime | endTime |
|:---|:---|:---|
| 日报 | anchorDate 00:00:00 | anchorDate 23:59:59 |
| 周报 | anchorDate 所在周的周一 00:00:00 | 周日 23:59:59(按周一为一周起点) |
| 半月报 | anchorDate 日期 ≤15 → 当月1日 00:00:00 ~ 当月15日 23:59:59;否则 16日 00:00:00 ~ 当月最后一天 23:59:59 |
| 月报 | anchorDate 所在月 1日 00:00:00 ~ 当月最后一天 23:59:59 |
| 自定义 | request.startTime | request.endTime |
## 4.3 ReportPeriodResolver
```java
public interface ReportPeriodResolver {
/** 根据周期与锚点日期,计算 [startTime, endTime] */
TimeWindow resolve(ReportPeriod period, LocalDate anchorDate);
}
```
```java
public class TimeWindow {
private LocalDateTime startTime;
private LocalDateTime endTime;
}
```
实现要点:
- 周一为一周起点(`DayOfWeek.MONDAY`)
- 月末用 `YearMonth.atEndOfMonth()`
- 半月报以 15 日为界
- 时区固定 `GMT+8`(与 `DeviceLog.ts` 的 `@JsonFormat timezone=GMT+8` 一致)
## 4.4 ReportRequest 同时支持周期与自定义
```java
public class ReportRequest {
private Long deviceId;
/** 报告周期;为 CUSTOM 时使用 startTime/endTime */
private ReportPeriod period;
/** 锚点日期(period != CUSTOM 时生效,默认今天) */
private LocalDate anchorDate;
/** 自定义开始时间(period == CUSTOM 时生效) */
private LocalDateTime startTime;
/** 自定义结束时间(period == CUSTOM 时生效) */
private LocalDateTime endTime;
private String templateCode;
private ReportFormat format;
}
```
校验规则:
- `period == null` 且 `startTime/endTime` 都给 → 按 CUSTOM 处理
- `period != CUSTOM` 时忽略 `startTime/endTime`,由 `ReportPeriodResolver` 计算
- `endTime` 必须 > `startTime`
---
# 5. 阈值来源设计(WarningAnalyzer 现实方案)
## 5.1 现状
`iot_alert` 表无阈值数值列,阈值规则藏在 LiteFlow `iot_scene` 规则链里(脚本化、动态、非结构化)。报告引擎直接解析 LiteFlow 脚本取阈值成本高、易错。
## 5.2 V1 方案:iot_report_threshold(报告专用阈值表)
报告场景需要**结构化、可读、可回溯**的阈值,与告警规则解耦:
```sql
CREATE TABLE iot_report_threshold (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_id BIGINT NOT NULL COMMENT '产品ID(0=全局默认)',
device_id BIGINT NOT NULL DEFAULT 0 COMMENT '设备ID(0=产品级)',
monitor_category_code VARCHAR(64) NOT NULL COMMENT '监测分类编码',
metric_identifier VARCHAR(128) NOT NULL COMMENT '物模型标识符',
warning_value DECIMAL(18,6) COMMENT '预警阈值',
alarm_value DECIMAL(18,6) COMMENT '报警阈值',
control_value DECIMAL(18,6) COMMENT '控制值(设计值,可选)',
unit VARCHAR(32) COMMENT '单位',
tenant_id BIGINT,
del_flag CHAR(1) DEFAULT '0',
create_by VARCHAR(64),
create_time DATETIME,
update_by VARCHAR(64),
update_time DATETIME,
KEY idx_device_metric (device_id, metric_identifier),
KEY idx_product_category (product_id, monitor_category_code)
) COMMENT '报告预警阈值配置';
```
查询优先级:`device_id > product_id > 全局默认(product_id=0)`。
## 5.3 WarningAnalyzer 只读结构化阈值
禁止 Analyzer 硬编码 `if(value>2)`。必须通过 `ReportThresholdProvider` 查询:
```java
public interface ReportThresholdProvider {
ReportThreshold get(MonitorCategory category, Long productId, Long deviceId, String metricIdentifier);
}
```
`ReportContext` 构建时一次性装入该设备所有相关阈值,Analyzer 直接读 Context。
## 5.4 与 iot_alert 的关系
- `iot_alert` + `iot_scene` → 实时告警推送(保持现状,不改)
- `iot_report_threshold` → 报告分析时的预警状态判定(新增)
两者独立,避免报告引擎侵入告警规则引擎。未来可考虑用 `iot_alert` 反推阈值,但 V1 不做。
---
# 6. 物模型与监测数据复用
## 6.1 监测指标筛选
`ThingsModel` 字段复用约定:
| 字段 | 报告用途 |
|:---|:---|
| `isMonitor=1` | 纳入监测分析的主指标(裂缝宽度、位移、轴力等) |
| `isHistory=1` | 查询历史时序数据(DeviceLogMapper.listHistory) |
| `isChart=1` | 生成趋势图 |
| `isCalculated=2` | **纵向计算(累积值)**,直接取缓存值作为"累计变化量",报告不重新计算 |
| `isCalculated=1` | 横向计算(公式含 `{}`),按 formula 计算 |
| `isCalculated=0` | 设备原始上报值 |
| `unit` | 报告数值单位 |
| `datatype` | integer/decimal/string/bool/array/enum |
| `formula` | 计算公式(`%s` 占位符) |
> **重要**:`isCalculated=2` 的累积值由现有缓存机制维护(见项目既有约定:isCalculated=2 从缓存模型值累积,无新状态表)。报告 Analyzer 直接读取该物模型的时序值作为"累计变化量",**不要**在报告里再实现一套累积逻辑。
## 6.2 数据查询入口
报告模块通过 `ReportDataProvider` 抽象调用现有 `DeviceLogMapper`:
```java
public interface ReportDataProvider {
/** 查询设备某标识符在时间窗口内的时序数据 */
List<MetricData> queryMetricData(Long deviceId, String identity,
LocalDateTime start, LocalDateTime end);
/** 查询设备最后一条上报值 */
MetricData queryLast(Long deviceId, String identity);
}
```
实现:内部委托 `DeviceLogMapper.listHistory`(MySQL)或 `TDDeviceLogMapper`(TDengine),按 `iot_device_log.identity` + `ts` 范围 + `logType IN (1,7)` 过滤。
## 6.3 MetricData(报告内部数据模型)
```java
public class MetricData {
private LocalDateTime timestamp;
private String identity;
private BigDecimal value;
private String unit;
private Integer isCalculated; // 0上报/1横向/2纵向
}
```
---
# 7. 当前架构中的模块职责
报告能力必须遵守现有模块边界:
```text
fastbee-admin ← REST 入口
fastbee-open-api ← 报告 Controller 放这里
fastbee-report ← 新增模块,报告引擎核心
fastbee-iot-service ← 设备/产品/物模型/日志领域(被复用)
fastbee-iot-data ← 时序数据访问(被复用)
fastbee-plugs/oss ← OSS 上传(被复用)
fastbee-common ← MonitorCategory / ReportPeriod 枚举
```
禁止:
- 把报告逻辑全塞进 `fastbee-admin`
- 把 Word 代码塞进 `fastbee-iot-service`
- 让 `fastbee-iot-service` 反向依赖 `fastbee-report`
---
# 8. 推荐新增模块:fastbee-report
```text
fastbee-report/
├── pom.xml
└── src/main/java/com/fastbee/report/
├── api/
│ ├── ReportEngine.java
│ ├── ReportRequest.java
│ └── ReportResult.java
├── core/
│ ├── DefaultReportEngine.java
│ ├── ReportContext.java
│ ├── ReportContextBuilder.java
│ └── ReportAssembler.java
├── category/
│ ├── MonitorCategoryResolver.java
│ └── DefaultMonitorCategoryResolver.java
├── period/
│ ├── ReportPeriodResolver.java
│ └── DefaultReportPeriodResolver.java
├── analyzer/
│ ├── ReportAnalyzer.java
│ ├── ReportAnalyzerFactory.java
│ └── DefaultReportAnalyzerFactory.java
├── analyzer/crack/
│ └── CrackMeterAnalyzer.java
├── analyzer/displacement/
│ └── DeepDisplacementAnalyzer.java
├── analyzer/axialforce/
│ └── AxialForceAnalyzer.java
├── analyzer/staticlevel/
│ └── StaticLevelAnalyzer.java
├── analyzer/vibratingwire/
│ └── VibratingWireAnalyzer.java
├── analysis/
│ ├── StatisticsCalculator.java
│ ├── TrendAnalyzer.java
│ ├── RateCalculator.java
│ ├── QualityAnalyzer.java
│ └── WarningAnalyzer.java
├── threshold/
│ ├── ReportThresholdProvider.java
│ ├── ReportThreshold.java
│ └── DbReportThresholdProvider.java
├── data/
│ ├── ReportDataProvider.java
│ ├── MetricData.java
│ └── DeviceDataSnapshot.java
├── model/
│ ├── ReportModel.java
│ ├── AnalysisResult.java
│ ├── Statistics.java
│ ├── TrendAnalysis.java
│ ├── QualityAnalysis.java
│ ├── WarningAnalysis.java
│ ├── AnalysisConclusion.java
│ └── ChartData.java
├── chart/
│ ├── ChartGenerator.java
│ ├── LineChartGenerator.java // JFreeChart
│ └── ProfileChartGenerator.java // 位移-深度剖面
├── renderer/
│ ├── ReportRenderer.java
│ ├── WordRenderer.java // poi-tl
│ └── WordTemplateResolver.java
├── record/
│ ├── ReportRecord.java
│ ├── ReportRecordMapper.java
│ └── ReportRecordService.java
└── exception/
└── ReportException.java
```
---
# 9. Maven 模块依赖方向
```text
fastbee-admin
↓
fastbee-open-api
↓
fastbee-report
↓
fastbee-iot-service + fastbee-iot-data + fastbee-oss
↓
MySQL / TDengine / Object Storage
```
**禁止** `fastbee-iot-service → fastbee-report` 反向依赖。报告是上层业务能力。
---
# 10. 新增 Maven 依赖
在 `fastbee-report/pom.xml` 引入(项目根 pom.xml 的 `dependencyManagement` 同步声明版本):
```xml
<!-- Word 模板引擎,基于 poi-ooxml,支持 {{var}} 占位符/循环表格/图片 -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.4</version>
</dependency>
<!-- Java 端图表生成(PNG 嵌入 Word) -->
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.4</version>
</dependency>
```
> poi-tl 1.10.x 兼容 POI 4.1.2;jfreechart 1.5.x 纯 Java、轻量。禁止再引入 docx4j 或 echarts 的 Java 包装。
---
# 11. ReportEngine
```java
public interface ReportEngine {
ReportResult generate(ReportRequest request);
}
```
默认实现:
```java
public class DefaultReportEngine implements ReportEngine {
private final ReportContextBuilder contextBuilder;
private final MonitorCategoryResolver categoryResolver;
private final ReportAnalyzerFactory analyzerFactory;
private final ReportAssembler reportAssembler;
private final ReportRenderer reportRenderer;
private final ReportPeriodResolver periodResolver;
private final ReportRecordService recordService;
@Override
public ReportResult generate(ReportRequest request) {
// 1. 参数校验 + 解析时间窗口
TimeWindow window = periodResolver.resolve(
request.getPeriod(), request.getAnchorDate());
if (request.getPeriod() == ReportPeriod.CUSTOM) {
window = new TimeWindow(request.getStartTime(), request.getEndTime());
}
// 2. 解析设备监测分类
MonitorCategory category = categoryResolver.resolveByDeviceId(request.getDeviceId());
// 3. 构建上下文(设备/产品/物模型/数据/阈值)
ReportContext context = contextBuilder.build(request, window, category);
// 4. 路由 Analyzer
ReportAnalyzer analyzer = analyzerFactory.get(category);
// 5. 执行分析
AnalysisResult analysis = analyzer.analyze(context, request);
// 6. 组装报告模型
ReportModel model = reportAssembler.assemble(context, analysis, request);
// 7. 渲染 Word
ReportResult result = reportRenderer.render(model, request);
// 8. 落库 ReportRecord
recordService.saveSuccess(request, window, category, result);
return result;
}
}
```
`ReportEngine` 不得包含具体设备分析逻辑。
---
# 12. ReportContext
Analyzer 的输入上下文,构建时一次性装配所有依赖数据:
```java
public class ReportContext {
private Project project;
private Device device;
private Product product;
private MonitorCategory category;
private TimeWindow window;
private List<ThingsModel> metrics; // isMonitor=1 的物模型
private Map<String, List<MetricData>> data; // identity -> 时序数据
private Map<String, ReportThreshold> thresholds; // identity -> 阈值
private ReportRequest request;
private Map<String, Object> attributes; // 扩展
}
```
`ReportContextBuilder` 统一完成:
```text
查设备 → 查产品 → 解析 MonitorCategory
→ 查物模型(isMonitor=1)
→ 查阈值(iot_report_threshold)
→ 查时序数据(DeviceLogMapper.listHistory)
→ 装入 ReportContext
```
Analyzer 不允许自己 `@Autowired` 业务 Service 查询。
---
# 13. ReportAnalyzer
```java
public interface ReportAnalyzer {
boolean supports(MonitorCategory category);
AnalysisResult analyze(ReportContext context, ReportRequest request);
}
```
---
# 14. 通用分析能力
所有设备共享:
```text
StatisticsCalculator 统计(当前/初始/最小/最大/平均/中位/变化量/累计变化)
RateCalculator 变化速率(mm/day, kN/day)—— 需考虑时间间隔/数据缺失/异常值
TrendAnalyzer 趋势(UP/DOWN/STABLE/FLUCTUATION/UNKNOWN + 斜率 + 置信度)
QualityAnalyzer 数据质量(缺失/重复/越界/突变/平线/完整率)
WarningAnalyzer 预警(读 ReportThreshold,输出 NORMAL/WARNING/ALARM)
```
### Statistics
```java
public class Statistics {
private BigDecimal current; // 当前值
private BigDecimal baseline; // 初始值(窗口首条或设备激活首条)
private BigDecimal min;
private BigDecimal max;
private BigDecimal average;
private BigDecimal median;
private BigDecimal delta; // 本期变化量 = current - baseline
private BigDecimal cumulative; // 累计变化量(优先取 isCalculated=2 物模型值)
private String unit;
}
```
### RateCalculator 禁止简单 `(last-first)`
必须考虑:时间间隔、数据缺失段、异常值剔除(3σ 或 IQR)、采样周期不均。
### TrendAnalysis
```java
public class TrendAnalysis {
private TrendType trend; // UP/DOWN/STABLE/FLUCTUATION/UNKNOWN
private BigDecimal slope; // 线性回归斜率
private BigDecimal confidence; // R²
private String description;
}
```
V1 用简单线性回归;未来可升级移动平均/指数平滑/异常点剔除/季节性分析。
### QualityAnalysis
```java
public class QualityAnalysis {
private int total;
private int valid;
private int missing;
private int duplicate;
private int abnormal;
private BigDecimal integrityRate; // 完整率 = valid/total
private String status;
}
```
### WarningAnalysis
```java
public class WarningAnalysis {
private WarningStatus status; // NORMAL/WARNING/ALARM/UNKNOWN
private BigDecimal current;
private BigDecimal warningThreshold;
private BigDecimal alarmThreshold;
private BigDecimal controlValue; // 设计控制值(可选)
private String description;
}
```
---
# 15. AnalysisResult
Analyzer 最终输出契约:
```java
public class AnalysisResult {
private Statistics statistics;
private TrendAnalysis trend;
private QualityAnalysis quality;
private WarningAnalysis warning;
private List<ChartData> charts;
private List<AnalysisConclusion> conclusions;
private Map<String, Object> extensions; // 设备专属分析结果
}
```
---
# 16. 设备专属分析
## 16.1 裂缝计(CRACK_METER)
```text
当前裂缝宽度、初始裂缝宽度、本期变化量、累计变化量
最大值、最小值、平均值、变化速率、趋势、预警状态
```
`extensions.put("crack", crackExtension)`
图表:`CRACK_TREND`(裂缝宽度时程图)
## 16.2 深部位移(DEEP_DISPLACEMENT)
不能当单值指标处理,需按深度维度组织:
```text
设备 → 深度 → 不同深度测点 → 位移值
```
输出:最大位移、最大位移深度、累计位移、位移速率、位移趋势、位移-深度剖面图。
图表:
- `DEEP_DISPLACEMENT_TREND`(位移时程图)
- `DEEP_DISPLACEMENT_PROFILE`(位移-深度剖面图)
## 16.3 轴力/压力计(AXIAL_FORCE)
```text
当前轴力、初始轴力、本期变化、累计变化、最大轴力、平均轴力
变化速率、趋势、控制值占比、预警状态
```
仅在阈值配置存在设计值/控制值/预警值/报警值时才评价。
## 16.4 静力水准仪(STATIC_LEVEL)
```text
当前相对高程、初始值、本期变化、累计沉降、最大沉降
沉降速率、趋势、差异沉降、预警状态
```
图表:`STATIC_LEVEL_TREND`(沉降时程)、`STATIC_LEVEL_COMPARE`(测点沉降对比)
## 16.5 振弦式采集仪(VIBRATING_WIRE)
区分原始物理量(频率/温度)与工程换算量:
```text
当前频率、平均频率、最大频率、最小频率、温度、工程换算值、趋势、数据质量
```
---
# 17. ReportAssembler
```java
public interface ReportAssembler {
ReportModel assemble(ReportContext context, AnalysisResult analysis, ReportRequest request);
}
```
职责:`ReportContext + AnalysisResult + ReportRequest → ReportModel`。
```java
public class ReportModel {
private ReportInfo report; // 报告编号、周期、时间窗口、生成时间
private Project project;
private Device device;
private Product product;
private MonitorCategory category;
private MonitoringSummary summary;
private AnalysisResult analysis;
private List<ChartData> charts;
private List<TableData> tables;
private Map<String, Object> variables; // Word 模板变量
}
```
WordRenderer 只依赖 `ReportModel`,**禁止查询数据库**。
---
# 18. WordRenderer(poi-tl)
```java
public interface ReportRenderer {
ReportResult render(ReportModel model, ReportRequest request);
}
```
## 18.1 渲染流程
```text
加载 Word 模板(WordTemplateResolver 按 templateCode + category 定位)
↓
poi-tl 渲染 {{}} 占位符
↓
插入图表 PNG(ChartData.image)
↓
生成 byte[]
↓
OssFactory.instance(redis).upload(byte[], path, "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
↓
返回 fileUrl
```
## 18.2 模板命名规范
```text
DEVICE_CRACK_METER_V1.docx
DEVICE_DEEP_DISPLACEMENT_V1.docx
DEVICE_AXIAL_FORCE_V1.docx
DEVICE_STATIC_LEVEL_V1.docx
DEVICE_VIBRATING_WIRE_V1.docx
```
## 18.3 Word 变量规范(poi-tl `{{}}` 语法)
```text
{{report.no}}
{{report.periodDesc}} 日报/周报/半月报/月报
{{report.startTime}}
{{report.endTime}}
{{project.name}}
{{project.code}}
{{device.name}}
{{device.serialNumber}}
{{product.monitorCategoryDesc}}
{{analysis.statistics.current}}
{{analysis.statistics.min}}
{{analysis.statistics.max}}
{{analysis.statistics.average}}
{{analysis.statistics.delta}}
{{analysis.statistics.cumulative}}
{{analysis.rate}}
{{analysis.trend.description}}
{{analysis.quality.integrityRate}}
{{analysis.warning.status}}
{{analysis.warning.description}}
{{conclusions}} 循环列表(poi-tl 循环表格)
{{charts.crackTrend}} 图片占位符
```
禁止 Word 模板直接引用数据库字段。
## 18.4 模板表
```sql
CREATE TABLE report_template (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
template_code VARCHAR(128) NOT NULL,
template_name VARCHAR(128),
monitor_category_code VARCHAR(64) NOT NULL,
version VARCHAR(32),
file_path VARCHAR(512),
status TINYINT DEFAULT 1,
tenant_id BIGINT,
create_time DATETIME,
update_time DATETIME,
UNIQUE KEY uk_code_version (template_code, version)
) COMMENT '报告模板';
```
---
# 19. 图表规范
```java
public class ChartData {
private String chartCode;
private String title;
private ChartType type; // LINE / BAR / PROFILE / SCATTER
private byte[] image; // PNG 字节
}
```
图表生成链路:
```text
MetricData → AnalysisResult → ChartData(由 ChartGenerator 用 JFreeChart 生成 PNG) → WordRenderer → DOCX
```
Java 端用 JFreeChart 生成 PNG,Word 只负责放置图片。禁止在前端生成图回传后端。
---
# 20. ReportRecord(报告生命周期)
```sql
CREATE TABLE report_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
report_no VARCHAR(64) NOT NULL COMMENT '报告编号 REPORT-yyyyMMddHHmmss-xxxx',
device_id BIGINT NOT NULL,
monitor_category_code VARCHAR(64) NOT NULL,
period VARCHAR(32) COMMENT 'DAY/WEEK/HALF_MONTH/MONTH/CUSTOM',
template_code VARCHAR(128),
template_version VARCHAR(32),
start_time DATETIME,
end_time DATETIME,
status VARCHAR(16) COMMENT 'CREATING/SUCCESS/FAILED',
file_url VARCHAR(512),
error_message VARCHAR(1024),
tenant_id BIGINT,
create_by VARCHAR(64),
create_time DATETIME,
KEY idx_device (device_id, create_time),
KEY idx_period (period, create_time)
) COMMENT '报告生成记录';
```
状态:`CREATING / SUCCESS / FAILED`。
---
# 21. API 设计
```text
POST /iot/report/device/generate 生成报告(同步)
GET /iot/report/list 报告列表
GET /iot/report/{reportNo} 报告详情
GET /iot/report/download/{reportNo} 下载(重定向 OSS URL)
```
### 生成请求
```json
POST /iot/report/device/generate
{
"deviceId": 10001,
"period": "WEEK",
"anchorDate": "2026-08-09",
"templateCode": "DEVICE_CRACK_METER_V1",
"format": "WORD"
}
```
或自定义时间范围:
```json
{
"deviceId": 10001,
"period": "CUSTOM",
"startTime": "2026-08-01 00:00:00",
"endTime": "2026-08-08 23:59:59",
"templateCode": "DEVICE_CRACK_METER_V1"
}
```
### 响应
```json
{
"reportNo": "REPORT-202608090001",
"status": "SUCCESS",
"fileName": "304259363251-周报-监测分析报告.docx",
"fileUrl": "https://..."
}
```
### 权限
按 CLAUDE.md 权限策略,报告接口跨页面复用,读接口用 `hasAnyPermi`:
```java
// 生成(写)
@PreAuthorize("@ss.hasPermi('iot:report:generate')")
// 列表/详情/下载(读,跨页面复用)
@PreAuthorize("@ss.hasAnyPermi('iot:report:list,iot:device:list')")
```
---
# 22. OSS 文件路径规范
```text
/report/yyyy/MM/dd/device/{deviceId}/{reportNo}.docx
例:/report/2026/08/09/device/10001/REPORT-202608090001.docx
```
复用 `OssFactory.instance(redis).upload(byte[], path, contentType)`,禁止把 Word 永久存服务器本地。
---
# 23. 异步化建议
V1 单设备 + 数据量 < 100 万 → 同步生成即可。
后续若数据量大/图表多/Word 耗时:
```text
API → fastbee-mq(RocketMQ) → ReportConsumer → ReportEngine → OSS → ReportRecord
```
复用现有 `fastbee-mq`。V1 不要为异步而异步。
---
# 24. 报告结论生成原则
结论必须基于 `Statistics + Trend + Quality + Warning` 结构化结果,**不是**让 AI 直接读原始数据。
V1 用规则引擎 + 模板文字:
```java
if (warning == WARNING) {
conclusion = "当前监测值达到项目配置的预警阈值,建议持续关注。";
}
```
未来可接 LLM 做自然语言润色,但 **LLM 不负责工程安全判定**。
---
# 25. 第一阶段报告内容(单设备固定结构)
```text
1. 监测结论摘要
2. 设备基本信息(含监测分类)
3. 数据质量分析
4. 监测数据统计
5. 趋势分析
6. 预警分析
7. 设备专项分析(按 MonitorCategory)
8. 趋势图
9. 综合分析结论
```
暂不包含:项目总体分析、多设备关联、施工阶段关联、工程风险评价。
---
# 26. 不允许的设计
```text
❌ ReportEngine 直接操作 Mapper
❌ WordRenderer 查询数据库
❌ Word 模板写 SQL
❌ Analyzer 硬编码工程阈值
❌ 所有设备写在一个 Analyzer
❌ if(category==xxx) 巨型判断
❌ AI 直接判断结构是否安全
❌ 把所有报告逻辑放 fastbee-admin
❌ Word 模板和数据库字段直接绑定
❌ 复用 DeviceType 作为分类路由键(应使用 MonitorCategory)
❌ 重复实现 OSS / 数据查询 / 图表能力
❌ 把 iot_category 当作监测分类路由键
❌ 报告模块反向依赖 fastbee-iot-service 之外的循环依赖
```
---
# 27. 测试要求
每个 Analyzer 必须有单元测试,覆盖:
```text
正常数据 / 数据为空 / 数据不足 / 数据缺失 / 数据异常
持续增长 / 持续下降 / 波动 / 达到预警 / 达到报警
```
测试数据放 `src/test/resources/report/`:
```text
crack/normal.json, warning.json, alarm.json, abnormal.json, missing.json
displacement/normal.json, warning.json, profile.json
```
不依赖线上真实数据。
`ReportPeriodResolver` 必须测试边界:周末/月末/闰年二月/半月报 15 日分界。
---
# 28. 第一阶段开发顺序
## Step 0:基础设施
- [ ] 根 `pom.xml` 声明 poi-tl、jfreechart 版本
- [ ] `iot_product` 加 `monitor_category_code` 列 + 存量数据回填
- [ ] 新建 `iot_report_threshold`、`report_template`、`report_record` 三张表
- [ ] `MonitorCategory`、`ReportPeriod` 枚举放 `fastbee-common`
- [ ] `Product.java` 加 `monitorCategoryCode` 字段
## Step 1:fastbee-report 模块骨架
- [ ] 创建 `fastbee-report` 模块,加入根 pom `<modules>`
- [ ] `ReportRequest` / `ReportResult` / `ReportEngine` / `ReportContext`
## Step 2:分类与周期
- [ ] `MonitorCategoryResolver` / `DefaultMonitorCategoryResolver`
- [ ] `ReportPeriodResolver` / `DefaultReportPeriodResolver` + 单测
## Step 3:Analyzer 框架
- [ ] `ReportAnalyzer` / `ReportAnalyzerFactory` / `DefaultReportAnalyzerFactory`
## Step 4:先只实现 CrackMeterAnalyzer
- [ ] 不同时写 5 种
## Step 5:通用分析能力
- [ ] `StatisticsCalculator` / `TrendAnalyzer` / `RateCalculator` / `QualityAnalyzer` / `WarningAnalyzer`
## Step 6:数据与阈值 Provider
- [ ] `ReportDataProvider`(委托 `DeviceLogMapper`)
- [ ] `ReportThresholdProvider`(查 `iot_report_threshold`)
## Step 7:AnalysisResult + ReportModel + ReportAssembler
## Step 8:ChartGenerator(JFreeChart 生成 PNG)
## Step 9:WordRenderer(poi-tl)
## Step 10:制作 `DEVICE_CRACK_METER_V1.docx` 模板
## Step 11:闭环验证
- [ ] 设备 `304259363251` + 周报 + Word 完整跑通
- [ ] OSS 上传 + ReportRecord 落库 + 下载
## Step 12:扩展其他分类
- [ ] `DeepDisplacementAnalyzer`
- [ ] `AxialForceAnalyzer`
- [ ] `StaticLevelAnalyzer`
- [ ] `VibratingWireAnalyzer`
---
# 29. Definition of Done
```text
[ ] fastbee-report 模块创建完成,根 pom 已注册
[ ] Maven 依赖无循环
[ ] iot_product.monitor_category_code 列已加 + 存量回填
[ ] iot_report_threshold / report_template / report_record 三张表已建
[ ] MonitorCategory / ReportPeriod 枚举已放 fastbee-common
[ ] ReportEngine / ReportRequest / ReportContext 创建完成
[ ] MonitorCategoryResolver 创建完成
[ ] ReportPeriodResolver 创建完成 + 边界单测
[ ] ReportAnalyzer / AnalyzerFactory 创建完成
[ ] CrackMeterAnalyzer 创建完成
[ ] StatisticsCalculator / TrendAnalyzer / RateCalculator / QualityAnalyzer / WarningAnalyzer 创建完成
[ ] ReportDataProvider / ReportThresholdProvider 创建完成
[ ] AnalysisResult / ReportModel / ReportAssembler 创建完成
[ ] ChartGenerator(JFreeChart)创建完成
[ ] WordRenderer(poi-tl)创建完成
[ ] Word 模板 DEVICE_CRACK_METER_V1.docx 完成
[ ] OSS 上传完成(复用 OssFactory)
[ ] ReportRecord 落库完成
[ ] POST /iot/report/device/generate 等 API 完成
[ ] 单元测试完成(含 ReportPeriodResolver 边界)
[ ] 设备 304259363251 周报可成功生成 DOCX
[ ] 异常情况返回 FAILED 并落库
[ ] 生成后的报告可重新下载
[ ] 日报/周报/半月报/月报四种周期均可生成
```
---
# 30. 最终架构目标
第一阶段:
```text
304259363251 (deviceId)
│
ReportRequest(period=WEEK)
│
ReportPeriodResolver → [周一00:00, 周日23:59]
│
MonitorCategoryResolver → CRACK_METER
│
ReportEngine
│
CrackMeterAnalyzer
│
AnalysisResult
│
ReportModel
│
WordRenderer(poi-tl) + ChartGenerator(JFreeChart)
│
byte[] → OssFactory → OSS
│
ReportRecord
│
返回下载地址
```
未来:
```text
Project
│
┌───────────┼────────────┐
▼ ▼ ▼
CRACK AXIAL DISPLACEMENT
│ │ │
▼ ▼ ▼
Analyzer Analyzer Analyzer
│ │ │
└───────────┼────────────┘
▼
ProjectReportEngine(聚合层)
│
▼
ProjectReportModel
│
▼
Word/PDF
```
> **单设备 Report Engine 是底层能力,项目报告只是未来的聚合层。**
---
# 31. AI Agent 执行规则
当 AI Agent 执行本 Skill 时:
## 第一原则:先读后写
重点搜索:`Device` / `Product` / `ThingsModel` / `DeviceLogMapper` / `TDDeviceLogMapper` / `OssFactory` / `Alert` / `Project` / `poi-ooxml`。禁止重复实现已有能力。
## 第二原则:先确认模块依赖
禁止循环依赖。`fastbee-report` 只能向下依赖 `fastbee-iot-service` / `fastbee-iot-data` / `fastbee-oss` / `fastbee-common`。
## 第三原则:先跑通裂缝计
验证完整链路后再扩展其他分类。不要一次性实现全部设备。
## 第四原则:复用优先
OSS / 数据查询 / Word / 图表,能复用就复用,不能复用才引入新依赖(poi-tl、jfreechart)。
## 第五原则:单一职责、低耦合、可测试、可扩展
---
# 32. 最终指令
你现在是 FastBee 项目的高级 Java 架构工程师。请严格按照本 Skill 实施。
执行前:
1. 阅读当前项目目录结构。
2. 搜索已有设备、物模型、DeviceLogMapper、TDDeviceLogMapper、OssFactory、Alert、Project、poi-ooxml 相关代码。
3. 判断哪些能力可以复用。
4. 不允许重复实现已有基础能力。
5. 不允许修改无关业务。
第一阶段只实现:
```text
单设备 + 报告周期(日/周/半月/月) + 裂缝计(CRACK_METER) + Word
```
必须先完成完整闭环。完成后再扩展:深部位移 / 轴力 / 静力水准 / 振弦。
代码必须遵循:
```text
ReportRequest(含 period)
↓
ReportPeriodResolver → TimeWindow
↓
MonitorCategoryResolver → MonitorCategory
↓
ReportContextBuilder → ReportContext
↓
ReportAnalyzerFactory.get(category)
↓
AnalysisResult
↓
ReportModel
↓
WordRenderer(poi-tl) + ChartGenerator(JFreeChart)
↓
OssFactory → OSS
↓
ReportRecord
```
不得破坏 FastBee 当前模块边界。
实现过程中优先保证:
```text
架构正确
数据正确
分析可测试
模板可替换
设备分类可扩展(MonitorCategory)
报告周期可扩展(ReportPeriod)
```
而不是追求一次实现所有功能。
最终输出:
```text
1. 修改文件列表
2. 新增文件列表
3. Maven 依赖变化
4. 数据库变化(iot_product 加列 + 3 张新表)
5. API 说明
6. Word 模板变量说明
7. 测试结果
8. 使用说明
9. 后续扩展建议
```