aps-agent/tests/golden/test_changeover.py

118 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# 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