init
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m3s

This commit is contained in:
Break
2026-06-16 15:15:50 +08:00
parent 84ab3bad99
commit 1f634eb465
21 changed files with 2776 additions and 41 deletions

View File

@@ -0,0 +1,143 @@
## ADDED Requirements
### Requirement: 批量归属决策
奇成迁移脚本 SHALL 使用 `mapping.yaml` 的批量归属规则决定卡和设备的目标店铺。奇成 `agent_id` 只能作为可选参考信息输出到审核文件MUST NOT 作为默认归属决策来源。
#### Scenario: 默认店铺分配设备
- **WHEN** `ownership_rules.device.mode = default_shop``default_target_shop_code = KWTX`
- **THEN** 所有未被 `overrides.devices` 覆盖的设备迁移 SQL 使用 `KWTX` 对应的 `tb_shop.id` 作为 `shop_id`
- **AND** `ownership_resolution.csv` 记录每台设备的决策来源为 `default_shop`
#### Scenario: 默认店铺分配独立卡
- **WHEN** `ownership_rules.standalone_card.mode = default_shop``default_target_shop_code = KWTX`
- **THEN** 所有未绑定设备且未被 `overrides.cards` 覆盖的卡迁移 SQL 使用 `KWTX` 对应的 `tb_shop.id` 作为 `shop_id`
#### Scenario: 覆盖项优先于批量规则
- **WHEN** 某设备在 `overrides.devices` 中配置了 `target_shop_code = OTHER`
- **THEN** 该设备使用 `OTHER` 作为目标店铺
- **AND** 审核文件记录决策来源为 `override`
#### Scenario: 目标店铺缺失阻断
- **WHEN** 规则要求资产分配到某个 `target_shop_code`,但新库不存在该店铺
- **THEN** 脚本 MUST 在 `errors.csv` 写入该资产的错误
- **AND** 脚本 MUST NOT 为该资产生成店铺分配 SQL
### Requirement: 显式设备主卡和套餐来源槽位
奇成迁移脚本 SHALL 支持在输入数据或 `mapping.yaml` 中显式指定设备 `current_slot``package_source_slot`。未逐行指定时,脚本 SHALL 使用 `ownership_rules.device.current_slot``ownership_rules.device.package_source_slot` 作为批量默认值。
#### Scenario: 使用批量默认槽位
- **WHEN** `devices.csv` 某设备未填写 `current_slot``package_source_slot`
- **AND** `ownership_rules.device.current_slot = 2`
- **AND** `ownership_rules.device.package_source_slot = 2`
- **THEN** 该设备的当前绑定卡和套餐来源卡均使用 `sim_iccid_2`
#### Scenario: 使用设备行覆盖槽位
- **WHEN** `devices.csv` 某设备填写 `current_slot = 1``package_source_slot = 1`
- **THEN** 该设备使用第 1 槽作为当前槽位和套餐来源槽位
- **AND** 该行配置优先于批量默认槽位
#### Scenario: 槽位无卡阻断设备套餐
- **WHEN** 某设备的 `package_source_slot = 2`,但 `sim_iccid_2` 为空
- **THEN** 脚本 MUST 在 `errors.csv` 写入该设备的错误
- **AND** 脚本 MUST NOT 为该设备生成套餐使用记录
### Requirement: 单卡与设备迁移分支分离
奇成迁移脚本 SHALL 分离独立卡套餐迁移和设备套餐迁移。独立卡套餐 MUST 写入 `usage_type = single_card` 并关联 `iot_card_id`;设备套餐 MUST 写入 `usage_type = device` 并关联 `device_id`
#### Scenario: 独立卡生成单卡套餐
- **WHEN** 某卡未绑定设备且命中可迁移套餐
- **THEN** 脚本为该卡生成 `tb_order.order_type = single_card`
- **AND** 脚本为该卡生成 `tb_package_usage.usage_type = single_card`
#### Scenario: 设备绑定卡生成设备套餐
- **WHEN** 某卡位于设备 `package_source_slot`
- **THEN** 脚本为该设备生成 `tb_order.order_type = device`
- **AND** 脚本为该设备生成 `tb_package_usage.usage_type = device`
#### Scenario: 设备非来源槽位不重复生成套餐
- **WHEN** 某卡绑定在设备非 `package_source_slot` 的槽位
- **THEN** 脚本 MUST NOT 为该卡单独生成主套餐使用记录
- **AND** 该卡仍 SHALL 生成基础卡资产和设备绑定 SQL
### Requirement: 当前生效和待生效套餐迁移
奇成迁移脚本 SHALL 迁移正式套餐中的当前生效套餐和未生效待生效套餐。当前生效套餐写入 `tb_package_usage.status = 1`;待生效套餐写入 `tb_package_usage.status = 0` 并设置稳定 `priority`
#### Scenario: 当前生效套餐迁移为 active
- **WHEN** 奇成某正式套餐处于当前生效状态
- **THEN** 脚本生成的 `tb_package_usage.status = 1`
- **AND** `activated_at``expires_at` 来源于奇成套餐生命周期时间
#### Scenario: 未生效套餐迁移为 pending
- **WHEN** 奇成某正式套餐属于未生效待生效套餐
- **THEN** 脚本生成的 `tb_package_usage.status = 0`
- **AND** `data_usage_mb = 0`
- **AND** `priority` 按待生效顺序稳定递增
#### Scenario: 已过期套餐不迁移
- **WHEN** 奇成某正式套餐已过期且不属于当前生效或待生效范围
- **THEN** 脚本 MUST NOT 为该套餐生成 `tb_package_usage`
- **AND** `package_resolution.csv` 记录跳过原因
#### Scenario: 目标套餐映射缺失阻断
- **WHEN** 某可迁移 legacy 套餐没有配置 `target_package_id`
- **THEN** 脚本 MUST 在 `errors.csv``package_resolution.csv` 中记录缺失映射
- **AND** 脚本 MUST NOT 为该套餐生成套餐使用记录
### Requirement: 迁移审核产物
奇成迁移脚本 SHALL 在生成 SQL 时同步输出可审计 CSV说明每个资产和套餐的最终决策。
#### Scenario: 输出归属审核文件
- **WHEN** 执行 `migrate_assets.py`
- **THEN** 脚本输出 `ownership_resolution.csv`
- **AND** 文件至少包含资产类型、资产标识、来源卡、目标店铺、决策来源、是否生成分配、原因
#### Scenario: 输出套餐审核文件
- **WHEN** 执行 `migrate_runtime.py`
- **THEN** 脚本输出 `package_resolution.csv`
- **AND** 文件至少包含资产类型、资产标识、来源卡、legacy 套餐 ID、legacy 套餐名称、目标套餐 ID、目标状态、优先级、决策、原因
#### Scenario: 摘要显示关键数量
- **WHEN** 脚本生成完成
- **THEN** `summary.txt` MUST 展示卡数、设备数、分配资产数、active 套餐数、pending 套餐数、错误数和跳过数
### Requirement: 迁移脚本只读和幂等边界
奇成迁移脚本 SHALL 保持对奇成库只读,并生成可重复执行的 PostgreSQL SQL 文件。脚本 MUST NOT 直接写新库业务数据。
#### Scenario: 奇成连接只读
- **WHEN** 脚本连接奇成库查询元数据和套餐生命周期
- **THEN** 连接 MUST 使用只读事务
#### Scenario: 生成 SQL 幂等
- **WHEN** 同一批次 SQL 被重复执行
- **THEN** 插入类语句 SHALL 使用唯一键或条件确保不会重复创建相同资产、订单、套餐使用记录和分配记录
#### Scenario: 不直接写新库
- **WHEN** 执行 Python 迁移脚本
- **THEN** 脚本只生成 SQL 和审核文件
- **AND** 脚本 MUST NOT 直接向新库写入业务表