## Context

轮询系统采用分片 Redis Sorted Set + Asynq 任务队列的架构，支持 4 种轮询类型（realname / carddata / package / protect）。每种类型对应一个 Asynq handler，所有 handler 通过 `PollingBase` 共享并发控制、缓存和重入队逻辑。

Gateway 已提供 `QueryCardStatus` 接口（返回 `CardStatus: "准备" | "正常" | "停机"`），IotCard model 已有 `NetworkStatus int`（0=停机，1=开机），但轮询系统从未主动调用该接口同步状态。

## Goals / Non-Goals

**Goals:**
- 新增第 5 种轮询类型 `polling:card_status`，与现有 4 种类型完全对等集成
- `PollingBase` 新增 `verboseLog bool` 字段，所有 handler 在成功查询到上游数据后可选输出详细日志
- 保持向后兼容：现有轮询配置不受影响，`card_status_check_interval` 为 NULL 时该类型不参与轮询

**Non-Goals:**
- 不修改停复机决策逻辑（`EvaluateAndAct` 已处理）
- 不新增 HTTP API（纯后台轮询任务）
- 不对现有 4 种 handler 做任何功能性改动，只追加 verbose log 调用

## Decisions

**决策 1：Gateway 状态映射规则**
- `"准备"` → `NetworkStatusOnline (1)`：卡已就绪，视为开机
- `"正常"` → `NetworkStatusOnline (1)`：正常使用
- `"停机"` → `NetworkStatusOffline (0)`：停机
- 理由：与现有 `realname` handler 的布尔映射风格一致，映射逻辑内联在 handler 内，不引入额外函数

**决策 2：状态变更后触发 EvaluateAndAct**
- 上游报告状态变化时，先更新 DB + 缓存，再调用 `stopResumeSvc.EvaluateAndAct(freshCard)`
- 理由：与 realname/carddata handler 行为完全一致，停复机决策集中在 `EvaluateAndAct` 中
- 替代方案：仅写 DB 不触发评估 —— 拒绝，因为状态变化后不及时评估可能导致卡停复机延迟

**决策 3：verboseLog 通过配置文件控制**
- 在 `pkg/config/config.go` 新增 `PollingConfig.VerboseLog bool`，`NewPollingBase` 接受该参数
- `verboseLog` 开启时，每次成功从 Gateway 获取数据后以 `Info` 级别输出日志
- 理由：配置文件方式符合项目 Viper 规范，比 Redis key 实现简单，重启代价可接受（轮询系统重启本身就有初始化流程）
- 替代方案：Redis key 动态开关 —— 额外的 Redis 查询开销，且 key 管理增加运维复杂度

**决策 4：allTaskTypes 驱动初始化和出队**
- `queue_manager.go` 中的 `allTaskTypes` slice 控制：初始化时写入哪些队列、`RemoveFromAllQueues` 清理哪些队列、Scheduler 出队哪些类型
- 新类型追加到该 slice 即可自动接入所有相关流程，无需修改调度器核心逻辑

**决策 5：数据库迁移**
- `tb_polling_config` 新增 `card_status_check_interval INT NULL`，NULL 表示不启用
- `tb_iot_card` 新增 `last_card_status_check_at TIMESTAMPTZ NULL`，与其他 `last_*_check_at` 字段保持一致
- 两列均 `DEFAULT NULL`，完全向后兼容，无需数据迁移

## Risks / Trade-offs

- **[风险] card_status 轮询调用量增加约 25%（原 4 种 → 5 种）** → 缓解：`card_status_check_interval` 可配置，初期设置较长间隔（如 300 秒），观察 Gateway 负载后调整
- **[风险] verbose_log 开启后日志量大幅增加** → 缓解：默认关闭（`false`），生产环境仅在排查问题时临时开启；日志文件已配置 Lumberjack 轮转
- **[权衡] 状态以上游为准** → 若上游短暂故障返回异常状态（如误报"停机"），handler 查询失败时会 warn 并重新入队，不更新状态；只有成功响应才触发写 DB

## Migration Plan

1. 执行数据库迁移（`make migrate-up`）
2. 更新 Worker 二进制并重启（无需 API 服务重启）
3. 在轮询配置后台为需要卡状态轮询的配置项设置 `card_status_check_interval` 值
4. 初始化器将在 Worker 启动时自动将全量卡写入新的 `polling:card_status` 队列
5. 如需回滚：将 `card_status_check_interval` 设置为 NULL 即可停止该类型轮询，无需重启

## Open Questions

无。
