更新一版
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"""cards.csv / devices.csv 加载与跨文件一致性校验。
|
||||
|
||||
新设计:CSV 只承载"业务方决策"——
|
||||
- cards.csv 只读 iccid + 可选覆盖列(target_shop_code、is_industry)
|
||||
新设计:CSV 只承载"资产清单和行级槽位决策"——
|
||||
- cards.csv 只读 iccid + is_industry
|
||||
- devices.csv 读设备元数据 + sim_iccid_*
|
||||
- 卡的运营商、套餐、msisdn、agent_id 等元数据全部从奇成查(见 legacy_query.fetch_card_meta)
|
||||
- 归属、默认当前槽位、默认旧套餐读取槽位从 mapping.yaml 解析
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -47,7 +48,7 @@ class DeviceRow:
|
||||
"""devices.csv 单行的解析结果。
|
||||
|
||||
简化为业务方核心关心的列:imei / virtual_no / device_* / max_sim_slots / sim_iccid_*。
|
||||
店铺归属由"主卡 sim_iccid_1 的 agent_id → mapping.agents.target_shop_code"推导。
|
||||
店铺归属、批量默认 current_slot 和旧套餐读取槽位 package_source_slot 由 mapping.yaml 决定。
|
||||
其他字段(manufacturer 等)直接用默认值,不暴露给业务方。
|
||||
"""
|
||||
|
||||
@@ -60,6 +61,9 @@ class DeviceRow:
|
||||
device_type: str = ""
|
||||
max_sim_slots: int = 1 # 不填时按非空 sim_iccid_* 的最大槽位推导
|
||||
current_slot: int = 1 # 默认 slot 1 为当前生效
|
||||
package_source_slot: int = 1 # 设备旧套餐读取槽位
|
||||
current_slot_source: str = "default"
|
||||
package_source_slot_source: str = "default"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -123,7 +127,7 @@ def _parse_int(raw: str, default: int = 0) -> int:
|
||||
# ---------------- 主入口 ----------------
|
||||
|
||||
|
||||
def load_assets(resources_dir: Path) -> tuple[list[CardRow], list[DeviceRow], list[ErrorRow]]:
|
||||
def load_assets(resources_dir: Path, mapping=None) -> tuple[list[CardRow], list[DeviceRow], list[ErrorRow]]:
|
||||
"""读 cards.csv + devices.csv,做格式校验与跨文件一致性校验。
|
||||
|
||||
返回:(合法卡列表, 合法设备列表, 异常行列表)。
|
||||
@@ -139,6 +143,8 @@ def load_assets(resources_dir: Path) -> tuple[list[CardRow], list[DeviceRow], li
|
||||
devices_path = resources_dir / "devices.csv"
|
||||
if devices_path.exists():
|
||||
_load_devices(devices_path, result)
|
||||
if mapping is not None:
|
||||
_apply_device_slot_rules(result, mapping)
|
||||
|
||||
_cross_check(result)
|
||||
|
||||
@@ -276,6 +282,23 @@ def _load_devices(path: Path, result: LoadResult) -> None:
|
||||
)
|
||||
continue
|
||||
|
||||
current_slot, current_slot_source = _parse_slot_with_source(raw.get("current_slot", ""), 1)
|
||||
if current_slot < 1 or current_slot > 4:
|
||||
result.errors.append(
|
||||
ErrorRow("devices.csv", idx, "current_slot", raw.get("current_slot", ""), "invalid_slot",
|
||||
"current_slot 必须是 1-4 的整数")
|
||||
)
|
||||
continue
|
||||
package_source_slot, package_source_slot_source = _parse_slot_with_source(
|
||||
raw.get("package_source_slot", ""), current_slot
|
||||
)
|
||||
if package_source_slot < 1 or package_source_slot > 4:
|
||||
result.errors.append(
|
||||
ErrorRow("devices.csv", idx, "package_source_slot", raw.get("package_source_slot", ""), "invalid_slot",
|
||||
"package_source_slot 必须是 1-4 的整数")
|
||||
)
|
||||
continue
|
||||
|
||||
result.devices.append(
|
||||
DeviceRow(
|
||||
line_no=idx,
|
||||
@@ -286,10 +309,63 @@ def _load_devices(path: Path, result: LoadResult) -> None:
|
||||
device_type=device_type,
|
||||
sim_iccids=sim_iccids,
|
||||
max_sim_slots=max_sim_slots,
|
||||
current_slot=current_slot,
|
||||
package_source_slot=package_source_slot,
|
||||
current_slot_source=current_slot_source,
|
||||
package_source_slot_source=package_source_slot_source,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _parse_slot_with_source(raw: str, default: int) -> tuple[int, str]:
|
||||
"""解析槽位并返回来源。"""
|
||||
v = _normalize_cell(raw)
|
||||
if not v:
|
||||
return default, "default"
|
||||
try:
|
||||
return int(v), "row"
|
||||
except ValueError:
|
||||
return 0, "row"
|
||||
|
||||
|
||||
def _apply_device_slot_rules(result: LoadResult, mapping) -> None:
|
||||
"""按 行配置 > 覆盖项 > 批量默认值 解析设备槽位。"""
|
||||
for device in result.devices:
|
||||
override = mapping.lookup_device_override(device.virtual_no)
|
||||
if device.current_slot_source != "row":
|
||||
if override and override.current_slot is not None:
|
||||
device.current_slot = override.current_slot
|
||||
device.current_slot_source = "override"
|
||||
elif mapping.ownership_rules.device.current_slot is not None:
|
||||
device.current_slot = mapping.ownership_rules.device.current_slot
|
||||
device.current_slot_source = "ownership_rules.device"
|
||||
if device.package_source_slot_source != "row":
|
||||
if override and override.package_source_slot is not None:
|
||||
device.package_source_slot = override.package_source_slot
|
||||
device.package_source_slot_source = "override"
|
||||
elif mapping.ownership_rules.device.package_source_slot is not None:
|
||||
device.package_source_slot = mapping.ownership_rules.device.package_source_slot
|
||||
device.package_source_slot_source = "ownership_rules.device"
|
||||
else:
|
||||
device.package_source_slot = device.current_slot
|
||||
device.package_source_slot_source = device.current_slot_source
|
||||
|
||||
for field_name, slot in (
|
||||
("current_slot", device.current_slot),
|
||||
("package_source_slot", device.package_source_slot),
|
||||
):
|
||||
if slot < 1 or slot > 4:
|
||||
result.errors.append(
|
||||
ErrorRow("devices.csv", device.line_no, field_name, str(slot), "invalid_slot",
|
||||
f"{field_name} 必须是 1-4 的整数")
|
||||
)
|
||||
elif slot > device.max_sim_slots:
|
||||
result.errors.append(
|
||||
ErrorRow("devices.csv", device.line_no, field_name, str(slot), "slot_overflow",
|
||||
f"{field_name}={slot} 超出 max_sim_slots={device.max_sim_slots}")
|
||||
)
|
||||
|
||||
|
||||
def _cross_check(result: LoadResult) -> None:
|
||||
"""校验设备与卡之间的一致性,并把 bound_device_virtual_no 回填到 CardRow。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user