@operato/twin-kernel 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capability.d.ts +50 -0
- package/dist/capability.js +44 -0
- package/dist/contract.d.ts +91 -1
- package/dist/contract.js +11 -2
- package/dist/domain-catalog.d.ts +25 -1
- package/dist/domain-catalog.js +12 -11
- package/dist/domain-definition.d.ts +91 -0
- package/dist/domain-definition.js +76 -0
- package/dist/flow-engine.d.ts +77 -1
- package/dist/flow-engine.js +271 -30
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/kernel.js +1 -1
- package/dist/mes-kernel.d.ts +48 -4
- package/dist/mes-kernel.js +185 -24
- package/dist/mes-profile.d.ts +5 -2
- package/dist/mes-profile.js +23 -7
- package/dist/state-projector.js +2 -2
- package/dist/wms-profile.d.ts +2 -0
- package/dist/wms-profile.js +9 -5
- package/dist/yms-profile.d.ts +2 -0
- package/dist/yms-profile.js +8 -8
- package/dist-cjs/index.cjs +573 -62
- package/package.json +1 -1
package/dist-cjs/index.cjs
CHANGED
|
@@ -23,6 +23,8 @@ __export(index_exports, {
|
|
|
23
23
|
BTT: () => BTT,
|
|
24
24
|
BTT_DELIVERY: () => BTT_DELIVERY,
|
|
25
25
|
BTT_PRODORDER: () => BTT_PRODORDER,
|
|
26
|
+
CAPABILITIES: () => CAPABILITIES,
|
|
27
|
+
CAPABILITY_KEYS: () => CAPABILITY_KEYS,
|
|
26
28
|
CMD: () => CMD,
|
|
27
29
|
DISP: () => DISP,
|
|
28
30
|
DOMAIN_CATALOG: () => DOMAIN_CATALOG,
|
|
@@ -33,7 +35,9 @@ __export(index_exports, {
|
|
|
33
35
|
MES_BIZSTEP: () => MES_BIZSTEP,
|
|
34
36
|
MES_NODE_TYPES: () => MES_NODE_TYPES,
|
|
35
37
|
MES_PART_GTINS: () => MES_PART_GTINS,
|
|
38
|
+
MES_PRODUCTS: () => MES_PRODUCTS,
|
|
36
39
|
MES_PRODUCT_GTINS: () => MES_PRODUCT_GTINS,
|
|
40
|
+
MES_TYPES: () => MES_TYPES,
|
|
37
41
|
MesKernel: () => MesKernel,
|
|
38
42
|
OP_EVENT: () => OP_EVENT,
|
|
39
43
|
StateProjector: () => StateProjector,
|
|
@@ -42,14 +46,19 @@ __export(index_exports, {
|
|
|
42
46
|
TwinRuntime: () => TwinRuntime,
|
|
43
47
|
UTC_OFFSET: () => UTC_OFFSET,
|
|
44
48
|
WMS_NODE_TYPES: () => WMS_NODE_TYPES,
|
|
49
|
+
WMS_TYPES: () => WMS_TYPES,
|
|
45
50
|
WmsKernel: () => WmsKernel,
|
|
46
51
|
YARD_BIZSTEP: () => YARD_BIZSTEP,
|
|
47
52
|
YMS_NODE_TYPES: () => YMS_NODE_TYPES,
|
|
53
|
+
YMS_TYPES: () => YMS_TYPES,
|
|
48
54
|
YmsKernel: () => YmsKernel,
|
|
49
55
|
aggregationEvent: () => aggregationEvent,
|
|
56
|
+
capabilitiesForType: () => capabilitiesForType,
|
|
50
57
|
compareStates: () => compareStates,
|
|
58
|
+
computeOee: () => computeOee,
|
|
51
59
|
constantDuration: () => constantDuration,
|
|
52
60
|
counterfactualAt: () => counterfactualAt,
|
|
61
|
+
deriveAttentions: () => deriveAttentions,
|
|
53
62
|
fefoPolicy: () => fefoPolicy,
|
|
54
63
|
firstFitPolicy: () => firstFitPolicy,
|
|
55
64
|
gdtiUri: () => gdtiUri,
|
|
@@ -63,8 +72,10 @@ __export(index_exports, {
|
|
|
63
72
|
sgtinClass: () => sgtinClass,
|
|
64
73
|
sgtinUri: () => sgtinUri,
|
|
65
74
|
ssccUri: () => ssccUri,
|
|
75
|
+
stateFieldsOf: () => stateFieldsOf,
|
|
66
76
|
transactionEvent: () => transactionEvent,
|
|
67
77
|
transformationEvent: () => transformationEvent,
|
|
78
|
+
validateDomainDefinition: () => validateDomainDefinition,
|
|
68
79
|
validateEpcisEvent: () => validateEpcisEvent
|
|
69
80
|
});
|
|
70
81
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -73,14 +84,78 @@ module.exports = __toCommonJS(index_exports);
|
|
|
73
84
|
var OP_EVENT = {
|
|
74
85
|
task: "task.status",
|
|
75
86
|
equipment: "equipment.status",
|
|
76
|
-
order: "order.status"
|
|
87
|
+
order: "order.status",
|
|
88
|
+
quality: "quality.output"
|
|
89
|
+
// 품질 산출(양품/불량) — OEE quality 입력. live 누적기가 이걸로 good/scrap 정확 추적.
|
|
77
90
|
};
|
|
78
91
|
var CMD = {
|
|
79
92
|
orderHold: "order.hold",
|
|
80
93
|
orderResume: "order.resume",
|
|
81
|
-
orderRelease: "order.release"
|
|
94
|
+
orderRelease: "order.release",
|
|
95
|
+
attentionAck: "attention.ack",
|
|
96
|
+
// 주목 신호 확인(OPC UA A&C acknowledge) — args:{id}
|
|
97
|
+
// Operable 코어 — 모든 operable 자원(설비·이동무버) 공통. args:{resourceId}. capability-keyed(무방언, equipment.* 아님).
|
|
98
|
+
resourceHold: "resource.hold",
|
|
99
|
+
// 계획 정지(정비/오프라인) — 배정 스킵
|
|
100
|
+
resourceResume: "resource.resume",
|
|
101
|
+
// 계획 정지 해제
|
|
102
|
+
resourceDown: "resource.down",
|
|
103
|
+
// 비계획 고장 주입 — args:{resourceId, durationMs?}
|
|
104
|
+
resourceRepair: "resource.repair",
|
|
105
|
+
// 즉시 수리
|
|
106
|
+
resourceResetMetrics: "resource.reset-metrics",
|
|
107
|
+
// OEE 계측 창 리셋
|
|
108
|
+
resourceAdd: "resource.add"
|
|
109
|
+
// 라이브 자원(무버) 추가 — args:{kind, homeNode, count?}. 런타임 구조 변이(what-if 아닌 실제 act)
|
|
82
110
|
};
|
|
83
111
|
|
|
112
|
+
// src/domain-definition.ts
|
|
113
|
+
var INTENTS = ["transport", "process", "dwell"];
|
|
114
|
+
function dupes(keys) {
|
|
115
|
+
const seen = /* @__PURE__ */ new Set();
|
|
116
|
+
const dup = /* @__PURE__ */ new Set();
|
|
117
|
+
for (const k of keys) {
|
|
118
|
+
if (seen.has(k)) dup.add(k);
|
|
119
|
+
else seen.add(k);
|
|
120
|
+
}
|
|
121
|
+
return [...dup];
|
|
122
|
+
}
|
|
123
|
+
function validateDomainDefinition(def) {
|
|
124
|
+
const v = [];
|
|
125
|
+
if (!def || typeof def !== "object") return ["domain definition \uC5C6\uC74C/\uAC1D\uCCB4 \uC544\uB2D8"];
|
|
126
|
+
if (typeof def.id !== "string" || !def.id) v.push("id \uB204\uB77D");
|
|
127
|
+
if (typeof def.label !== "string" || !def.label) v.push("label \uB204\uB77D");
|
|
128
|
+
if (!Array.isArray(def.nodeTypes) || def.nodeTypes.length === 0) v.push("nodeTypes \uBE44\uC5B4\uC788\uC74C");
|
|
129
|
+
if (!Array.isArray(def.resourceTypes)) v.push("resourceTypes \uBC30\uC5F4 \uC544\uB2D8");
|
|
130
|
+
const nodeKeys2 = new Set((def.nodeTypes || []).map((n) => n.key));
|
|
131
|
+
const resKeys = new Set((def.resourceTypes || []).map((r) => r.key));
|
|
132
|
+
const matKeys = new Set((def.materials || []).map((m) => m.key));
|
|
133
|
+
const opKeys = new Set((def.operations || []).map((o) => o.key));
|
|
134
|
+
const routeKeys = new Set((def.routes || []).map((r) => r.key));
|
|
135
|
+
for (const [name, arr] of [["nodeTypes", def.nodeTypes], ["resourceTypes", def.resourceTypes], ["materials", def.materials], ["operations", def.operations], ["routes", def.routes], ["recipes", def.recipes]]) {
|
|
136
|
+
for (const d of dupes((arr || []).map((x) => x.key))) v.push(`${name} \uD0A4 \uC911\uBCF5: ${d}`);
|
|
137
|
+
}
|
|
138
|
+
for (const o of def.operations || []) {
|
|
139
|
+
if (!INTENTS.includes(o.intent)) v.push(`operation '${o.key}' intent \uBD80\uC815: ${o.intent}`);
|
|
140
|
+
if (o.nodeType && !nodeKeys2.has(o.nodeType)) v.push(`operation '${o.key}' nodeType '${o.nodeType}' \uBBF8\uC815\uC758`);
|
|
141
|
+
if (o.resourceType && !resKeys.has(o.resourceType)) v.push(`operation '${o.key}' resourceType '${o.resourceType}' \uBBF8\uC815\uC758`);
|
|
142
|
+
if (o.intent === "dwell" && o.resourceType) v.push(`operation '${o.key}' dwell \uC778\uB370 resourceType \uC9C0\uC815\uB428(\uBB34\uC790\uC6D0\uC774\uC5B4\uC57C)`);
|
|
143
|
+
}
|
|
144
|
+
for (const r of def.routes || []) {
|
|
145
|
+
if (!Array.isArray(r.steps) || r.steps.length === 0) v.push(`route '${r.key}' steps \uBE44\uC5B4\uC788\uC74C`);
|
|
146
|
+
for (const s of r.steps || []) if (!opKeys.has(s)) v.push(`route '${r.key}' step '${s}' \uBBF8\uC815\uC758 operation`);
|
|
147
|
+
}
|
|
148
|
+
for (const rc of def.recipes || []) {
|
|
149
|
+
if (rc.route && !routeKeys.has(rc.route)) v.push(`recipe '${rc.key}' route '${rc.route}' \uBBF8\uC815\uC758`);
|
|
150
|
+
if (!rc.outputs?.length) v.push(`recipe '${rc.key}' outputs \uBE44\uC5B4\uC788\uC74C`);
|
|
151
|
+
for (const p of [...rc.inputs || [], ...rc.outputs || []]) {
|
|
152
|
+
if (!matKeys.has(p.material)) v.push(`recipe '${rc.key}' material '${p.material}' \uBBF8\uC815\uC758`);
|
|
153
|
+
if (typeof p.qty !== "number" || p.qty <= 0) v.push(`recipe '${rc.key}' material '${p.material}' qty \uBD80\uC815`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return v;
|
|
157
|
+
}
|
|
158
|
+
|
|
84
159
|
// src/divergence.ts
|
|
85
160
|
function diffBy(predicted, actual, idOf, valOf) {
|
|
86
161
|
const p = new Map(predicted.map((e) => [idOf(e), valOf(e)]));
|
|
@@ -212,7 +287,7 @@ var StateProjector = class {
|
|
|
212
287
|
orders = /* @__PURE__ */ new Map();
|
|
213
288
|
revision = 0;
|
|
214
289
|
constructor(board) {
|
|
215
|
-
for (const n of board.nodes) this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity });
|
|
290
|
+
for (const n of board.nodes) this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parentId: n.parentId });
|
|
216
291
|
for (const m of board.movers) this.movers.set(m.id, { id: m.id, kind: m.kind, status: "idle", location: m.homeNode });
|
|
217
292
|
}
|
|
218
293
|
/** 마스터 동기 — 로케이션 추가/변경/제거. */
|
|
@@ -292,7 +367,7 @@ var StateProjector = class {
|
|
|
292
367
|
for (const it of this.items.values()) occ.set(it.location, (occ.get(it.location) ?? 0) + 1);
|
|
293
368
|
return {
|
|
294
369
|
revision: this.revision,
|
|
295
|
-
nodes: [...this.master.values()].map((n) => ({ id: n.id, type: n.type, capacity: n.capacity, occupancy: occ.get(n.id) ?? 0 })),
|
|
370
|
+
nodes: [...this.master.values()].map((n) => ({ id: n.id, type: n.type, capacity: n.capacity, occupancy: occ.get(n.id) ?? 0, parentId: n.parentId })),
|
|
296
371
|
items: [...this.items.values()].map((i) => ({ epc: i.epc, gtin: i.gtin, location: i.location, disposition: i.disposition })),
|
|
297
372
|
tasks: [...this.tasks.values()].map((t) => ({ ...t })),
|
|
298
373
|
movers: [...this.movers.values()].map((m) => ({ ...m })),
|
|
@@ -456,6 +531,57 @@ var BTT = {
|
|
|
456
531
|
so: "urn:epcglobal:cbv:btt:so"
|
|
457
532
|
};
|
|
458
533
|
var WMS_NODE_TYPES = ["dock", "storage", "staging", "dock-ship"];
|
|
534
|
+
var WMS_TYPES = [
|
|
535
|
+
...WMS_NODE_TYPES.map((k) => ({ key: k, role: "node", label: `twin.type.${k}`, standardClass: { epcis: "bizLocation" }, identity: { scheme: "gs1:SGLN" }, capabilities: ["storable"] })),
|
|
536
|
+
{ key: "forklift", role: "mover", label: "twin.type.forklift", standardClass: { epcis: "object", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["mobile", "operable"] }
|
|
537
|
+
];
|
|
538
|
+
|
|
539
|
+
// src/capability.ts
|
|
540
|
+
var CAPABILITIES = {
|
|
541
|
+
operable: {
|
|
542
|
+
key: "operable",
|
|
543
|
+
label: "\uC6B4\uC601",
|
|
544
|
+
semantics: "\uB2A5\uB3D9 \uC790\uC6D0\uC758 \uC6B4\uC601 \uC0C1\uD0DC(\uC720\uD734/\uAC00\uB3D9/\uACE0\uC7A5). status \uAD50\uCC28 \uAD00\uC2EC\uC0AC\uB97C \uC5EC\uAE30 \uD558\uB098\uB85C.",
|
|
545
|
+
stateFields: ["status"],
|
|
546
|
+
results: ["statusChanged"]
|
|
547
|
+
},
|
|
548
|
+
storable: {
|
|
549
|
+
key: "storable",
|
|
550
|
+
label: "\uC800\uC7A5",
|
|
551
|
+
semantics: "\uC544\uC774\uD15C\uC744 \uBCF4\uC720\uD558\uB294 \uC704\uCE58 \u2014 \uC810\uC720/\uC6A9\uB7C9. (\uC52C \uAE30\uC81C: Capacity)",
|
|
552
|
+
stateFields: ["occupancy", "capacity"],
|
|
553
|
+
invariants: ["0 <= occupancy <= capacity (capacity>0)"],
|
|
554
|
+
results: ["occupancyChanged"]
|
|
555
|
+
},
|
|
556
|
+
mobile: {
|
|
557
|
+
key: "mobile",
|
|
558
|
+
label: "\uC774\uB3D9",
|
|
559
|
+
semantics: "\uC790\uC6D0 \uC790\uC2E0\uC774 \uB178\uB4DC \uAC04 \uC774\uB3D9. Transferable(\uC544\uC774\uD15C \uC774\uB3D9)\uACFC \uB2E4\uB984. (\uC52C \uAE30\uC81C: CarrierLine)",
|
|
560
|
+
stateFields: ["location", "motion"],
|
|
561
|
+
models: ["Motion"],
|
|
562
|
+
results: ["moved", "motionTick"]
|
|
563
|
+
},
|
|
564
|
+
processable: {
|
|
565
|
+
key: "processable",
|
|
566
|
+
label: "\uAC00\uACF5",
|
|
567
|
+
semantics: "\uBCC0\uD658/\uAC00\uACF5 \uC218\uD589 \u2014 \uC0B0\uCD9C(\uC591\uD488/\uBD88\uB7C9). \uC6B4\uC601 status \uB294 Operable \uC870\uD569. progress \uBC29\uCD9C\uC740 \uD6C4\uC18D.",
|
|
568
|
+
stateFields: ["output"],
|
|
569
|
+
results: ["completed"]
|
|
570
|
+
},
|
|
571
|
+
trackable: {
|
|
572
|
+
key: "trackable",
|
|
573
|
+
label: "\uCD94\uC801",
|
|
574
|
+
semantics: "\uC624\uB354/\uC544\uC774\uD15C \uC0DD\uC560 \uCD94\uC801 \u2014 \uC0DD\uC560\uB2E8\uACC4(\uB3C4\uBA54\uC778 \uB77C\uBCA8, \uBB34\uBC29\uC5B8)\xB7\uC9C4\uD589\xB7\uBCF4\uB958.",
|
|
575
|
+
stateFields: ["lifecycle", "progress", "held"],
|
|
576
|
+
results: ["lifecycleChanged"]
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
var CAPABILITY_KEYS = ["operable", "storable", "mobile", "processable", "trackable"];
|
|
580
|
+
function stateFieldsOf(caps) {
|
|
581
|
+
const out = /* @__PURE__ */ new Set();
|
|
582
|
+
for (const c of caps) for (const f of CAPABILITIES[c]?.stateFields ?? []) out.add(f);
|
|
583
|
+
return [...out];
|
|
584
|
+
}
|
|
459
585
|
|
|
460
586
|
// src/yms-profile.ts
|
|
461
587
|
var YARD_BIZSTEP = {
|
|
@@ -475,6 +601,10 @@ function graiUri(companyPrefix, assetType, serial) {
|
|
|
475
601
|
return `urn:epc:id:grai:${companyPrefix}.${assetType}.${String(serial).padStart(6, "0")}`;
|
|
476
602
|
}
|
|
477
603
|
var YMS_NODE_TYPES = ["gate", "yard-slot", "dock-door", "staging"];
|
|
604
|
+
var YMS_TYPES = [
|
|
605
|
+
...YMS_NODE_TYPES.map((k) => ({ key: k, role: "node", label: `twin.type.${k}`, standardClass: { epcis: "bizLocation" }, identity: { scheme: "gs1:SGLN" }, capabilities: ["storable"] })),
|
|
606
|
+
{ key: "hostler", role: "mover", label: "twin.type.hostler", standardClass: { epcis: "object", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["mobile", "operable"] }
|
|
607
|
+
];
|
|
478
608
|
|
|
479
609
|
// src/mes-profile.ts
|
|
480
610
|
var MES_BIZSTEP = {
|
|
@@ -489,15 +619,35 @@ var BTT_PRODORDER = "urn:epcglobal:cbv:btt:prodorder";
|
|
|
489
619
|
function sgtinUri(companyPrefix, itemRef, serial) {
|
|
490
620
|
return `urn:epc:id:sgtin:${companyPrefix}.${itemRef}.${serial}`;
|
|
491
621
|
}
|
|
492
|
-
var MES_NODE_TYPES = ["raw-store", "cut-station", "weld-station", "fg-store"];
|
|
622
|
+
var MES_NODE_TYPES = ["raw-store", "cut-station", "weld-station", "paint-booth", "assembly-line", "fg-store"];
|
|
623
|
+
var MES_NODE_CLS = {
|
|
624
|
+
"raw-store": { epcis: "bizLocation" },
|
|
625
|
+
"cut-station": { isa95: "WorkCenter", epcis: "bizLocation" },
|
|
626
|
+
"weld-station": { isa95: "WorkCenter", epcis: "bizLocation" },
|
|
627
|
+
"paint-booth": { isa95: "WorkCenter", epcis: "bizLocation" },
|
|
628
|
+
"assembly-line": { isa95: "WorkCenter", epcis: "bizLocation" },
|
|
629
|
+
"fg-store": { epcis: "bizLocation" }
|
|
630
|
+
};
|
|
631
|
+
var MES_TYPES = [
|
|
632
|
+
...MES_NODE_TYPES.map((k) => ({ key: k, role: "node", label: `twin.type.${k}`, standardClass: MES_NODE_CLS[k] ?? {}, identity: { scheme: "gs1:SGLN" }, capabilities: ["storable"] })),
|
|
633
|
+
{ key: "cutter", role: "mover", label: "twin.type.cutter", standardClass: { isa95: "Equipment", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["processable", "operable"] },
|
|
634
|
+
{ key: "welder", role: "mover", label: "twin.type.welder", standardClass: { isa95: "Equipment", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["processable", "operable"] },
|
|
635
|
+
{ key: "painter", role: "mover", label: "twin.type.painter", standardClass: { isa95: "Equipment", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["processable", "operable"] },
|
|
636
|
+
{ key: "assembler", role: "mover", label: "twin.type.assembler", standardClass: { isa95: "Equipment", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["processable", "operable"] }
|
|
637
|
+
];
|
|
493
638
|
|
|
494
639
|
// src/domain-catalog.ts
|
|
640
|
+
var nodeKeys = (types) => types.filter((t) => t.role === "node").map((t) => t.key);
|
|
495
641
|
var DOMAIN_CATALOG = {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
642
|
+
// label 은 언어 중립 i18n 키(twin.system.<code>) — 사람 언어는 표현계층이 렌더(L2).
|
|
643
|
+
wms: { system: "wms", label: "twin.system.wms", types: WMS_TYPES, nodeTypes: nodeKeys(WMS_TYPES) },
|
|
644
|
+
yms: { system: "yms", label: "twin.system.yms", types: YMS_TYPES, nodeTypes: nodeKeys(YMS_TYPES) },
|
|
645
|
+
mes: { system: "mes", label: "twin.system.mes", types: MES_TYPES, nodeTypes: nodeKeys(MES_TYPES) }
|
|
499
646
|
};
|
|
500
647
|
var DOMAIN_SYSTEMS = ["wms", "yms", "mes"];
|
|
648
|
+
function capabilitiesForType(system, typeKey) {
|
|
649
|
+
return DOMAIN_CATALOG[system]?.types.find((t) => t.key === typeKey)?.capabilities ?? [];
|
|
650
|
+
}
|
|
501
651
|
|
|
502
652
|
// src/allocation-policy.ts
|
|
503
653
|
function freeBinsFirstFit(slots) {
|
|
@@ -646,6 +796,95 @@ function mulberry32(seed) {
|
|
|
646
796
|
} });
|
|
647
797
|
return fn;
|
|
648
798
|
}
|
|
799
|
+
function deriveAttentions(view, acked) {
|
|
800
|
+
const out = [];
|
|
801
|
+
for (const m of view.movers) {
|
|
802
|
+
if (m.status === "down") {
|
|
803
|
+
out.push({
|
|
804
|
+
id: `breakdown:${m.id}`,
|
|
805
|
+
kind: "breakdown",
|
|
806
|
+
severity: "critical",
|
|
807
|
+
anchor: { moverId: m.id, nodeId: m.location },
|
|
808
|
+
params: { moverId: m.id, ...m.location ? { nodeId: m.location } : {} },
|
|
809
|
+
recommendedActions: [
|
|
810
|
+
{ code: "act.repair", command: CMD.resourceRepair, args: { resourceId: m.id } },
|
|
811
|
+
{ code: "act.hold-until-repair", command: CMD.resourceHold, args: { resourceId: m.id } }
|
|
812
|
+
],
|
|
813
|
+
suggestedAction: { code: "act.repair", command: CMD.resourceRepair, args: { resourceId: m.id } }
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
for (const n of view.nodes) {
|
|
818
|
+
if ((n.capacity ?? 0) > 0) {
|
|
819
|
+
const r = (n.occupancy ?? 0) / n.capacity;
|
|
820
|
+
if (r >= 0.9) {
|
|
821
|
+
const saturated = r >= 1;
|
|
822
|
+
out.push({
|
|
823
|
+
id: `bottleneck:${n.id}`,
|
|
824
|
+
kind: "bottleneck",
|
|
825
|
+
severity: saturated ? "high" : "medium",
|
|
826
|
+
anchor: { nodeId: n.id },
|
|
827
|
+
params: { nodeId: n.id, occupancy: n.occupancy ?? 0, capacity: n.capacity ?? 0, ratioPct: Math.round(r * 100), saturated: saturated ? 1 : 0 },
|
|
828
|
+
recommendedActions: [{ code: "advice.add-resource" }, { code: "advice.downstream-priority" }]
|
|
829
|
+
// 권고만(command 없음) — 표현계층이 advice.* 코드로 렌더
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
for (const m of view.movers) {
|
|
835
|
+
const total = (m.goodCount ?? 0) + (m.scrapCount ?? 0);
|
|
836
|
+
if (total >= 10) {
|
|
837
|
+
const rate = (m.scrapCount ?? 0) / total;
|
|
838
|
+
if (rate >= 0.15) out.push({
|
|
839
|
+
id: `scrap:${m.id}`,
|
|
840
|
+
kind: "scrap-high",
|
|
841
|
+
severity: rate >= 0.3 ? "high" : "medium",
|
|
842
|
+
anchor: { moverId: m.id, nodeId: m.location },
|
|
843
|
+
params: { moverId: m.id, goodCount: m.goodCount ?? 0, scrapCount: m.scrapCount ?? 0, ratePct: Math.round(rate * 100) },
|
|
844
|
+
recommendedActions: [
|
|
845
|
+
{ code: "act.hold-for-inspection", command: CMD.resourceHold, args: { resourceId: m.id } },
|
|
846
|
+
{ code: "act.reset-metrics", command: CMD.resourceResetMetrics, args: { resourceId: m.id } }
|
|
847
|
+
],
|
|
848
|
+
suggestedAction: { code: "act.hold-for-inspection", command: CMD.resourceHold, args: { resourceId: m.id } }
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
for (const o of view.orders) {
|
|
853
|
+
if (o.held) out.push({
|
|
854
|
+
id: `hold:${o.id}`,
|
|
855
|
+
kind: "hold",
|
|
856
|
+
severity: "medium",
|
|
857
|
+
anchor: { orderId: o.id },
|
|
858
|
+
params: { orderId: o.id },
|
|
859
|
+
recommendedActions: [{ code: "act.resume-order", command: CMD.orderResume, args: { orderId: o.id } }],
|
|
860
|
+
suggestedAction: { code: "act.resume-order", command: CMD.orderResume, args: { orderId: o.id } }
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
if (acked) {
|
|
864
|
+
for (const a of out) if (acked.has(a.id)) a.state = "acknowledged";
|
|
865
|
+
}
|
|
866
|
+
return out;
|
|
867
|
+
}
|
|
868
|
+
function computeOee(c, nowMs) {
|
|
869
|
+
const planned = Math.max(0, nowMs - (c.metricsSinceMs ?? 0) - (c.holdMs ?? 0));
|
|
870
|
+
const uptime = Math.max(0, planned - c.setupMs - c.downMs);
|
|
871
|
+
const availability = planned > 0 ? uptime / planned : 1;
|
|
872
|
+
const performance = uptime > 0 ? Math.min(1, c.runMs / uptime) : c.runMs > 0 ? 1 : 0;
|
|
873
|
+
const totalQ = c.goodCount + c.scrapCount;
|
|
874
|
+
const quality = totalQ > 0 ? c.goodCount / totalQ : 1;
|
|
875
|
+
return {
|
|
876
|
+
availability,
|
|
877
|
+
performance,
|
|
878
|
+
quality,
|
|
879
|
+
overall: availability * performance * quality,
|
|
880
|
+
runMs: c.runMs,
|
|
881
|
+
setupMs: c.setupMs,
|
|
882
|
+
downMs: c.downMs,
|
|
883
|
+
idleMs: Math.max(0, uptime - c.runMs),
|
|
884
|
+
goodCount: c.goodCount,
|
|
885
|
+
scrapCount: c.scrapCount
|
|
886
|
+
};
|
|
887
|
+
}
|
|
649
888
|
var FlowEngine = class {
|
|
650
889
|
tenantId;
|
|
651
890
|
nodes = /* @__PURE__ */ new Map();
|
|
@@ -674,7 +913,7 @@ var FlowEngine = class {
|
|
|
674
913
|
}
|
|
675
914
|
// ── TwinKernel (mechanics, 도메인 무관) ───────────────────────────────────
|
|
676
915
|
loadBoard(def) {
|
|
677
|
-
for (const n of def.nodes) this.nodes.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, occupancy: 0, status: "idle" });
|
|
916
|
+
for (const n of def.nodes) this.nodes.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, occupancy: 0, status: "idle", parentId: n.parentId });
|
|
678
917
|
for (const m of def.movers) {
|
|
679
918
|
const mover = { id: m.id, kind: m.kind, location: m.homeNode, status: "idle", taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0 };
|
|
680
919
|
if (m.mtbfMs !== void 0) {
|
|
@@ -685,6 +924,54 @@ var FlowEngine = class {
|
|
|
685
924
|
this.movers.set(m.id, mover);
|
|
686
925
|
}
|
|
687
926
|
}
|
|
927
|
+
/**
|
|
928
|
+
* what-if 구성 변주 — fork(또는 실행 중) 엔진에 무버 추가. loadBoard 무버 삽입과 동일 규약.
|
|
929
|
+
* 기본은 mtbf 미지정(고장 없는 신뢰 자원) → sampleExp(rng) 무소비라 baseline fork 와 깨끗이 비교 가능.
|
|
930
|
+
*/
|
|
931
|
+
addMover(m) {
|
|
932
|
+
if (this.movers.has(m.id)) return;
|
|
933
|
+
const mover = { id: m.id, kind: m.kind, location: m.homeNode, status: "idle", taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0 };
|
|
934
|
+
if (m.mtbfMs !== void 0) {
|
|
935
|
+
mover.mtbfMs = m.mtbfMs;
|
|
936
|
+
mover.mttrMs = m.mttrMs;
|
|
937
|
+
mover.nextFailureMs = this.sampleExp(m.mtbfMs);
|
|
938
|
+
}
|
|
939
|
+
this.movers.set(m.id, mover);
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* 관측 상태 주입(라이브 예측용, kernel-unification P1) — 외부 관측 스냅샷(재고·무버·노드)과
|
|
943
|
+
* 저널 오더(원값+라인)로 이 커널의 맵을 채운다. tick 으로 만든 게 아니라 "현재 관측된 현실"을 심어
|
|
944
|
+
* 이후 fork/tick 으로 예측한다. 라이브 런타임은 여전히 projector 미러 — 이 커널은 예측용 임시본.
|
|
945
|
+
* 오더는 남은 데맨드(라인별 requested-fulfilled)를 'created' 로 복원(현재 재고에서 재계획).
|
|
946
|
+
* 진행 중 개별 task 의 내부 상태는 관측만으론 복원 불가 → 재계획에 맡김(정직한 한계).
|
|
947
|
+
*/
|
|
948
|
+
hydrateObserved(snap, orders = []) {
|
|
949
|
+
for (const n of snap.nodes) this.nodes.set(n.id, { id: n.id, type: n.type, capacity: n.capacity ?? 0, occupancy: n.occupancy ?? 0, status: "idle", parentId: n.parentId });
|
|
950
|
+
this.items.clear();
|
|
951
|
+
for (const it of snap.items) this.items.set(it.epc, { epc: it.epc, location: it.location, disposition: it.disposition ?? DISP.sellable, gtin: it.gtin, qty: it.qty ?? 1 });
|
|
952
|
+
for (const m of snap.movers) this.movers.set(m.id, { id: m.id, kind: m.kind, location: m.location ?? "", status: "idle", taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0 });
|
|
953
|
+
for (const o of orders) {
|
|
954
|
+
const lines = (o.lines ?? []).map((l) => ({ gtin: l.gtin, requested: l.requested - (l.fulfilled ?? 0) })).filter((l) => l.requested > 0);
|
|
955
|
+
const remaining = lines.reduce((s, l) => s + l.requested, 0);
|
|
956
|
+
if (remaining <= 0) continue;
|
|
957
|
+
this.orders.set(o.orderId, { id: o.orderId, kind: o.kind, status: "created", requested: remaining, fulfilled: 0, bizTransaction: "", allocated: [], picked: [], shipmentEpc: null, lines });
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
/** what-if 구성 변주 — 노드 용량 변경(fork 대상). 존재하면 true. */
|
|
961
|
+
setNodeCapacity(nodeId, capacity) {
|
|
962
|
+
const n = this.nodes.get(nodeId);
|
|
963
|
+
if (!n) return false;
|
|
964
|
+
n.capacity = Math.max(0, capacity);
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* forecast 몬테카를로 — fork 의 RNG 만 재시드(시나리오·상태·gens·in-flight 는 보존).
|
|
969
|
+
* "현재 조건 지속"을 유지한 채 **미래 확률만** 변주(도착·고장 타이밍 등) → run 마다 다른 표본.
|
|
970
|
+
* (monteCarloForecast 은 scenario.load 로 gens 를 갈아끼우므로 "현재 조건"이 깨진다 — 그 대안.)
|
|
971
|
+
*/
|
|
972
|
+
reseed(seed) {
|
|
973
|
+
this.rng = mulberry32(seed >>> 0);
|
|
974
|
+
}
|
|
688
975
|
onEvent(handler) {
|
|
689
976
|
this.handlers.push(handler);
|
|
690
977
|
return () => {
|
|
@@ -697,26 +984,92 @@ var FlowEngine = class {
|
|
|
697
984
|
* 도메인 커맨드(order.release 등)는 handleCommand 로 위임. 커맨드는 sim 상태를 변이하고
|
|
698
985
|
* State 델타를 유발한다(command → 행위 → 관측 폐루프).
|
|
699
986
|
*/
|
|
987
|
+
_acked = /* @__PURE__ */ new Set();
|
|
988
|
+
// 확인(ack)된 주목 신호 id — 조건 지속돼도 acknowledged 로 표시(재발 시 재활성)
|
|
700
989
|
dispatch(cmd) {
|
|
701
990
|
const ok = () => ({ commandId: cmd.commandId, accepted: true });
|
|
702
|
-
const fail = (
|
|
991
|
+
const fail = (errorCode, errorParams) => ({ commandId: cmd.commandId, accepted: false, errorCode, errorParams, error: errorCode });
|
|
703
992
|
switch (cmd.type) {
|
|
704
993
|
case CMD.orderHold:
|
|
705
994
|
case CMD.orderResume: {
|
|
706
995
|
const orderId = cmd.args?.orderId;
|
|
707
996
|
const order = orderId ? this.orders.get(orderId) : void 0;
|
|
708
|
-
if (!order) return fail(
|
|
997
|
+
if (!order) return fail("order-not-found", { orderId: orderId ?? "" });
|
|
709
998
|
order.held = cmd.type === CMD.orderHold;
|
|
710
999
|
this.emitOrder(order);
|
|
711
1000
|
return ok();
|
|
712
1001
|
}
|
|
1002
|
+
case CMD.attentionAck: {
|
|
1003
|
+
const id = cmd.args?.id;
|
|
1004
|
+
if (id) this._acked.add(id);
|
|
1005
|
+
return ok();
|
|
1006
|
+
}
|
|
1007
|
+
// Operable 코어 — 자원(설비·무버) 제어. capability-keyed(resourceId), 모든 operable 자원 공통.
|
|
1008
|
+
case CMD.resourceHold:
|
|
1009
|
+
case CMD.resourceResume: {
|
|
1010
|
+
const m = this.movers.get(cmd.args?.resourceId ?? "");
|
|
1011
|
+
if (!m) return fail("resource-not-found");
|
|
1012
|
+
m.held = cmd.type === CMD.resourceHold;
|
|
1013
|
+
this.emitMover(m);
|
|
1014
|
+
return ok();
|
|
1015
|
+
}
|
|
1016
|
+
case CMD.resourceDown: {
|
|
1017
|
+
const a = cmd.args;
|
|
1018
|
+
const m = this.movers.get(a?.resourceId ?? "");
|
|
1019
|
+
if (!m) return fail("resource-not-found");
|
|
1020
|
+
if (m.status !== "down") {
|
|
1021
|
+
m.status = "down";
|
|
1022
|
+
m.repairUntilMs = this.clockMs + (Number(a?.durationMs) || m.mttrMs || 6e4);
|
|
1023
|
+
this.emitMover(m);
|
|
1024
|
+
}
|
|
1025
|
+
return ok();
|
|
1026
|
+
}
|
|
1027
|
+
case CMD.resourceRepair: {
|
|
1028
|
+
const m = this.movers.get(cmd.args?.resourceId ?? "");
|
|
1029
|
+
if (!m) return fail("resource-not-found");
|
|
1030
|
+
if (m.status === "down") {
|
|
1031
|
+
m.status = m.taskId ? "busy" : "idle";
|
|
1032
|
+
m.repairUntilMs = void 0;
|
|
1033
|
+
if (m.mtbfMs !== void 0) m.nextFailureMs = this.clockMs + this.sampleExp(m.mtbfMs);
|
|
1034
|
+
this.emitMover(m);
|
|
1035
|
+
}
|
|
1036
|
+
return ok();
|
|
1037
|
+
}
|
|
1038
|
+
case CMD.resourceResetMetrics: {
|
|
1039
|
+
const m = this.movers.get(cmd.args?.resourceId ?? "");
|
|
1040
|
+
if (!m) return fail("resource-not-found");
|
|
1041
|
+
m.runMs = 0;
|
|
1042
|
+
m.setupMs = 0;
|
|
1043
|
+
m.downMs = 0;
|
|
1044
|
+
m.goodCount = 0;
|
|
1045
|
+
m.scrapCount = 0;
|
|
1046
|
+
m.holdMs = 0;
|
|
1047
|
+
m.metricsSinceMs = this.clockMs;
|
|
1048
|
+
this.emitMover(m);
|
|
1049
|
+
return ok();
|
|
1050
|
+
}
|
|
1051
|
+
case CMD.resourceAdd: {
|
|
1052
|
+
const a = cmd.args;
|
|
1053
|
+
if (!a?.kind) return fail("kind-required");
|
|
1054
|
+
if (!a?.homeNode || !this.nodes.has(a.homeNode)) return fail("home-node-not-found", { homeNode: a?.homeNode ?? "" });
|
|
1055
|
+
const count = Math.max(1, Math.min(50, Number(a.count) || 1));
|
|
1056
|
+
let seq = this.movers.size;
|
|
1057
|
+
for (let i = 0; i < count; i++) {
|
|
1058
|
+
let id = `${a.kind}-${++seq}`;
|
|
1059
|
+
while (this.movers.has(id)) id = `${a.kind}-${++seq}`;
|
|
1060
|
+
this.addMover({ id, kind: a.kind, homeNode: a.homeNode });
|
|
1061
|
+
const m = this.movers.get(id);
|
|
1062
|
+
if (m) this.emitMover(m);
|
|
1063
|
+
}
|
|
1064
|
+
return ok();
|
|
1065
|
+
}
|
|
713
1066
|
default:
|
|
714
1067
|
return this.handleCommand(cmd);
|
|
715
1068
|
}
|
|
716
1069
|
}
|
|
717
1070
|
/** 도메인 커맨드 처리(order.release 등). 기본은 거절 — 도메인이 override. */
|
|
718
1071
|
handleCommand(cmd) {
|
|
719
|
-
return { commandId: cmd.commandId, accepted: false,
|
|
1072
|
+
return { commandId: cmd.commandId, accepted: false, errorCode: "unknown-command", errorParams: { type: cmd.type }, error: `unknown-command: ${cmd.type}` };
|
|
720
1073
|
}
|
|
721
1074
|
scenario = {
|
|
722
1075
|
load: (def) => {
|
|
@@ -755,15 +1108,30 @@ var FlowEngine = class {
|
|
|
755
1108
|
nodes: [...this.nodes.values()].map((n) => ({ ...n })),
|
|
756
1109
|
items: [...this.items.values()].map((i) => ({ epc: i.epc, gtin: i.gtin, qty: i.qty, location: i.location, disposition: i.disposition, expiry: i.expiry })),
|
|
757
1110
|
movers: [...this.movers.values()].map((m) => {
|
|
758
|
-
const s = { id: m.id, kind: m.kind, location: m.location, status: m.status, taskId: m.taskId ?? void 0, oee: this.oeeOf(m) };
|
|
1111
|
+
const s = { id: m.id, kind: m.kind, location: m.location, status: m.status, taskId: m.taskId ?? void 0, oee: this.oeeOf(m), held: m.held };
|
|
759
1112
|
const t = m.taskId ? this.tasks.get(m.taskId) : void 0;
|
|
760
1113
|
if (t && t.status === "in-progress" && t.intent !== "process") s.motion = { fromNode: t.fromNode, toNode: t.toNode, startedAtSimMs: this.clockMs - (t.durationMs - t.remainingMs), durationMs: t.durationMs, progress: this.progressOf(t), elapsedMs: t.durationMs - t.remainingMs };
|
|
761
1114
|
return s;
|
|
762
1115
|
}),
|
|
763
|
-
tasks: [...this.tasks.values()].map((t) => ({ id: t.id, kind: t.kind, status: t.status, itemRefs: [t.itemEpc], fromNode: t.fromNode, toNode: t.toNode, resourceRef: t.resource ?? void 0, progress: t.status === "in-progress" ? this.progressOf(t) : void 0 })),
|
|
764
|
-
orders: [...this.orders.values()].map((o) => ({ id: o.id, kind: o.kind, status: o.status, progress: o.requested ? o.fulfilled / o.requested : 0, held: o.held }))
|
|
1116
|
+
tasks: [...this.tasks.values()].map((t) => ({ id: t.id, kind: t.kind, status: t.status, itemRefs: [t.itemEpc], fromNode: t.fromNode, toNode: t.toNode, resourceRef: t.resource ?? void 0, orderId: t.orderId, progress: t.status === "in-progress" ? this.progressOf(t) : void 0 })),
|
|
1117
|
+
orders: [...this.orders.values()].map((o) => ({ id: o.id, kind: o.kind, status: o.status, progress: o.requested ? o.fulfilled / o.requested : 0, held: o.held })),
|
|
1118
|
+
attentions: this.computeAttentions()
|
|
765
1119
|
};
|
|
766
1120
|
}
|
|
1121
|
+
/*
|
|
1122
|
+
* 주목 신호 판단 — 상태(노드·무버·오더)에서 도메인 조건을 평가해 Attention 방출.
|
|
1123
|
+
* severity=ISA-18.2 우선순위 계열, kind=도메인 라벨. UI 는 판단 안 함(임계값 여기 소유).
|
|
1124
|
+
* 도메인별 추가 판단은 서브클래스가 override 로 확장(super.computeAttentions() 합성).
|
|
1125
|
+
*/
|
|
1126
|
+
computeAttentions() {
|
|
1127
|
+
const out = deriveAttentions(
|
|
1128
|
+
{ movers: [...this.movers.values()], nodes: [...this.nodes.values()], orders: [...this.orders.values()] },
|
|
1129
|
+
this._acked
|
|
1130
|
+
);
|
|
1131
|
+
const present = new Set(out.map((a) => a.id));
|
|
1132
|
+
for (const id of [...this._acked]) if (!present.has(id)) this._acked.delete(id);
|
|
1133
|
+
return out;
|
|
1134
|
+
}
|
|
767
1135
|
/**
|
|
768
1136
|
* fork — 현재 상태를 정확히 복제한 새 엔진 (디지털트윈 본연: "현재로부터 예측").
|
|
769
1137
|
* 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로 굴려 forecast·발산(predicted vs actual) 검사에 쓴다.
|
|
@@ -883,27 +1251,11 @@ var FlowEngine = class {
|
|
|
883
1251
|
if (!m) return;
|
|
884
1252
|
if (good) m.goodCount++;
|
|
885
1253
|
else m.scrapCount++;
|
|
1254
|
+
this.emitOp(OP_EVENT.quality, { moverId: m.id, good, goodCount: m.goodCount, scrapCount: m.scrapCount });
|
|
886
1255
|
}
|
|
887
1256
|
/** 무버 OEE(스냅샷 파생) — Availability×Performance×Quality. planned = 설비 존재 sim 시간(clockMs). */
|
|
888
1257
|
oeeOf(m) {
|
|
889
|
-
|
|
890
|
-
const uptime = Math.max(0, planned - m.setupMs - m.downMs);
|
|
891
|
-
const availability = planned > 0 ? uptime / planned : 1;
|
|
892
|
-
const performance = uptime > 0 ? Math.min(1, m.runMs / uptime) : m.runMs > 0 ? 1 : 0;
|
|
893
|
-
const totalQ = m.goodCount + m.scrapCount;
|
|
894
|
-
const quality = totalQ > 0 ? m.goodCount / totalQ : 1;
|
|
895
|
-
return {
|
|
896
|
-
availability,
|
|
897
|
-
performance,
|
|
898
|
-
quality,
|
|
899
|
-
overall: availability * performance * quality,
|
|
900
|
-
runMs: m.runMs,
|
|
901
|
-
setupMs: m.setupMs,
|
|
902
|
-
downMs: m.downMs,
|
|
903
|
-
idleMs: Math.max(0, uptime - m.runMs),
|
|
904
|
-
goodCount: m.goodCount,
|
|
905
|
-
scrapCount: m.scrapCount
|
|
906
|
-
};
|
|
1258
|
+
return computeOee(m, this.clockMs);
|
|
907
1259
|
}
|
|
908
1260
|
/** 정책에 넘길 특정 타입 노드의 관측 뷰 — 예약(그 노드로 향하는 in-flight task) 포함. */
|
|
909
1261
|
slotViews(nodeType) {
|
|
@@ -924,7 +1276,7 @@ var FlowEngine = class {
|
|
|
924
1276
|
for (const h of this.handlers) h(e);
|
|
925
1277
|
}
|
|
926
1278
|
emitTask(t) {
|
|
927
|
-
this.emitOp(OP_EVENT.task, { taskId: t.id, kind: t.kind, status: t.status, fromNode: t.fromNode, toNode: t.toNode, itemRefs: [t.itemEpc], resourceRef: t.resource ?? void 0 });
|
|
1279
|
+
this.emitOp(OP_EVENT.task, { taskId: t.id, orderId: t.orderId, kind: t.kind, status: t.status, fromNode: t.fromNode, toNode: t.toNode, itemRefs: [t.itemEpc], resourceRef: t.resource ?? void 0 });
|
|
928
1280
|
}
|
|
929
1281
|
emitMover(m, motion) {
|
|
930
1282
|
this.emitOp(OP_EVENT.equipment, { moverId: m.id, kind: m.kind, status: m.status, location: m.location, motion });
|
|
@@ -948,16 +1300,21 @@ var FlowEngine = class {
|
|
|
948
1300
|
*/
|
|
949
1301
|
processFailures(dt) {
|
|
950
1302
|
for (const m of this.movers.values()) {
|
|
951
|
-
if (m.mtbfMs === void 0) continue;
|
|
952
1303
|
if (m.status === "down") {
|
|
953
1304
|
m.downMs += dt;
|
|
954
|
-
if (this.clockMs >=
|
|
1305
|
+
if (m.repairUntilMs != null && this.clockMs >= m.repairUntilMs) {
|
|
955
1306
|
m.status = m.taskId ? "busy" : "idle";
|
|
956
1307
|
m.repairUntilMs = void 0;
|
|
957
|
-
m.nextFailureMs = this.clockMs + this.sampleExp(m.mtbfMs);
|
|
1308
|
+
if (m.mtbfMs !== void 0) m.nextFailureMs = this.clockMs + this.sampleExp(m.mtbfMs);
|
|
958
1309
|
this.emitMover(m);
|
|
959
1310
|
}
|
|
960
|
-
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
if (m.held) {
|
|
1314
|
+
if (m.status === "idle") m.holdMs = (m.holdMs ?? 0) + dt;
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
if (m.mtbfMs !== void 0 && this.clockMs >= (m.nextFailureMs ?? Infinity)) {
|
|
961
1318
|
m.status = "down";
|
|
962
1319
|
m.repairUntilMs = this.clockMs + this.sampleExp(m.mttrMs ?? m.mtbfMs);
|
|
963
1320
|
m.nextFailureMs = void 0;
|
|
@@ -971,8 +1328,9 @@ var FlowEngine = class {
|
|
|
971
1328
|
generate() {
|
|
972
1329
|
for (const g of this.gens) {
|
|
973
1330
|
while (this.clockMs >= g.nextMs) {
|
|
974
|
-
|
|
975
|
-
|
|
1331
|
+
const stimulus = g.spec.stimulus ?? (g.spec.kind === "outbound-order" ? "order" : "arrival");
|
|
1332
|
+
if (stimulus === "order") this.onOrder(g.spec);
|
|
1333
|
+
else this.onArrival(g.spec);
|
|
976
1334
|
g.nextMs += this.intervalMs(g.spec);
|
|
977
1335
|
}
|
|
978
1336
|
}
|
|
@@ -989,7 +1347,7 @@ var FlowEngine = class {
|
|
|
989
1347
|
this.emitTask(t);
|
|
990
1348
|
continue;
|
|
991
1349
|
}
|
|
992
|
-
const mover = [...this.movers.values()].find((m) => m.status === "idle" && (t.resourceType === void 0 || m.kind === t.resourceType));
|
|
1350
|
+
const mover = [...this.movers.values()].find((m) => m.status === "idle" && !m.held && (t.resourceType === void 0 || m.kind === t.resourceType));
|
|
993
1351
|
if (!mover) continue;
|
|
994
1352
|
if (t.setupMs && t.changeoverKey !== void 0 && mover.lastChangeoverKey !== void 0 && mover.lastChangeoverKey !== t.changeoverKey) {
|
|
995
1353
|
t.appliedSetupMs = t.setupMs;
|
|
@@ -1088,7 +1446,7 @@ var WmsKernel = class extends FlowEngine {
|
|
|
1088
1446
|
if (cmd.type === CMD.orderRelease) {
|
|
1089
1447
|
const a = cmd.args;
|
|
1090
1448
|
const lines = a?.lines ?? (a?.gtin ? [{ gtin: a.gtin, qty: a.qty ?? 1 }] : []);
|
|
1091
|
-
if (lines.length === 0) return { commandId: cmd.commandId, accepted: false, error: "order.release: gtin
|
|
1449
|
+
if (lines.length === 0) return { commandId: cmd.commandId, accepted: false, errorCode: "order-release-needs-lines", error: "order.release: gtin or lines required" };
|
|
1092
1450
|
this.createSalesOrder(lines);
|
|
1093
1451
|
return { commandId: cmd.commandId, accepted: true };
|
|
1094
1452
|
}
|
|
@@ -1347,6 +1705,7 @@ var YmsKernel = class extends FlowEngine {
|
|
|
1347
1705
|
// src/mes-kernel.ts
|
|
1348
1706
|
var CYCLE_MS = 4e4;
|
|
1349
1707
|
var SETUP_MS = 15e3;
|
|
1708
|
+
var MES_CMD = { changeover: "mes.changeover" };
|
|
1350
1709
|
var CP2 = "0614141";
|
|
1351
1710
|
var WIP_ITEMREF = "066666";
|
|
1352
1711
|
var WIP_GTIN = sgtinClass(CP2, WIP_ITEMREF);
|
|
@@ -1359,17 +1718,49 @@ var PRODUCTS = [
|
|
|
1359
1718
|
];
|
|
1360
1719
|
var MES_PART_GTINS = { partA: PART_A.gtin, partB: PART_B.gtin };
|
|
1361
1720
|
var MES_PRODUCT_GTINS = { p1: PRODUCTS[0].gtin, p2: PRODUCTS[1].gtin };
|
|
1721
|
+
var MES_PRODUCTS = PRODUCTS.map((p) => ({ gtin: p.gtin, label: p.key }));
|
|
1722
|
+
var ROUTE = [
|
|
1723
|
+
{ kind: "cut", node: "cut-station", resource: "cutter" },
|
|
1724
|
+
{ kind: "weld", node: "weld-station", resource: "welder" },
|
|
1725
|
+
{ kind: "paint", node: "paint-booth", resource: "painter" },
|
|
1726
|
+
{ kind: "assembly", node: "assembly-line", resource: "assembler" }
|
|
1727
|
+
];
|
|
1362
1728
|
var MesKernel = class extends FlowEngine {
|
|
1363
1729
|
wipSeq = 0;
|
|
1364
1730
|
prodSeq = 0;
|
|
1365
|
-
|
|
1731
|
+
/** 정의-구동 모드(선택). 미지정 시 레거시 하드코딩 경로 — byte-identical. */
|
|
1732
|
+
mesSpec;
|
|
1733
|
+
constructor(tenantId, policy = firstFitPolicy, mesSpec) {
|
|
1366
1734
|
super(tenantId, policy);
|
|
1735
|
+
this.mesSpec = mesSpec;
|
|
1367
1736
|
}
|
|
1368
1737
|
productOf(gtin) {
|
|
1369
1738
|
return PRODUCTS.find((p) => p.gtin === gtin);
|
|
1370
1739
|
}
|
|
1740
|
+
/**
|
|
1741
|
+
* MES 도메인 커맨드(Tier 2) — mes.changeover: 설비를 제품 gtin 으로 강제 전환.
|
|
1742
|
+
* 자동 체인지오버(task.changeoverKey 상이 시 셋업)의 수동 버전 — 운영자가 사전 전환(툴링 교체) 지시.
|
|
1743
|
+
* 이미 그 제품이면 no-op, 아니면 셋업(SETUP_MS, OEE 가용성 손실) + lastChangeoverKey 각인
|
|
1744
|
+
* (이후 그 제품 task 는 자동 셋업 생략). command → 변이 → State 델타(폐루프).
|
|
1745
|
+
*/
|
|
1746
|
+
handleCommand(cmd) {
|
|
1747
|
+
if (cmd.type === MES_CMD.changeover) {
|
|
1748
|
+
const a = cmd.args;
|
|
1749
|
+
if (!a?.resourceId || !a?.gtin) return { commandId: cmd.commandId, accepted: false, errorCode: "changeover-needs-args", error: "mes.changeover: resourceId and gtin required" };
|
|
1750
|
+
const m = this.movers.get(a.resourceId);
|
|
1751
|
+
if (!m) return { commandId: cmd.commandId, accepted: false, errorCode: "resource-not-found", errorParams: { resourceId: a.resourceId }, error: `resource-not-found: ${a.resourceId}` };
|
|
1752
|
+
if (m.lastChangeoverKey !== a.gtin) {
|
|
1753
|
+
m.setupMs += SETUP_MS;
|
|
1754
|
+
m.lastChangeoverKey = a.gtin;
|
|
1755
|
+
this.emitMover(m);
|
|
1756
|
+
}
|
|
1757
|
+
return { commandId: cmd.commandId, accepted: true };
|
|
1758
|
+
}
|
|
1759
|
+
return super.handleCommand(cmd);
|
|
1760
|
+
}
|
|
1371
1761
|
/** 부품 수령(다품종) — skuMix 의 gtin 으로 부품 종류 결정. */
|
|
1372
1762
|
onArrival(spec) {
|
|
1763
|
+
if (this.mesSpec) return this.onArrivalDef(spec);
|
|
1373
1764
|
const rawStore = this.nodeByType("raw-store");
|
|
1374
1765
|
if (!rawStore) return;
|
|
1375
1766
|
const gtin = this.pickGtin(spec.content.skuMix);
|
|
@@ -1382,6 +1773,7 @@ var MesKernel = class extends FlowEngine {
|
|
|
1382
1773
|
}
|
|
1383
1774
|
/** 작업지시 — 제품 2종 교대(체인지오버 유발). 제품 gtin 을 오더에 기록. */
|
|
1384
1775
|
onOrder(_spec) {
|
|
1776
|
+
if (this.mesSpec) return this.onOrderDef(_spec);
|
|
1385
1777
|
const product = PRODUCTS[this.orderSeq % PRODUCTS.length];
|
|
1386
1778
|
const id = `order-${++this.orderSeq}`;
|
|
1387
1779
|
const wo = gdtiUri(CP2, "403", ++this.soSeq);
|
|
@@ -1389,11 +1781,13 @@ var MesKernel = class extends FlowEngine {
|
|
|
1389
1781
|
this.orders.set(id, order);
|
|
1390
1782
|
this.emitOrder(order);
|
|
1391
1783
|
}
|
|
1392
|
-
/** 할당 — 제품 BOM 각 라인의 부품 예약(하나라도 부족하면 대기) +
|
|
1784
|
+
/** 할당 — 제품 BOM 각 라인의 부품 예약(하나라도 부족하면 대기) + 라우트 첫 스테이션(절단) 태스크. */
|
|
1393
1785
|
allocate(o) {
|
|
1394
|
-
|
|
1786
|
+
if (this.mesSpec) return this.allocateDef(o);
|
|
1787
|
+
const s0 = ROUTE[0];
|
|
1788
|
+
const first = this.nodeByType(s0.node);
|
|
1395
1789
|
const product = this.productOf(o.gtin);
|
|
1396
|
-
if (!
|
|
1790
|
+
if (!first || !product) return;
|
|
1397
1791
|
const picks = [];
|
|
1398
1792
|
for (const line of product.bom) {
|
|
1399
1793
|
const available = [...this.items.values()].filter((i) => i.gtin === line.part.gtin && i.disposition === DISP.sellable && this.nodes.get(i.location)?.type === "raw-store").map((i) => ({ epc: i.epc, location: i.location, qty: 1 }));
|
|
@@ -1406,40 +1800,146 @@ var MesKernel = class extends FlowEngine {
|
|
|
1406
1800
|
o.allocated.push(epc);
|
|
1407
1801
|
}
|
|
1408
1802
|
this.emit(transactionEvent({ eventTime: this.now(), action: "ADD", bizStep: MES_BIZSTEP.producing, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: o.bizTransaction }], epcList: o.allocated.slice() }));
|
|
1409
|
-
|
|
1803
|
+
this.emitStation(o, s0, o.allocated[0], product.gtin);
|
|
1804
|
+
o.status = "op-" + s0.kind;
|
|
1805
|
+
this.emitOrder(o);
|
|
1806
|
+
}
|
|
1807
|
+
/** 라우트 스테이션 태스크 발행(공통) — 제자리 가공(process), 이종 자원, 제품 전환 셋업. */
|
|
1808
|
+
emitStation(o, stage, itemEpc, changeoverKey) {
|
|
1809
|
+
const node = this.nodeByType(stage.node);
|
|
1810
|
+
const task = { id: `task-${++this.taskSeq}`, kind: stage.kind, status: "created", itemEpc, fromNode: node.id, toNode: node.id, resource: null, remainingMs: 0, durationMs: this.durationOf({ kind: stage.kind, fromNode: node.id, toNode: node.id, resourceKind: stage.resource }, CYCLE_MS), orderId: o.id, resourceType: stage.resource, changeoverKey, setupMs: SETUP_MS, intent: "process" };
|
|
1410
1811
|
this.tasks.set(task.id, task);
|
|
1411
1812
|
this.emitTask(task);
|
|
1412
|
-
o.status = "op-cut";
|
|
1413
|
-
this.emitOrder(o);
|
|
1414
1813
|
}
|
|
1415
|
-
/** op 완료 = 변환.
|
|
1814
|
+
/** op 완료 = 변환. 라우트 인덱스로 분기: 첫=BOM 소비→WIP, 중간=WIP→WIP, 마지막(조립)=WIP→완성차(수율→OEE 품질). */
|
|
1416
1815
|
onTaskComplete(t) {
|
|
1816
|
+
if (this.mesSpec) return this.onTaskCompleteDef(t);
|
|
1417
1817
|
const order = this.orders.get(t.orderId);
|
|
1418
1818
|
const product = this.productOf(order.gtin);
|
|
1419
|
-
const
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1819
|
+
const i = ROUTE.findIndex((s) => s.kind === t.kind);
|
|
1820
|
+
const node = this.nodes.get(t.toNode);
|
|
1821
|
+
const isLast = i === ROUTE.length - 1;
|
|
1822
|
+
if (!isLast) {
|
|
1423
1823
|
const inputs = order.allocated.slice();
|
|
1424
1824
|
const wip2 = sgtinUri(CP2, WIP_ITEMREF, ++this.wipSeq);
|
|
1425
|
-
this.transform(inputs, [{ epc: wip2, gtin: WIP_GTIN, qty: 1, location:
|
|
1825
|
+
this.transform(inputs, [{ epc: wip2, gtin: WIP_GTIN, qty: 1, location: node.id, disposition: DISP.in_progress }], { bizStep: MES_BIZSTEP.producing, disposition: DISP.in_progress, transformationId: order.bizTransaction, readPoint: node.id, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: order.bizTransaction }] });
|
|
1426
1826
|
order.allocated = [wip2];
|
|
1427
|
-
const
|
|
1428
|
-
this.
|
|
1429
|
-
|
|
1430
|
-
order.status = "op-weld";
|
|
1827
|
+
const next = ROUTE[i + 1];
|
|
1828
|
+
this.emitStation(order, next, wip2, product.gtin);
|
|
1829
|
+
order.status = "op-" + next.kind;
|
|
1431
1830
|
this.emitOrder(order);
|
|
1432
1831
|
return;
|
|
1433
1832
|
}
|
|
1434
|
-
const weld = this.nodes.get(t.toNode);
|
|
1435
1833
|
const fgStore = this.nodeByType("fg-store");
|
|
1436
1834
|
const wip = order.allocated[0];
|
|
1437
1835
|
const good = this.rng() < YIELD;
|
|
1438
1836
|
this.recordOutput(t.resource, good);
|
|
1439
1837
|
const disp = good ? DISP.sellable : DISP.non_sellable;
|
|
1440
1838
|
const outputEpc = sgtinUri(CP2, product.ref, ++this.prodSeq);
|
|
1441
|
-
this.transform([wip], [{ epc: outputEpc, gtin: product.gtin, qty: 1, location: fgStore.id, disposition: disp }], { bizStep: MES_BIZSTEP.producing, disposition: disp, transformationId: order.bizTransaction, readPoint:
|
|
1442
|
-
this.emit(objectEvent({ eventTime, action: "OBSERVE", bizStep: MES_BIZSTEP.storing, disposition: disp, epcList: [outputEpc], quantityList: [{ epcClass: product.gtin, quantity: 1 }], readPoint: fgStore.id, bizLocation: fgStore.id }));
|
|
1839
|
+
this.transform([wip], [{ epc: outputEpc, gtin: product.gtin, qty: 1, location: fgStore.id, disposition: disp }], { bizStep: MES_BIZSTEP.producing, disposition: disp, transformationId: order.bizTransaction, readPoint: node.id, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: order.bizTransaction }] });
|
|
1840
|
+
this.emit(objectEvent({ eventTime: this.now(), action: "OBSERVE", bizStep: MES_BIZSTEP.storing, disposition: disp, epcList: [outputEpc], quantityList: [{ epcClass: product.gtin, quantity: 1 }], readPoint: fgStore.id, bizLocation: fgStore.id }));
|
|
1841
|
+
order.allocated = [];
|
|
1842
|
+
order.fulfilled = 1;
|
|
1843
|
+
order.status = good ? "produced" : "scrapped";
|
|
1844
|
+
this.emitOrder(order);
|
|
1845
|
+
}
|
|
1846
|
+
// ── 정의-구동 모드 (도메인 정의 데이터로 실행 — 레거시와 분리, 하드코딩 대체) ──
|
|
1847
|
+
recipeDef() {
|
|
1848
|
+
const d = this.mesSpec.definition;
|
|
1849
|
+
return this.mesSpec.recipeKey ? d.recipes?.find((r) => r.key === this.mesSpec.recipeKey) : d.recipes?.[0];
|
|
1850
|
+
}
|
|
1851
|
+
/** 자재 키 → 구체 gtin 클래스(idpat). 구체 식별은 바인딩+prefix 로 인스턴스가 주입. */
|
|
1852
|
+
classOf(materialKey) {
|
|
1853
|
+
return sgtinClass(this.mesSpec.companyPrefix, this.mesSpec.binding[materialKey]);
|
|
1854
|
+
}
|
|
1855
|
+
serialOf(materialKey, serial) {
|
|
1856
|
+
return sgtinUri(this.mesSpec.companyPrefix, this.mesSpec.binding[materialKey], serial);
|
|
1857
|
+
}
|
|
1858
|
+
/** recipe.route → 오퍼레이션 시퀀스 해소. */
|
|
1859
|
+
routeOps() {
|
|
1860
|
+
const d = this.mesSpec.definition;
|
|
1861
|
+
const route = d.routes?.find((r) => r.key === this.recipeDef().route);
|
|
1862
|
+
return (route?.steps ?? []).map((sk) => d.operations?.find((o) => o.key === sk)).filter((o) => !!o);
|
|
1863
|
+
}
|
|
1864
|
+
/** 정의 모드 수령 — skuMix gtin 이 레시피 입력 자재면 raw-store 에 생성. */
|
|
1865
|
+
onArrivalDef(spec) {
|
|
1866
|
+
const rawStore = this.nodeByType("raw-store");
|
|
1867
|
+
if (!rawStore) return;
|
|
1868
|
+
const gtin = this.pickGtin(spec.content.skuMix);
|
|
1869
|
+
const inputKey = this.recipeDef().inputs.map((i) => i.material).find((k) => this.classOf(k) === gtin);
|
|
1870
|
+
if (!inputKey) return;
|
|
1871
|
+
const epc = this.serialOf(inputKey, ++this.epcSeq);
|
|
1872
|
+
this.items.set(epc, { epc, gtin, qty: 1, location: rawStore.id, disposition: DISP.sellable });
|
|
1873
|
+
rawStore.occupancy++;
|
|
1874
|
+
this.emit(objectEvent({ eventTime: this.now(), action: "ADD", bizStep: MES_BIZSTEP.receiving, disposition: DISP.sellable, epcList: [epc], quantityList: [{ epcClass: gtin, quantity: 1 }], readPoint: rawStore.id, bizLocation: rawStore.id }));
|
|
1875
|
+
}
|
|
1876
|
+
/** 정의 모드 작업지시 — 레시피 산출물 1개. */
|
|
1877
|
+
onOrderDef(_spec) {
|
|
1878
|
+
const rc = this.recipeDef();
|
|
1879
|
+
const id = `order-${++this.orderSeq}`;
|
|
1880
|
+
const wo = gdtiUri(this.mesSpec.companyPrefix, "403", ++this.soSeq);
|
|
1881
|
+
const order = { id, kind: "workorder", status: "created", gtin: this.classOf(rc.outputs[0].material), requested: 1, fulfilled: 0, bizTransaction: wo, allocated: [], picked: [] };
|
|
1882
|
+
this.orders.set(id, order);
|
|
1883
|
+
this.emitOrder(order);
|
|
1884
|
+
}
|
|
1885
|
+
/** 정의 모드 할당 — 레시피 입력 BOM 전량 확보 후 첫 라우트 스텝 태스크. */
|
|
1886
|
+
allocateDef(o) {
|
|
1887
|
+
const ops = this.routeOps();
|
|
1888
|
+
if (!ops.length || !ops[0].nodeType || !this.nodeByType(ops[0].nodeType)) return;
|
|
1889
|
+
const rc = this.recipeDef();
|
|
1890
|
+
const picks = [];
|
|
1891
|
+
for (const line of rc.inputs) {
|
|
1892
|
+
const g = this.classOf(line.material);
|
|
1893
|
+
const available = [...this.items.values()].filter((i) => i.gtin === g && i.disposition === DISP.sellable && this.nodes.get(i.location)?.type === "raw-store").map((i) => ({ epc: i.epc, location: i.location, qty: 1 }));
|
|
1894
|
+
const chosen = this.policy.selectStock({ gtin: g, qty: line.qty, available });
|
|
1895
|
+
if (chosen.length < line.qty) return;
|
|
1896
|
+
picks.push(...chosen);
|
|
1897
|
+
}
|
|
1898
|
+
for (const epc of picks) {
|
|
1899
|
+
this.items.get(epc).disposition = DISP.reserved;
|
|
1900
|
+
o.allocated.push(epc);
|
|
1901
|
+
}
|
|
1902
|
+
this.emit(transactionEvent({ eventTime: this.now(), action: "ADD", bizStep: MES_BIZSTEP.producing, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: o.bizTransaction }], epcList: o.allocated.slice() }));
|
|
1903
|
+
this.emitStationDef(o, ops[0], o.allocated[0]);
|
|
1904
|
+
o.status = "op-" + ops[0].key;
|
|
1905
|
+
this.emitOrder(o);
|
|
1906
|
+
}
|
|
1907
|
+
emitStationDef(o, op, itemEpc) {
|
|
1908
|
+
const node = this.nodeByType(op.nodeType);
|
|
1909
|
+
const task = { id: `task-${++this.taskSeq}`, kind: op.key, status: "created", itemEpc, fromNode: node.id, toNode: node.id, resource: null, remainingMs: 0, durationMs: this.durationOf({ kind: op.key, fromNode: node.id, toNode: node.id, resourceKind: op.resourceType }, CYCLE_MS), orderId: o.id, resourceType: op.resourceType, changeoverKey: o.gtin, setupMs: SETUP_MS, intent: op.intent };
|
|
1910
|
+
this.tasks.set(task.id, task);
|
|
1911
|
+
this.emitTask(task);
|
|
1912
|
+
}
|
|
1913
|
+
/** 정의 모드 완료 — 라우트 인덱스: 중간=WIP 변환+다음 스텝, 마지막=완제품(수율). */
|
|
1914
|
+
onTaskCompleteDef(t) {
|
|
1915
|
+
const order = this.orders.get(t.orderId);
|
|
1916
|
+
const rc = this.recipeDef();
|
|
1917
|
+
const ops = this.routeOps();
|
|
1918
|
+
const i = ops.findIndex((s) => s.key === t.kind);
|
|
1919
|
+
const node = this.nodes.get(t.toNode);
|
|
1920
|
+
const bizStep = ops[i]?.bizStep ?? MES_BIZSTEP.producing;
|
|
1921
|
+
const isLast = i === ops.length - 1;
|
|
1922
|
+
if (!isLast) {
|
|
1923
|
+
const inputs = order.allocated.slice();
|
|
1924
|
+
const wip2 = sgtinUri(this.mesSpec.companyPrefix, "WIP", ++this.wipSeq);
|
|
1925
|
+
const wipGtin = sgtinClass(this.mesSpec.companyPrefix, "WIP");
|
|
1926
|
+
this.transform(inputs, [{ epc: wip2, gtin: wipGtin, qty: 1, location: node.id, disposition: DISP.in_progress }], { bizStep, disposition: DISP.in_progress, transformationId: order.bizTransaction, readPoint: node.id, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: order.bizTransaction }] });
|
|
1927
|
+
order.allocated = [wip2];
|
|
1928
|
+
const next = ops[i + 1];
|
|
1929
|
+
this.emitStationDef(order, next, wip2);
|
|
1930
|
+
order.status = "op-" + next.key;
|
|
1931
|
+
this.emitOrder(order);
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
const fgStore = this.nodeByType("fg-store");
|
|
1935
|
+
const wip = order.allocated[0];
|
|
1936
|
+
const good = this.rng() < YIELD;
|
|
1937
|
+
this.recordOutput(t.resource, good);
|
|
1938
|
+
const disp = good ? DISP.sellable : DISP.non_sellable;
|
|
1939
|
+
const outEpc = this.serialOf(rc.outputs[0].material, ++this.prodSeq);
|
|
1940
|
+
const outGtin = this.classOf(rc.outputs[0].material);
|
|
1941
|
+
this.transform([wip], [{ epc: outEpc, gtin: outGtin, qty: 1, location: fgStore.id, disposition: disp }], { bizStep, disposition: disp, transformationId: order.bizTransaction, readPoint: node.id, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: order.bizTransaction }] });
|
|
1942
|
+
this.emit(objectEvent({ eventTime: this.now(), action: "OBSERVE", bizStep: MES_BIZSTEP.storing, disposition: disp, epcList: [outEpc], quantityList: [{ epcClass: outGtin, quantity: 1 }], readPoint: fgStore.id, bizLocation: fgStore.id }));
|
|
1443
1943
|
order.allocated = [];
|
|
1444
1944
|
order.fulfilled = 1;
|
|
1445
1945
|
order.status = good ? "produced" : "scrapped";
|
|
@@ -1452,6 +1952,8 @@ var MesKernel = class extends FlowEngine {
|
|
|
1452
1952
|
BTT,
|
|
1453
1953
|
BTT_DELIVERY,
|
|
1454
1954
|
BTT_PRODORDER,
|
|
1955
|
+
CAPABILITIES,
|
|
1956
|
+
CAPABILITY_KEYS,
|
|
1455
1957
|
CMD,
|
|
1456
1958
|
DISP,
|
|
1457
1959
|
DOMAIN_CATALOG,
|
|
@@ -1462,7 +1964,9 @@ var MesKernel = class extends FlowEngine {
|
|
|
1462
1964
|
MES_BIZSTEP,
|
|
1463
1965
|
MES_NODE_TYPES,
|
|
1464
1966
|
MES_PART_GTINS,
|
|
1967
|
+
MES_PRODUCTS,
|
|
1465
1968
|
MES_PRODUCT_GTINS,
|
|
1969
|
+
MES_TYPES,
|
|
1466
1970
|
MesKernel,
|
|
1467
1971
|
OP_EVENT,
|
|
1468
1972
|
StateProjector,
|
|
@@ -1471,14 +1975,19 @@ var MesKernel = class extends FlowEngine {
|
|
|
1471
1975
|
TwinRuntime,
|
|
1472
1976
|
UTC_OFFSET,
|
|
1473
1977
|
WMS_NODE_TYPES,
|
|
1978
|
+
WMS_TYPES,
|
|
1474
1979
|
WmsKernel,
|
|
1475
1980
|
YARD_BIZSTEP,
|
|
1476
1981
|
YMS_NODE_TYPES,
|
|
1982
|
+
YMS_TYPES,
|
|
1477
1983
|
YmsKernel,
|
|
1478
1984
|
aggregationEvent,
|
|
1985
|
+
capabilitiesForType,
|
|
1479
1986
|
compareStates,
|
|
1987
|
+
computeOee,
|
|
1480
1988
|
constantDuration,
|
|
1481
1989
|
counterfactualAt,
|
|
1990
|
+
deriveAttentions,
|
|
1482
1991
|
fefoPolicy,
|
|
1483
1992
|
firstFitPolicy,
|
|
1484
1993
|
gdtiUri,
|
|
@@ -1492,7 +2001,9 @@ var MesKernel = class extends FlowEngine {
|
|
|
1492
2001
|
sgtinClass,
|
|
1493
2002
|
sgtinUri,
|
|
1494
2003
|
ssccUri,
|
|
2004
|
+
stateFieldsOf,
|
|
1495
2005
|
transactionEvent,
|
|
1496
2006
|
transformationEvent,
|
|
2007
|
+
validateDomainDefinition,
|
|
1497
2008
|
validateEpcisEvent
|
|
1498
2009
|
});
|