# Room Status Moment 集成方案

## 1. 背景
我们需要将一个新的数据库表 `room_status.room_status_moment`（快照表）集成到现有的 Kafka 处理流程中。
该表用于存储每个房间/设备的最新状态。
现有的逻辑（批量插入到 `rcu_action_events`）必须保持不变。

## 2. 数据库配置
新表位于一个独立的数据库中（可能是 `log_platform`，或者现有数据库中的新模式 `room_status`）。
我们将添加对 `ROOM_STATUS` 独立连接池的支持，以确保灵活性。

**环境变量配置：**
```env
# 现有数据库配置
DB_HOST=...
...

# 新 Room Status 数据库配置 (如果未提供，默认使用现有数据库，但使用独立的连接池)
ROOM_STATUS_DB_HOST=...
ROOM_STATUS_DB_PORT=...
ROOM_STATUS_DB_USER=...
ROOM_STATUS_DB_PASSWORD=...
ROOM_STATUS_DB_DATABASE=log_platform  <-- SQL 脚本中的目标数据库名
ROOM_STATUS_DB_SCHEMA=room_status
```

## 3. 字段映射策略

目标表：`room_status.room_status_moment`
唯一键：`(hotel_id, room_id, device_id)`

| 源字段 (Kafka) | 目标字段 | 更新逻辑 |
| :--- | :--- | :--- |
| `hotel_id` | `hotel_id` | 主键/索引键 |
| `room_id` | `room_id` | 主键/索引键 |
| `device_id` | `device_id` | 主键/索引键 |
| `ts_ms` | `ts_ms` | 始终更新为最新值 |
| `sys_lock_status` | `sys_lock_status` | 直接映射 (如果存在) |
| `device_list` (0x36) <br> `control_list` (0x0F) | `dev_loops` (JSONB) | **合并策略 (Merge)**: <br> Key: `001002003` (Type(3)+Addr(3)+Loop(3)) <br> Value: `dev_data` (int) <br> 操作: `old_json || new_json` (旧值合并新值) |
| `fault_list` (0x36) | `faulty_device_count` (JSONB) | **替换策略 (Replace)**: <br> 由于 0x36 上报的是完整故障列表，我们直接覆盖该字段。<br> 内容: `{dev_type, dev_addr, dev_loop, error_type, error_data}` 的列表 |
| `fault_list` -> item `error_type=1` | `online_status` | 如果 `error_data=1` -> 离线 (0) <br> 如果 `error_data=0` -> 在线 (1) <br> *需要验证具体的映射约定* |

**关于在线状态 (Online Status) 的说明**:
文档描述: "0x01: 0:在线 1:离线"。
表字段 `online_status` 类型为 INT2。
约定：通常 1=在线, 0=离线。
逻辑:
- 如果故障类型 0x01, 数据 0 (在线) -> 设置 `online_status` = 1
- 如果故障类型 0x01, 数据 1 (离线) -> 设置 `online_status` = 0
- 否则 -> 不更新 `online_status`

## 4. Upsert 逻辑 (PostgreSQL)

我们将使用 `INSERT ... ON CONFLICT DO UPDATE` 语法。

```sql
INSERT INTO room_status.room_status_moment (
  guid, ts_ms, hotel_id, room_id, device_id,
  sys_lock_status, online_status,
  dev_loops, faulty_device_count
) VALUES (
  $guid, $ts_ms, $hotel_id, $room_id, $device_id,
  $sys_lock_status, $online_status,
  $dev_loops::jsonb, $faulty_device_count::jsonb
)
ON CONFLICT (hotel_id, room_id, device_id)
DO UPDATE SET
  ts_ms = EXCLUDED.ts_ms,
  -- 仅在新数据不为空时更新 sys_lock_status
  sys_lock_status = COALESCE(EXCLUDED.sys_lock_status, room_status.room_status_moment.sys_lock_status),
  -- 仅在新数据不为空时更新 online_status
  online_status = COALESCE(EXCLUDED.online_status, room_status.room_status_moment.online_status),
  -- 合并 dev_loops
  dev_loops = CASE
    WHEN EXCLUDED.dev_loops IS NULL THEN room_status.room_status_moment.dev_loops
    ELSE COALESCE(room_status.room_status_moment.dev_loops, '{}'::jsonb) || EXCLUDED.dev_loops
  END,
  -- 如果存在则替换 faulty_device_count
  faulty_device_count = COALESCE(EXCLUDED.faulty_device_count, room_status.room_status_moment.faulty_device_count)
WHERE
  -- 可选优化：仅在时间戳更新时写入
  -- 注意：对于 JSON 合并，很难在不计算的情况下检测是否相等。
  -- 我们依赖 ts_ms 的变化来表示数据的“新鲜度”。
  EXCLUDED.ts_ms >= room_status.room_status_moment.ts_ms
;
```

## 5. 架构变更

1.  **`src/config/config.js`**: 添加 `roomStatusDb` 配置项。
2.  **`src/db/roomStatusManager.js`**: 新增单例类，用于管理 `log_platform` 的数据库连接池。
3.  **`src/db/statusBatchProcessor.js`**: 针对 `room_status_moment` 的专用批量处理器。
    *   **原因**: Upsert 逻辑复杂，且与 `rcu_action_events` 的追加写日志模式不同。
    *   它需要在批处理内聚合每个设备的更新，以减少数据库负载（去重）。
4.  **`src/processor/statusExtractor.js`**: 辅助工具，用于将 `KafkaPayload` 转换为 `StatusRow` 数据结构。
5.  **`src/index.js`**: 挂载新的处理器逻辑。

## 6. 去重策略 (内存批量聚合)
由于 `room_status_moment` 是快照表，如果我们在 1 秒内收到同一设备的 10 次更新：
- 我们只需要写入 **最后一次** 的状态（或合并后的状态）。
- `StatusBatchProcessor` 应该维护一个映射: `Map<Key(hotel,room,device), LatestData>`。
- 在 Flush 时，将 Map 的值转换为批量 Upsert 操作。
- **约束**: `dev_loops` 的更新如果是针对不同回路的，可能需要累积合并。
- **优化策略**:
    - 如果 `dev_loops` 是部分更新，我们不能简单地取最后一条消息。
    - 但是，在短时间的批处理窗口（例如 500ms）内，我们可以在内存中将它们合并后再发送给数据库。
    - 结构: `Map<Key, MergedState>`
    - 逻辑: `MergedState.dev_loops = Object.assign({}, old.dev_loops, new.dev_loops)`

## 7. 执行计划
1.  添加配置 (Config) 和数据库管理器 (DB Manager)。
2.  实现 `StatusExtractor` (将 Kafka 载荷转换为快照数据)。
3.  实现 `StatusBatchProcessor` (包含内存合并逻辑)。
4.  更新 `processKafkaMessage`，使其同时返回 `LogRows` (现有) 和 `StatusUpdate` (新增)。
5.  在主循环中处理分发。
