# ============================================================ # SC-04 约束配置中心黄金测试 # ============================================================ from __future__ import annotations 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