@operato/twin-kernel 0.6.14 → 0.7.0
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 +115 -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 +77 -15
- package/dist/domain-catalog.js +79 -14
- package/dist/domain-definition.d.ts +17 -1
- package/dist/ems-kernel.d.ts +121 -0
- package/dist/ems-kernel.js +284 -0
- package/dist/ems-profile.d.ts +16 -0
- package/dist/ems-profile.js +111 -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 +3 -0
- package/dist/index.js +2 -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 +530 -56
- package/package.json +1 -1
package/dist/flow-engine.d.ts
CHANGED
|
@@ -135,7 +135,7 @@ export interface FlowTask {
|
|
|
135
135
|
* **실제로 들어가고 나온 자재** — ISA-95 `JobResponse.MaterialActual`.
|
|
136
136
|
*
|
|
137
137
|
* 명세(계획)가 아니라 **일어난 일**이다. 투입 인원·설비가 이미 작업 델타에 타고 있으니 자재도 같은
|
|
138
|
-
* 채널에 실어야 실적을 **한 곳에서** 읽는다(EPCIS 이벤트에서
|
|
138
|
+
* 채널에 실어야 실적을 **한 곳에서** 읽는다(EPCIS 이벤트에서 다시 계산하려면 작업과 잇는 끈이 없다).
|
|
139
139
|
*/
|
|
140
140
|
materialActual?: {
|
|
141
141
|
definitionId: string;
|
|
@@ -340,7 +340,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
340
340
|
*/
|
|
341
341
|
observe(): void;
|
|
342
342
|
/**
|
|
343
|
-
* 돌면서 공장을
|
|
343
|
+
* 돌면서 공장을 전환한다 — **현실이 안 멈추므로 미러도 멈출 수 없다.**
|
|
344
344
|
*
|
|
345
345
|
* ── 왜 관측 구동에만 여는가 ────────────────────────────────────────────────
|
|
346
346
|
* 미러는 이미 있는 현실을 따라갈 뿐이라, 설비가 한 대 늘었다고 멈췄다 서는 것은 그 사이의
|
|
@@ -353,7 +353,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
353
353
|
* 자원은 버리되 **몇 개를 버렸는지 돌려준다.** 여기서는 그 판단을 커널 자신의 선언 표에도
|
|
354
354
|
* 그대로 옮긴다. 관측층만 갈면 사라진 설비가 커널 표에 남아 **화면이 없는 설비를 그린다.**
|
|
355
355
|
*
|
|
356
|
-
* 관측으로 알게 된 자리는 선언에 없어도 지운 뒤 다시
|
|
356
|
+
* 관측으로 알게 된 자리는 선언에 없어도 지운 뒤 다시 주입한다 — 다음 스냅샷에서 관측층이
|
|
357
357
|
* 되살리므로(`settleObserved` → `hydrateObserved`), 여기서 지켜야 할 것은 **선언분**뿐이다.
|
|
358
358
|
*/
|
|
359
359
|
adoptStructure(def: TwinModelDef): StructureShift;
|
|
@@ -432,7 +432,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
432
432
|
protected computeAttentions(): Attention[];
|
|
433
433
|
/**
|
|
434
434
|
* fork — 현재 상태를 정확히 복제한 새 엔진 (디지털트윈 본연: "현재로부터 예측").
|
|
435
|
-
* 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로
|
|
435
|
+
* 원본(live/sim)은 계속 진행, fork 는 what-if 를 앞으로 시뮬레이션해 forecast·발산(predicted vs actual) 검사에 쓴다.
|
|
436
436
|
* fork 는 자기 구독자·시나리오를 갖고 원본과 격리(handlers·gens 비움, generating=false).
|
|
437
437
|
* rng 는 fork 의 시나리오 load 시 재시드(드레인 예측은 생성 없어 rng 무관·결정적).
|
|
438
438
|
*/
|
|
@@ -441,7 +441,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
441
441
|
* 이 커널의 **지금**(ms) — 시각으로 바뀌는 모든 판정의 단일 기준.
|
|
442
442
|
*
|
|
443
443
|
* ── 관측 모드에서 시계가 멈춰 있었다 ────────────────────────────────────
|
|
444
|
-
* `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을
|
|
444
|
+
* `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을 진행시키지 않는다). 그래서 라이브 트윈의 "지금" 이
|
|
445
445
|
* **BASE_EPOCH(2026-01-01)에 얼어 있었다.** 실 이벤트는 실제 시각을 달고 오므로 결과가 이렇게 된다:
|
|
446
446
|
* · **납기 초과를 영원히 보고하지 않는다** — 실 납기가 항상 미래로 보인다
|
|
447
447
|
* · 폐기·도입예정 판정이 틀린다(§EffectivePeriod)
|
|
@@ -472,14 +472,14 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
472
472
|
};
|
|
473
473
|
protected randInt(min: number, max: number): number;
|
|
474
474
|
/**
|
|
475
|
-
* 관측 구동(P0 스파이크) — **이벤트로 커널을
|
|
475
|
+
* 관측 구동(P0 스파이크) — **이벤트로 커널을 실행한다.**
|
|
476
476
|
*
|
|
477
477
|
* 상태를 만드는 구동이 둘인데(시뮬 `tick` / 미러 `apply`) 지금은 **모델도 둘**이라 한쪽만 고치면
|
|
478
|
-
*
|
|
478
|
+
* 어긋난다(2026-08-01 하루에 아홉 곳). 근본 해법은 **한 상태 모델 두 구동**이고, 이것은 그 실현
|
|
479
479
|
* 가능성을 재는 스파이크다(design/plans/kernel-unification-live-observe.md P0).
|
|
480
480
|
*
|
|
481
481
|
* 여기서는 **이미 검증된 조각을 조립**한다: 투영기가 이벤트를 접고, 그 결과를 씨앗 경로
|
|
482
|
-
* (`hydrateObserved`)로 커널 상태에
|
|
482
|
+
* (`hydrateObserved`)로 커널 상태에 주입한다. 그래서 관측으로 실행한 커널을 그대로 `fork`·`tick` 할 수
|
|
483
483
|
* 있다 — "미러에서 예측한다" 가 별도 배관 없이 성립하는지가 이 스파이크의 질문이다.
|
|
484
484
|
*
|
|
485
485
|
* **비용은 정직하게**: 이벤트마다 전체를 다시 심으므로 O(상태 크기)다. P1 에서 반영 로직을 순수
|
|
@@ -512,14 +512,14 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
512
512
|
*/
|
|
513
513
|
protected routeKeys(): string[] | undefined;
|
|
514
514
|
/**
|
|
515
|
-
* **이 공장이 하루 몇 대를 낼 수 있는가** —
|
|
515
|
+
* **이 공장이 하루 몇 대를 낼 수 있는가** — 실행해 보지 않고 답한다.
|
|
516
516
|
*
|
|
517
517
|
* 커널이 직접 답하는 이유: 필요한 사실이 전부 여기 있다(공정 명세·설비와 신뢰도·인원·물리자산·
|
|
518
518
|
* 자리·근무 달력·시각 기준). 밖에서 모으면 그 값을 옮겨 적게 되고, 한쪽만 바뀌는 순간 "충분하다"
|
|
519
519
|
* 가 조용히 거짓이 된다. 계산 자체는 순수 함수(`analyzeCapacity`)에 맡긴다.
|
|
520
520
|
*
|
|
521
521
|
* 달력은 **자원이 선언한 것**을 쓴다. 자원마다 다른 달력을 쓰는 현장이면 대표를 고를 수 없으므로
|
|
522
|
-
* 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면
|
|
522
|
+
* 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면 상한이 틀린 채로
|
|
523
523
|
* 그럴듯해 보인다.
|
|
524
524
|
*/
|
|
525
525
|
capacity(opts: {
|
|
@@ -562,7 +562,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
562
562
|
/**
|
|
563
563
|
* task 소요 산출 — 우선순위: **① 추정기(이력 보정) → ② 명세(ISA-95 Duration + 변동) → ③ 도메인 상수.**
|
|
564
564
|
*
|
|
565
|
-
* 이 순서인 이유: 실측에서 배운 값이 선언값을 이기고, 선언값이 우리가 코드에
|
|
565
|
+
* 이 순서인 이유: 실측에서 배운 값이 선언값을 이기고, 선언값이 우리가 코드에 고정한 상수를 이긴다.
|
|
566
566
|
* 셋 중 무엇을 썼는지는 `specCoverage()` 로 드러낸다 — 상수를 쓴 것이 조용히 넘어가지 않게.
|
|
567
567
|
* "얼마"만 소비하고 "경로"는 씬이 소유한다(좌표-free 유지).
|
|
568
568
|
*/
|
|
@@ -585,7 +585,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
585
585
|
private noteSpecUse;
|
|
586
586
|
private noteParamUse;
|
|
587
587
|
/**
|
|
588
|
-
* 시뮬 명세 자기보고 — **어디까지 데이터로 말했고 어디부터 우리가
|
|
588
|
+
* 시뮬 명세 자기보고 — **어디까지 데이터로 말했고 어디부터 우리가 코드에 고정한 상수인가.**
|
|
589
589
|
*
|
|
590
590
|
* 시뮬레이션 결과를 받는 쪽이 이걸 봐야 한다: 소요시간이 전부 기본값이면 그 예측으로 말할 수 있는 것은
|
|
591
591
|
* "같은 조건에서의 상대 비교" 뿐이고 "몇 시에 끝난다" 는 근거가 없다. 그 구분을 숫자로 드러낸다.
|
|
@@ -653,7 +653,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
653
653
|
*/
|
|
654
654
|
protected reserve(epcs: string[], bizStep: string): void;
|
|
655
655
|
/**
|
|
656
|
-
* 처분 변화 관측 — **상태와 이벤트를 한 번에.** 둘을 따로 쓰면 반드시
|
|
656
|
+
* 처분 변화 관측 — **상태와 이벤트를 한 번에.** 둘을 따로 쓰면 반드시 어긋난다.
|
|
657
657
|
*
|
|
658
658
|
* 실제로 양쪽으로 갈라져 있었다: 할당은 상태만 바꾸고 이벤트를 안 냈고(미러가 모름), 야드 도크
|
|
659
659
|
* 도착은 이벤트만 내고 상태를 안 바꿨다(이벤트와 상태가 다른 말). 적합성 하네스가 둘 다 잡았다.
|
|
@@ -758,7 +758,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
758
758
|
* 지금은 자리를 따지지 않는다 — 따지려면 자산 이송 작업이 먼저 있어야 한다).
|
|
759
759
|
*/
|
|
760
760
|
private claimAssets;
|
|
761
|
-
/** 확보한 자산을 작업에
|
|
761
|
+
/** 확보한 자산을 작업에 배정한다 — 싣는 물류단위(SSCC)가 있으면 연결한다(GRAI ↔ SSCC). */
|
|
762
762
|
private assignAssets;
|
|
763
763
|
/**
|
|
764
764
|
* 작업이 끝나면 자산을 놓아 준다 — **사람과 다른 점: 자산은 도착 자리에 남는다**(물건이므로).
|
|
@@ -805,7 +805,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
805
805
|
* 이 작업이 **딛고 선 것**이 아직 있나 — 없으면 무엇이 없는지 답한다.
|
|
806
806
|
*
|
|
807
807
|
* 작업은 혼자 서지 못한다: 옮길 **물품**과, (있다면) 그것을 시킨 **오더** 위에 선다. 진행 중에
|
|
808
|
-
* 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이
|
|
808
|
+
* 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이 주입하지 않아서.
|
|
809
809
|
*
|
|
810
810
|
* 그때 도메인 훅은 없는 것을 딛으려다 던진다(`order.gtin` · `item.location`). 그 예외 하나가
|
|
811
811
|
* **예측 전체를 죽였다** — 사용자에게는 기능이 통째로 사라진 것으로 보였다. 그래서 완료 **전에**
|
|
@@ -859,7 +859,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
859
859
|
* 자원 하나의 **가용 능력** — 계약의 판정에 이 커널의 시각·시간대·필수 시험을 채워 넘긴다.
|
|
860
860
|
*
|
|
861
861
|
* 배정도 스냅샷도 이 자리를 지난다. 예전에는 배정이 조건을 늘어놓고 화면이 플래그를 보고 짐작해서,
|
|
862
|
-
* **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(
|
|
862
|
+
* **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(대기 중인 것은 맞지만 쓸 수 있는 것은 아니다).
|
|
863
863
|
* 판정에 필요한 둘(시각·등급 상속)을 아는 것은 커널뿐이므로, 답도 커널이 낸다.
|
|
864
864
|
*/
|
|
865
865
|
private capabilityOfResource;
|
|
@@ -870,7 +870,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
870
870
|
* 여기서는 고르기만 한다 — 확정은 호출부가 다른 자원까지 확보한 뒤에 한다.
|
|
871
871
|
*/
|
|
872
872
|
private claimEquipment;
|
|
873
|
-
/** 확보한 사람을 작업에
|
|
873
|
+
/** 확보한 사람을 작업에 배정한다(설비까지 확정된 뒤). */
|
|
874
874
|
private assignCrew;
|
|
875
875
|
/** 작업이 끝나면 사람을 놓아 준다 — 설비 해제와 별개 경로. */
|
|
876
876
|
private releaseCrew;
|
|
@@ -897,7 +897,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
897
897
|
* 유효 기간 밖인가 — **네 번째 이유**(§Effectivity). 설비·사람·자산이 같은 규칙을 쓴다.
|
|
898
898
|
*
|
|
899
899
|
* 시뮬 시각을 ISO 로 풀어 계약의 `effectivityAt` 에 넘긴다. **판정은 커널에 두지 않는다** —
|
|
900
|
-
* 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면
|
|
900
|
+
* 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면 어긋난다.
|
|
901
901
|
*/
|
|
902
902
|
protected effectivityOf(r: EffectivePeriod): Effectivity | undefined;
|
|
903
903
|
/**
|
|
@@ -912,7 +912,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
912
912
|
/**
|
|
913
913
|
* 스냅샷·델타에 실을 조각 — **선언한 기간과 판정을 함께** 낸다.
|
|
914
914
|
*
|
|
915
|
-
* 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해
|
|
915
|
+
* 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해 어긋난다.
|
|
916
916
|
* 선언이 없으면 아무것도 붙이지 않는다(대부분의 자원이 그렇다 — 필드를 늘리지 않는다).
|
|
917
917
|
*/
|
|
918
918
|
protected effectivePart(r: EffectivePeriod): Partial<EffectivePeriod> & {
|
|
@@ -922,7 +922,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
922
922
|
* 지금 근무 시간 밖인가 — **캘린더가 있으면 그것으로, 없으면 옛 `window` 로** 판정한다.
|
|
923
923
|
*
|
|
924
924
|
* `window`(시 단위 하나)는 캘린더의 특수한 경우다. 둘을 한 함수로 모아 두면 인원·설비가 같은 규칙을
|
|
925
|
-
* 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히
|
|
925
|
+
* 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히 어긋난다).
|
|
926
926
|
*/
|
|
927
927
|
protected offCalendar(r: {
|
|
928
928
|
window?: {
|
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,6 @@ 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 type { EnergyState, DemandWindowState, MeterPointState } from './ems-kernel.ts';
|
|
30
33
|
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,5 @@ 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";
|
|
30
32
|
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
|
*/
|