96 lines
3.1 KiB
Python
96 lines
3.1 KiB
Python
|
|
# ============================================================
|
||
|
|
# 外部算法排产 Skill 黄金测试
|
||
|
|
# ============================================================
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from server.agent_core.skills import SkillRegistry
|
||
|
|
from server.aps_domain.scheduling_dto import (
|
||
|
|
apply_flex_solution, world_to_flex_problem,
|
||
|
|
)
|
||
|
|
from server.engines import get_engine
|
||
|
|
from server.engines.external_engine import ExternalEngine, run_external_flex
|
||
|
|
from server.integrations.algo_skill_stub import solve_problem
|
||
|
|
from server.state.seed import ensure_flex_seed, seed_world
|
||
|
|
|
||
|
|
|
||
|
|
class _MemStore:
|
||
|
|
def __init__(self, data):
|
||
|
|
self.data = data
|
||
|
|
self._c = {}
|
||
|
|
|
||
|
|
def next_id(self, kind: str) -> int:
|
||
|
|
self._c[kind] = self._c.get(kind, 0) + 1
|
||
|
|
return self._c[kind]
|
||
|
|
|
||
|
|
def save(self) -> None:
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
def test_stub_solves_flex_problem():
|
||
|
|
world = seed_world()
|
||
|
|
ensure_flex_seed(world)
|
||
|
|
problem = world_to_flex_problem(world)
|
||
|
|
assert problem.orders
|
||
|
|
assert problem.routings
|
||
|
|
assert problem.resources
|
||
|
|
sol = solve_problem(problem)
|
||
|
|
assert sol.status in ("FEASIBLE", "OPTIMAL")
|
||
|
|
assert sol.operations
|
||
|
|
assert sol.runId
|
||
|
|
# 写回
|
||
|
|
counters = {}
|
||
|
|
|
||
|
|
def nid(k):
|
||
|
|
counters[k] = counters.get(k, 0) + 1
|
||
|
|
return counters[k]
|
||
|
|
|
||
|
|
summary = apply_flex_solution(world, sol, nid, skill_id="algo.stub")
|
||
|
|
assert summary["woCount"] >= 1
|
||
|
|
assert summary["skillId"] == "algo.stub"
|
||
|
|
assert any(v.get("engineType") == "EXTERNAL" for v in world["flexScheduleVersions"])
|
||
|
|
|
||
|
|
|
||
|
|
def test_external_engine_local_stub(tmp_path, monkeypatch):
|
||
|
|
monkeypatch.setenv("APS_SKILLS_PATH", str(tmp_path / "skills.json"))
|
||
|
|
# 重置单例
|
||
|
|
import server.agent_core.skills as sk
|
||
|
|
sk._registry = None
|
||
|
|
world = seed_world()
|
||
|
|
ensure_flex_seed(world)
|
||
|
|
eng = get_engine("EXTERNAL:algo.stub")
|
||
|
|
assert isinstance(eng, ExternalEngine)
|
||
|
|
counters = {}
|
||
|
|
|
||
|
|
def nid(k):
|
||
|
|
counters[k] = counters.get(k, 0) + 1
|
||
|
|
return counters[k]
|
||
|
|
|
||
|
|
from server.engines.base import EngineParams
|
||
|
|
from server.timeutil import add_minutes, fmt_date, today0
|
||
|
|
params = EngineParams(
|
||
|
|
orderIds=[], engineType="EXTERNAL", strategyTemplate="EXTERNAL",
|
||
|
|
planningHorizonDays=14, startDate=fmt_date(add_minutes(today0(), 24 * 60)),
|
||
|
|
)
|
||
|
|
world["_externalSkillId"] = "algo.stub"
|
||
|
|
result = eng.solve(world, params, nid)
|
||
|
|
assert result.engineType == "EXTERNAL"
|
||
|
|
assert result.woCount >= 1
|
||
|
|
assert any(r.startswith("skill:") for r in result.evidenceRefs)
|
||
|
|
assert any(r.startswith("run:") for r in result.evidenceRefs)
|
||
|
|
|
||
|
|
|
||
|
|
def test_run_external_flex_and_registry_health(tmp_path, monkeypatch):
|
||
|
|
monkeypatch.setenv("APS_SKILLS_PATH", str(tmp_path / "skills.json"))
|
||
|
|
import server.agent_core.skills as sk
|
||
|
|
sk._registry = None
|
||
|
|
reg = SkillRegistry(path=str(tmp_path / "skills.json"))
|
||
|
|
health = reg.health("algo.stub")
|
||
|
|
assert health and health[0]["ok"] is True
|
||
|
|
|
||
|
|
world = seed_world()
|
||
|
|
ensure_flex_seed(world)
|
||
|
|
store = _MemStore(world)
|
||
|
|
summary = run_external_flex(store, skill_id="algo.stub")
|
||
|
|
assert summary["woCount"] >= 1
|
||
|
|
assert summary["runId"]
|