aps-agent/tests/golden/test_adjust.py

127 lines
5.2 KiB
Python

# ============================================================
# EX-04 甘特拖拽调程黄金测试
# ============================================================
from __future__ import annotations
from server.aps_domain.adjust import commit_flex_adjust, preview_flex_adjust, snap_dt
from server.aps_domain.flex import run_flex_schedule
from server.timeutil import add_minutes, parse_dt
from server.state.seed import seed_world
class _MemStore:
def __init__(self, data):
self.data = data
def next_id(self, kind: str) -> int:
key = f"_c_{kind}"
self.data[key] = self.data.get(key, 3000) + 1
return self.data[key]
def save(self):
pass
def test_snap_dt_15min():
dt = parse_dt("2026-07-23 08:07")
assert snap_dt(dt).minute in (0, 15)
assert snap_dt(parse_dt("2026-07-23 08:08")).minute == 15
def test_preview_ok_and_overlap_fail():
store = _MemStore(seed_world())
run_flex_schedule(store, sort_mode="BOTTLENECK", actor="test")
wos = [w for w in store.data["flexWorkOrders"]
if w["versionId"] == store.data["flexScheduleVersions"][-1]["id"]]
assert wos
from server.timeutil import fmt_dt
# 找一条能后移 15 分钟的工单
ok = None
for wo in wos:
later = fmt_dt(add_minutes(parse_dt(wo["plannedStartTime"]), 15))
cand = preview_flex_adjust(store.data, wo["id"], later)
if cand["ok"] and cand["moves"]:
ok = cand
break
assert ok is not None, "种子排产后应至少有一条可平移 15 分钟的工单"
# 故意拖到同设备另一工单起点 → 应报不可行(若存在同伴)
wo0 = next(w for w in wos if w["id"] == ok["woId"])
same_eq = [w for w in wos if w["equipmentId"] == wo0["equipmentId"] and w["id"] != wo0["id"]]
if same_eq:
bad = preview_flex_adjust(store.data, wo0["id"], same_eq[0]["plannedStartTime"])
assert bad["ok"] is False or bad["reasons"] is not None
def test_commit_creates_new_version():
store = _MemStore(seed_world())
run_flex_schedule(store, sort_mode="BOTTLENECK", actor="test")
before = len(store.data["flexScheduleVersions"])
from server.timeutil import fmt_dt
wos = [w for w in store.data["flexWorkOrders"]
if w["versionId"] == store.data["flexScheduleVersions"][-1]["id"]]
target = None
later = None
for wo in wos:
cand_start = fmt_dt(add_minutes(parse_dt(wo["plannedStartTime"]), 15))
prev = preview_flex_adjust(store.data, wo["id"], cand_start)
if prev["ok"] and prev["moves"]:
target, later = wo, cand_start
break
assert target is not None
r = commit_flex_adjust(store, target["id"], later, actor="test")
assert len(store.data["flexScheduleVersions"]) == before + 1
assert r["versionNo"]
latest = store.data["flexScheduleVersions"][-1]["id"]
assert any(w.get("adjustedFrom") for w in store.data["flexWorkOrders"]
if w["versionId"] == latest)
def test_preview_does_not_mutate():
store = _MemStore(seed_world())
run_flex_schedule(store, sort_mode="BOTTLENECK", actor="test")
wo = next(w for w in store.data["flexWorkOrders"]
if w["versionId"] == store.data["flexScheduleVersions"][-1]["id"])
old = wo["plannedStartTime"]
from server.timeutil import fmt_dt
preview_flex_adjust(store.data, wo["id"],
fmt_dt(add_minutes(parse_dt(old), 60)))
assert wo["plannedStartTime"] == old
assert len(store.data["flexScheduleVersions"]) == 1
def test_fixed_preview_and_commit():
from server.aps_domain.adjust import commit_fixed_adjust, preview_fixed_adjust
from server.engines import get_engine
from server.engines.base import EngineParams
from server.timeutil import fmt_date, fmt_dt, today0
store = _MemStore(seed_world())
ep = EngineParams(
orderIds=[], engineType="RULE", strategyTemplate="COMPREHENSIVE",
planningHorizonDays=14,
startDate=fmt_date(add_minutes(today0(), 24 * 60)),
)
get_engine("RULE").solve(store.data, ep, store.next_id)
before = len(store.data["scheduleVersions"])
latest = store.data["scheduleVersions"][-1]
po_ids = {p["id"] for p in store.data["productionOrders"]
if p.get("schedulingVersionId") == latest["id"]}
wos = [w for w in store.data["workOrders"] if w["productionOrderId"] in po_ids]
assert wos
target = None
later = None
for wo in wos:
cand = fmt_dt(add_minutes(parse_dt(wo["plannedStartTime"]), 15))
prev = preview_fixed_adjust(store.data, wo["id"], cand)
if prev["ok"] and prev["moves"]:
target, later = wo, cand
break
assert target is not None, "应至少有一条可平移 15 分钟的固定轨工单"
r = commit_fixed_adjust(store, target["id"], later, actor="test")
assert len(store.data["scheduleVersions"]) == before + 1
assert r["versionNo"].startswith("V")
new_vid = store.data["scheduleVersions"][-1]["id"]
new_po_ids = {p["id"] for p in store.data["productionOrders"]
if p.get("schedulingVersionId") == new_vid}
assert any(w.get("adjustedFrom") for w in store.data["workOrders"]
if w["productionOrderId"] in new_po_ids)