From c64fca09e1fd8af7eb277da0c690b2bc7f62d29f Mon Sep 17 00:00:00 2001 From: ssk Date: Thu, 3 Sep 2026 19:18:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B4=E7=90=86=E9=80=9A=E7=94=A8=20Optimize?= =?UTF-8?q?=20=E6=A8=A1=E6=8B=9F=E6=95=B0=E6=8D=AE=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/round-86-generic-world-pack-plan.md | 19 ++++++ .../test_optimize_simulation_world_pack.py | 64 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 docs/round-86-generic-world-pack-plan.md create mode 100644 tests/golden/test_optimize_simulation_world_pack.py diff --git a/docs/round-86-generic-world-pack-plan.md b/docs/round-86-generic-world-pack-plan.md new file mode 100644 index 0000000..bc92fca --- /dev/null +++ b/docs/round-86-generic-world-pack-plan.md @@ -0,0 +1,19 @@ +# Round 86: 通用 Optimize 模拟数据包 + +## 目标 + +把 `optimize/deliverables/kangni-simulation-package-v1` 的 10 个模拟订单、72 条工序转换成 APS 已有的通用 `world pack`,用于对话式 Optimize 调试和回归测试。 + +## 设计约束 + +- 不在 APS 生产代码中增加康尼专用 loader,也不让运行时识别客户名或外部包目录。 +- 数据包只填充现有 `flexOrders`、`flexRoutings`、`flexOperations`、`flexEquipment`、`flexMaterials`、`salesOrders` 和 `flexCalendar` 契约。 +- `flexParams.dataGrade=synthetic`,并保留来源包名;该数据只用于开发调试,不作为生产排产输入。 +- 原始数据的 8:00-17:00 班次保存在 `flexParams.sourceCalendar`。由于当前 PoolEngine 按分钟落盘且不拆跨班次工序,调试日历使用全天可排,保证严格 V2 校验能验证结果。 +- 小数分钟工时向上取整到分钟,原值写入 `sourceStdTimePerUnit`/`sourceStandardTime`。 + +## 验收 + +- `load_pack` 能加载数据包并出现在 `list_packs()`。 +- 7 个 Optimize 调度规则(EDD、SPT、PRIORITY、FIFO、LPT、CR、ATC)均产生 10 条虚拟产线、72 条工单,且 V2 严格校验通过。 +- 目标分支仍保持为 `codex/integrate-optimize`;本轮只在 `round/86-generic-world-pack` 开发,合并前单独确认。 diff --git a/tests/golden/test_optimize_simulation_world_pack.py b/tests/golden/test_optimize_simulation_world_pack.py new file mode 100644 index 0000000..40f936e --- /dev/null +++ b/tests/golden/test_optimize_simulation_world_pack.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import copy +import json +from pathlib import Path + +from server.aps_domain.closed_loop_runtime import run_closed_loop_candidate +from server.state.packs import load_pack, list_packs + + +PACK_PATH = Path(__file__).resolve().parents[2] / "server" / "data" / "packs" / "optimize-simulation-v1.json" +RULES = ("EDD", "SPT", "PRIORITY", "FIFO", "LPT", "CR", "ATC") + + +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 test_simulation_world_pack_is_a_generic_aps_fixture(): + pack = json.loads(PACK_PATH.read_text(encoding="utf-8")) + world = load_pack(str(PACK_PATH)) + + assert pack["packVersion"] == 1 + assert pack["name"] == "Optimize synthetic debug V1" + assert world["flexParams"]["dataGrade"] == "synthetic" + assert len(world["flexOrders"]) == 10 + assert len(world["salesOrders"]) == 10 + assert len(world["flexRoutings"]) == 72 + assert len(world["flexEquipment"]) == 6 + assert all(row["source"] == "SYNTHETIC" for row in world["flexOrders"]) + assert {row["productCode"] for row in world["flexRoutings"]} == { + row["productCode"] for row in world["flexOrders"] + } + assert any(item["file"] == PACK_PATH.name for item in list_packs()) + + +def test_simulation_world_pack_runs_all_optimize_dispatch_rules(): + source_world = load_pack(str(PACK_PATH)) + + for rule in RULES: + world = copy.deepcopy(source_world) + result = run_closed_loop_candidate( + world, + _next_id_factory(), + business_date="2026-08-02", + engine_type="OPTIMIZE", + sort_mode=rule, + strict=True, + ) + + assert result["solveStatus"] == "FEASIBLE", rule + assert result["engineType"] == "OPTIMIZE", rule + assert result["algorithmId"] == f"optimize.{rule.lower()}", rule + assert result["orderCount"] == 10, rule + assert result["vlCount"] == 10, rule + assert result["woCount"] == 72, rule + assert result["validation"]["valid"] is True, rule + assert result["validation"]["hardViolations"] == [], rule