aps-agent/tests/golden/test_kangni_mapping_validat...

133 lines
5.0 KiB
Python

# ============================================================
# 康尼映射表解析契约黄金测试(真实数据验证工程师)
# 覆盖:设备能力/模具适配映射表表头契约、共享设备全工序覆盖、
# 映射表存在时 build_flex 优先读取并输出推断说明。
# ============================================================
from __future__ import annotations
from pathlib import Path
import pytest
from server.aps_domain.kangni_intake import (
build_flex_equipment_and_molds,
parse_equipment_capability_map,
parse_mold_adaptation_map,
)
from tests.external_data import external_dir
KANGNI_DATA_DIR = external_dir("KANGNI_DATA_DIR", "kangni")
EQUIP_HEADER = [
"设备编号", "设备名称", "可执行工序编号", "单件工时", "可动率",
"是否可移动", "移动耗时(分钟)", "区域编码", "适配模具编号", "状态", "备注",
]
MOLD_HEADER = [
"模具编号", "模具名称", "适用工序编号", "适配设备编号",
"寿命上限", "已用寿命", "区域编码", "换型耗时(分钟)", "状态", "备注",
]
def _write_equip_map(tmp_path: Path) -> Path:
from openpyxl import Workbook
p = tmp_path / "设备能力映射模板.xlsx"
wb = Workbook()
ws = wb.active
ws.title = "设备能力"
ws.append(EQUIP_HEADER)
ws.append(["EQ-SHARED-CG-01", "共享装配设备(待现场确认)", "OP-1,OP-2",
"OP-1:27;OP-2:5", 0.95, "否", 0, "ZONE-CG", "", "RUNNING",
"保守假设,待现场确认"])
ws.append(["CO01001", "落地式直读光谱仪", "", "", 0.95, "否", 0,
"ZONE-CG", "", "RUNNING", "真实设备,能力待现场确认"])
wb.save(p)
return p
def _write_mold_map(tmp_path: Path) -> Path:
from openpyxl import Workbook
p = tmp_path / "模具适配映射模板.xlsx"
wb = Workbook()
ws = wb.active
ws.title = "模具适配"
ws.append(MOLD_HEADER)
ws.append(["00000001", "胶条切割打孔工装", "", "", "", "", "ZONE-CG", "",
"AVAILABLE", "真实模具,工序/寿命待现场确认"])
wb.save(p)
return p
def test_equipment_map_parses_exact_header_contract(tmp_path: Path):
rows = parse_equipment_capability_map(_write_equip_map(tmp_path))
assert len(rows) == 2
shared = rows[0]
assert shared["code"] == "EQ-SHARED-CG-01"
assert shared["capabilities"] == ["OP-1", "OP-2"]
assert shared["opStdTime"] == {"OP-1": 27.0, "OP-2": 5.0}
assert shared["movable"] is False
assert shared["availabilityRate"] == 0.95
assert shared["status"] == "RUNNING"
assert rows[1]["code"] == "CO01001"
assert rows[1]["capabilities"] == []
def test_mold_map_parses_exact_header_contract(tmp_path: Path):
rows = parse_mold_adaptation_map(_write_mold_map(tmp_path))
assert len(rows) == 1
row = rows[0]
assert row["code"] == "00000001"
assert row["name"] == "胶条切割打孔工装"
assert row["operationCode"] == ""
assert row["adaptableEquipment"] == []
assert row["lifeTotal"] == 0.0
assert row["lifeUsed"] == 0.0
assert row["status"] == "AVAILABLE"
def test_build_flex_prefers_mapping_table_and_emits_inference(tmp_path: Path):
equip = _write_equip_map(tmp_path)
mold = _write_mold_map(tmp_path)
assert equip.parent == mold.parent
equipment, molds, mold_ops, infs = build_flex_equipment_and_molds(
tmp_path, [], {"OP-1": 27.0, "OP-2": 5.0}, ["OP-1", "OP-2"],
{"zone": {"code": "ZONE-CG"}, "availabilityRate": 0.95},
)
assert len(equipment) == 2
assert not equipment[0]["inferred"]
assert equipment[0]["capabilities"] == ["OP-1", "OP-2"]
assert len(molds) == 1
assert mold_ops == set()
assert any("设备能力映射表已读取" in x for x in infs)
assert any("模具适配映射表已读取" in x for x in infs)
@pytest.mark.skipif(not (KANGNI_DATA_DIR / "工时.xlsx").exists(),
reason="康尼现场工时表不在本机")
def test_shared_equipment_covers_all_real_kangni_operations():
from openpyxl import load_workbook
equip_path = KANGNI_DATA_DIR / "设备能力映射模板.xlsx"
if not equip_path.exists():
pytest.skip("康尼/数据 设备能力映射模板.xlsx 未生成")
wb = load_workbook(KANGNI_DATA_DIR / "工时.xlsx", read_only=True, data_only=True)
real_ops = set()
for sn in wb.sheetnames:
ws = wb[sn]
for row in ws.iter_rows(min_row=2, values_only=True):
code = str(row[1]).strip() if len(row) > 1 and row[1] is not None else ""
if code:
real_ops.add(code)
wb.close()
assert real_ops, "工时.xlsx 未读到任何工序编号"
rows = parse_equipment_capability_map(equip_path)
shared = [r for r in rows if r["code"] == "EQ-SHARED-CG-01"]
assert shared, "缺少共享设备占位行 EQ-SHARED-CG-01"
caps = set(shared[0]["capabilities"])
missing = sorted(real_ops - caps)
assert not missing, f"共享设备未覆盖真实工序: {missing}"
assert all(op in shared[0]["opStdTime"] for op in real_ops)