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

42 KiB
Raw Blame History

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 目标

补齐报告能力链路:

IoT 数据
    ↓
ReportRequest(设备 + 周期/时间范围 + 模板)
    ↓
ReportContext(设备/产品/物模型/监测数据/阈值)
    ↓
ReportAnalyzer(按 MonitorCategory 路由)
    ↓
AnalysisResult(统计/趋势/质量/预警/结论)
    ↓
ReportModel
    ↓
WordRenderer(poi-tl + JFreeChart)
    ↓
DOCX → OSS → ReportRecord

最终形成:

IoT 数据  →  Report Engine  →  专业监测分析报告 (.docx)

2. 核心设计原则

2.1 Report Engine 不直接理解数据库

禁止 ReportEngine → 直接操作 Mapper → 查询数据库 → 分析 → Word。

推荐:

数据访问层(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 数据库改动

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

@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 增加:

@ApiModelProperty("监测分类编码(报告引擎路由键)")
private String monitorCategoryCode;

3.2.4 MonitorCategoryResolver

位置:fastbee-report/src/main/java/com/fastbee/report/category/MonitorCategoryResolver.java

public interface MonitorCategoryResolver {
    MonitorCategory resolve(Long productId);
    MonitorCategory resolveByDeviceId(Long deviceId);
}

实现:先查 iot_product.monitor_category_code;为空时返回 UNKNOWN(第一阶段不实现启发式回退,避免误判;后台需维护分类)。

3.2.5 AnalyzerFactory 按 MonitorCategory 路由

public interface ReportAnalyzerFactory {
    ReportAnalyzer get(MonitorCategory category);
}
@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 的关系

两者正交,互不替代:

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

@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

public interface ReportPeriodResolver {
    /** 根据周期与锚点日期,计算 [startTime, endTime] */
    TimeWindow resolve(ReportPeriod period, LocalDate anchorDate);
}
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 同时支持周期与自定义

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(报告专用阈值表)

报告场景需要结构化、可读、可回溯的阈值,与告警规则解耦:

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 查询:

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:

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(报告内部数据模型)

public class MetricData {
    private LocalDateTime timestamp;
    private String identity;
    private BigDecimal value;
    private String unit;
    private Integer isCalculated; // 0上报/1横向/2纵向
}

7. 当前架构中的模块职责

报告能力必须遵守现有模块边界:

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

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 模块依赖方向

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 同步声明版本):

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

public interface ReportEngine {
    ReportResult generate(ReportRequest request);
}

默认实现:

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 的输入上下文,构建时一次性装配所有依赖数据:

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 统一完成:

查设备 → 查产品 → 解析 MonitorCategory
       → 查物模型(isMonitor=1)
       → 查阈值(iot_report_threshold)
       → 查时序数据(DeviceLogMapper.listHistory)
       → 装入 ReportContext

Analyzer 不允许自己 @Autowired 业务 Service 查询。


13. ReportAnalyzer

public interface ReportAnalyzer {
    boolean supports(MonitorCategory category);
    AnalysisResult analyze(ReportContext context, ReportRequest request);
}

14. 通用分析能力

所有设备共享:

StatisticsCalculator   统计(当前/初始/最小/最大/平均/中位/变化量/累计变化)
RateCalculator         变化速率(mm/day, kN/day)—— 需考虑时间间隔/数据缺失/异常值
TrendAnalyzer          趋势(UP/DOWN/STABLE/FLUCTUATION/UNKNOWN + 斜率 + 置信度)
QualityAnalyzer        数据质量(缺失/重复/越界/突变/平线/完整率)
WarningAnalyzer        预警(读 ReportThreshold,输出 NORMAL/WARNING/ALARM)

Statistics

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

public class TrendAnalysis {
    private TrendType trend;        // UP/DOWN/STABLE/FLUCTUATION/UNKNOWN
    private BigDecimal slope;       // 线性回归斜率
    private BigDecimal confidence;  // R²
    private String description;
}

V1 用简单线性回归;未来可升级移动平均/指数平滑/异常点剔除/季节性分析。

QualityAnalysis

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

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 最终输出契约:

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)

当前裂缝宽度、初始裂缝宽度、本期变化量、累计变化量
最大值、最小值、平均值、变化速率、趋势、预警状态

extensions.put("crack", crackExtension)

图表:CRACK_TREND(裂缝宽度时程图)

16.2 深部位移(DEEP_DISPLACEMENT)

不能当单值指标处理,需按深度维度组织:

设备 → 深度 → 不同深度测点 → 位移值

输出:最大位移、最大位移深度、累计位移、位移速率、位移趋势、位移-深度剖面图。

图表:

  • DEEP_DISPLACEMENT_TREND(位移时程图)
  • DEEP_DISPLACEMENT_PROFILE(位移-深度剖面图)

16.3 轴力/压力计(AXIAL_FORCE)

当前轴力、初始轴力、本期变化、累计变化、最大轴力、平均轴力
变化速率、趋势、控制值占比、预警状态

仅在阈值配置存在设计值/控制值/预警值/报警值时才评价。

16.4 静力水准仪(STATIC_LEVEL)

当前相对高程、初始值、本期变化、累计沉降、最大沉降
沉降速率、趋势、差异沉降、预警状态

图表:STATIC_LEVEL_TREND(沉降时程)、STATIC_LEVEL_COMPARE(测点沉降对比)

16.5 振弦式采集仪(VIBRATING_WIRE)

区分原始物理量(频率/温度)与工程换算量:

当前频率、平均频率、最大频率、最小频率、温度、工程换算值、趋势、数据质量

17. ReportAssembler

public interface ReportAssembler {
    ReportModel assemble(ReportContext context, AnalysisResult analysis, ReportRequest request);
}

职责:ReportContext + AnalysisResult + ReportRequest → ReportModel。

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)

public interface ReportRenderer {
    ReportResult render(ReportModel model, ReportRequest request);
}

18.1 渲染流程

加载 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 模板命名规范

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 {{}} 语法)

{{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 模板表

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. 图表规范

public class ChartData {
    private String chartCode;
    private String title;
    private ChartType type;   // LINE / BAR / PROFILE / SCATTER
    private byte[] image;     // PNG 字节
}

图表生成链路:

MetricData → AnalysisResult → ChartData(由 ChartGenerator 用 JFreeChart 生成 PNG) → WordRenderer → DOCX

Java 端用 JFreeChart 生成 PNG,Word 只负责放置图片。禁止在前端生成图回传后端。


20. ReportRecord(报告生命周期)

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 设计

POST /iot/report/device/generate        生成报告(同步)
GET  /iot/report/list                   报告列表
GET  /iot/report/{reportNo}             报告详情
GET  /iot/report/download/{reportNo}    下载(重定向 OSS URL)

生成请求

POST /iot/report/device/generate
{
  "deviceId": 10001,
  "period": "WEEK",
  "anchorDate": "2026-08-09",
  "templateCode": "DEVICE_CRACK_METER_V1",
  "format": "WORD"
}

或自定义时间范围:

{
  "deviceId": 10001,
  "period": "CUSTOM",
  "startTime": "2026-08-01 00:00:00",
  "endTime": "2026-08-08 23:59:59",
  "templateCode": "DEVICE_CRACK_METER_V1"
}

响应

{
  "reportNo": "REPORT-202608090001",
  "status": "SUCCESS",
  "fileName": "304259363251-周报-监测分析报告.docx",
  "fileUrl": "https://..."
}

权限

按 CLAUDE.md 权限策略,报告接口跨页面复用,读接口用 hasAnyPermi:

// 生成(写)
@PreAuthorize("@ss.hasPermi('iot:report:generate')")

// 列表/详情/下载(读,跨页面复用)
@PreAuthorize("@ss.hasAnyPermi('iot:report:list,iot:device:list')")

22. OSS 文件路径规范

/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 耗时:

API → fastbee-mq(RocketMQ) → ReportConsumer → ReportEngine → OSS → ReportRecord

复用现有 fastbee-mq。V1 不要为异步而异步。


24. 报告结论生成原则

结论必须基于 Statistics + Trend + Quality + Warning 结构化结果,不是让 AI 直接读原始数据。

V1 用规则引擎 + 模板文字:

if (warning == WARNING) {
    conclusion = "当前监测值达到项目配置的预警阈值,建议持续关注。";
}

未来可接 LLM 做自然语言润色,但 LLM 不负责工程安全判定。


25. 第一阶段报告内容(单设备固定结构)

1. 监测结论摘要
2. 设备基本信息(含监测分类)
3. 数据质量分析
4. 监测数据统计
5. 趋势分析
6. 预警分析
7. 设备专项分析(按 MonitorCategory)
8. 趋势图
9. 综合分析结论

暂不包含:项目总体分析、多设备关联、施工阶段关联、工程风险评价。


26. 不允许的设计

❌ ReportEngine 直接操作 Mapper
❌ WordRenderer 查询数据库
❌ Word 模板写 SQL
❌ Analyzer 硬编码工程阈值
❌ 所有设备写在一个 Analyzer
❌ if(category==xxx) 巨型判断
❌ AI 直接判断结构是否安全
❌ 把所有报告逻辑放 fastbee-admin
❌ Word 模板和数据库字段直接绑定
❌ 复用 DeviceType 作为分类路由键(应使用 MonitorCategory)
❌ 重复实现 OSS / 数据查询 / 图表能力
❌ 把 iot_category 当作监测分类路由键
❌ 报告模块反向依赖 fastbee-iot-service 之外的循环依赖

27. 测试要求

每个 Analyzer 必须有单元测试,覆盖:

正常数据 / 数据为空 / 数据不足 / 数据缺失 / 数据异常
持续增长 / 持续下降 / 波动 / 达到预警 / 达到报警

测试数据放 src/test/resources/report/:

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

[ ] 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. 最终架构目标

第一阶段:

              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
                    │
            返回下载地址

未来:

                    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. 不允许修改无关业务。

第一阶段只实现:

单设备 + 报告周期(日/周/半月/月) + 裂缝计(CRACK_METER) + Word

必须先完成完整闭环。完成后再扩展:深部位移 / 轴力 / 静力水准 / 振弦。

代码必须遵循:

ReportRequest(含 period)
    ↓
ReportPeriodResolver → TimeWindow
    ↓
MonitorCategoryResolver → MonitorCategory
    ↓
ReportContextBuilder → ReportContext
    ↓
ReportAnalyzerFactory.get(category)
    ↓
AnalysisResult
    ↓
ReportModel
    ↓
WordRenderer(poi-tl) + ChartGenerator(JFreeChart)
    ↓
OssFactory → OSS
    ↓
ReportRecord

不得破坏 FastBee 当前模块边界。

实现过程中优先保证:

架构正确
数据正确
分析可测试
模板可替换
设备分类可扩展(MonitorCategory)
报告周期可扩展(ReportPeriod)

而不是追求一次实现所有功能。

最终输出:

1. 修改文件列表
2. 新增文件列表
3. Maven 依赖变化
4. 数据库变化(iot_product 加列 + 3 张新表)
5. API 说明
6. Word 模板变量说明
7. 测试结果
8. 使用说明
9. 后续扩展建议