# ============================================================ # OR-02 排产参数 / 客户等级权重黄金测试 # ============================================================ from __future__ import annotations from server.agent_core.intent import parse_fast from server.aps_domain.params import ( apply_params_update, confirmation_for_params_update, get_schedule_params, ) 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, parse_dt, 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 _two_orders_world(vip_due_offset=10, c_due_offset=5): """构造仅两单、同优先级的世界,便于观察等级权重对开工顺序的影响。""" world = seed_world() base = today0() due_vip = fmt_date(add_minutes(base, vip_due_offset * 24 * 60)) due_c = fmt_date(add_minutes(base, c_due_offset * 24 * 60)) order_date = fmt_date(base) product = next(m for m in world["materials"] if m["type"] == "FINISHED_PRODUCT") def make(oid, no, level, due): return { "id": oid, "orderNo": no, "customerId": f"C{oid}", "customerName": f"客户{level}", "customerLevel": level, "orderDate": order_date, "deliveryDate": due, "priority": 5, "manualPriority": None, "status": "CONFIRMED", "source": "MANUAL", "specialRequirements": "", "totalAmount": 1000, "isRush": False, "rushStrategy": None, "changes": [], "createdBy": "test", "createdAt": order_date + " 09:00", "updatedAt": order_date + " 09:00", "items": [{ "id": oid * 10, "orderId": oid, "lineNo": 1, "productId": product["id"], "productName": product["name"], "productCode": product["code"], "quantity": 100, "unit": "件", "bomVersion": "V1.0", "routingVersion": "V1.0", "status": "PENDING", "note": "", }], } world["salesOrders"] = [ make(1, "SO-VIP-1", "VIP", due_vip), make(2, "SO-C-1", "C", due_c), ] world["scheduleVersions"] = [] world["productionOrders"] = [] world["workOrders"] = [] world["conflicts"] = [] return world def _run(world, strategy="COMPREHENSIVE"): start = fmt_date(add_minutes(today0(), 24 * 60)) params = EngineParams(orderIds=[], engineType="RULE", strategyTemplate=strategy, planningHorizonDays=14, startDate=start) return get_engine("RULE").solve(world, params, _next_id_factory()) def _earliest_level(world) -> str: pos = world["productionOrders"] earliest = min(pos, key=lambda p: parse_dt(p["plannedStartDate"])) so = next(s for s in world["salesOrders"] if s["id"] == earliest["salesOrderId"]) return so["customerLevel"] def test_default_params_have_level_weights(): p = get_schedule_params(seed_world()) assert p["customerLevelWeights"]["VIP"] == 3.0 assert p["customerLevelWeights"]["C"] == 1.0 def test_vip_weight_pulls_order_earlier(): """提高 VIP 权重后,即便 VIP 交期更晚,综合策略下 VIP 也应更早开工。""" # 默认:VIP=3,C=1;VIP 交期更晚 → 仍可能 VIP 先(等级键优先) w1 = _two_orders_world(vip_due_offset=12, c_due_offset=5) _run(w1) assert _earliest_level(w1) == "VIP" # 把 VIP 压到 1、C 提到 10 → C 应先开工 w2 = _two_orders_world(vip_due_offset=12, c_due_offset=5) apply_params_update(w2, {"customerLevelWeights": {"VIP": 1, "C": 10}}) _run(w2) assert _earliest_level(w2) == "C" # 再把 VIP 提到 20 → VIP 又应先开工 w3 = _two_orders_world(vip_due_offset=12, c_due_offset=5) apply_params_update(w3, {"customerLevelWeights": {"VIP": 20, "C": 1}}) _run(w3) assert _earliest_level(w3) == "VIP" def test_delivery_first_still_edd(): """交期优先仍以交期为主键(G5 不变式)。""" world = _two_orders_world(vip_due_offset=12, c_due_offset=5) apply_params_update(world, {"customerLevelWeights": {"VIP": 20, "C": 1}}) _run(world, strategy="DELIVERY_FIRST") assert _earliest_level(world) == "C" # C 交期更早 def test_params_update_roundtrip_and_confirm_text(): world = seed_world() title, lines = confirmation_for_params_update( world, {"customerLevelWeights": {"VIP": 9}}) assert "排产参数" in title assert any("VIP" in x for x in lines) applied = apply_params_update(world, {"customerLevelWeights": {"VIP": 9}}) assert applied["after"]["customerLevelWeights"]["VIP"] == 9.0 apply_params_update(world, {"resetDefaults": True}) assert get_schedule_params(world)["customerLevelWeights"]["VIP"] == 3.0 def test_intent_params(): world = seed_world() assert parse_fast("查看排产参数", world).intent == "params.query" assert parse_fast("提高VIP权重到10", world).intent == "params.update" assert parse_fast("提高VIP权重到10", world).params.get("vipWeight") == 10.0 assert parse_fast("恢复默认排产参数", world).params.get("resetDefaults") is True