aps-agent/tests/golden/test_changeover.py

118 lines
4.8 KiB
Python
Raw Normal View History

# ============================================================
# MD-06 换型矩阵黄金测试
# ============================================================
from __future__ import annotations
from server.aps_domain.changeover import (
build_changeover_view,
extra_setup_minutes,
lookup_setup_minutes,
product_family,
)
from server.engines.base import EngineParams
from server.engines.rule_engine import RuleEngine
from server.state.seed import seed_world
def test_seed_families_and_matrix():
world = seed_world()
a = next(m for m in world["materials"] if m["code"] == "CTRL-A")
c = next(m for m in world["materials"] if m["code"] == "CTRL-C")
assert product_family(a) == "CTRL-STD"
assert product_family(c) == "CTRL-HF"
assert lookup_setup_minutes(world, "CTRL-STD", "CTRL-HF") == 45
assert lookup_setup_minutes(world, "CTRL-STD", "CTRL-STD") == 0
assert extra_setup_minutes(world, None, "CTRL-HF") == 0
view = build_changeover_view(world)
assert len(view["rows"]) >= 2
assert any(f["code"] == "CTRL-STD" for f in view["families"])
def test_missing_pair_uses_default_cross():
world = seed_world()
world["changeoverMatrix"] = []
assert lookup_setup_minutes(world, "CTRL-STD", "CTRL-HF") == 30
def test_rule_engine_applies_changeover_on_family_switch():
world = seed_world()
# 强制 STD 与 HF 只能进同一条线,保证出现跨族换型
world["lineProducts"] = [
{"id": 1, "lineId": 1, "productId": 1, "standardCapacity": 1000, "priority": 1, "setupTime": 30},
{"id": 2, "lineId": 1, "productId": 2, "standardCapacity": 800, "priority": 1, "setupTime": 30},
{"id": 3, "lineId": 1, "productId": 3, "standardCapacity": 600, "priority": 1, "setupTime": 30},
]
engine = RuleEngine("RULE")
counter = {"n": 1000}
def next_id(_k: str) -> int:
counter["n"] += 1
return counter["n"]
params = EngineParams(
strategyTemplate="FIFO",
name="changeover-test",
constraints={"materialKit": False, "changeover": True, "capacity": True, "dueDate": True,
"equipment": True, "personnel": True, "tooling": True, "freeze": True},
)
result = engine.solve(world, params, next_id)
assert result.versionId
version = next(v for v in world["scheduleVersions"] if v["id"] == result.versionId)
assert version.get("totalChangeoverMin", 0) > 0
pos = [p for p in world["productionOrders"] if p.get("schedulingVersionId") == result.versionId]
assert any((p.get("changeoverMin") or 0) > 0 for p in pos)
def test_changeover_constraint_off_skips_matrix():
world = seed_world()
engine = RuleEngine("RULE")
counter = {"n": 2000}
def next_id(_k: str) -> int:
counter["n"] += 1
return counter["n"]
params = EngineParams(
strategyTemplate="FIFO",
name="no-changeover",
constraints={"materialKit": False, "changeover": False, "capacity": True, "dueDate": True,
"equipment": True, "personnel": True, "tooling": True, "freeze": True},
)
result = engine.solve(world, params, next_id)
version = next(v for v in world["scheduleVersions"] if v["id"] == result.versionId)
assert version.get("totalChangeoverMin", 0) == 0
def test_changeover_min_beats_fifo_on_shared_line():
"""SC-07:同线多族时,CHANGEOVER_MIN 累计换型不应高于 FIFO。"""
from server.engines.base import EngineParams
from server.engines.rule_engine import RuleEngine
def _run(strategy: str) -> float:
world = seed_world()
world["lineProducts"] = [
{"id": 1, "lineId": 1, "productId": 1, "standardCapacity": 1000, "priority": 1, "setupTime": 30},
{"id": 2, "lineId": 1, "productId": 2, "standardCapacity": 800, "priority": 1, "setupTime": 30},
{"id": 3, "lineId": 1, "productId": 3, "standardCapacity": 600, "priority": 1, "setupTime": 30},
]
counter = {"n": 3000}
def next_id(_k: str) -> int:
counter["n"] += 1
return counter["n"]
params = EngineParams(
strategyTemplate=strategy, name=f"sc07-{strategy}",
constraints={"materialKit": False, "changeover": True, "capacity": True, "dueDate": True,
"equipment": True, "personnel": True, "tooling": True, "freeze": True},
)
result = RuleEngine("RULE").solve(world, params, next_id)
ver = next(v for v in world["scheduleVersions"] if v["id"] == result.versionId)
return float(ver.get("totalChangeoverMin") or 0)
fifo_co = _run("FIFO")
min_co = _run("CHANGEOVER_MIN")
assert min_co <= fifo_co
# 同线多族场景下 FIFO 通常会跨族;最小化应显著压低(允许相等)
assert min_co < fifo_co or fifo_co == 0