@operato/twin-kernel 0.6.14 → 0.7.1
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 +29 -1
- package/dist/capability.js +23 -1
- package/dist/capacity.d.ts +5 -5
- package/dist/capacity.js +7 -7
- package/dist/contract.d.ts +176 -32
- package/dist/contract.js +66 -7
- package/dist/counterfactual.d.ts +1 -1
- package/dist/counterfactual.js +2 -2
- package/dist/domain-catalog.d.ts +83 -17
- package/dist/domain-catalog.js +99 -14
- package/dist/domain-definition.d.ts +17 -1
- package/dist/ems-kernel.d.ts +83 -0
- package/dist/ems-kernel.js +307 -0
- package/dist/ems-profile.d.ts +16 -0
- package/dist/ems-profile.js +133 -0
- package/dist/energy-attribution.d.ts +208 -0
- package/dist/energy-attribution.js +229 -0
- package/dist/energy-ingest.d.ts +39 -0
- package/dist/energy-ingest.js +91 -0
- package/dist/epcis.d.ts +3 -3
- package/dist/epcis.js +2 -2
- package/dist/event-journal.d.ts +2 -2
- package/dist/event-journal.js +1 -1
- package/dist/flow-engine.d.ts +20 -20
- package/dist/flow-engine.js +40 -40
- package/dist/forecast.js +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/kernel.d.ts +3 -3
- package/dist/kernel.js +5 -5
- package/dist/mes-kernel.d.ts +2 -2
- package/dist/mes-kernel.js +2 -2
- package/dist/mes-profile.js +25 -1
- package/dist/observed-reducer.d.ts +25 -4
- package/dist/observed-reducer.js +30 -9
- package/dist/operations-capability.d.ts +8 -8
- package/dist/operations-capability.js +2 -2
- package/dist/task-fold.d.ts +1 -1
- package/dist/task-fold.js +1 -1
- package/dist/twin-observer.js +1 -1
- package/dist/wms-profile.d.ts +0 -11
- package/dist/wms-profile.js +17 -2
- package/dist/yms-profile.js +16 -1
- package/dist-cjs/index.cjs +814 -56
- package/package.json +1 -1
package/dist/flow-engine.js
CHANGED
|
@@ -88,7 +88,7 @@ nowIso) {
|
|
|
88
88
|
*
|
|
89
89
|
* 재는 방법은 스냅샷만으로 답할 수 있어야 한다(추세를 쌓아 두지 않는다). 그래서 같은 공정에서
|
|
90
90
|
* **기다리는 일과 하고 있는 일의 비**를 본다 — 처리 능력이 충분하면 대기가 길게 늘지 않는다.
|
|
91
|
-
* 공정이
|
|
91
|
+
* 공정이 전혀 진행되지 않으면(하는 일 0, 기다리는 일 여럿) 그것이 가장 강한 신호다.
|
|
92
92
|
*
|
|
93
93
|
* 공정 이름은 작업이 스스로 말하는 것(`kind`)을 쓴다. 설비 종류로 환산하지 않는다 — 그 대응은
|
|
94
94
|
* 현장마다 다르고, 커널이 그 방언을 알면 보편 계약이 깨진다.
|
|
@@ -100,7 +100,7 @@ nowIso) {
|
|
|
100
100
|
* 막혔을 때의 경고까지 같이 무시하게 된다.
|
|
101
101
|
*
|
|
102
102
|
* 쉬는 중인지는 **설비가 이미 말하고 있다**(`offShift`, 달력에서 파생). 여기서 달력을 다시 읽지
|
|
103
|
-
* 않는다 — 두 벌이 되면
|
|
103
|
+
* 않는다 — 두 벌이 되면 어긋난다. 설비를 아예 모르면 판단하지 않는다(줄만 보고 단정하지 않는다).
|
|
104
104
|
*/
|
|
105
105
|
const shutdown = (view.equipment?.length ?? 0) > 0 && view.equipment.every(m => m.offShift);
|
|
106
106
|
if (view.tasks?.length && !shutdown) {
|
|
@@ -157,7 +157,7 @@ nowIso) {
|
|
|
157
157
|
}
|
|
158
158
|
/* 납기 초과 — **표준 `EndTime` 이 있어야 판정할 수 있다.** 없으면 신호를 만들지 않는다(없는 납기를
|
|
159
159
|
지연으로도 정시로도 말하지 않는다). 이미 끝난 오더는 대상이 아니다.
|
|
160
|
-
심각도는 얼마나 늦었는지로
|
|
160
|
+
심각도는 얼마나 늦었는지로 구분한다 — 방금 넘긴 것과 하루 넘긴 것을 같게 부르면 신호가 무의미해진다. */
|
|
161
161
|
for (const o of view.orders) {
|
|
162
162
|
if (o.status === 'completed' || o.status === 'fulfilled')
|
|
163
163
|
continue;
|
|
@@ -288,7 +288,7 @@ export class FlowEngine {
|
|
|
288
288
|
/*
|
|
289
289
|
* ── 선언 하나를 상태 하나로 짓는 자리 ─────────────────────────────────────
|
|
290
290
|
* 최초 적재(`loadTwinModel`)와 가동 중 교체(`adoptStructure`)가 **같은 구성을 쓴다.** 두 곳에서
|
|
291
|
-
* 따로 지으면 곧 갈라지고,
|
|
291
|
+
* 따로 지으면 곧 갈라지고, 어긋난 쪽으로 들어온 자원만 필드 하나가 비는 식으로 조용히 다르다.
|
|
292
292
|
*/
|
|
293
293
|
buildLocation(n) {
|
|
294
294
|
return { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, occupancy: 0, status: 'idle', parentId: n.parentId };
|
|
@@ -312,7 +312,7 @@ export class FlowEngine {
|
|
|
312
312
|
this.observeMode = true;
|
|
313
313
|
}
|
|
314
314
|
/**
|
|
315
|
-
* 돌면서 공장을
|
|
315
|
+
* 돌면서 공장을 전환한다 — **현실이 안 멈추므로 미러도 멈출 수 없다.**
|
|
316
316
|
*
|
|
317
317
|
* ── 왜 관측 구동에만 여는가 ────────────────────────────────────────────────
|
|
318
318
|
* 미러는 이미 있는 현실을 따라갈 뿐이라, 설비가 한 대 늘었다고 멈췄다 서는 것은 그 사이의
|
|
@@ -325,7 +325,7 @@ export class FlowEngine {
|
|
|
325
325
|
* 자원은 버리되 **몇 개를 버렸는지 돌려준다.** 여기서는 그 판단을 커널 자신의 선언 표에도
|
|
326
326
|
* 그대로 옮긴다. 관측층만 갈면 사라진 설비가 커널 표에 남아 **화면이 없는 설비를 그린다.**
|
|
327
327
|
*
|
|
328
|
-
* 관측으로 알게 된 자리는 선언에 없어도 지운 뒤 다시
|
|
328
|
+
* 관측으로 알게 된 자리는 선언에 없어도 지운 뒤 다시 주입한다 — 다음 스냅샷에서 관측층이
|
|
329
329
|
* 되살리므로(`settleObserved` → `hydrateObserved`), 여기서 지켜야 할 것은 **선언분**뿐이다.
|
|
330
330
|
*/
|
|
331
331
|
adoptStructure(def) {
|
|
@@ -337,7 +337,7 @@ export class FlowEngine {
|
|
|
337
337
|
this.classDefs = { personnel: def.personnelClasses, equipment: def.equipmentClasses, asset: def.assetClasses, material: def.materialClasses };
|
|
338
338
|
this.materialDefs = new Map((def.materialDefinitions ?? []).filter(d => d?.id).map(d => [d.id, d]));
|
|
339
339
|
/* **살아남은 것은 그대로 둔다** — 다시 지으면 가동시간·양품수가 0 으로 돌아간다(OEE 가 리셋된다).
|
|
340
|
-
선언이 바뀐 트윈에서 성과 지표가 조용히 끊기는 것이 이
|
|
340
|
+
선언이 바뀐 트윈에서 성과 지표가 조용히 끊기는 것이 이 구조 전환의 가장 비싼 오류다. */
|
|
341
341
|
const sync = (map, declared, build) => {
|
|
342
342
|
const ids = new Set(declared.map(d => d.id));
|
|
343
343
|
let dropped = 0;
|
|
@@ -429,22 +429,22 @@ export class FlowEngine {
|
|
|
429
429
|
this.revision = from;
|
|
430
430
|
}
|
|
431
431
|
hydrateObserved(snap, orders = []) {
|
|
432
|
-
/* 확인 처리를 먼저 이어받는다 — 아래에서 상태를
|
|
432
|
+
/* 확인 처리를 먼저 이어받는다 — 아래에서 상태를 주입하면 곧바로 주목 신호가 계산되므로, 늦게
|
|
433
433
|
* 이어받으면 그 한 번은 확인 안 된 것으로 계산된다(화면이 잠깐 빨개진다). */
|
|
434
434
|
for (const id of snap.acked ?? [])
|
|
435
435
|
this._acked.add(id);
|
|
436
436
|
/* **관측된 것을 버리지 않는다.** 예전에는 자리·설비 상태를 'idle' 로, OEE 누적을 0 으로 덮고
|
|
437
437
|
* 물품의 로트·단위·소속·마스터데이터를 떨어뜨렸다. 씨앗이 잃은 것은 **예측도 모른다** —
|
|
438
|
-
* 고장 난 설비를 정상으로, 진행 중인 일을 없는 것으로 놓고 미래를
|
|
438
|
+
* 고장 난 설비를 정상으로, 진행 중인 일을 없는 것으로 놓고 미래를 실행하면 답이 낙관 쪽으로 치우친다. */
|
|
439
439
|
for (const n of snap.locations) {
|
|
440
440
|
this.locations.set(n.id, { id: n.id, type: n.type, capacity: n.capacity ?? 0, parallelism: n.parallelism, occupancy: n.occupancy ?? 0, status: n.status ?? 'idle', parentId: n.parentId });
|
|
441
441
|
}
|
|
442
442
|
this.items.clear();
|
|
443
443
|
for (const it of snap.items) {
|
|
444
|
-
/* **부분마다 한 줄로
|
|
444
|
+
/* **부분마다 한 줄로 주입한다** — `epc` 로 키를 잡으면 같은 로트의 두 부분이 하나로 접혀
|
|
445
445
|
씨앗에서 재고가 줄어든다(§MaterialSubLot 에서 겪은 것과 같은 오류의 세 번째 자리). */
|
|
446
446
|
this.items.set(itemKeyOf(it), {
|
|
447
|
-
/* 품번 키·로트는 식별자에서 파생되므로
|
|
447
|
+
/* 품번 키·로트는 식별자에서 파생되므로 주입하지 않는다(스냅샷이 다시 낸다 — 두 벌을 두면 어긋난다). */
|
|
448
448
|
epc: it.epc, location: it.location, disposition: it.disposition ?? DISP.sellable,
|
|
449
449
|
...(it.subLotId ? { subLotId: it.subLotId } : {}),
|
|
450
450
|
...(it.definitionId ? { definitionId: it.definitionId } : {}),
|
|
@@ -480,7 +480,7 @@ export class FlowEngine {
|
|
|
480
480
|
* **잰 값을 이어받으면 잰 구간도 이어받아야 한다.**
|
|
481
481
|
*
|
|
482
482
|
* 카운터만 되살리고 창을 지금으로 두면 둘이 어긋난다: 이어받은 가동 8시간이 방금 시작한 창
|
|
483
|
-
* 안에 앉아,
|
|
483
|
+
* 안에 앉아, 대기 중인 설비가 **가동률 100%·유휴 0초**로 보인다(화면에서 관측됨).
|
|
484
484
|
*
|
|
485
485
|
* 창은 지표 자신이 말해 준다 — 계획 시간은 가동·준비·고장·유휴의 합이다. 그래서 그 합만큼
|
|
486
486
|
* 뒤로 물려 창을 다시 세운다. 지표가 없으면 창도 없다(그때는 지금부터 새로 잰다).
|
|
@@ -488,7 +488,7 @@ export class FlowEngine {
|
|
|
488
488
|
...(oee
|
|
489
489
|
? { metricsSinceMs: this.clockMs - ((oee.runMs ?? 0) + (oee.setupMs ?? 0) + (oee.downMs ?? 0) + (oee.idleMs ?? 0)) }
|
|
490
490
|
: {}),
|
|
491
|
-
/* 계획 정지를 이어받는다 — 잃으면 씨앗이 **정비 중인 설비를 가용으로 놓고** 미래를
|
|
491
|
+
/* 계획 정지를 이어받는다 — 잃으면 씨앗이 **정비 중인 설비를 가용으로 놓고** 미래를 시뮬레이션한다
|
|
492
492
|
(예측이 낙관 쪽으로 치우친다). 씨앗 왕복 대조가 이것을 잡았다. */
|
|
493
493
|
...(m.held ? { held: true } : {}),
|
|
494
494
|
/* 관측 스냅샷이 들고 있는 것은 관측을 따른다(미러가 보드에서 읽어 실어 온다). */
|
|
@@ -530,7 +530,7 @@ export class FlowEngine {
|
|
|
530
530
|
}
|
|
531
531
|
/* 진행 중이던 작업을 이어 붙인다 — 없으면 예측이 "일이 하나도 없는 현장" 에서 출발한다.
|
|
532
532
|
* 남은 시간을 모르면 **진척을 꾸미지 않고** 미착수(created)로 되돌린다: 그 일이 남아 있다는 사실은
|
|
533
|
-
* 지키면서, 얼마나 진행됐는지는 모른다고 말하는 쪽이 정직하다(커널이 다시 배정해
|
|
533
|
+
* 지키면서, 얼마나 진행됐는지는 모른다고 말하는 쪽이 정직하다(커널이 다시 배정해 실행한다). */
|
|
534
534
|
/*
|
|
535
535
|
* **주체가 없는 작업은 되살리지 않는다.**
|
|
536
536
|
*
|
|
@@ -597,8 +597,8 @@ export class FlowEngine {
|
|
|
597
597
|
orphaned++;
|
|
598
598
|
continue;
|
|
599
599
|
}
|
|
600
|
-
/* 오더도 같다 — 이미 이행된 오더는
|
|
601
|
-
남으면 완료 시점에 없는 오더를 딛는다.
|
|
600
|
+
/* 오더도 같다 — 이미 이행된 오더는 주입하지 않으므로(위 `remaining <= 0`), 그 오더에 딸린 작업만
|
|
601
|
+
남으면 완료 시점에 없는 오더를 딛는다. 주입 단계에서 함께 뺀다. */
|
|
602
602
|
if (t.orderId && !seededOrderIds.has(t.orderId)) {
|
|
603
603
|
orphaned++;
|
|
604
604
|
continue;
|
|
@@ -626,7 +626,7 @@ export class FlowEngine {
|
|
|
626
626
|
mv.taskId = t.id;
|
|
627
627
|
}
|
|
628
628
|
}
|
|
629
|
-
/* 사람도 같다 — 진행 중이던 작업에 투입돼 있던 사람은 여전히
|
|
629
|
+
/* 사람도 같다 — 진행 중이던 작업에 투입돼 있던 사람은 여전히 점유되어 있어야 한다. */
|
|
630
630
|
if (known && t.status === 'in-progress') {
|
|
631
631
|
const restored = this.tasks.get(t.id);
|
|
632
632
|
if (restored)
|
|
@@ -716,7 +716,7 @@ export class FlowEngine {
|
|
|
716
716
|
this._acked.add(id);
|
|
717
717
|
/*
|
|
718
718
|
* **사실로 남긴다.** 확인 처리는 사람이 한 행위라 상태에서 다시 계산될 수 없다. 저널에
|
|
719
|
-
* 남기지 않으면 재기동하면 확인해 둔 신호가 다시 빨개지고, 과거를
|
|
719
|
+
* 남기지 않으면 재기동하면 확인해 둔 신호가 다시 빨개지고, 과거를 다시 계산해도 그때 무엇을
|
|
720
720
|
* 확인했는지 알 수 없다. 다른 커맨드(보류·재개)가 델타를 내는 것과 같은 길이다.
|
|
721
721
|
*/
|
|
722
722
|
this.emitOp(OP_EVENT.attentionAck, { id, at: this.now() });
|
|
@@ -837,7 +837,7 @@ export class FlowEngine {
|
|
|
837
837
|
* 시뮬이 아무 표시도 안 하면 소비처가 두 스냅샷을 같은 규칙으로 읽지 못한다. */
|
|
838
838
|
locations: [...this.locations.values()].map(n => {
|
|
839
839
|
const { status, ...rest } = n;
|
|
840
|
-
/* 상태는 저장값이 아니라 포화도 파생 — 미러와 **같은 함수**를 쓴다(규칙이 둘이면
|
|
840
|
+
/* 상태는 저장값이 아니라 포화도 파생 — 미러와 **같은 함수**를 쓴다(규칙이 둘이면 어긋난다). */
|
|
841
841
|
const derived = locationStatusOf(n);
|
|
842
842
|
return { ...rest, ...(derived ? { status: derived } : {}), origin: 'master' };
|
|
843
843
|
}),
|
|
@@ -887,7 +887,7 @@ export class FlowEngine {
|
|
|
887
887
|
return st;
|
|
888
888
|
}),
|
|
889
889
|
/* 스냅샷이 **델타보다 가난하면 안 된다** — 예전에는 소요·남은 시간을 빼고 내보내서, 이 스냅샷으로
|
|
890
|
-
* 다른 커널을
|
|
890
|
+
* 다른 커널을 주입하면(hydrateObserved) 진행 중이던 작업을 이어 굴릴 수 없었다(미러 스냅샷은
|
|
891
891
|
* 델타에서 왔으므로 갖고 있었다 — 같은 계약을 두 구동이 다르게 채우던 자리). */
|
|
892
892
|
tasks: [...this.tasks.values()].map(t => ({
|
|
893
893
|
id: t.id, kind: t.kind, status: t.status, itemRefs: [t.itemEpc],
|
|
@@ -971,7 +971,7 @@ export class FlowEngine {
|
|
|
971
971
|
}
|
|
972
972
|
/**
|
|
973
973
|
* fork — 현재 상태를 정확히 복제한 새 엔진 (디지털트윈 본연: "현재로부터 예측").
|
|
974
|
-
* 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로
|
|
974
|
+
* 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로 시뮬레이션해 forecast·발산(predicted vs actual) 검사에 쓴다.
|
|
975
975
|
* fork 는 자기 구독자·시나리오를 갖고 원본과 격리(handlers·gens 비움, generating=false).
|
|
976
976
|
* rng 는 fork 의 시나리오 load 시 재시드(드레인 예측은 생성 없어 rng 무관·결정적).
|
|
977
977
|
*/
|
|
@@ -1004,7 +1004,7 @@ export class FlowEngine {
|
|
|
1004
1004
|
* 이 커널의 **지금**(ms) — 시각으로 바뀌는 모든 판정의 단일 기준.
|
|
1005
1005
|
*
|
|
1006
1006
|
* ── 관측 모드에서 시계가 멈춰 있었다 ────────────────────────────────────
|
|
1007
|
-
* `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을
|
|
1007
|
+
* `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을 진행시키지 않는다). 그래서 라이브 트윈의 "지금" 이
|
|
1008
1008
|
* **BASE_EPOCH(2026-01-01)에 얼어 있었다.** 실 이벤트는 실제 시각을 달고 오므로 결과가 이렇게 된다:
|
|
1009
1009
|
* · **납기 초과를 영원히 보고하지 않는다** — 실 납기가 항상 미래로 보인다
|
|
1010
1010
|
* · 폐기·도입예정 판정이 틀린다(§EffectivePeriod)
|
|
@@ -1039,14 +1039,14 @@ export class FlowEngine {
|
|
|
1039
1039
|
}
|
|
1040
1040
|
randInt(min, max) { return max <= min ? min : min + Math.floor(this.rng() * (max - min + 1)); }
|
|
1041
1041
|
/**
|
|
1042
|
-
* 관측 구동(P0 스파이크) — **이벤트로 커널을
|
|
1042
|
+
* 관측 구동(P0 스파이크) — **이벤트로 커널을 실행한다.**
|
|
1043
1043
|
*
|
|
1044
1044
|
* 상태를 만드는 구동이 둘인데(시뮬 `tick` / 미러 `apply`) 지금은 **모델도 둘**이라 한쪽만 고치면
|
|
1045
|
-
*
|
|
1045
|
+
* 어긋난다(2026-08-01 하루에 아홉 곳). 근본 해법은 **한 상태 모델 두 구동**이고, 이것은 그 실현
|
|
1046
1046
|
* 가능성을 재는 스파이크다(design/plans/kernel-unification-live-observe.md P0).
|
|
1047
1047
|
*
|
|
1048
1048
|
* 여기서는 **이미 검증된 조각을 조립**한다: 투영기가 이벤트를 접고, 그 결과를 씨앗 경로
|
|
1049
|
-
* (`hydrateObserved`)로 커널 상태에
|
|
1049
|
+
* (`hydrateObserved`)로 커널 상태에 주입한다. 그래서 관측으로 실행한 커널을 그대로 `fork`·`tick` 할 수
|
|
1050
1050
|
* 있다 — "미러에서 예측한다" 가 별도 배관 없이 성립하는지가 이 스파이크의 질문이다.
|
|
1051
1051
|
*
|
|
1052
1052
|
* **비용은 정직하게**: 이벤트마다 전체를 다시 심으므로 O(상태 크기)다. P1 에서 반영 로직을 순수
|
|
@@ -1112,14 +1112,14 @@ export class FlowEngine {
|
|
|
1112
1112
|
return undefined;
|
|
1113
1113
|
}
|
|
1114
1114
|
/**
|
|
1115
|
-
* **이 공장이 하루 몇 대를 낼 수 있는가** —
|
|
1115
|
+
* **이 공장이 하루 몇 대를 낼 수 있는가** — 실행해 보지 않고 답한다.
|
|
1116
1116
|
*
|
|
1117
1117
|
* 커널이 직접 답하는 이유: 필요한 사실이 전부 여기 있다(공정 명세·설비와 신뢰도·인원·물리자산·
|
|
1118
1118
|
* 자리·근무 달력·시각 기준). 밖에서 모으면 그 값을 옮겨 적게 되고, 한쪽만 바뀌는 순간 "충분하다"
|
|
1119
1119
|
* 가 조용히 거짓이 된다. 계산 자체는 순수 함수(`analyzeCapacity`)에 맡긴다.
|
|
1120
1120
|
*
|
|
1121
1121
|
* 달력은 **자원이 선언한 것**을 쓴다. 자원마다 다른 달력을 쓰는 현장이면 대표를 고를 수 없으므로
|
|
1122
|
-
* 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면
|
|
1122
|
+
* 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면 상한이 틀린 채로
|
|
1123
1123
|
* 그럴듯해 보인다.
|
|
1124
1124
|
*/
|
|
1125
1125
|
capacity(opts) {
|
|
@@ -1209,14 +1209,14 @@ export class FlowEngine {
|
|
|
1209
1209
|
/**
|
|
1210
1210
|
* task 소요 산출 — 우선순위: **① 추정기(이력 보정) → ② 명세(ISA-95 Duration + 변동) → ③ 도메인 상수.**
|
|
1211
1211
|
*
|
|
1212
|
-
* 이 순서인 이유: 실측에서 배운 값이 선언값을 이기고, 선언값이 우리가 코드에
|
|
1212
|
+
* 이 순서인 이유: 실측에서 배운 값이 선언값을 이기고, 선언값이 우리가 코드에 고정한 상수를 이긴다.
|
|
1213
1213
|
* 셋 중 무엇을 썼는지는 `specCoverage()` 로 드러낸다 — 상수를 쓴 것이 조용히 넘어가지 않게.
|
|
1214
1214
|
* "얼마"만 소비하고 "경로"는 씬이 소유한다(좌표-free 유지).
|
|
1215
1215
|
*/
|
|
1216
1216
|
durationOf(ctx, fallbackMs) {
|
|
1217
1217
|
const estimated = this.durationEstimator?.estimate(ctx);
|
|
1218
1218
|
/* 추정기가 답한 것은 **실측·계산에서 온 값**이므로 선언값과 구별해 기록한다 — "현장이 선언했다" 와
|
|
1219
|
-
* "이력에서 배웠다" 는 예측의 자격이 다르다(후자가 더 강하다).
|
|
1219
|
+
* "이력에서 배웠다" 는 예측의 자격이 다르다(후자가 더 강하다). 합치면 그 차이가 사라진다. */
|
|
1220
1220
|
if (typeof estimated === 'number') {
|
|
1221
1221
|
this.noteSpecUse(ctx.kind, 'measured');
|
|
1222
1222
|
return estimated;
|
|
@@ -1309,7 +1309,7 @@ export class FlowEngine {
|
|
|
1309
1309
|
this.specUse.set(kind, { duration: 'default', params: new Set([id]) });
|
|
1310
1310
|
}
|
|
1311
1311
|
/**
|
|
1312
|
-
* 시뮬 명세 자기보고 — **어디까지 데이터로 말했고 어디부터 우리가
|
|
1312
|
+
* 시뮬 명세 자기보고 — **어디까지 데이터로 말했고 어디부터 우리가 코드에 고정한 상수인가.**
|
|
1313
1313
|
*
|
|
1314
1314
|
* 시뮬레이션 결과를 받는 쪽이 이걸 봐야 한다: 소요시간이 전부 기본값이면 그 예측으로 말할 수 있는 것은
|
|
1315
1315
|
* "같은 조건에서의 상대 비교" 뿐이고 "몇 시에 끝난다" 는 근거가 없다. 그 구분을 숫자로 드러낸다.
|
|
@@ -1317,7 +1317,7 @@ export class FlowEngine {
|
|
|
1317
1317
|
specCoverage() {
|
|
1318
1318
|
const operations = [...this.specUse.entries()].map(([kind, u]) => {
|
|
1319
1319
|
const spec = this.operationSpecs.get(kind);
|
|
1320
|
-
/* 실측 분포를 쓴 경우가 우선 — 선언 명세의 변동보다 실제로
|
|
1320
|
+
/* 실측 분포를 쓴 경우가 우선 — 선언 명세의 변동보다 실제로 실행한 것이 사실이다. */
|
|
1321
1321
|
const variability = u.variability ?? spec?.variability?.distribution;
|
|
1322
1322
|
return {
|
|
1323
1323
|
kind,
|
|
@@ -1409,7 +1409,7 @@ export class FlowEngine {
|
|
|
1409
1409
|
this.observeDisposition(epcs, DISP.reserved, bizStep);
|
|
1410
1410
|
}
|
|
1411
1411
|
/**
|
|
1412
|
-
* 처분 변화 관측 — **상태와 이벤트를 한 번에.** 둘을 따로 쓰면 반드시
|
|
1412
|
+
* 처분 변화 관측 — **상태와 이벤트를 한 번에.** 둘을 따로 쓰면 반드시 어긋난다.
|
|
1413
1413
|
*
|
|
1414
1414
|
* 실제로 양쪽으로 갈라져 있었다: 할당은 상태만 바꾸고 이벤트를 안 냈고(미러가 모름), 야드 도크
|
|
1415
1415
|
* 도착은 이벤트만 내고 상태를 안 바꿨다(이벤트와 상태가 다른 말). 적합성 하네스가 둘 다 잡았다.
|
|
@@ -1682,7 +1682,7 @@ export class FlowEngine {
|
|
|
1682
1682
|
}
|
|
1683
1683
|
return picked;
|
|
1684
1684
|
}
|
|
1685
|
-
/** 확보한 자산을 작업에
|
|
1685
|
+
/** 확보한 자산을 작업에 배정한다 — 싣는 물류단위(SSCC)가 있으면 연결한다(GRAI ↔ SSCC). */
|
|
1686
1686
|
assignAssets(t, gear) {
|
|
1687
1687
|
if (!gear.length)
|
|
1688
1688
|
return;
|
|
@@ -1848,7 +1848,7 @@ export class FlowEngine {
|
|
|
1848
1848
|
* 이 작업이 **딛고 선 것**이 아직 있나 — 없으면 무엇이 없는지 답한다.
|
|
1849
1849
|
*
|
|
1850
1850
|
* 작업은 혼자 서지 못한다: 옮길 **물품**과, (있다면) 그것을 시킨 **오더** 위에 선다. 진행 중에
|
|
1851
|
-
* 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이
|
|
1851
|
+
* 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이 주입하지 않아서.
|
|
1852
1852
|
*
|
|
1853
1853
|
* 그때 도메인 훅은 없는 것을 딛으려다 던진다(`order.gtin` · `item.location`). 그 예외 하나가
|
|
1854
1854
|
* **예측 전체를 죽였다** — 사용자에게는 기능이 통째로 사라진 것으로 보였다. 그래서 완료 **전에**
|
|
@@ -1966,7 +1966,7 @@ export class FlowEngine {
|
|
|
1966
1966
|
* 자원 하나의 **가용 능력** — 계약의 판정에 이 커널의 시각·시간대·필수 시험을 채워 넘긴다.
|
|
1967
1967
|
*
|
|
1968
1968
|
* 배정도 스냅샷도 이 자리를 지난다. 예전에는 배정이 조건을 늘어놓고 화면이 플래그를 보고 짐작해서,
|
|
1969
|
-
* **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(
|
|
1969
|
+
* **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(대기 중인 것은 맞지만 쓸 수 있는 것은 아니다).
|
|
1970
1970
|
* 판정에 필요한 둘(시각·등급 상속)을 아는 것은 커널뿐이므로, 답도 커널이 낸다.
|
|
1971
1971
|
*/
|
|
1972
1972
|
capabilityOfResource(r, directClassIds, defs,
|
|
@@ -2041,7 +2041,7 @@ export class FlowEngine {
|
|
|
2041
2041
|
}
|
|
2042
2042
|
return picked.length ? picked : null;
|
|
2043
2043
|
}
|
|
2044
|
-
/** 확보한 사람을 작업에
|
|
2044
|
+
/** 확보한 사람을 작업에 배정한다(설비까지 확정된 뒤). */
|
|
2045
2045
|
assignCrew(t, crew) {
|
|
2046
2046
|
if (!crew.length)
|
|
2047
2047
|
return;
|
|
@@ -2099,7 +2099,7 @@ export class FlowEngine {
|
|
|
2099
2099
|
* 유효 기간 밖인가 — **네 번째 이유**(§Effectivity). 설비·사람·자산이 같은 규칙을 쓴다.
|
|
2100
2100
|
*
|
|
2101
2101
|
* 시뮬 시각을 ISO 로 풀어 계약의 `effectivityAt` 에 넘긴다. **판정은 커널에 두지 않는다** —
|
|
2102
|
-
* 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면
|
|
2102
|
+
* 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면 어긋난다.
|
|
2103
2103
|
*/
|
|
2104
2104
|
effectivityOf(r) {
|
|
2105
2105
|
if (!r.effectiveStart && !r.effectiveEnd)
|
|
@@ -2123,7 +2123,7 @@ export class FlowEngine {
|
|
|
2123
2123
|
/**
|
|
2124
2124
|
* 스냅샷·델타에 실을 조각 — **선언한 기간과 판정을 함께** 낸다.
|
|
2125
2125
|
*
|
|
2126
|
-
* 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해
|
|
2126
|
+
* 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해 어긋난다.
|
|
2127
2127
|
* 선언이 없으면 아무것도 붙이지 않는다(대부분의 자원이 그렇다 — 필드를 늘리지 않는다).
|
|
2128
2128
|
*/
|
|
2129
2129
|
effectivePart(r) {
|
|
@@ -2140,7 +2140,7 @@ export class FlowEngine {
|
|
|
2140
2140
|
* 지금 근무 시간 밖인가 — **캘린더가 있으면 그것으로, 없으면 옛 `window` 로** 판정한다.
|
|
2141
2141
|
*
|
|
2142
2142
|
* `window`(시 단위 하나)는 캘린더의 특수한 경우다. 둘을 한 함수로 모아 두면 인원·설비가 같은 규칙을
|
|
2143
|
-
* 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히
|
|
2143
|
+
* 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히 어긋난다).
|
|
2144
2144
|
*/
|
|
2145
2145
|
offCalendar(r) {
|
|
2146
2146
|
return offCalendarAt(r, this.nowMs(), this.boardDef?.utcOffsetMinutes);
|
|
@@ -2434,7 +2434,7 @@ export class FlowEngine {
|
|
|
2434
2434
|
eq.taskId = null;
|
|
2435
2435
|
if (t.intent !== 'process')
|
|
2436
2436
|
eq.location = t.toNode; // 운반만 위치 이동; process 는 제자리
|
|
2437
|
-
/* 함께 잡힌 설비도 같은 규칙으로 놓아 준다 — 대표만 풀면 나머지가 영원히
|
|
2437
|
+
/* 함께 잡힌 설비도 같은 규칙으로 놓아 준다 — 대표만 풀면 나머지가 영원히 점유된다. */
|
|
2438
2438
|
for (const id of t.resources ?? []) {
|
|
2439
2439
|
if (id === eq.id)
|
|
2440
2440
|
continue;
|
package/dist/forecast.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Probabilistic Forecast — 몬테카를로 예측 (단일 결정값 → 분포).
|
|
3
3
|
*
|
|
4
|
-
* 예측은 본질적으로 불확실하다. 현재 상태를 fork 하고 seed 를 달리해(확률적 미래) N회
|
|
4
|
+
* 예측은 본질적으로 불확실하다. 현재 상태를 fork 하고 seed 를 달리해(확률적 미래) N회 실행해,
|
|
5
5
|
* 관심 지표를 분포로 준다: "언제 끝나?"가 아니라 "P50/P90 완료시각·재고소진 확률".
|
|
6
6
|
* fork(현재예측) + 시나리오 재시드로 미래만 변주 — 현재 상태(재고·오더·태스크)는 보존.
|
|
7
7
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from './event-journal.ts';
|
|
|
8
8
|
export * from './divergence.ts';
|
|
9
9
|
export * from './epcis.ts';
|
|
10
10
|
export * from './wms-profile.ts';
|
|
11
|
+
export * from './ems-profile.ts';
|
|
11
12
|
export * from './capability.ts';
|
|
12
13
|
export * from './domain-catalog.ts';
|
|
13
14
|
export * from './allocation-policy.ts';
|
|
@@ -27,4 +28,9 @@ export * from './operations-capability.ts';
|
|
|
27
28
|
export { WmsKernel } from './kernel.ts';
|
|
28
29
|
export { YmsKernel } from './yms-kernel.ts';
|
|
29
30
|
export { MesKernel, MES_PART_GTINS, MES_PRODUCT_GTINS, MES_PRODUCTS } from './mes-kernel.ts';
|
|
31
|
+
export { EmsKernel, DEMAND_WINDOW_MS, demandWindowStart } from './ems-kernel.ts';
|
|
32
|
+
export { ingestEnergyRecords, isEnergyRecord } from './energy-ingest.ts';
|
|
33
|
+
export { attributeEnergy, energyIntensity, energyOfWindows } from './energy-attribution.ts';
|
|
34
|
+
export type { AttributionBasis, AttributionResult, EnergyConsumer, EnergyPool, EnergyShare, IntensityInput, IntensityResult, IntensityDenominator, WeightKind, WindowedEnergy } from './energy-attribution.ts';
|
|
35
|
+
export type { EnergyRecord, EnergyIngestOptions, EnergyIngestResult } from './energy-ingest.ts';
|
|
30
36
|
export * from './vocabulary.ts';
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./event-journal.js";
|
|
|
8
8
|
export * from "./divergence.js";
|
|
9
9
|
export * from "./epcis.js";
|
|
10
10
|
export * from "./wms-profile.js";
|
|
11
|
+
export * from "./ems-profile.js";
|
|
11
12
|
export * from "./capability.js";
|
|
12
13
|
export * from "./domain-catalog.js";
|
|
13
14
|
export * from "./allocation-policy.js";
|
|
@@ -27,4 +28,8 @@ export * from "./operations-capability.js";
|
|
|
27
28
|
export { WmsKernel } from "./kernel.js";
|
|
28
29
|
export { YmsKernel } from "./yms-kernel.js";
|
|
29
30
|
export { MesKernel, MES_PART_GTINS, MES_PRODUCT_GTINS, MES_PRODUCTS } from "./mes-kernel.js";
|
|
31
|
+
export { EmsKernel, DEMAND_WINDOW_MS, demandWindowStart } from "./ems-kernel.js";
|
|
32
|
+
export { ingestEnergyRecords, isEnergyRecord } from "./energy-ingest.js";
|
|
33
|
+
export { attributeEnergy, energyIntensity, energyOfWindows } from "./energy-attribution.js";
|
|
34
|
+
/* 에너지 상태 타입은 **계약**에 있다(상태의 모양은 계약이다) — contract 의 `export *` 가 이미 낸다. */
|
|
30
35
|
export * from "./vocabulary.js";
|
package/dist/kernel.d.ts
CHANGED
|
@@ -35,11 +35,11 @@ export declare class WmsKernel extends FlowEngine {
|
|
|
35
35
|
* 자재는 **작업이 일어나는 자리에** 있어야 소비되고, 산출물은 `in_progress` 로 태어나 **팔 수 있는
|
|
36
36
|
* 재고가 아니다.** 그래서 부품을 작업대로 옮기고, 가공하고, 나온 것을 보관 자리로 되돌린다.
|
|
37
37
|
*
|
|
38
|
-
* **한 오더에 사슬 하나만**
|
|
38
|
+
* **한 오더에 사슬 하나만** 실행한다. 매 tick 마다 다시 발행하면 같은 부품을 두 번 끌어오는 작업이
|
|
39
39
|
* 쌓이고, 그중 하나만 성공한 뒤 나머지는 영원히 재료를 기다린다.
|
|
40
40
|
*/
|
|
41
41
|
private makeShortLines;
|
|
42
|
-
/** 이 오더가
|
|
42
|
+
/** 이 오더가 실행 중인 생산 사슬이 있나 — 이송·가공·되돌리기 중 하나라도 열려 있으면 그렇다. */
|
|
43
43
|
private hasOpenMakeChain;
|
|
44
44
|
/** 지금 재고 — 판단 함수가 보는 형태로. */
|
|
45
45
|
private stockLines;
|
|
@@ -53,7 +53,7 @@ export declare class WmsKernel extends FlowEngine {
|
|
|
53
53
|
* 들여놓는 것**이다: 산출물은 `in_progress` 로 태어나 팔 수 있는 재고가 아니다.
|
|
54
54
|
*
|
|
55
55
|
* ── 왜 팔레트로 묶나 ───────────────────────────────────────────────────────
|
|
56
|
-
* 코어의 산출은 **클래스+수량 줄**이고 그 키에는 자리가
|
|
56
|
+
* 코어의 산출은 **클래스+수량 줄**이고 그 키에는 자리가 고정되어 있다(`품목@자리`). 창고의 출고 경로는
|
|
57
57
|
* 직렬 물류단위(팔레트 SSCC)를 다루므로, 그 줄을 그대로 출고에 태우면 키를 EPC 로 착각해 조회가
|
|
58
58
|
* 깨진다(실제로 그렇게 터졌다). 억지로 태우면 EPCIS 에도 **EPC 가 아닌 문자열**이 `epcList` 로
|
|
59
59
|
* 나가는데, 그것은 코어가 산출에서 경계한 바로 그 일이다.
|
package/dist/kernel.js
CHANGED
|
@@ -164,7 +164,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
164
164
|
/** 태스크 완료 — 이동 반영 후 putaway=storing, pick=picking(+전량 시 pack→stage→ship). */
|
|
165
165
|
onTaskComplete(t) {
|
|
166
166
|
/*
|
|
167
|
-
* **생산 사슬의 걸음들을 먼저
|
|
167
|
+
* **생산 사슬의 걸음들을 먼저 구분한다.** 아래 이동 처리는 "주체가 팔레트 하나" 를 전제하는데,
|
|
168
168
|
* 가공은 클래스+수량 소비/산출이라 주체가 없다 — 그대로 두면 물품을 찾지 못해 터진다(실제로
|
|
169
169
|
* 그랬다). 걸음마다 무엇이 끝난 것인지 다르므로 각각 답한다.
|
|
170
170
|
*/
|
|
@@ -211,7 +211,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
211
211
|
* 자재는 **작업이 일어나는 자리에** 있어야 소비되고, 산출물은 `in_progress` 로 태어나 **팔 수 있는
|
|
212
212
|
* 재고가 아니다.** 그래서 부품을 작업대로 옮기고, 가공하고, 나온 것을 보관 자리로 되돌린다.
|
|
213
213
|
*
|
|
214
|
-
* **한 오더에 사슬 하나만**
|
|
214
|
+
* **한 오더에 사슬 하나만** 실행한다. 매 tick 마다 다시 발행하면 같은 부품을 두 번 끌어오는 작업이
|
|
215
215
|
* 쌓이고, 그중 하나만 성공한 뒤 나머지는 영원히 재료를 기다린다.
|
|
216
216
|
*/
|
|
217
217
|
makeShortLines(o) {
|
|
@@ -239,7 +239,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
239
239
|
return; // 한 번에 한 라인씩 — 부품을 여러 라인이 동시에 다투지 않게
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
/** 이 오더가
|
|
242
|
+
/** 이 오더가 실행 중인 생산 사슬이 있나 — 이송·가공·되돌리기 중 하나라도 열려 있으면 그렇다. */
|
|
243
243
|
hasOpenMakeChain(orderId) {
|
|
244
244
|
for (const t of this.tasks.values()) {
|
|
245
245
|
if (t.orderId !== orderId || t.status === 'completed')
|
|
@@ -274,7 +274,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
274
274
|
/* 물품을 지목하지 않는다 — 클래스+수량 소비/산출이므로 주체가 팔레트 하나가 아니다.
|
|
275
275
|
코어의 작업 게이트는 빈 `itemEpc` 를 견딘다(`t.itemEpc && …` 조건부 확인). */
|
|
276
276
|
/* **의도를 process 로 밝힌다** — 코어가 이 표시로 이동(모션)과 변환을 가르고, 이 커널의 완료 훅도
|
|
277
|
-
그것으로 판단한다. "선언된 오퍼레이션인가" 로
|
|
277
|
+
그것으로 판단한다. "선언된 오퍼레이션인가" 로 구분하면 산출을 선언한 이동 작업까지 삼킨다. */
|
|
278
278
|
this.pushTask(o, operation, '', at, at, 'process');
|
|
279
279
|
}
|
|
280
280
|
pushTask(o, kind, itemEpc, from, to, intent) {
|
|
@@ -293,7 +293,7 @@ export class WmsKernel extends FlowEngine {
|
|
|
293
293
|
* 들여놓는 것**이다: 산출물은 `in_progress` 로 태어나 팔 수 있는 재고가 아니다.
|
|
294
294
|
*
|
|
295
295
|
* ── 왜 팔레트로 묶나 ───────────────────────────────────────────────────────
|
|
296
|
-
* 코어의 산출은 **클래스+수량 줄**이고 그 키에는 자리가
|
|
296
|
+
* 코어의 산출은 **클래스+수량 줄**이고 그 키에는 자리가 고정되어 있다(`품목@자리`). 창고의 출고 경로는
|
|
297
297
|
* 직렬 물류단위(팔레트 SSCC)를 다루므로, 그 줄을 그대로 출고에 태우면 키를 EPC 로 착각해 조회가
|
|
298
298
|
* 깨진다(실제로 그렇게 터졌다). 억지로 태우면 EPCIS 에도 **EPC 가 아닌 문자열**이 `epcList` 로
|
|
299
299
|
* 나가는데, 그것은 코어가 산출에서 경계한 바로 그 일이다.
|
package/dist/mes-kernel.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare class MesKernel extends FlowEngine {
|
|
|
27
27
|
* **산출을 두 곳에서 만들지 않는다** — 기동 때 막는다.
|
|
28
28
|
*
|
|
29
29
|
* MES 는 레시피로 산출을 만든다: WIP 사슬(스텝마다 앞의 WIP 을 먹고 다음 WIP 을 낸다) · **직렬번호**
|
|
30
|
-
* (회사 프리픽스 + 일련번호) · **수율**(양품/불량이 처분을
|
|
30
|
+
* (회사 프리픽스 + 일련번호) · **수율**(양품/불량이 처분을 구분한다) · 생산오더 연결. 코어의 일반
|
|
31
31
|
* 자재 명세(`use: 'produced'`)는 **비직렬 클래스+수량**이라 그 넷을 표현하지 못한다.
|
|
32
32
|
*
|
|
33
33
|
* 그래서 MES 를 일반 선언으로 옮기지 않는다 — 옮기면 정체성·수율·오더 연결을 **잃는다**(§4-20).
|
|
@@ -56,7 +56,7 @@ export declare class MesKernel extends FlowEngine {
|
|
|
56
56
|
/**
|
|
57
57
|
* MES 는 완료 시점에 **오더와 제품 정의**를 딛고 선다 — 둘 중 하나만 없어도 끝맺을 수 없다.
|
|
58
58
|
*
|
|
59
|
-
* 씨앗이 이미 이행된 오더를
|
|
59
|
+
* 씨앗이 이미 이행된 오더를 주입하지 않으므로(남은 수량 0) 그 오더에 딸린 작업만 남을 수 있고,
|
|
60
60
|
* 관측이 오더 연결을 담지 못한 작업도 있다. 그때 예전에는 `order.gtin` 에서 던졌고 **그 예외
|
|
61
61
|
* 하나가 정확도 추세 전체를 죽였다.** 이제 코어가 이 답을 보고 그 작업만 접는다.
|
|
62
62
|
*/
|
package/dist/mes-kernel.js
CHANGED
|
@@ -86,7 +86,7 @@ export class MesKernel extends FlowEngine {
|
|
|
86
86
|
* **산출을 두 곳에서 만들지 않는다** — 기동 때 막는다.
|
|
87
87
|
*
|
|
88
88
|
* MES 는 레시피로 산출을 만든다: WIP 사슬(스텝마다 앞의 WIP 을 먹고 다음 WIP 을 낸다) · **직렬번호**
|
|
89
|
-
* (회사 프리픽스 + 일련번호) · **수율**(양품/불량이 처분을
|
|
89
|
+
* (회사 프리픽스 + 일련번호) · **수율**(양품/불량이 처분을 구분한다) · 생산오더 연결. 코어의 일반
|
|
90
90
|
* 자재 명세(`use: 'produced'`)는 **비직렬 클래스+수량**이라 그 넷을 표현하지 못한다.
|
|
91
91
|
*
|
|
92
92
|
* 그래서 MES 를 일반 선언으로 옮기지 않는다 — 옮기면 정체성·수율·오더 연결을 **잃는다**(§4-20).
|
|
@@ -193,7 +193,7 @@ export class MesKernel extends FlowEngine {
|
|
|
193
193
|
/**
|
|
194
194
|
* MES 는 완료 시점에 **오더와 제품 정의**를 딛고 선다 — 둘 중 하나만 없어도 끝맺을 수 없다.
|
|
195
195
|
*
|
|
196
|
-
* 씨앗이 이미 이행된 오더를
|
|
196
|
+
* 씨앗이 이미 이행된 오더를 주입하지 않으므로(남은 수량 0) 그 오더에 딸린 작업만 남을 수 있고,
|
|
197
197
|
* 관측이 오더 연결을 담지 못한 작업도 있다. 그때 예전에는 `order.gtin` 에서 던졌고 **그 예외
|
|
198
198
|
* 하나가 정확도 추세 전체를 죽였다.** 이제 코어가 이 답을 보고 그 작업만 접는다.
|
|
199
199
|
*/
|
package/dist/mes-profile.js
CHANGED
|
@@ -26,8 +26,32 @@ const MES_LOCATION_CLS = {
|
|
|
26
26
|
'assembly-line': { isa95: 'WorkCenter', epcis: 'bizLocation' },
|
|
27
27
|
'fg-store': { epcis: 'bizLocation' }
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* 자리의 **ISA-95 계층 단** — 여기가 단이 가장 크게 갈리는 곳이다.
|
|
31
|
+
*
|
|
32
|
+
* · `assembly-line` = 라인 → `ProductionLine`(`WorkCenter` 의 한 종류)
|
|
33
|
+
* · `cut-station`·`weld-station`·`paint-booth` = 라인 안의 작업 자리 → `WorkCell`(`WorkUnit` 의 한 종류)
|
|
34
|
+
* · `raw-store`·`fg-store` = 보관 구역 → `StorageZone`
|
|
35
|
+
*
|
|
36
|
+
* ── 남은 어긋남 (2026-08-14, 지금 고치지 않는다) ─────────────────────────────
|
|
37
|
+
* 스테이션들의 `standardClass.isa95` 는 예전부터 `WorkCenter` 로 적혀 있다. 그런데 `WorkCenter` 는
|
|
38
|
+
* ProcessCell·ProductionLine·ProductionUnit·StorageZone 의 **총칭**이므로, 라인 안의 작업 자리는
|
|
39
|
+
* 엄밀히는 `WorkUnit` 계열이다. 즉 클래스 투영이 한 단 위를 가리키고 있다.
|
|
40
|
+
*
|
|
41
|
+
* 여기서 그것을 바꾸지 않는다: `standardClass` 는 적합성 표가 세는 값이고(`isa95-coverage`),
|
|
42
|
+
* 바꾸면 그 표의 숫자가 함께 움직인다. **단(`level`)과 클래스(`standardClass`)는 다른 축**이므로
|
|
43
|
+
* 단을 옳게 적는 것으로 계층 질의는 살아난다. 클래스 재검토는 적합성 표와 함께 다룰 일이다.
|
|
44
|
+
*/
|
|
45
|
+
const MES_LOCATION_LEVEL = {
|
|
46
|
+
'raw-store': 'StorageZone',
|
|
47
|
+
'cut-station': 'WorkCell',
|
|
48
|
+
'weld-station': 'WorkCell',
|
|
49
|
+
'paint-booth': 'WorkCell',
|
|
50
|
+
'assembly-line': 'ProductionLine',
|
|
51
|
+
'fg-store': 'StorageZone'
|
|
52
|
+
};
|
|
29
53
|
export const MES_TYPES = [
|
|
30
|
-
...MES_LOCATION_TYPES.map((k) => ({ key: k, role: 'location', label: `twin.type.${k}`, standardClass: MES_LOCATION_CLS[k] ?? {}, identity: { scheme: 'gs1:SGLN' }, capabilities: ['storable'] })),
|
|
54
|
+
...MES_LOCATION_TYPES.map((k) => ({ key: k, role: 'location', label: `twin.type.${k}`, standardClass: MES_LOCATION_CLS[k] ?? {}, identity: { scheme: 'gs1:SGLN' }, level: MES_LOCATION_LEVEL[k], capabilities: ['storable'] })),
|
|
31
55
|
{ key: 'cutter', role: 'equipment', label: 'twin.type.cutter', standardClass: { isa95: 'Equipment', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['processable', 'operable'] },
|
|
32
56
|
{ key: 'welder', role: 'equipment', label: 'twin.type.welder', standardClass: { isa95: 'Equipment', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['processable', 'operable'] },
|
|
33
57
|
{ key: 'painter', role: 'equipment', label: 'twin.type.painter', standardClass: { isa95: 'Equipment', iso55000: 'Asset' }, identity: { scheme: 'gs1:GIAI' }, capabilities: ['processable', 'operable'] },
|
|
@@ -28,6 +28,23 @@ export interface ProjectedState {
|
|
|
28
28
|
correctiveEventIDs: string[];
|
|
29
29
|
eventID?: string;
|
|
30
30
|
}[];
|
|
31
|
+
/**
|
|
32
|
+
* 반영하지 못한 사건의 종류 — **리비전은 올라갔는데 상태가 비어 있는 이유**를 여기서 말한다.
|
|
33
|
+
*
|
|
34
|
+
* 모르는 `eventType` 은 전방 호환을 위해 버린다(새 커넥터가 옛 커널에 보내는 것을 막을 수 없다).
|
|
35
|
+
* 그런데 **버렸다는 사실까지 버리면** 화면은 「비었다」와 「담을 줄 몰랐다」를 구별할 수 없다.
|
|
36
|
+
* 에너지 사건(`energy.*`)이 물류 리듀서에 들어오는 것이 정확히 그 경우다 — 리비전만 올라가고
|
|
37
|
+
* 아무것도 담기지 않는다. 그것을 조용히 두면 트윈은 「정상인데 빈」 모습이 된다.
|
|
38
|
+
*
|
|
39
|
+
* 비어 있지 않으면 뜻은 하나다: **이 트윈에 맞지 않는 어휘가 들어오고 있다**(커넥터 배선 오류이거나
|
|
40
|
+
* 커널이 낡았다). 세는 것에 그치고 판단은 소비처가 한다 — 여기서 던지면 저널 재생이 멈춘다.
|
|
41
|
+
*/
|
|
42
|
+
unhandled?: {
|
|
43
|
+
eventType: string;
|
|
44
|
+
count: number;
|
|
45
|
+
firstAtMs?: number;
|
|
46
|
+
lastAtMs?: number;
|
|
47
|
+
}[];
|
|
31
48
|
locations: LocationState[];
|
|
32
49
|
items: ItemState[];
|
|
33
50
|
/** 사람 — 등급·교대·투입. 인원을 선언하지 않은 트윈에서는 빈 배열. */
|
|
@@ -63,11 +80,13 @@ export declare class ObservedReducer {
|
|
|
63
80
|
revision: number;
|
|
64
81
|
/** 받은 정정 선언 — 상태에 반영하지 않되 **버리지도 않는다**(소비처가 볼 수 있게). */
|
|
65
82
|
private corrections;
|
|
83
|
+
/** 반영하지 못한 사건의 종류별 집계 — 원문은 쌓지 않는다(저널에 이미 있다). */
|
|
84
|
+
private unhandled;
|
|
66
85
|
/**
|
|
67
86
|
* 자원별 **교대 선언** — 미러가 "지금 근무 중인가" 를 스스로 판정하기 위한 재료.
|
|
68
87
|
*
|
|
69
88
|
* 상태(`EquipmentState`)에 넣지 않는다: 시뮬 스냅샷도 이것을 내보내지 않으므로 넣으면 두 구동이
|
|
70
|
-
*
|
|
89
|
+
* 어긋난다(적합성 하네스가 `mirrorOnly` 로 잡는다). 이것은 **판정의 입력**이고, 나가는 것은 판정뿐이다.
|
|
71
90
|
*/
|
|
72
91
|
private shifts;
|
|
73
92
|
/**
|
|
@@ -81,14 +100,14 @@ export declare class ObservedReducer {
|
|
|
81
100
|
private utcOffsetMinutes?;
|
|
82
101
|
constructor(model: TwinModelDef);
|
|
83
102
|
/**
|
|
84
|
-
* **구조를
|
|
103
|
+
* **구조를 전환한다** — 관측된 사실은 지키고 토폴로지만 새 선언으로 바꾼다.
|
|
85
104
|
*
|
|
86
105
|
* 공장은 바뀐다. 도장 부스를 넷 더 놓고, 라인을 하나 접는다. 그런데 지금까지는 구조가 바뀌면
|
|
87
106
|
* **그 트윈의 저널을 통째로 지우는 것**이 유일한 길이었다 — 안 지우면 옛 이벤트를 새 공장에 대고
|
|
88
107
|
* 접게 되어 이력이 거짓말을 한다. 역사를 잃거나 거짓말을 하거나, 둘뿐이었다.
|
|
89
108
|
*
|
|
90
109
|
* 셋째 길이 이것이다: 이벤트가 **자기 구조를 달고** 다니고, 재생은 구조가 바뀌는 지점에서 여기를
|
|
91
|
-
* 불러
|
|
110
|
+
* 불러 전환한 뒤 이어 접는다. 그러면 "그때 그 공장의 사실" 로 계속 읽힌다.
|
|
92
111
|
*
|
|
93
112
|
* ── 무엇을 지키고 무엇을 버리는가 ───────────────────────────────────────
|
|
94
113
|
* **관측은 지킨다** — 물품·오더·작업·집합은 구조와 무관한 사실이다(팔레트는 부스를 늘려도 그대로다).
|
|
@@ -129,13 +148,15 @@ export declare class ObservedReducer {
|
|
|
129
148
|
/**
|
|
130
149
|
* **미러의 "지금"** — 지금까지 들은 것 중 가장 늦은 발생 시각.
|
|
131
150
|
*
|
|
132
|
-
* 미러는 스스로 시간을
|
|
151
|
+
* 미러는 스스로 시간을 진행시키지 않지만, 시각으로만 일어나는 사실(유효 기간 만료)을 판정하려면 기준이
|
|
133
152
|
* 필요하다. 미러의 정직한 기준은 **"내가 마지막으로 들은 시점"** 이다. 아무것도 못 들었으면
|
|
134
153
|
* 판정하지 않는다(모르면 단정하지 않는다).
|
|
135
154
|
*/
|
|
136
155
|
private observedAtMs?;
|
|
137
156
|
/** 이벤트 1건 반영 — eventType 으로 EPCIS vs 운영 델타 분기. */
|
|
138
157
|
apply(e: CanonicalEnvelope): void;
|
|
158
|
+
/** 반영하지 못한 사건을 종류별로 센다 — 처음·마지막 시각을 함께 남겨 「언제부터」에 답한다. */
|
|
159
|
+
private noteUnhandled;
|
|
139
160
|
private applyEpcis;
|
|
140
161
|
/**
|
|
141
162
|
* 물품 한 건 병합 — **아는 것을 잃지 않는다.** 새로 온 값이 우선, 없으면 기존 값 유지.
|