99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
# ============================================================
|
|
# SC-04 约束配置中心黄金测试
|
|
# ============================================================
|
|
from __future__ import annotations
|
|
|
|
from server.agent_core.intent import parse_fast
|
|
from server.aps_domain.constraints import (
|
|
apply_profile_save, engine_constraint_flags, get_constraint_profile,
|
|
hard_blocking_conflicts, material_shortage_severity,
|
|
)
|
|
from server.engines import get_engine
|
|
from server.engines.base import EngineParams
|
|
from server.state.seed import seed_world
|
|
from server.timeutil import add_minutes, fmt_date, today0
|
|
|
|
|
|
def _next_id_factory():
|
|
counters: dict[str, int] = {}
|
|
|
|
def next_id(kind: str) -> int:
|
|
counters[kind] = counters.get(kind, 0) + 1
|
|
return counters[kind]
|
|
|
|
return next_id
|
|
|
|
|
|
def _run(world, strategy="COMPREHENSIVE"):
|
|
start = fmt_date(add_minutes(today0(), 24 * 60))
|
|
params = EngineParams(
|
|
orderIds=[], engineType="RULE", strategyTemplate=strategy,
|
|
planningHorizonDays=14, startDate=start,
|
|
constraints=engine_constraint_flags(world),
|
|
)
|
|
return get_engine("RULE").solve(world, params, _next_id_factory())
|
|
|
|
|
|
def _zero_all_stocks(world):
|
|
for m in world["materials"]:
|
|
if m.get("type") != "FINISHED_PRODUCT":
|
|
m["stock"] = 0
|
|
m["inTransit"] = 0
|
|
|
|
|
|
def test_default_material_kit_is_soft():
|
|
world = seed_world()
|
|
assert material_shortage_severity(world) == "MINOR"
|
|
c6 = next(c for c in get_constraint_profile(world)["constraints"] if c["id"] == "C6_material_kit")
|
|
assert c6["kind"] == "soft" and c6["enabled"]
|
|
|
|
|
|
def test_disable_material_kit_skips_shortage_conflict():
|
|
world = seed_world()
|
|
_zero_all_stocks(world)
|
|
apply_profile_save(world, {"constraints": {"C6_material_kit": {"enabled": False}}})
|
|
assert material_shortage_severity(world) is None
|
|
_run(world)
|
|
shortages = [c for c in world["conflicts"] if c["conflictType"] == "MATERIAL_SHORTAGE"]
|
|
assert shortages == []
|
|
|
|
|
|
def test_soft_kit_reports_risk_without_blocking_publish_gate():
|
|
world = seed_world()
|
|
_zero_all_stocks(world)
|
|
# 默认软约束
|
|
result = _run(world)
|
|
shortages = [c for c in world["conflicts"] if c["conflictType"] == "MATERIAL_SHORTAGE"]
|
|
assert shortages
|
|
assert all(c["severity"] == "MINOR" for c in shortages)
|
|
blockers = hard_blocking_conflicts(world, version_id=result.versionId)
|
|
assert not any(b["conflictType"] == "MATERIAL_SHORTAGE" for b in blockers)
|
|
|
|
|
|
def test_hard_kit_blocks_publish_gate():
|
|
world = seed_world()
|
|
_zero_all_stocks(world)
|
|
apply_profile_save(world, {"constraints": {"C6_material_kit": {"kind": "hard", "enabled": True}}})
|
|
result = _run(world)
|
|
shortages = [c for c in world["conflicts"] if c["conflictType"] == "MATERIAL_SHORTAGE"]
|
|
assert shortages and all(c["severity"] == "MAJOR" for c in shortages)
|
|
blockers = hard_blocking_conflicts(world, version_id=result.versionId)
|
|
assert any(b["conflictType"] == "MATERIAL_SHORTAGE" for b in blockers)
|
|
|
|
|
|
def test_version_snapshots_constraint_profile():
|
|
world = seed_world()
|
|
apply_profile_save(world, {"constraints": {"C6_material_kit": {"enabled": False}}})
|
|
_run(world)
|
|
snap = world["scheduleVersions"][-1].get("constraintProfile")
|
|
assert snap and snap["constraints"]["C6_material_kit"]["enabled"] is False
|
|
|
|
|
|
def test_intent_constraint_commands():
|
|
world = seed_world()
|
|
assert parse_fast("查看约束配置", world).intent == "constraint.profile.query"
|
|
assert parse_fast("关闭物料齐套", world).intent == "constraint.profile.save"
|
|
assert parse_fast("关闭物料齐套", world).params["constraints"]["C6_material_kit"]["enabled"] is False
|
|
assert parse_fast("齐套改为硬约束", world).params["constraints"]["C6_material_kit"]["kind"] == "hard"
|
|
assert parse_fast("恢复默认约束", world).params.get("resetDefaults") is True
|