修复模拟数据包订单契约与日期基准,消除交期看板 500
/api/world/due 直取订单客户字段,而模拟数据包只填了 8 个字段,导致 KeyError 500。补齐 10 张订单的订单契约字段(客户/等级/加急/下单日等),并把 baseDate 从转换日 2026-09-03 改为案例业务日 2026-08-02,避免世界日期随运行日漂移、黄金测试输入每天变化。验证:隔离实例 GET /api/world/due 返回 200(10 行);定向测试 8 passed, 1 skipped。
This commit is contained in:
parent
cc151c6fc3
commit
7de23a0ccd
|
|
@ -2,8 +2,8 @@
|
||||||
"packVersion": 1,
|
"packVersion": 1,
|
||||||
"name": "Optimize synthetic debug V1",
|
"name": "Optimize synthetic debug V1",
|
||||||
"aliases": ["Optimize 模拟数据", "Optimize synthetic data", "simulation"],
|
"aliases": ["Optimize 模拟数据", "Optimize synthetic data", "simulation"],
|
||||||
"description": "Generic APS world pack: 10 synthetic orders, 72 operations, and 6 virtual resources for Optimize development debugging only.",
|
"description": "Generic APS world pack: 10 synthetic orders, 72 operations, and 6 virtual resources for Optimize development debugging only. baseDate is the case business day of the replayed Kangni case.",
|
||||||
"baseDate": "2026-09-03",
|
"baseDate": "2026-08-02",
|
||||||
"world": {
|
"world": {
|
||||||
"factories": [],
|
"factories": [],
|
||||||
"workshops": [],
|
"workshops": [],
|
||||||
|
|
@ -147,10 +147,23 @@
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"orderNo": "102285668",
|
"orderNo": "102285668",
|
||||||
|
"customerId": "CUST001",
|
||||||
|
"customerName": "模拟客户A",
|
||||||
|
"customerLevel": "A",
|
||||||
|
"orderDate": "2026-06-25",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-08-08",
|
"deliveryDate": "2026-08-08",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 1000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-06-25 09:00",
|
||||||
|
"updatedAt": "2026-06-25 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 1,
|
"id": 1,
|
||||||
|
|
@ -167,10 +180,23 @@
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"orderNo": "102289293",
|
"orderNo": "102289293",
|
||||||
|
"customerId": "CUST002",
|
||||||
|
"customerName": "模拟客户B",
|
||||||
|
"customerLevel": "VIP",
|
||||||
|
"orderDate": "2026-07-06",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-07-21",
|
"deliveryDate": "2026-07-21",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 1000.0,
|
||||||
|
"isRush": true,
|
||||||
|
"rushStrategy": "STRATEGY_SHIFT",
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-07-06 09:00",
|
||||||
|
"updatedAt": "2026-07-06 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
|
|
@ -187,10 +213,23 @@
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"orderNo": "102342090",
|
"orderNo": "102342090",
|
||||||
|
"customerId": "CUST003",
|
||||||
|
"customerName": "模拟客户C",
|
||||||
|
"customerLevel": "B",
|
||||||
|
"orderDate": "2026-01-15",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-01-15",
|
"deliveryDate": "2026-01-15",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 2000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-01-15 09:00",
|
||||||
|
"updatedAt": "2026-01-15 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
|
|
@ -207,10 +246,23 @@
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
"orderNo": "102342085",
|
"orderNo": "102342085",
|
||||||
|
"customerId": "CUST004",
|
||||||
|
"customerName": "模拟客户C",
|
||||||
|
"customerLevel": "B",
|
||||||
|
"orderDate": "2026-01-15",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-01-15",
|
"deliveryDate": "2026-01-15",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 2000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-01-15 09:00",
|
||||||
|
"updatedAt": "2026-01-15 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
|
|
@ -227,10 +279,23 @@
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"orderNo": "102342092",
|
"orderNo": "102342092",
|
||||||
|
"customerId": "CUST005",
|
||||||
|
"customerName": "模拟客户D",
|
||||||
|
"customerLevel": "C",
|
||||||
|
"orderDate": "2026-01-15",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-01-15",
|
"deliveryDate": "2026-01-15",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 4000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-01-15 09:00",
|
||||||
|
"updatedAt": "2026-01-15 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
|
|
@ -247,10 +312,23 @@
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"orderNo": "102352579",
|
"orderNo": "102352579",
|
||||||
|
"customerId": "CUST006",
|
||||||
|
"customerName": "模拟客户A",
|
||||||
|
"customerLevel": "A",
|
||||||
|
"orderDate": "2026-04-03",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-04-03",
|
"deliveryDate": "2026-04-03",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 1000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-04-03 09:00",
|
||||||
|
"updatedAt": "2026-04-03 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
|
|
@ -267,10 +345,23 @@
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"orderNo": "102314266",
|
"orderNo": "102314266",
|
||||||
|
"customerId": "CUST007",
|
||||||
|
"customerName": "模拟客户D",
|
||||||
|
"customerLevel": "C",
|
||||||
|
"orderDate": "2026-03-16",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-03-16",
|
"deliveryDate": "2026-03-16",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 1000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-03-16 09:00",
|
||||||
|
"updatedAt": "2026-03-16 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
|
|
@ -287,10 +378,23 @@
|
||||||
{
|
{
|
||||||
"id": 8,
|
"id": 8,
|
||||||
"orderNo": "102375367",
|
"orderNo": "102375367",
|
||||||
|
"customerId": "CUST008",
|
||||||
|
"customerName": "模拟客户B",
|
||||||
|
"customerLevel": "VIP",
|
||||||
|
"orderDate": "2026-04-03",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-04-03",
|
"deliveryDate": "2026-04-03",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 1000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-04-03 09:00",
|
||||||
|
"updatedAt": "2026-04-03 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 8,
|
"id": 8,
|
||||||
|
|
@ -307,10 +411,23 @@
|
||||||
{
|
{
|
||||||
"id": 9,
|
"id": 9,
|
||||||
"orderNo": "102375368",
|
"orderNo": "102375368",
|
||||||
|
"customerId": "CUST009",
|
||||||
|
"customerName": "模拟客户C",
|
||||||
|
"customerLevel": "B",
|
||||||
|
"orderDate": "2026-04-03",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-04-03",
|
"deliveryDate": "2026-04-03",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 1000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-04-03 09:00",
|
||||||
|
"updatedAt": "2026-04-03 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 9,
|
"id": 9,
|
||||||
|
|
@ -327,10 +444,23 @@
|
||||||
{
|
{
|
||||||
"id": 10,
|
"id": 10,
|
||||||
"orderNo": "102378688",
|
"orderNo": "102378688",
|
||||||
|
"customerId": "CUST010",
|
||||||
|
"customerName": "模拟客户A",
|
||||||
|
"customerLevel": "A",
|
||||||
|
"orderDate": "2026-04-03",
|
||||||
"status": "APPROVED",
|
"status": "APPROVED",
|
||||||
"deliveryDate": "2026-04-03",
|
"deliveryDate": "2026-04-03",
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
|
"manualPriority": null,
|
||||||
"source": "FLEX",
|
"source": "FLEX",
|
||||||
|
"specialRequirements": "",
|
||||||
|
"totalAmount": 1000.0,
|
||||||
|
"isRush": false,
|
||||||
|
"rushStrategy": null,
|
||||||
|
"changes": [],
|
||||||
|
"createdBy": "synthetic-pack",
|
||||||
|
"createdAt": "2026-04-03 09:00",
|
||||||
|
"updatedAt": "2026-04-03 09:00",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 10,
|
"id": 10,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
# 把「一套完整世界数据」封装为可重放的 JSON 包:
|
# 把「一套完整世界数据」封装为可重放的 JSON 包:
|
||||||
# - 演示厂 = server/data/packs/demo.json(由脚本从种子生成)
|
# - 演示厂 = server/data/packs/demo.json(由脚本从种子生成)
|
||||||
# - 行业种子 / 客户现场数据都可做成包,data.reset 即「重放数据包」
|
# - 行业种子 / 客户现场数据都可做成包,data.reset 即「重放数据包」
|
||||||
# 日期重基:包内记录 baseDate(生成日),加载时把所有 YYYY-MM-DD
|
# 日期重基:包内记录 baseDate(世界业务基准日:演示包=生成日,回放包=案例业务日),加载时把所有 YYYY-MM-DD
|
||||||
# 字面日期整体平移到「今天」,保证任意运行日行为一致(黄金测试稳定)。
|
# 字面日期整体平移到「今天」,保证任意运行日行为一致(黄金测试稳定)。
|
||||||
# ============================================================
|
# ============================================================
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
from datetime import date
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from server.aps_domain.closed_loop_runtime import run_closed_loop_candidate
|
from server.aps_domain.closed_loop_runtime import run_closed_loop_candidate
|
||||||
|
from server.aps_domain.views import due_view
|
||||||
from server.state.packs import load_pack, list_packs
|
from server.state.packs import load_pack, list_packs
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -62,3 +64,30 @@ def test_simulation_world_pack_runs_all_optimize_dispatch_rules():
|
||||||
assert result["woCount"] == 72, rule
|
assert result["woCount"] == 72, rule
|
||||||
assert result["validation"]["valid"] is True, rule
|
assert result["validation"]["valid"] is True, rule
|
||||||
assert result["validation"]["hardViolations"] == [], rule
|
assert result["validation"]["hardViolations"] == [], rule
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulation_world_pack_keeps_sales_order_fields_the_due_board_reads():
|
||||||
|
"""交期承诺看板直取订单客户字段,数据包必须按订单契约补齐(回归:/api/world/due 500)。"""
|
||||||
|
world = load_pack(str(PACK_PATH))
|
||||||
|
|
||||||
|
rows = due_view(world)
|
||||||
|
|
||||||
|
assert len(rows) == len(world["salesOrders"]) == 10
|
||||||
|
assert all(row["customerName"] and row["customerLevel"] for row in rows)
|
||||||
|
assert all(isinstance(row["isRush"], bool) for row in rows)
|
||||||
|
assert any(row["isRush"] for row in rows) # 至少一张插单,加急标记在 UI 有数据可验
|
||||||
|
assert {row["customerLevel"] for row in rows} >= {"VIP", "A", "B", "C"} # 等级过滤有数据可筛
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulation_world_pack_timeline_follows_its_case_business_day():
|
||||||
|
"""回归:baseDate 必须是案例业务日,否则世界日期随运行日漂移、黄金测试输入每天变。
|
||||||
|
|
||||||
|
源案例(kangni-simulation-package-v1)的业务日交期结构固定为「9 张逾期 + 最晚交期在业务日 +6 天」,
|
||||||
|
世界日期整体贴着 baseDate 平移后,这个结构在任意运行日都应原样落在"今天"上。
|
||||||
|
"""
|
||||||
|
world = load_pack(str(PACK_PATH))
|
||||||
|
due_dates = sorted(date.fromisoformat(order["dueDate"]) for order in world["flexOrders"])
|
||||||
|
today = date.today()
|
||||||
|
|
||||||
|
assert (due_dates[-1] - today).days == 6 # 最晚交期 = 案例业务日 +6 天
|
||||||
|
assert sum(1 for due in due_dates if due < today) == 9 # 案例本身是 9 张逾期单
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue