# 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 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 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 com.deepoove poi-tl 1.10.4 org.jfree jfreechart 1.5.4 ``` > 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 metrics; // isMonitor=1 的物模型 private Map> data; // identity -> 时序数据 private Map thresholds; // identity -> 阈值 private ReportRequest request; private Map 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 charts; private List conclusions; private Map 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 charts; private List tables; private Map 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 `` - [ ] `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. 后续扩展建议 ```