@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/mes-kernel.js
CHANGED
|
@@ -14,6 +14,8 @@ import { DISP, objectEvent, transactionEvent, gdtiUri, sgtinClass } from "./epci
|
|
|
14
14
|
import { MES_BIZSTEP, BTT_PRODORDER, sgtinUri } from "./mes-profile.js";
|
|
15
15
|
const CYCLE_MS = 40_000;
|
|
16
16
|
const SETUP_MS = 15_000; // 체인지오버(제품 전환) 셋업 — OEE 가용성 손실
|
|
17
|
+
// MES 도메인 커맨드(Tier 2, 무방언 — MES 어휘는 MES 커널 소유). handleCommand 로 처리.
|
|
18
|
+
const MES_CMD = { changeover: 'mes.changeover' };
|
|
17
19
|
const CP = '0614141';
|
|
18
20
|
const WIP_ITEMREF = '066666';
|
|
19
21
|
const WIP_GTIN = sgtinClass(CP, WIP_ITEMREF);
|
|
@@ -30,15 +32,56 @@ const PRODUCTS = [
|
|
|
30
32
|
export const MES_PART_GTINS = { partA: PART_A.gtin, partB: PART_B.gtin };
|
|
31
33
|
/** 편의 — 완제품 클래스(제품 2종). */
|
|
32
34
|
export const MES_PRODUCT_GTINS = { p1: PRODUCTS[0].gtin, p2: PRODUCTS[1].gtin };
|
|
35
|
+
/** 레거시 MES 제품 목록(gtin+라벨) — mes.changeover 대상 제품 소싱용. 정의-구동 전환 시 recipe 출력으로 교체. */
|
|
36
|
+
export const MES_PRODUCTS = PRODUCTS.map(p => ({ gtin: p.gtin, label: p.key }));
|
|
37
|
+
/*
|
|
38
|
+
* 트레일러 제조 라우트 — 순차 스테이션. 각 스테이션 완료가 다음 WIP 로 변환 + 다음 스테이션 태스크 발행.
|
|
39
|
+
* 첫 스테이션(cut)은 BOM 부품 N→WIP, 중간은 WIP→WIP, 마지막(assembly)은 WIP→완성차(+수율/OEE).
|
|
40
|
+
* 각 스테이션 = 이종 자원(resource) + 제품 전환 시 체인지오버 셋업.
|
|
41
|
+
*/
|
|
42
|
+
const ROUTE = [
|
|
43
|
+
{ kind: 'cut', node: 'cut-station', resource: 'cutter' },
|
|
44
|
+
{ kind: 'weld', node: 'weld-station', resource: 'welder' },
|
|
45
|
+
{ kind: 'paint', node: 'paint-booth', resource: 'painter' },
|
|
46
|
+
{ kind: 'assembly', node: 'assembly-line', resource: 'assembler' }
|
|
47
|
+
];
|
|
33
48
|
export class MesKernel extends FlowEngine {
|
|
34
49
|
wipSeq = 0;
|
|
35
50
|
prodSeq = 0;
|
|
36
|
-
|
|
51
|
+
/** 정의-구동 모드(선택). 미지정 시 레거시 하드코딩 경로 — byte-identical. */
|
|
52
|
+
mesSpec;
|
|
53
|
+
constructor(tenantId, policy = firstFitPolicy, mesSpec) {
|
|
37
54
|
super(tenantId, policy);
|
|
55
|
+
this.mesSpec = mesSpec;
|
|
38
56
|
}
|
|
39
57
|
productOf(gtin) { return PRODUCTS.find(p => p.gtin === gtin); }
|
|
58
|
+
/**
|
|
59
|
+
* MES 도메인 커맨드(Tier 2) — mes.changeover: 설비를 제품 gtin 으로 강제 전환.
|
|
60
|
+
* 자동 체인지오버(task.changeoverKey 상이 시 셋업)의 수동 버전 — 운영자가 사전 전환(툴링 교체) 지시.
|
|
61
|
+
* 이미 그 제품이면 no-op, 아니면 셋업(SETUP_MS, OEE 가용성 손실) + lastChangeoverKey 각인
|
|
62
|
+
* (이후 그 제품 task 는 자동 셋업 생략). command → 변이 → State 델타(폐루프).
|
|
63
|
+
*/
|
|
64
|
+
handleCommand(cmd) {
|
|
65
|
+
if (cmd.type === MES_CMD.changeover) {
|
|
66
|
+
const a = cmd.args;
|
|
67
|
+
if (!a?.resourceId || !a?.gtin)
|
|
68
|
+
return { commandId: cmd.commandId, accepted: false, errorCode: 'changeover-needs-args', error: 'mes.changeover: resourceId and gtin required' };
|
|
69
|
+
const m = this.movers.get(a.resourceId);
|
|
70
|
+
if (!m)
|
|
71
|
+
return { commandId: cmd.commandId, accepted: false, errorCode: 'resource-not-found', errorParams: { resourceId: a.resourceId }, error: `resource-not-found: ${a.resourceId}` };
|
|
72
|
+
if (m.lastChangeoverKey !== a.gtin) {
|
|
73
|
+
m.setupMs += SETUP_MS; // 셋업 = OEE 가용성 손실
|
|
74
|
+
m.lastChangeoverKey = a.gtin;
|
|
75
|
+
this.emitMover(m);
|
|
76
|
+
}
|
|
77
|
+
return { commandId: cmd.commandId, accepted: true };
|
|
78
|
+
}
|
|
79
|
+
return super.handleCommand(cmd);
|
|
80
|
+
}
|
|
40
81
|
/** 부품 수령(다품종) — skuMix 의 gtin 으로 부품 종류 결정. */
|
|
41
82
|
onArrival(spec) {
|
|
83
|
+
if (this.mesSpec)
|
|
84
|
+
return this.onArrivalDef(spec);
|
|
42
85
|
const rawStore = this.nodeByType('raw-store');
|
|
43
86
|
if (!rawStore)
|
|
44
87
|
return;
|
|
@@ -53,6 +96,8 @@ export class MesKernel extends FlowEngine {
|
|
|
53
96
|
}
|
|
54
97
|
/** 작업지시 — 제품 2종 교대(체인지오버 유발). 제품 gtin 을 오더에 기록. */
|
|
55
98
|
onOrder(_spec) {
|
|
99
|
+
if (this.mesSpec)
|
|
100
|
+
return this.onOrderDef(_spec);
|
|
56
101
|
const product = PRODUCTS[this.orderSeq % PRODUCTS.length];
|
|
57
102
|
const id = `order-${++this.orderSeq}`;
|
|
58
103
|
const wo = gdtiUri(CP, '403', ++this.soSeq);
|
|
@@ -60,11 +105,14 @@ export class MesKernel extends FlowEngine {
|
|
|
60
105
|
this.orders.set(id, order);
|
|
61
106
|
this.emitOrder(order);
|
|
62
107
|
}
|
|
63
|
-
/** 할당 — 제품 BOM 각 라인의 부품 예약(하나라도 부족하면 대기) +
|
|
108
|
+
/** 할당 — 제품 BOM 각 라인의 부품 예약(하나라도 부족하면 대기) + 라우트 첫 스테이션(절단) 태스크. */
|
|
64
109
|
allocate(o) {
|
|
65
|
-
|
|
110
|
+
if (this.mesSpec)
|
|
111
|
+
return this.allocateDef(o);
|
|
112
|
+
const s0 = ROUTE[0];
|
|
113
|
+
const first = this.nodeByType(s0.node);
|
|
66
114
|
const product = this.productOf(o.gtin);
|
|
67
|
-
if (!
|
|
115
|
+
if (!first || !product)
|
|
68
116
|
return;
|
|
69
117
|
// BOM 전 라인 확보 확인(부족하면 아무것도 예약 안 하고 대기)
|
|
70
118
|
const picks = [];
|
|
@@ -82,42 +130,155 @@ export class MesKernel extends FlowEngine {
|
|
|
82
130
|
o.allocated.push(epc);
|
|
83
131
|
}
|
|
84
132
|
this.emit(transactionEvent({ eventTime: this.now(), action: 'ADD', bizStep: MES_BIZSTEP.producing, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: o.bizTransaction }], epcList: o.allocated.slice() }));
|
|
85
|
-
|
|
133
|
+
this.emitStation(o, s0, o.allocated[0], product.gtin);
|
|
134
|
+
o.status = 'op-' + s0.kind;
|
|
135
|
+
this.emitOrder(o);
|
|
136
|
+
}
|
|
137
|
+
/** 라우트 스테이션 태스크 발행(공통) — 제자리 가공(process), 이종 자원, 제품 전환 셋업. */
|
|
138
|
+
emitStation(o, stage, itemEpc, changeoverKey) {
|
|
139
|
+
const node = this.nodeByType(stage.node);
|
|
140
|
+
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' };
|
|
86
141
|
this.tasks.set(task.id, task);
|
|
87
142
|
this.emitTask(task);
|
|
88
|
-
o.status = 'op-cut';
|
|
89
|
-
this.emitOrder(o);
|
|
90
143
|
}
|
|
91
|
-
/** op 완료 = 변환.
|
|
144
|
+
/** op 완료 = 변환. 라우트 인덱스로 분기: 첫=BOM 소비→WIP, 중간=WIP→WIP, 마지막(조립)=WIP→완성차(수율→OEE 품질). */
|
|
92
145
|
onTaskComplete(t) {
|
|
146
|
+
if (this.mesSpec)
|
|
147
|
+
return this.onTaskCompleteDef(t);
|
|
93
148
|
const order = this.orders.get(t.orderId);
|
|
94
149
|
const product = this.productOf(order.gtin);
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
150
|
+
const i = ROUTE.findIndex(s => s.kind === t.kind);
|
|
151
|
+
const node = this.nodes.get(t.toNode);
|
|
152
|
+
const isLast = i === ROUTE.length - 1;
|
|
153
|
+
if (!isLast) {
|
|
154
|
+
// 중간 스테이션 — 입력(첫=BOM 전부 / 이후=이전 WIP) → 다음 WIP, 다음 스테이션 태스크.
|
|
155
|
+
const inputs = order.allocated.slice();
|
|
100
156
|
const wip = sgtinUri(CP, WIP_ITEMREF, ++this.wipSeq);
|
|
101
|
-
|
|
102
|
-
this.transform(inputs, [{ epc: wip, gtin: WIP_GTIN, qty: 1, location: cut.id, disposition: DISP.in_progress }], { bizStep: MES_BIZSTEP.producing, disposition: DISP.in_progress, transformationId: order.bizTransaction, readPoint: cut.id, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: order.bizTransaction }] });
|
|
157
|
+
this.transform(inputs, [{ epc: wip, 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 }] });
|
|
103
158
|
order.allocated = [wip];
|
|
104
|
-
const
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
order.status = 'op-weld';
|
|
159
|
+
const next = ROUTE[i + 1];
|
|
160
|
+
this.emitStation(order, next, wip, product.gtin);
|
|
161
|
+
order.status = 'op-' + next.kind;
|
|
108
162
|
this.emitOrder(order);
|
|
109
163
|
return;
|
|
110
164
|
}
|
|
111
|
-
//
|
|
112
|
-
const weld = this.nodes.get(t.toNode);
|
|
165
|
+
// 마지막 스테이션(조립) → 완성차 (transform 1→1, disposition 으로 수율 loss → OEE 품질)
|
|
113
166
|
const fgStore = this.nodeByType('fg-store');
|
|
114
167
|
const wip = order.allocated[0];
|
|
115
168
|
const good = this.rng() < YIELD;
|
|
116
|
-
this.recordOutput(t.resource, good); // OEE 품질(
|
|
169
|
+
this.recordOutput(t.resource, good); // OEE 품질(마지막 자원별 양품/불량)
|
|
117
170
|
const disp = good ? DISP.sellable : DISP.non_sellable;
|
|
118
171
|
const outputEpc = sgtinUri(CP, product.ref, ++this.prodSeq);
|
|
119
|
-
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:
|
|
120
|
-
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 }));
|
|
172
|
+
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 }] });
|
|
173
|
+
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 }));
|
|
174
|
+
order.allocated = [];
|
|
175
|
+
order.fulfilled = 1;
|
|
176
|
+
order.status = good ? 'produced' : 'scrapped';
|
|
177
|
+
this.emitOrder(order);
|
|
178
|
+
}
|
|
179
|
+
// ── 정의-구동 모드 (도메인 정의 데이터로 실행 — 레거시와 분리, 하드코딩 대체) ──
|
|
180
|
+
recipeDef() {
|
|
181
|
+
const d = this.mesSpec.definition;
|
|
182
|
+
return (this.mesSpec.recipeKey ? d.recipes?.find(r => r.key === this.mesSpec.recipeKey) : d.recipes?.[0]);
|
|
183
|
+
}
|
|
184
|
+
/** 자재 키 → 구체 gtin 클래스(idpat). 구체 식별은 바인딩+prefix 로 인스턴스가 주입. */
|
|
185
|
+
classOf(materialKey) {
|
|
186
|
+
return sgtinClass(this.mesSpec.companyPrefix, this.mesSpec.binding[materialKey]);
|
|
187
|
+
}
|
|
188
|
+
serialOf(materialKey, serial) {
|
|
189
|
+
return sgtinUri(this.mesSpec.companyPrefix, this.mesSpec.binding[materialKey], serial);
|
|
190
|
+
}
|
|
191
|
+
/** recipe.route → 오퍼레이션 시퀀스 해소. */
|
|
192
|
+
routeOps() {
|
|
193
|
+
const d = this.mesSpec.definition;
|
|
194
|
+
const route = d.routes?.find(r => r.key === this.recipeDef().route);
|
|
195
|
+
return (route?.steps ?? []).map(sk => d.operations?.find(o => o.key === sk)).filter((o) => !!o);
|
|
196
|
+
}
|
|
197
|
+
/** 정의 모드 수령 — skuMix gtin 이 레시피 입력 자재면 raw-store 에 생성. */
|
|
198
|
+
onArrivalDef(spec) {
|
|
199
|
+
const rawStore = this.nodeByType('raw-store');
|
|
200
|
+
if (!rawStore)
|
|
201
|
+
return;
|
|
202
|
+
const gtin = this.pickGtin(spec.content.skuMix);
|
|
203
|
+
const inputKey = this.recipeDef().inputs.map(i => i.material).find(k => this.classOf(k) === gtin);
|
|
204
|
+
if (!inputKey)
|
|
205
|
+
return;
|
|
206
|
+
const epc = this.serialOf(inputKey, ++this.epcSeq);
|
|
207
|
+
this.items.set(epc, { epc, gtin, qty: 1, location: rawStore.id, disposition: DISP.sellable });
|
|
208
|
+
rawStore.occupancy++;
|
|
209
|
+
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 }));
|
|
210
|
+
}
|
|
211
|
+
/** 정의 모드 작업지시 — 레시피 산출물 1개. */
|
|
212
|
+
onOrderDef(_spec) {
|
|
213
|
+
const rc = this.recipeDef();
|
|
214
|
+
const id = `order-${++this.orderSeq}`;
|
|
215
|
+
const wo = gdtiUri(this.mesSpec.companyPrefix, '403', ++this.soSeq);
|
|
216
|
+
const order = { id, kind: 'workorder', status: 'created', gtin: this.classOf(rc.outputs[0].material), requested: 1, fulfilled: 0, bizTransaction: wo, allocated: [], picked: [] };
|
|
217
|
+
this.orders.set(id, order);
|
|
218
|
+
this.emitOrder(order);
|
|
219
|
+
}
|
|
220
|
+
/** 정의 모드 할당 — 레시피 입력 BOM 전량 확보 후 첫 라우트 스텝 태스크. */
|
|
221
|
+
allocateDef(o) {
|
|
222
|
+
const ops = this.routeOps();
|
|
223
|
+
if (!ops.length || !ops[0].nodeType || !this.nodeByType(ops[0].nodeType))
|
|
224
|
+
return;
|
|
225
|
+
const rc = this.recipeDef();
|
|
226
|
+
const picks = [];
|
|
227
|
+
for (const line of rc.inputs) {
|
|
228
|
+
const g = this.classOf(line.material);
|
|
229
|
+
const available = [...this.items.values()]
|
|
230
|
+
.filter(i => i.gtin === g && i.disposition === DISP.sellable && this.nodes.get(i.location)?.type === 'raw-store')
|
|
231
|
+
.map(i => ({ epc: i.epc, location: i.location, qty: 1 }));
|
|
232
|
+
const chosen = this.policy.selectStock({ gtin: g, qty: line.qty, available });
|
|
233
|
+
if (chosen.length < line.qty)
|
|
234
|
+
return; // 자재 부족 → 대기
|
|
235
|
+
picks.push(...chosen);
|
|
236
|
+
}
|
|
237
|
+
for (const epc of picks) {
|
|
238
|
+
this.items.get(epc).disposition = DISP.reserved;
|
|
239
|
+
o.allocated.push(epc);
|
|
240
|
+
}
|
|
241
|
+
this.emit(transactionEvent({ eventTime: this.now(), action: 'ADD', bizStep: MES_BIZSTEP.producing, bizTransactionList: [{ type: BTT_PRODORDER, bizTransaction: o.bizTransaction }], epcList: o.allocated.slice() }));
|
|
242
|
+
this.emitStationDef(o, ops[0], o.allocated[0]);
|
|
243
|
+
o.status = 'op-' + ops[0].key;
|
|
244
|
+
this.emitOrder(o);
|
|
245
|
+
}
|
|
246
|
+
emitStationDef(o, op, itemEpc) {
|
|
247
|
+
const node = this.nodeByType(op.nodeType);
|
|
248
|
+
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 };
|
|
249
|
+
this.tasks.set(task.id, task);
|
|
250
|
+
this.emitTask(task);
|
|
251
|
+
}
|
|
252
|
+
/** 정의 모드 완료 — 라우트 인덱스: 중간=WIP 변환+다음 스텝, 마지막=완제품(수율). */
|
|
253
|
+
onTaskCompleteDef(t) {
|
|
254
|
+
const order = this.orders.get(t.orderId);
|
|
255
|
+
const rc = this.recipeDef();
|
|
256
|
+
const ops = this.routeOps();
|
|
257
|
+
const i = ops.findIndex(s => s.key === t.kind);
|
|
258
|
+
const node = this.nodes.get(t.toNode);
|
|
259
|
+
const bizStep = ops[i]?.bizStep ?? MES_BIZSTEP.producing;
|
|
260
|
+
const isLast = i === ops.length - 1;
|
|
261
|
+
if (!isLast) {
|
|
262
|
+
const inputs = order.allocated.slice();
|
|
263
|
+
const wip = sgtinUri(this.mesSpec.companyPrefix, 'WIP', ++this.wipSeq);
|
|
264
|
+
const wipGtin = sgtinClass(this.mesSpec.companyPrefix, 'WIP');
|
|
265
|
+
this.transform(inputs, [{ epc: wip, 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 }] });
|
|
266
|
+
order.allocated = [wip];
|
|
267
|
+
const next = ops[i + 1];
|
|
268
|
+
this.emitStationDef(order, next, wip);
|
|
269
|
+
order.status = 'op-' + next.key;
|
|
270
|
+
this.emitOrder(order);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const fgStore = this.nodeByType('fg-store');
|
|
274
|
+
const wip = order.allocated[0];
|
|
275
|
+
const good = this.rng() < YIELD;
|
|
276
|
+
this.recordOutput(t.resource, good);
|
|
277
|
+
const disp = good ? DISP.sellable : DISP.non_sellable;
|
|
278
|
+
const outEpc = this.serialOf(rc.outputs[0].material, ++this.prodSeq);
|
|
279
|
+
const outGtin = this.classOf(rc.outputs[0].material);
|
|
280
|
+
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 }] });
|
|
281
|
+
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 }));
|
|
121
282
|
order.allocated = [];
|
|
122
283
|
order.fulfilled = 1;
|
|
123
284
|
order.status = good ? 'produced' : 'scrapped';
|
package/dist/mes-profile.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TwinTypeInfo } from './domain-catalog.ts';
|
|
1
2
|
export declare const MES_BIZSTEP: {
|
|
2
3
|
readonly receiving: "urn:epcglobal:cbv:bizstep:receiving";
|
|
3
4
|
readonly producing: "urn:epcglobal:cbv:bizstep:commissioning";
|
|
@@ -7,5 +8,7 @@ export declare const MES_BIZSTEP: {
|
|
|
7
8
|
export declare const BTT_PRODORDER = "urn:epcglobal:cbv:btt:prodorder";
|
|
8
9
|
/** 직렬 SGTIN URI (원자재 단위·완제품). */
|
|
9
10
|
export declare function sgtinUri(companyPrefix: string, itemRef: string, serial: number): string;
|
|
10
|
-
/** MES 노드 타입 카탈로그 — 커널이 키로 쓰는 제조 로케이션 타입(nodeByType/.type). 도메인 SSOT.
|
|
11
|
-
|
|
11
|
+
/** MES 노드 타입 카탈로그 — 커널이 키로 쓰는 제조 로케이션 타입(nodeByType/.type). 도메인 SSOT.
|
|
12
|
+
* 트레일러 제조 라인: 자재→프레임 절단→용접→도장→조립→완성차(kernel ROUTE 와 일치). */
|
|
13
|
+
export declare const MES_NODE_TYPES: readonly ["raw-store", "cut-station", "weld-station", "paint-booth", "assembly-line", "fg-store"];
|
|
14
|
+
export declare const MES_TYPES: TwinTypeInfo[];
|
package/dist/mes-profile.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* MES Profile — 제조(manufacturing) 버티컬. 세 번째 프로파일 = base 를 "변환"으로 가장 강하게 stress.
|
|
3
|
-
* 재료 소비 → 제품 생산(이동 아님). EPCIS 2.0 TransformationEvent + epcis 빌더 재사용.
|
|
4
|
-
* (MES=ISA-95 앵커지만 이벤트 모델은 EPCIS TransformationEvent 가 제조 추적의 정합 표준.)
|
|
5
|
-
*/
|
|
6
1
|
// MES bizStep — CBV 재사용. producing = commissioning(신규 제품 커미셔닝).
|
|
7
2
|
export const MES_BIZSTEP = {
|
|
8
3
|
receiving: 'urn:epcglobal:cbv:bizstep:receiving', // 원자재 수령
|
|
@@ -15,5 +10,26 @@ export const BTT_PRODORDER = 'urn:epcglobal:cbv:btt:prodorder';
|
|
|
15
10
|
export function sgtinUri(companyPrefix, itemRef, serial) {
|
|
16
11
|
return `urn:epc:id:sgtin:${companyPrefix}.${itemRef}.${serial}`;
|
|
17
12
|
}
|
|
18
|
-
/** MES 노드 타입 카탈로그 — 커널이 키로 쓰는 제조 로케이션 타입(nodeByType/.type). 도메인 SSOT.
|
|
19
|
-
|
|
13
|
+
/** MES 노드 타입 카탈로그 — 커널이 키로 쓰는 제조 로케이션 타입(nodeByType/.type). 도메인 SSOT.
|
|
14
|
+
* 트레일러 제조 라인: 자재→프레임 절단→용접→도장→조립→완성차(kernel ROUTE 와 일치). */
|
|
15
|
+
export const MES_NODE_TYPES = ['raw-store', 'cut-station', 'weld-station', 'paint-booth', 'assembly-line', 'fg-store'];
|
|
16
|
+
/*
|
|
17
|
+
* 트윈 타입 서술(ADR-0018 확장) — 노드 키는 MES_NODE_TYPES 단일 출처에서 파생 + 무버(자원) 타입 추가.
|
|
18
|
+
* MES=ISA-95 앵커: 스테이션=WorkCenter, 저장소=bizLocation. 무버(가공설비)=Equipment/자산(GIAI). key 는 flow resourceType 과 일치.
|
|
19
|
+
*/
|
|
20
|
+
// label 은 언어 중립 i18n 키(twin.type.<key>) — cls(표준 클래스)만 노드별 메타로 유지. 사람 언어는 표현계층(L2).
|
|
21
|
+
const MES_NODE_CLS = {
|
|
22
|
+
'raw-store': { epcis: 'bizLocation' },
|
|
23
|
+
'cut-station': { isa95: 'WorkCenter', epcis: 'bizLocation' },
|
|
24
|
+
'weld-station': { isa95: 'WorkCenter', epcis: 'bizLocation' },
|
|
25
|
+
'paint-booth': { isa95: 'WorkCenter', epcis: 'bizLocation' },
|
|
26
|
+
'assembly-line': { isa95: 'WorkCenter', epcis: 'bizLocation' },
|
|
27
|
+
'fg-store': { epcis: 'bizLocation' }
|
|
28
|
+
};
|
|
29
|
+
export const MES_TYPES = [
|
|
30
|
+
...MES_NODE_TYPES.map((k) => ({ key: k, role: 'node', label: `twin.type.${k}`, standardClass: MES_NODE_CLS[k] ?? {}, identity: { scheme: 'gs1:SGLN' }, capabilities: ['storable'] })),
|
|
31
|
+
{ key: 'cutter', role: 'mover', label: 'twin.type.cutter', standardClass: { isa95: 'Equipment', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['processable', 'operable'] },
|
|
32
|
+
{ key: 'welder', role: 'mover', label: 'twin.type.welder', standardClass: { isa95: 'Equipment', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['processable', 'operable'] },
|
|
33
|
+
{ key: 'painter', role: 'mover', label: 'twin.type.painter', standardClass: { isa95: 'Equipment', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['processable', 'operable'] },
|
|
34
|
+
{ key: 'assembler', role: 'mover', label: 'twin.type.assembler', standardClass: { isa95: 'Equipment', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['processable', 'operable'] }
|
|
35
|
+
];
|
package/dist/state-projector.js
CHANGED
|
@@ -21,7 +21,7 @@ export class StateProjector {
|
|
|
21
21
|
revision = 0;
|
|
22
22
|
constructor(board) {
|
|
23
23
|
for (const n of board.nodes)
|
|
24
|
-
this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity });
|
|
24
|
+
this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parentId: n.parentId });
|
|
25
25
|
// 무버 기준선(마스터) — equipment.status 델타로 갱신됨.
|
|
26
26
|
for (const m of board.movers)
|
|
27
27
|
this.movers.set(m.id, { id: m.id, kind: m.kind, status: 'idle', location: m.homeNode });
|
|
@@ -114,7 +114,7 @@ export class StateProjector {
|
|
|
114
114
|
occ.set(it.location, (occ.get(it.location) ?? 0) + 1);
|
|
115
115
|
return {
|
|
116
116
|
revision: this.revision,
|
|
117
|
-
nodes: [...this.master.values()].map(n => ({ id: n.id, type: n.type, capacity: n.capacity, occupancy: occ.get(n.id) ?? 0 })),
|
|
117
|
+
nodes: [...this.master.values()].map(n => ({ id: n.id, type: n.type, capacity: n.capacity, occupancy: occ.get(n.id) ?? 0, parentId: n.parentId })),
|
|
118
118
|
items: [...this.items.values()].map(i => ({ epc: i.epc, gtin: i.gtin, location: i.location, disposition: i.disposition })),
|
|
119
119
|
tasks: [...this.tasks.values()].map(t => ({ ...t })),
|
|
120
120
|
movers: [...this.movers.values()].map(m => ({ ...m })),
|
package/dist/wms-profile.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TwinTypeInfo } from './domain-catalog.ts';
|
|
1
2
|
export declare const BIZSTEP: {
|
|
2
3
|
readonly receiving: "urn:epcglobal:cbv:bizstep:receiving";
|
|
3
4
|
readonly storing: "urn:epcglobal:cbv:bizstep:storing";
|
|
@@ -16,3 +17,4 @@ export declare const BTT: {
|
|
|
16
17
|
* 도메인 어휘 SSOT: 호스트·UI 는 이걸 소싱하고 재선언하지 않는다(방언 금지).
|
|
17
18
|
*/
|
|
18
19
|
export declare const WMS_NODE_TYPES: readonly ["dock", "storage", "staging", "dock-ship"];
|
|
20
|
+
export declare const WMS_TYPES: TwinTypeInfo[];
|
package/dist/wms-profile.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* WMS Profile — 물류창고 어휘(CBV bizStep + business transaction type).
|
|
3
|
-
* EPCIS machinery(타입·빌더·검증기·URI·DISP)는 도메인-중립 `epcis.ts` 에 있다.
|
|
4
|
-
* 설계 SoT: operato-twin/design/profiles/wms.md §11
|
|
5
|
-
*/
|
|
6
1
|
// bizStep — CBV URN (wms.md §11 확정 어휘)
|
|
7
2
|
export const BIZSTEP = {
|
|
8
3
|
receiving: 'urn:epcglobal:cbv:bizstep:receiving',
|
|
@@ -23,3 +18,12 @@ export const BTT = {
|
|
|
23
18
|
* 도메인 어휘 SSOT: 호스트·UI 는 이걸 소싱하고 재선언하지 않는다(방언 금지).
|
|
24
19
|
*/
|
|
25
20
|
export const WMS_NODE_TYPES = ['dock', 'storage', 'staging', 'dock-ship'];
|
|
21
|
+
/*
|
|
22
|
+
* 트윈 타입 서술(ADR-0018 확장) — 노드 키는 WMS_NODE_TYPES 단일 출처에서 파생 + 무버 타입 추가.
|
|
23
|
+
* WMS=EPCIS 도메인: 로케이션=bizLocation(SGLN), 무버(지게차)=추적 오브젝트/자산(GIAI). 능력은 씬 소유라 미포함.
|
|
24
|
+
*/
|
|
25
|
+
// label 은 언어 중립 i18n 키(twin.type.<key>) — 사람 언어는 표현계층이 렌더(design/plans/i18n.md L2).
|
|
26
|
+
export const WMS_TYPES = [
|
|
27
|
+
...WMS_NODE_TYPES.map((k) => ({ key: k, role: 'node', label: `twin.type.${k}`, standardClass: { epcis: 'bizLocation' }, identity: { scheme: 'gs1:SGLN' }, capabilities: ['storable'] })),
|
|
28
|
+
{ key: 'forklift', role: 'mover', label: 'twin.type.forklift', standardClass: { epcis: 'object', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['mobile', 'operable'] }
|
|
29
|
+
];
|
package/dist/yms-profile.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TwinTypeInfo } from './domain-catalog.ts';
|
|
1
2
|
export declare const YARD_BIZSTEP: {
|
|
2
3
|
readonly arriving: "urn:epcglobal:cbv:bizstep:arriving";
|
|
3
4
|
readonly staging: "urn:epcglobal:cbv:bizstep:staging";
|
|
@@ -11,3 +12,4 @@ export declare const BTT_DELIVERY = "urn:epcglobal:cbv:btt:deliv";
|
|
|
11
12
|
export declare function graiUri(companyPrefix: string, assetType: string, serial: number): string;
|
|
12
13
|
/** YMS 노드 타입 카탈로그 — 커널이 키로 쓰는 야드 로케이션 타입(nodeByType/slotViews/.type). 도메인 SSOT. */
|
|
13
14
|
export declare const YMS_NODE_TYPES: readonly ["gate", "yard-slot", "dock-door", "staging"];
|
|
15
|
+
export declare const YMS_TYPES: TwinTypeInfo[];
|
package/dist/yms-profile.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* YMS Profile — yard(야드) 버티컬. 공유 코어 재사용 검증용 두 번째 프로파일.
|
|
3
|
-
* EPCIS 빌더(objectEvent 등)·DISP 는 epcis 에서 재사용하고, 야드 어휘만 더한다.
|
|
4
|
-
* (설계: profiles/yms.md, 03-reference-standards §YMS=GS1 EPCIS zone)
|
|
5
|
-
*
|
|
6
|
-
* 목적: contract·state-projector·runtime·allocation-policy·EPCIS 빌더가 WMS 전용이 아니라
|
|
7
|
-
* 도메인-일반임을 야드로 증명. 코어 수정 없이 프로파일만 추가되면 헌장 원칙 2 검증.
|
|
8
|
-
*/
|
|
9
1
|
// 야드 bizStep — CBV (yms.md §11).
|
|
10
2
|
export const YARD_BIZSTEP = {
|
|
11
3
|
arriving: 'urn:epcglobal:cbv:bizstep:arriving', // 게이트-인
|
|
@@ -22,3 +14,11 @@ export function graiUri(companyPrefix, assetType, serial) {
|
|
|
22
14
|
}
|
|
23
15
|
/** YMS 노드 타입 카탈로그 — 커널이 키로 쓰는 야드 로케이션 타입(nodeByType/slotViews/.type). 도메인 SSOT. */
|
|
24
16
|
export const YMS_NODE_TYPES = ['gate', 'yard-slot', 'dock-door', 'staging'];
|
|
17
|
+
/*
|
|
18
|
+
* 트윈 타입 서술(ADR-0018 확장) — 노드 키는 YMS_NODE_TYPES 단일 출처에서 파생 + 무버 타입 추가.
|
|
19
|
+
* YMS=EPCIS zone: 로케이션/존=bizLocation(SGLN), 무버(야드 트랙터)=오브젝트/자산(GIAI).
|
|
20
|
+
*/
|
|
21
|
+
export const YMS_TYPES = [
|
|
22
|
+
...YMS_NODE_TYPES.map((k) => ({ key: k, role: 'node', label: `twin.type.${k}`, standardClass: { epcis: 'bizLocation' }, identity: { scheme: 'gs1:SGLN' }, capabilities: ['storable'] })),
|
|
23
|
+
{ key: 'hostler', role: 'mover', label: 'twin.type.hostler', standardClass: { epcis: 'object', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['mobile', 'operable'] }
|
|
24
|
+
];
|