Files
smart_grid_iot/.trae/specs/add-icc-analog-data/spec.md
T

5.6 KiB
Raw Blame History

ICC 模拟量数据同步 Spec(add-icc-analog-data)

Why

现有 electricity_data(线路用电信息表)仅有一版占位定义(单列 electricity),与 ICC 平台「消防最新模拟量信息分页查询」接口(POST /evo-apigw/evo-fdbu/{version}/analog/subsystem/page,实测返回 {deviceCode, uniqueCode, analogType, analogName, analogUnit, analogValue},无时间字段)不对应。本次按接口返回数据重建表结构,并实现真实同步服务与定时任务,将已同步线路对应探测器的模拟量数据写入 electricity_data。

What Changes

  • 表结构重建(BREAKING):
    • 删除占位列 electricity → 通用模拟量行:analog_type(analogType)、analog_value NUMERIC(14,4)(analogValue)、analog_name(analogName,如:剩余电流)、analog_unit(analogUnit,如:mA)
    • icc_device_code 改为 imei(映射 uniqueCode)
    • 删除 equip_id(用户确认不需要客户设备关联)
    • 保留:line_id(NOT NULL → electric_line)、line_code(NOT NULL,= deviceCode 冗余 + 幂等键)、line_name(冗余展示)、customer_id(NOT NULL,逻辑隔离键)、collect_time(NOT NULL,分区键)
    • collect_time 语义(实测确认):ICC「最新模拟量分页查询」接口实际不返回 reportTime 时间字段,经用户确认以同步时间作为采集时间(分区键)
    • 唯一幂等键:UNIQUE (line_code, analog_type, collect_time)(同一次同步内重复上报覆盖更新)
    • 按月 RANGE 分区(分区键 collect_time),迁移建当月及后续月份分区
  • 同步服务(复用 icc_customer_auth 真实 OAuth2 鉴权):
    • 先读取已同步的 electric_line(line_code = detectorCode),按 deviceCodes 分批(每批 ≤50)POST 模拟量接口,pageSize=500 短页停止全量拉取
    • 预取 deviceCode→(line_id, customer_id) 映射避免 N+1;imei=uniqueCode、analog_type=analogType、analog_name=analogName、analog_unit=analogUnit、analog_value=analogValue 数值解析、collect_time=同步时间
    • 按唯一键 (line_code, analog_type, collect_time) 幂等 upsert;写 icc_sync_log(sync_type=electricity_data);失败仅记日志不抛业务错
  • 定时任务:jobKey=icc_analog_sync(默认每 5 分钟,cron */5 * * * *,sys_job 种子幂等),注册进 job_registry/JobRuntime(syncIccAnalogs)
  • 同步接口:POST /api/electricity-data/sync(JWT+RBAC device:list,返回 pulled/inserted/updated/skipped/synced);不做前端页面/菜单
  • 迁移与文档:新增 database/migrations/20261024_icc_analog_data.sql(建表/分区/索引/注释 + sys_job 种子),同步 database/db_init.sql 与 系统设计/表结构新增及变更.md

Impact

  • Affected specs:add-icc-line-sync(electric_line 为模拟量关联目标,先同步线路)
  • Affected code:
    • 后端新增:server/src/entity/electricity_data.entity.ts、server/src/task/icc_analog_mapper.ts、server/src/service/icc_analog_sync.service.ts、server/src/task/icc_analog_sync.task.ts、server/src/controller/electricity_data.controller.ts
    • 后端修改:server/src/task/job_registry.ts、server/src/service/job_scheduler.service.ts、server/src/middleware/rbac.middleware.ts
    • 数据库:新增 database/migrations/20261024_icc_analog_data.sql;修改 database/db_init.sql、database/db_seed.sql(sys_job 种子)
    • 设计文档:系统设计/表结构新增及变更.md
    • 前端:无改动

ADDED Requirements

Requirement: electricity_data 表结构按 AnalogInfoVO 重建

系统 SHALL 将 electricity_data 重建为按月分区时序表:id(复合主键含 collect_time)、line_id(NOT NULL FK→electric_line)、line_code(NOT NULL)、imei、line_name、customer_id(NOT NULL)、analog_type、analog_name、analog_unit、analog_value NUMERIC(14,4)、collect_time TIMESTAMPTZ(NOT NULL 分区键,同步时间),唯一键 UNIQUE (line_code, analog_type, collect_time),索引 customer_id/line_id/collect_time。

Scenario: 迁移执行成功

  • WHEN 执行迁移到生产库
  • THEN electricity_data 含上述字段/分区/唯一键/索引;db_init.sql 与迁移一致

Requirement: 真实 ICC 模拟量拉取同步

系统 SHALL 复用 getIccAccessToken/iccFetch,以已同步线路的 line_code(detectorCode)分批作为 deviceCodes,POST 模拟量接口分页拉取(pageSize=500 短页停止),以同步时间作为 collect_time,按 (line_code, analog_type, collect_time) 幂等 upsert,写 icc_sync_log(sync_type=electricity_data),失败仅记日志不抛。

Scenario: 同步成功

  • WHEN 定时任务或 POST /api/electricity-data/sync 触发
  • THEN 返回 {pulled, inserted, updated, skipped, synced};同键重复上报覆盖更新;无线路匹配的行跳过

Scenario: 同步失败

  • WHEN ICC 拉取异常
  • THEN 仅写 icc_sync_log 失败日志,接口返回 code 200 与当前计数

Requirement: 定时任务注册

系统 SHALL 注册 icc_analog_sync 定时任务(默认每 5 分钟,cron */5 * * * *),注册进 job_registry BUILTIN_JOBS 与 JobRuntime.syncIccAnalogs,sys_job 种子幂等。

Scenario: 任务可执行

  • WHEN 任务调度触发
  • THEN 调用同步服务并将结果写入执行记录

REMOVED Requirements

Requirement: 占位 electricity 单列结构(旧 electricity_data)

Reason:与模拟量接口返回结构不对应。 Migration:electricity → analog_type/analog_value;icc_device_code → imei;equip_id 删除;collect_time 改由同步时间填充(接口实测无 reportTime)。