216 lines
17 KiB
Python
216 lines
17 KiB
Python
|
|
# ============================================================
|
|||
|
|
# 种子数据工厂(moduleId: state-seed, 可重生 ✅)
|
|||
|
|
# 从 legacy aps-frontend/js/data.js 的 seed() 按行移植:
|
|||
|
|
# - 数据键名保持 camelCase 与 JS 版一致(前端/存储直接复用)
|
|||
|
|
# - 固定日期改为"相对今天"的偏移(黄金测试与任意运行日都稳定)
|
|||
|
|
# - Math.random 改为固定种子 Random(42)(可复现,§9.5.2 deterministic)
|
|||
|
|
# ============================================================
|
|||
|
|
from __future__ import annotations # 前向类型引用
|
|||
|
|
|
|||
|
|
import random # 标准随机库(用固定种子保证确定性)
|
|||
|
|
from typing import Any # 类型标注
|
|||
|
|
|
|||
|
|
from server.timeutil import add_minutes, fmt_date, fmt_dt, today0 # 日期工具(与 data.js $utils 对齐)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def seed_world() -> dict[str, Any]:
|
|||
|
|
"""生成一套自洽的演示工厂数据(权力等级 P0:纯构造,不落盘)。"""
|
|||
|
|
rng = random.Random(42) # 固定种子:设备参数可复现
|
|||
|
|
base = today0() # 相对基准:今天零点
|
|||
|
|
data: dict[str, Any] = { # 世界状态骨架(键集与 data.js 一致)
|
|||
|
|
"factories": [], "workshops": [], "lines": [], "workstations": [],
|
|||
|
|
"operations": [], "routings": [], "routingSteps": [], "materials": [],
|
|||
|
|
"boms": [], "bomItems": [], "lineProducts": [], "workstationOperations": [],
|
|||
|
|
"equipment": [], "teams": [], "shifts": [], "shiftCalendar": [],
|
|||
|
|
"maintenance": [], "salesOrders": [], "productionOrders": [], "workOrders": [],
|
|||
|
|
"purchaseOrders": [], "outsourceOrders": [], # MRP 建议表(订单分解产物,DRAFT 建议单)
|
|||
|
|
"scheduleVersions": [], "conflicts": [], "logs": [], "auditEvents": [],
|
|||
|
|
# 排产参数(§9.8 业务参数层的 M1 形态)
|
|||
|
|
"scheduleParams": {
|
|||
|
|
"defaultEngine": "RULE", # M1 仅 RULE 真实现
|
|||
|
|
"planningHorizonDays": 14, # 计划展望期
|
|||
|
|
"timeGranularityMinutes": 15, # 排产时间粒度
|
|||
|
|
"deliveryBufferRatio": 0.95, # 交期缓冲比
|
|||
|
|
"weights": {"tardiness": 0.4, "cost": 0.3, "utilization": 0.2, "balance": 0.1}, # 目标权重
|
|||
|
|
"freezeWindowHours": 24, # 冻结窗口
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ---- 工厂与车间 ----
|
|||
|
|
data["factories"].append({"id": 1, "code": "F001", "name": "演示工厂", "timezone": "Asia/Shanghai", "address": "演示园区", "status": "ACTIVE"}) # 单工厂(示例数据,非真实客户)
|
|||
|
|
data["workshops"].append({"id": 1, "factoryId": 1, "code": "WS01", "name": "一车间", "status": "ACTIVE"}) # 车间 1
|
|||
|
|
data["workshops"].append({"id": 2, "factoryId": 1, "code": "WS02", "name": "二车间", "status": "ACTIVE"}) # 车间 2
|
|||
|
|
|
|||
|
|
# ---- 产线(3 条,含效率因子与替代产线) ----
|
|||
|
|
data["lines"].append({"id": 1, "workshopId": 1, "code": "L001", "name": "装配产线A", "capacityPerDay": 1000, "taktTime": 0.5, "efficiencyFactor": 1.0, "status": "ACTIVE", "alternativeLineIds": [3]})
|
|||
|
|
data["lines"].append({"id": 2, "workshopId": 1, "code": "L002", "name": "装配产线B", "capacityPerDay": 800, "taktTime": 0.6, "efficiencyFactor": 0.95, "status": "ACTIVE", "alternativeLineIds": [1]})
|
|||
|
|
data["lines"].append({"id": 3, "workshopId": 2, "code": "L003", "name": "装配产线C", "capacityPerDay": 600, "taktTime": 0.8, "efficiencyFactor": 0.9, "status": "ACTIVE", "alternativeLineIds": [1]})
|
|||
|
|
|
|||
|
|
# ---- 工位(13 个:产线, 编码, 名称, 顺序号) ----
|
|||
|
|
ws_rows = [
|
|||
|
|
(1, "WS001", "SMT-01", 1), (1, "WS002", "DIP-01", 2), (1, "WS003", "ASSY-01", 3), (1, "WS004", "TEST-01", 4), (1, "WS005", "PKG-01", 5),
|
|||
|
|
(2, "WS006", "SMT-02", 1), (2, "WS007", "ASSY-02", 2), (2, "WS008", "TEST-02", 3), (2, "WS009", "PKG-02", 4),
|
|||
|
|
(3, "WS010", "SMT-03", 1), (3, "WS011", "DIP-03", 2), (3, "WS012", "TEST-03", 3), (3, "WS013", "PKG-03", 4),
|
|||
|
|
]
|
|||
|
|
for i, (line_id, code, name, seq) in enumerate(ws_rows): # 逐行展开为工位记录
|
|||
|
|
data["workstations"].append({"id": i + 1, "lineId": line_id, "code": code, "name": name, "sequenceNo": seq, "status": "ACTIVE"})
|
|||
|
|
|
|||
|
|
# ---- 工序(5 道) ----
|
|||
|
|
data["operations"] += [
|
|||
|
|
{"id": 1, "code": "OP10", "name": "SMT贴片", "type": "INTERNAL", "standardTime": 0.5},
|
|||
|
|
{"id": 2, "code": "OP20", "name": "DIP插件", "type": "INTERNAL", "standardTime": 0.8},
|
|||
|
|
{"id": 3, "code": "OP30", "name": "组装", "type": "INTERNAL", "standardTime": 1.0},
|
|||
|
|
{"id": 4, "code": "OP40", "name": "测试", "type": "INTERNAL", "standardTime": 0.6},
|
|||
|
|
{"id": 5, "code": "OP50", "name": "包装", "type": "INTERNAL", "standardTime": 0.3},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# ---- 工艺路线(3 个产品各一条默认路线) ----
|
|||
|
|
data["routings"] += [
|
|||
|
|
{"id": 1, "productId": 1, "version": "V1.0", "versionName": "控制器A工艺V1.0", "isDefault": True, "status": "ACTIVE"},
|
|||
|
|
{"id": 2, "productId": 2, "version": "V1.0", "versionName": "控制器B工艺V1.0", "isDefault": True, "status": "ACTIVE"},
|
|||
|
|
{"id": 3, "productId": 3, "version": "V1.0", "versionName": "控制器C工艺V1.0", "isDefault": True, "status": "ACTIVE"},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# ---- 工艺步骤(routingId, operationId, 顺序, 前序, 准备分钟, 单件分钟, 等待, 转移, 是否外协) ----
|
|||
|
|
step_rows = [
|
|||
|
|
(1, 1, 1, None, 30, 0.5, 0, 10, False), (1, 2, 2, 1, 30, 0.8, 0, 10, False), (1, 3, 3, 2, 20, 1.0, 0, 10, False), (1, 4, 4, 3, 20, 0.6, 0, 10, False), (1, 5, 5, 4, 10, 0.3, 0, 10, False),
|
|||
|
|
(2, 1, 1, None, 30, 0.4, 0, 10, False), (2, 3, 2, 1, 20, 0.9, 0, 10, False), (2, 4, 3, 2, 20, 0.5, 0, 10, False), (2, 5, 4, 3, 10, 0.3, 0, 10, False),
|
|||
|
|
(3, 1, 1, None, 30, 0.6, 0, 10, False), (3, 2, 2, 1, 30, 1.0, 0, 10, False), (3, 4, 3, 2, 20, 0.7, 0, 10, False), (3, 5, 4, 3, 10, 0.3, 0, 10, False),
|
|||
|
|
]
|
|||
|
|
for i, (rid, oid, seq, prev, setup, run, wait, transfer, ext) in enumerate(step_rows): # 展开为步骤记录
|
|||
|
|
data["routingSteps"].append({"id": i + 1, "routingId": rid, "operationId": oid, "sequenceNo": seq, "prevStepId": prev,
|
|||
|
|
"setupTime": setup, "runTimePerUnit": run, "waitTime": wait, "transferTime": transfer, "isExternal": ext})
|
|||
|
|
|
|||
|
|
# ---- 物料(3 成品 + 7 原材料,库存/在途决定齐套结果) ----
|
|||
|
|
data["materials"] += [
|
|||
|
|
{"id": 1, "code": "CTRL-A", "name": "智能控制器A型", "spec": "标准版", "type": "FINISHED_PRODUCT", "unit": "件", "safetyStock": 50, "procurementLeadTime": 0, "stock": 20, "inTransit": 0},
|
|||
|
|
{"id": 2, "code": "CTRL-B", "name": "智能控制器B型", "spec": "经济版", "type": "FINISHED_PRODUCT", "unit": "件", "safetyStock": 50, "procurementLeadTime": 0, "stock": 30, "inTransit": 0},
|
|||
|
|
{"id": 3, "code": "CTRL-C", "name": "智能控制器C型", "spec": "高频版", "type": "FINISHED_PRODUCT", "unit": "件", "safetyStock": 30, "procurementLeadTime": 0, "stock": 10, "inTransit": 0},
|
|||
|
|
{"id": 10, "code": "PCB-001", "name": "标准PCB板", "spec": "FR-4", "type": "RAW_MATERIAL", "unit": "块", "safetyStock": 500, "procurementLeadTime": 7, "stock": 1800, "inTransit": 500},
|
|||
|
|
{"id": 11, "code": "RC-001", "name": "电阻电容包", "spec": "混合包", "type": "RAW_MATERIAL", "unit": "包", "safetyStock": 300, "procurementLeadTime": 5, "stock": 900, "inTransit": 300},
|
|||
|
|
{"id": 12, "code": "CASE-001", "name": "塑料外壳", "spec": "白色", "type": "RAW_MATERIAL", "unit": "个", "safetyStock": 400, "procurementLeadTime": 10, "stock": 1200, "inTransit": 0},
|
|||
|
|
{"id": 13, "code": "SCR-001", "name": "螺丝包", "spec": "M3", "type": "RAW_MATERIAL", "unit": "包", "safetyStock": 200, "procurementLeadTime": 3, "stock": 800, "inTransit": 0},
|
|||
|
|
{"id": 14, "code": "WIRE-001", "name": "线材", "spec": "22AWG", "type": "RAW_MATERIAL", "unit": "米", "safetyStock": 1000, "procurementLeadTime": 4, "stock": 3500, "inTransit": 0},
|
|||
|
|
{"id": 15, "code": "LABEL-001", "name": "标签", "spec": "防伪", "type": "RAW_MATERIAL", "unit": "张", "safetyStock": 500, "procurementLeadTime": 2, "stock": 2500, "inTransit": 0},
|
|||
|
|
{"id": 16, "code": "PCB-002", "name": "高频PCB板", "spec": "高频", "type": "RAW_MATERIAL", "unit": "块", "safetyStock": 200, "procurementLeadTime": 10, "stock": 350, "inTransit": 200},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# ---- BOM(3 个产品各一版默认 BOM) ----
|
|||
|
|
data["boms"] += [
|
|||
|
|
{"id": 1, "productId": 1, "version": "V2.1", "versionName": "A型BOM V2.1", "isDefault": True, "status": "ACTIVE"},
|
|||
|
|
{"id": 2, "productId": 2, "version": "V1.0", "versionName": "B型BOM V1.0", "isDefault": True, "status": "ACTIVE"},
|
|||
|
|
{"id": 3, "productId": 3, "version": "V1.0", "versionName": "C型BOM V1.0", "isDefault": True, "status": "ACTIVE"},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# ---- BOM 明细(bomId, materialId, 单件用量, 消耗工序, 是否关键料) ----
|
|||
|
|
bom_rows = [
|
|||
|
|
(1, 10, 1, 1, False), (1, 11, 1, 1, False), (1, 12, 1, 3, False), (1, 13, 1, 3, False), (1, 14, 0.5, 3, False), (1, 15, 1, 5, False),
|
|||
|
|
(2, 10, 1, 1, False), (2, 11, 1.2, 1, False), (2, 12, 1, 3, False), (2, 13, 1, 3, False), (2, 14, 0.6, 3, False), (2, 15, 1, 5, False),
|
|||
|
|
(3, 16, 1, 1, True), (3, 11, 1.5, 1, False), (3, 12, 1, 3, False), (3, 13, 1, 3, False), (3, 14, 0.4, 3, False), (3, 15, 1, 5, False),
|
|||
|
|
]
|
|||
|
|
for i, (bid, mid, qty, oid, key) in enumerate(bom_rows): # 展开为 BOM 明细
|
|||
|
|
data["bomItems"].append({"id": i + 1, "bomId": bid, "materialId": mid, "quantity": qty, "operationId": oid, "isKeyMaterial": key})
|
|||
|
|
|
|||
|
|
# ---- 产线-产品配置(选线依据:priority 越小越优先) ----
|
|||
|
|
data["lineProducts"] += [
|
|||
|
|
{"id": 1, "lineId": 1, "productId": 1, "standardCapacity": 1000, "priority": 1, "setupTime": 30},
|
|||
|
|
{"id": 2, "lineId": 1, "productId": 3, "standardCapacity": 600, "priority": 2, "setupTime": 40},
|
|||
|
|
{"id": 3, "lineId": 2, "productId": 2, "standardCapacity": 800, "priority": 1, "setupTime": 30},
|
|||
|
|
{"id": 4, "lineId": 2, "productId": 1, "standardCapacity": 700, "priority": 2, "setupTime": 40},
|
|||
|
|
{"id": 5, "lineId": 3, "productId": 1, "standardCapacity": 500, "priority": 3, "setupTime": 45},
|
|||
|
|
{"id": 6, "lineId": 3, "productId": 3, "standardCapacity": 600, "priority": 1, "setupTime": 30},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# ---- 工位-工序配置(工位能做哪道工序) ----
|
|||
|
|
wso_rows = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 1), (7, 3), (8, 4), (9, 5), (10, 1), (11, 2), (12, 4), (13, 5)]
|
|||
|
|
for i, (wsid, oid) in enumerate(wso_rows): # 展开为工位工序记录
|
|||
|
|
data["workstationOperations"].append({"id": i + 1, "workstationId": wsid, "operationId": oid, "setupTime": 20, "runTimePerUnit": 0.5, "isPrimary": True})
|
|||
|
|
|
|||
|
|
# ---- 设备(每工位一台,参数用固定种子随机→确定性) ----
|
|||
|
|
for i, ws in enumerate(data["workstations"]): # 遍历工位生成设备
|
|||
|
|
data["equipment"].append({
|
|||
|
|
"id": i + 1, "code": f"EQ{i + 1:03d}", "name": ws["name"] + "设备", "model": "MDL-" + ws["code"],
|
|||
|
|
"workstationId": ws["id"], "capacityPerHour": 120,
|
|||
|
|
"efficiencyFactor": round(0.95 + rng.random() * 0.05, 3), # 效率因子(确定性随机)
|
|||
|
|
"availabilityRate": round(0.92 + rng.random() * 0.07, 3), # 可动率(确定性随机)
|
|||
|
|
"status": "MAINTENANCE" if rng.random() > 0.9 else "RUNNING", # 少数设备处于保养态
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# ---- 班组(6 个) ----
|
|||
|
|
data["teams"] += [
|
|||
|
|
{"id": 1, "code": "T001", "name": "贴片班组A", "workshopId": 1, "memberCount": 12, "skillLevel": "L3", "status": "ACTIVE"},
|
|||
|
|
{"id": 2, "code": "T002", "name": "插件班组A", "workshopId": 1, "memberCount": 8, "skillLevel": "L2", "status": "ACTIVE"},
|
|||
|
|
{"id": 3, "code": "T003", "name": "组装班组A", "workshopId": 1, "memberCount": 15, "skillLevel": "L3", "status": "ACTIVE"},
|
|||
|
|
{"id": 4, "code": "T004", "name": "测试班组A", "workshopId": 1, "memberCount": 10, "skillLevel": "L4", "status": "ACTIVE"},
|
|||
|
|
{"id": 5, "code": "T005", "name": "包装班组A", "workshopId": 1, "memberCount": 8, "skillLevel": "L2", "status": "ACTIVE"},
|
|||
|
|
{"id": 6, "code": "T006", "name": "综合班组B", "workshopId": 1, "memberCount": 20, "skillLevel": "L3", "status": "ACTIVE"},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# ---- 班次(早班/中班,含休息段) ----
|
|||
|
|
data["shifts"] += [
|
|||
|
|
{"id": 1, "code": "D", "name": "早班", "startTime": "08:00", "endTime": "16:00", "breakPeriods": [{"start": "12:00", "end": "13:00"}], "isOvertime": False, "status": "ACTIVE"},
|
|||
|
|
{"id": 2, "code": "N", "name": "中班", "startTime": "16:00", "endTime": "23:59", "breakPeriods": [{"start": "18:00", "end": "18:30"}], "isOvertime": False, "status": "ACTIVE"},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# ---- 班次日历(未来 30 天 × 产线 × 班次;周末不排班;覆盖引擎 30 天占槽窗口) ----
|
|||
|
|
for i in range(30): # 未来 30 天
|
|||
|
|
date = add_minutes(base, i * 24 * 60) # 第 i 天
|
|||
|
|
date_str = fmt_date(date) # 日期字符串
|
|||
|
|
is_weekend = date.weekday() >= 5 # 周六(5)/周日(6)
|
|||
|
|
for line in data["lines"]: # 每条产线
|
|||
|
|
for shift in data["shifts"]: # 每个班次
|
|||
|
|
data["shiftCalendar"].append({
|
|||
|
|
"id": len(data["shiftCalendar"]) + 1, # 顺序 ID
|
|||
|
|
"lineId": line["id"], "date": date_str, "shiftId": shift["id"],
|
|||
|
|
"isWorking": not is_weekend, # 周末停排
|
|||
|
|
"teamId": 6 if line["id"] == 2 else (line["id"] if shift["id"] == 1 else 6), # 班组指派(演示口径)
|
|||
|
|
"maxWorkers": 10, # 班次容量
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# ---- 维保计划(第 3 天上午 ASSY-01 设备保养 → 制造设备冲突场景) ----
|
|||
|
|
mnt_day = fmt_date(add_minutes(base, 2 * 24 * 60)) # 后天
|
|||
|
|
data["maintenance"].append({"id": 1, "equipmentId": 3, "type": "MAINTENANCE",
|
|||
|
|
"plannedStart": mnt_day + " 09:00", "plannedEnd": mnt_day + " 12:00",
|
|||
|
|
"status": "PLANNED", "description": "ASSY-01 季度保养"})
|
|||
|
|
|
|||
|
|
# ---- 销售订单(7 张;日期相对今天偏移,保证任意运行日行为一致) ----
|
|||
|
|
# 行结构:(客户, 等级, 下单偏移天, 交期偏移天, 优先级, 状态, 是否插单, 产品ID, 数量)
|
|||
|
|
order_rows = [
|
|||
|
|
("青岛科技", "A", -2, 9, 3, "CONFIRMED", False, 1, 500),
|
|||
|
|
("海尔智家", "VIP", -3, 6, 1, "CONFIRMED", False, 2, 300),
|
|||
|
|
("海信电子", "B", -2, 8, 5, "CONFIRMED", False, 1, 800),
|
|||
|
|
("中车集团", "A", -4, 12, 4, "CONFIRMED", False, 3, 400),
|
|||
|
|
("北汽新能源", "C", -1, 14, 8, "SUBMITTED", False, 2, 600),
|
|||
|
|
("比亚迪", "VIP", -1, 4, 1, "CONFIRMED", True, 3, 200),
|
|||
|
|
("青岛科技", "A", 0, 10, 3, "CONFIRMED", False, 2, 500),
|
|||
|
|
]
|
|||
|
|
for i, (cust, level, o_off, d_off, prio, status, rush, pid, qty) in enumerate(order_rows): # 展开为订单
|
|||
|
|
order_date = fmt_date(add_minutes(base, o_off * 24 * 60)) # 下单日期(相对偏移)
|
|||
|
|
delivery = fmt_date(add_minutes(base, d_off * 24 * 60)) # 交期(相对偏移)
|
|||
|
|
product = next(m for m in data["materials"] if m["id"] == pid) # 关联产品
|
|||
|
|
data["salesOrders"].append({
|
|||
|
|
"id": i + 1,
|
|||
|
|
"orderNo": "SO" + base.strftime("%Y%m%d") + f"{i + 1:03d}", # 订单号:SO+今天+序号
|
|||
|
|
"customerId": f"CUST{i + 1:03d}", "customerName": cust, "customerLevel": level,
|
|||
|
|
"orderDate": order_date, "deliveryDate": delivery,
|
|||
|
|
"priority": prio, "manualPriority": None, "status": status,
|
|||
|
|
"source": "MANUAL" if i % 2 == 0 else "API", # 来源交替(演示口径)
|
|||
|
|
"specialRequirements": "需使用环保材料包装" if i == 0 else "",
|
|||
|
|
"totalAmount": qty * (100 + (i * 37) % 200), # 金额(确定性伪随机)
|
|||
|
|
"isRush": rush, "rushStrategy": "STRATEGY_SHIFT" if rush else None,
|
|||
|
|
"changes": [], "createdBy": "admin",
|
|||
|
|
"createdAt": order_date + " 09:00", "updatedAt": order_date + " 09:00",
|
|||
|
|
"items": [{ # 单行订单项(M1 保持一单一项)
|
|||
|
|
"id": i * 10 + 1, "orderId": i + 1, "lineNo": 1,
|
|||
|
|
"productId": pid, "productName": product["name"], "productCode": product["code"],
|
|||
|
|
"quantity": qty, "unit": "件", "bomVersion": "V1.0", "routingVersion": "V1.0",
|
|||
|
|
"status": "PENDING", "note": "",
|
|||
|
|
}],
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
# ---- 初始日志(审计雏形 §3.6:系统初始化事件) ----
|
|||
|
|
data["logs"].append({"id": 1, "type": "INFO", "category": "SCHEDULE", "operationType": "SYSTEM",
|
|||
|
|
"targetType": "SCHEDULE", "targetId": 1, "action": "系统初始化",
|
|||
|
|
"description": "种子数据生成完成", "operator": "system", "createdAt": fmt_dt(base)})
|
|||
|
|
return data # 返回完整世界状态
|