@operato/twin-kernel 0.7.51 → 0.7.53
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/contract.d.ts +53 -0
- package/dist/contract.js +44 -0
- package/dist/flow-engine.js +6 -3
- package/dist/mes-kernel.d.ts +15 -4
- package/dist/mes-kernel.js +27 -6
- package/dist-cjs/index.cjs +40 -8
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -7,7 +7,60 @@ export interface CanonicalEnvelope<T = unknown> {
|
|
|
7
7
|
correlationId?: string;
|
|
8
8
|
data: T;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* 작업의 상태 — **ISA-95 근거.** 1차 출처: `JobOrderType.DispatchStatus`(지시 쪽) +
|
|
12
|
+
* `JobResponseType.JobState`(실적 쪽). 대조 기록: `design/plans/isa95-coverage.md` §3-2
|
|
13
|
+
* (「`tasks.status` ↔ `DispatchStatus`+`JobState`」).
|
|
14
|
+
*
|
|
15
|
+
* ── 왜 우리 이름을 쓰나 ─────────────────────────────────────────────────────
|
|
16
|
+
* 표준은 **지시(`JobOrder`)와 실적(`JobResponse`)을 나눈다.** 우리 `TaskState` 는 그 둘을 한 몸에
|
|
17
|
+
* 든다 — 상태 전이와 배정 결과를 함께 들고 진행·완료까지 같은 개체가 표현한다. 그래서 `jobOrders`
|
|
18
|
+
* 로 부르면 **표준 이름을 쓰면서 다른 뜻으로 쓰는 것**이 된다. 대조 문서가 그 판정을 적어 두었다.
|
|
19
|
+
*
|
|
20
|
+
* ── 왜 **닫혀** 있나 ────────────────────────────────────────────────────────
|
|
21
|
+
* 커널이 이 넷으로 접는다: 성과 폴드가 착수·완료로 갈리고(`foldTaskRecords`), 주목 계산이
|
|
22
|
+
* `created`·`assigned` 를 대기로 센다, 배정 루프는 `created` 만 집는다. 그러니 낱말이 하나 늘면
|
|
23
|
+
* 그 셋이 조용히 달라진다 — 유입 문이 이 넷만 받는 이유다(다른 낱말은 오류 없이 사라진다).
|
|
24
|
+
*/
|
|
10
25
|
export type TaskStatus = 'created' | 'assigned' | 'in-progress' | 'completed';
|
|
26
|
+
/** 오더가 **종결 상태**로 인정되는 낱말 — 이 밖은 진행 중이다(§`isOrderTerminal`). */
|
|
27
|
+
export declare const ORDER_TERMINAL_STATUS: readonly ["completed", "cancelled"];
|
|
28
|
+
/**
|
|
29
|
+
* 이 오더는 **끝났나** — 판정을 한 곳에 둔다(`locationStatusOf`·`dueStatusOf` 와 같은 규율).
|
|
30
|
+
*
|
|
31
|
+
* ── 왜 함수여야 하나 (2026-08-23) ───────────────────────────────────────────
|
|
32
|
+
* 이 판정이 없던 동안 호스트의 성과 폴드가 **낱말을 동의어 목록으로 추론했다**
|
|
33
|
+
* (`completed`·`fulfilled`·`done`·`finished`). 그 방식이 낸 대가가 둘이다.
|
|
34
|
+
*
|
|
35
|
+
* ① **원본의 낱말이 목록에 없으면 완료가 조용히 0 이 된다.** 실제로 그랬다 — 첫 실 시스템의
|
|
36
|
+
* `FINISHED` 가 빠져 있었고, 사람이 화면을 보고 의심해서야 드러났다. 그리고 그때 목록에 낱말을
|
|
37
|
+
* 더한 것은 진짜 빈 자리(커넥터가 자기 상태 표를 안 쓰고 있었다)를 가린 땜빵이었다.
|
|
38
|
+
* ② **시뮬 트윈에서는 언제나 0 이었다.** 시뮬은 오더를 이행하면서 `fulfilled` 만 올리고 `status` 는
|
|
39
|
+
* 바꾸지 않는다(그런 대입이 코드에 없다). 그래서 낱말로 묻는 판정은 시뮬의 완료를 **한 건도**
|
|
40
|
+
* 세지 못했다. 낱말이 틀린 것이 아니라 **묻는 축이 틀렸다.**
|
|
41
|
+
*
|
|
42
|
+
* ── 표준이 그 축을 이미 갈라 두었다 ─────────────────────────────────────────
|
|
43
|
+
* `orders` 는 `OperationsRequest` 쪽이고 그 상태는 `RequestState` 다 — **표준도 그 값을 열거하지
|
|
44
|
+
* 않는다.** 그래서 이 축이 열려 있는 것은 방언이 아니라 표준 정합이다. 대신 표준은 「됐나」를 **실적**
|
|
45
|
+
* 에서 읽는다(`JobResponse`·`SegmentResponse`, 그리고 요구는 `SegmentRequirement.Quantity`).
|
|
46
|
+
*
|
|
47
|
+
* 그러므로 이 판정의 1차 근거는 **양**이다: 요구한 만큼 이행됐으면 끝난 것이고, 그 사실은 어느 원본의
|
|
48
|
+
* 어느 낱말과도 무관하다. 낱말은 **양으로 말할 수 없는 종결**을 위해 함께 본다 — 취소, 그리고 요구량을
|
|
49
|
+
* 채우지 못한 채 닫힌 오더.
|
|
50
|
+
*
|
|
51
|
+
* ── 도메인이 상태에 다른 것을 적는다 ────────────────────────────────────────
|
|
52
|
+
* MES 커널은 이 자리에 **진행 단계**를 적는다(`op-<공정키>`, `blocked-seed-incomplete`). 그 값들은
|
|
53
|
+
* 종결이 아니므로 이 판정이 옳게 「아니다」로 답한다. 다만 진행 단계를 상태 문자열에 적는 것 자체가
|
|
54
|
+
* 표준의 모양은 아니다(그것은 `SegmentResponse` 의 일이다) — 그 빚은 ADR-0039 에 조건과 함께 적었다.
|
|
55
|
+
*
|
|
56
|
+
* 모르면 `false` 다 — 「끝나지 않았다」가 아니라 **「끝났다고 말할 근거가 없다」**이고, 성과는 근거가
|
|
57
|
+
* 있는 것만 센다(없는 완료를 세면 처리량이 조용히 부풀려진다).
|
|
58
|
+
*/
|
|
59
|
+
export declare function isOrderTerminal(o: {
|
|
60
|
+
status?: string;
|
|
61
|
+
requested?: number;
|
|
62
|
+
fulfilled?: number;
|
|
63
|
+
}): boolean;
|
|
11
64
|
/**
|
|
12
65
|
* 자리의 상태 — **포화도에서 파생한다.** 저장하는 값이 아니다.
|
|
13
66
|
*
|
package/dist/contract.js
CHANGED
|
@@ -32,6 +32,50 @@
|
|
|
32
32
|
* 지키는 하네스: `derived-not-input` · `actuals-come-from-transitions` · `accumulators-survive-restart` ·
|
|
33
33
|
* `purpose-conformance`.
|
|
34
34
|
*/
|
|
35
|
+
/** 오더가 **종결 상태**로 인정되는 낱말 — 이 밖은 진행 중이다(§`isOrderTerminal`). */
|
|
36
|
+
export const ORDER_TERMINAL_STATUS = ['completed', 'cancelled'];
|
|
37
|
+
/**
|
|
38
|
+
* 이 오더는 **끝났나** — 판정을 한 곳에 둔다(`locationStatusOf`·`dueStatusOf` 와 같은 규율).
|
|
39
|
+
*
|
|
40
|
+
* ── 왜 함수여야 하나 (2026-08-23) ───────────────────────────────────────────
|
|
41
|
+
* 이 판정이 없던 동안 호스트의 성과 폴드가 **낱말을 동의어 목록으로 추론했다**
|
|
42
|
+
* (`completed`·`fulfilled`·`done`·`finished`). 그 방식이 낸 대가가 둘이다.
|
|
43
|
+
*
|
|
44
|
+
* ① **원본의 낱말이 목록에 없으면 완료가 조용히 0 이 된다.** 실제로 그랬다 — 첫 실 시스템의
|
|
45
|
+
* `FINISHED` 가 빠져 있었고, 사람이 화면을 보고 의심해서야 드러났다. 그리고 그때 목록에 낱말을
|
|
46
|
+
* 더한 것은 진짜 빈 자리(커넥터가 자기 상태 표를 안 쓰고 있었다)를 가린 땜빵이었다.
|
|
47
|
+
* ② **시뮬 트윈에서는 언제나 0 이었다.** 시뮬은 오더를 이행하면서 `fulfilled` 만 올리고 `status` 는
|
|
48
|
+
* 바꾸지 않는다(그런 대입이 코드에 없다). 그래서 낱말로 묻는 판정은 시뮬의 완료를 **한 건도**
|
|
49
|
+
* 세지 못했다. 낱말이 틀린 것이 아니라 **묻는 축이 틀렸다.**
|
|
50
|
+
*
|
|
51
|
+
* ── 표준이 그 축을 이미 갈라 두었다 ─────────────────────────────────────────
|
|
52
|
+
* `orders` 는 `OperationsRequest` 쪽이고 그 상태는 `RequestState` 다 — **표준도 그 값을 열거하지
|
|
53
|
+
* 않는다.** 그래서 이 축이 열려 있는 것은 방언이 아니라 표준 정합이다. 대신 표준은 「됐나」를 **실적**
|
|
54
|
+
* 에서 읽는다(`JobResponse`·`SegmentResponse`, 그리고 요구는 `SegmentRequirement.Quantity`).
|
|
55
|
+
*
|
|
56
|
+
* 그러므로 이 판정의 1차 근거는 **양**이다: 요구한 만큼 이행됐으면 끝난 것이고, 그 사실은 어느 원본의
|
|
57
|
+
* 어느 낱말과도 무관하다. 낱말은 **양으로 말할 수 없는 종결**을 위해 함께 본다 — 취소, 그리고 요구량을
|
|
58
|
+
* 채우지 못한 채 닫힌 오더.
|
|
59
|
+
*
|
|
60
|
+
* ── 도메인이 상태에 다른 것을 적는다 ────────────────────────────────────────
|
|
61
|
+
* MES 커널은 이 자리에 **진행 단계**를 적는다(`op-<공정키>`, `blocked-seed-incomplete`). 그 값들은
|
|
62
|
+
* 종결이 아니므로 이 판정이 옳게 「아니다」로 답한다. 다만 진행 단계를 상태 문자열에 적는 것 자체가
|
|
63
|
+
* 표준의 모양은 아니다(그것은 `SegmentResponse` 의 일이다) — 그 빚은 ADR-0039 에 조건과 함께 적었다.
|
|
64
|
+
*
|
|
65
|
+
* 모르면 `false` 다 — 「끝나지 않았다」가 아니라 **「끝났다고 말할 근거가 없다」**이고, 성과는 근거가
|
|
66
|
+
* 있는 것만 센다(없는 완료를 세면 처리량이 조용히 부풀려진다).
|
|
67
|
+
*/
|
|
68
|
+
export function isOrderTerminal(o) {
|
|
69
|
+
const status = String(o?.status ?? '').trim().toLowerCase();
|
|
70
|
+
if (ORDER_TERMINAL_STATUS.includes(status))
|
|
71
|
+
return true;
|
|
72
|
+
/* 양으로 판정 — 요구량이 없으면 판정하지 않는다(0 을 「다 됐다」로 읽지 않는다). */
|
|
73
|
+
const requested = o?.requested;
|
|
74
|
+
const fulfilled = o?.fulfilled;
|
|
75
|
+
if (typeof requested !== 'number' || !(requested > 0))
|
|
76
|
+
return false;
|
|
77
|
+
return typeof fulfilled === 'number' && fulfilled >= requested;
|
|
78
|
+
}
|
|
35
79
|
/**
|
|
36
80
|
* 자리의 상태 — **포화도에서 파생한다.** 저장하는 값이 아니다.
|
|
37
81
|
*
|
package/dist/flow-engine.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 통합 타입은 도메인 필드를 옵셔널로 넓혀(FlowItem.gtin?, FlowOrder.shipmentEpc? 등) 두 도메인을 담는다.
|
|
10
10
|
* (roadmap Phase5 발견 → 추출. [[project_flow_single_base_vision]] FlowLocation 단일 base 방향과 정합.)
|
|
11
11
|
*/
|
|
12
|
-
import { OP_EVENT, CMD, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, classClosure, capabilityOf, requiredTestsFor, priorityRank, dueStatusOf, effectivityAt, offCalendarAt, offCalendarReasonAt, minuteOfDayAt, activeShiftAt, subLotIdOf, itemKeyOf, identityGroundingOf } from "./contract.js";
|
|
12
|
+
import { OP_EVENT, CMD, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, classClosure, capabilityOf, requiredTestsFor, priorityRank, dueStatusOf, isOrderTerminal, effectivityAt, offCalendarAt, offCalendarReasonAt, minuteOfDayAt, activeShiftAt, subLotIdOf, itemKeyOf, identityGroundingOf } from "./contract.js";
|
|
13
13
|
import { ObservedReducer } from "./observed-reducer.js";
|
|
14
14
|
import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP, objectUri, bizTransactionUri, gdtiUri } from "./epcis.js";
|
|
15
15
|
import { OP_PARAM } from "./domain-definition.js";
|
|
@@ -217,7 +217,9 @@ thresholds) {
|
|
|
217
217
|
지연으로도 정시로도 말하지 않는다). 이미 끝난 오더는 대상이 아니다.
|
|
218
218
|
심각도는 얼마나 늦었는지로 구분한다 — 방금 넘긴 것과 하루 넘긴 것을 같게 부르면 신호가 무의미해진다. */
|
|
219
219
|
for (const o of view.orders) {
|
|
220
|
-
|
|
220
|
+
/* 끝난 오더는 지연 신호의 대상이 아니다 — 판정은 한 곳에 있다(§`isOrderTerminal`).
|
|
221
|
+
예전에는 여기서 낱말 둘을 견줬고, 그러면 양으로만 끝난 오더(시뮬이 그렇다)가 영원히 늦었다고 말한다. */
|
|
222
|
+
if (isOrderTerminal(o))
|
|
221
223
|
continue;
|
|
222
224
|
if (dueStatusOf(o, nowIso) !== 'late')
|
|
223
225
|
continue;
|
|
@@ -2113,7 +2115,8 @@ export class FlowEngine {
|
|
|
2113
2115
|
committedDemands() {
|
|
2114
2116
|
const out = [];
|
|
2115
2117
|
for (const o of this.orders.values()) {
|
|
2116
|
-
|
|
2118
|
+
/* 같은 판정을 쓴다 — 남은 수요를 세는 자리와 지연을 세는 자리가 다른 규칙을 쓰면 어긋난다. */
|
|
2119
|
+
if (isOrderTerminal(o))
|
|
2117
2120
|
continue;
|
|
2118
2121
|
const remaining = Math.max(0, (o.requested ?? 0) - (o.fulfilled ?? 0));
|
|
2119
2122
|
if (!remaining)
|
package/dist/mes-kernel.d.ts
CHANGED
|
@@ -70,10 +70,21 @@ export declare class MesKernel extends FlowEngine {
|
|
|
70
70
|
* 그 트윈은 「도는데 아무 일도 안 일어나는」 모습이 된다 — 실제로 `allocateDef` 가 첫 공정의 자리를
|
|
71
71
|
* 못 찾으면 조용히 `return` 했고, 오더는 영원히 배정되지 않은 채 화면에는 「running」이라 적혔다.
|
|
72
72
|
*
|
|
73
|
-
* ── 좁히지
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* 선언을
|
|
73
|
+
* ── 「지나는 것」만 본다 — 좁히지 않았던 것을 되돌렸다 (2026-08-23) ──────────
|
|
74
|
+
* 처음에는 선언된 **모든** 자재·공정을 검사했다. 근거는 「여러 트윈이 한 선언을 공유하는 사정은 이
|
|
75
|
+
* 코드에 없다 — 호스트는 트윈마다 자기 모델에서 선언을 만든다(`productionSpecOf(model)`)」였다.
|
|
76
|
+
* **그 전제가 틀렸다.** 선언을 만드는 두 번째 길이 이미 있었다: 카탈로그다. `trailer-manufacturing`
|
|
77
|
+
* 은 스스로 「자재 반입→보관→공정 변환→완제품 반출 … 양끝 야드 물류(YMS·WMS·MES)」라 말한다 —
|
|
78
|
+
* **한 선언이 사이트 전체를 덮고, 도메인마다 다른 트윈이 그 일부를 지난다.**
|
|
79
|
+
*
|
|
80
|
+
* 그래서 MES 판(여섯 노드)이 `receive`→`dock-door` · `park`→`yard-slot` · `dispatch`→`gate` 때문에
|
|
81
|
+
* **아예 열리지 않았다.** 셋 다 이 트윈이 지나지 않는 공정이다. 없는 사정을 상상하지 말라던 규율을
|
|
82
|
+
* 지키려다, **있는 사정을 못 본 채로** 검사를 넓힌 것이 잘못이었다.
|
|
83
|
+
*
|
|
84
|
+
* 좁혀도 이 가드가 지키려던 것은 그대로 남는다. 막으려던 실패는 **「오더가 영원히 배정되지 않는
|
|
85
|
+
* 것」**이고, 그것은 `allocateDef` 가 지나는 공정의 자리를 못 찾을 때만 생긴다(§`routeOps`). 지나지
|
|
86
|
+
* 않는 공정은 그 실패를 만들 수 없다. 그러니 **레시피가 쓰는 라우트의 단계**와 **그 레시피가 드나
|
|
87
|
+
* 내는 자재**만 본다 — 검사의 값은 유지되고, 거짓 실패만 사라진다.
|
|
77
88
|
*
|
|
78
89
|
* 모아서 한 번에 말한다 — 하나씩 실패하면 사람이 같은 기동을 열 번 반복한다.
|
|
79
90
|
*/
|
package/dist/mes-kernel.js
CHANGED
|
@@ -223,10 +223,21 @@ export class MesKernel extends FlowEngine {
|
|
|
223
223
|
* 그 트윈은 「도는데 아무 일도 안 일어나는」 모습이 된다 — 실제로 `allocateDef` 가 첫 공정의 자리를
|
|
224
224
|
* 못 찾으면 조용히 `return` 했고, 오더는 영원히 배정되지 않은 채 화면에는 「running」이라 적혔다.
|
|
225
225
|
*
|
|
226
|
-
* ── 좁히지
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* 선언을
|
|
226
|
+
* ── 「지나는 것」만 본다 — 좁히지 않았던 것을 되돌렸다 (2026-08-23) ──────────
|
|
227
|
+
* 처음에는 선언된 **모든** 자재·공정을 검사했다. 근거는 「여러 트윈이 한 선언을 공유하는 사정은 이
|
|
228
|
+
* 코드에 없다 — 호스트는 트윈마다 자기 모델에서 선언을 만든다(`productionSpecOf(model)`)」였다.
|
|
229
|
+
* **그 전제가 틀렸다.** 선언을 만드는 두 번째 길이 이미 있었다: 카탈로그다. `trailer-manufacturing`
|
|
230
|
+
* 은 스스로 「자재 반입→보관→공정 변환→완제품 반출 … 양끝 야드 물류(YMS·WMS·MES)」라 말한다 —
|
|
231
|
+
* **한 선언이 사이트 전체를 덮고, 도메인마다 다른 트윈이 그 일부를 지난다.**
|
|
232
|
+
*
|
|
233
|
+
* 그래서 MES 판(여섯 노드)이 `receive`→`dock-door` · `park`→`yard-slot` · `dispatch`→`gate` 때문에
|
|
234
|
+
* **아예 열리지 않았다.** 셋 다 이 트윈이 지나지 않는 공정이다. 없는 사정을 상상하지 말라던 규율을
|
|
235
|
+
* 지키려다, **있는 사정을 못 본 채로** 검사를 넓힌 것이 잘못이었다.
|
|
236
|
+
*
|
|
237
|
+
* 좁혀도 이 가드가 지키려던 것은 그대로 남는다. 막으려던 실패는 **「오더가 영원히 배정되지 않는
|
|
238
|
+
* 것」**이고, 그것은 `allocateDef` 가 지나는 공정의 자리를 못 찾을 때만 생긴다(§`routeOps`). 지나지
|
|
239
|
+
* 않는 공정은 그 실패를 만들 수 없다. 그러니 **레시피가 쓰는 라우트의 단계**와 **그 레시피가 드나
|
|
240
|
+
* 내는 자재**만 본다 — 검사의 값은 유지되고, 거짓 실패만 사라진다.
|
|
230
241
|
*
|
|
231
242
|
* 모아서 한 번에 말한다 — 하나씩 실패하면 사람이 같은 기동을 열 번 반복한다.
|
|
232
243
|
*/
|
|
@@ -241,12 +252,22 @@ export class MesKernel extends FlowEngine {
|
|
|
241
252
|
'The kernel no longer carries a built-in demo product — what is made is decided by the model.');
|
|
242
253
|
}
|
|
243
254
|
const have = new Set([...this.locations.values()].map(l => l.type));
|
|
255
|
+
const decl = this.productionSpec.definition;
|
|
256
|
+
/* 이 트윈이 지나는 공정 — 선언된 레시피가 쓰는 라우트의 단계들. */
|
|
257
|
+
const routeKeys = new Set(this.distinctRouteKeys());
|
|
258
|
+
const traversed = new Set((decl.routes ?? []).filter(r => routeKeys.has(r.key)).flatMap(r => r.steps ?? []));
|
|
259
|
+
/* 이 트윈이 드나내는 자재 — 그 레시피들의 입력과 산출. */
|
|
260
|
+
const handled = new Set(this.recipesDef().flatMap(r => [...(r.inputs ?? []), ...(r.outputs ?? [])].map(x => x.material)));
|
|
244
261
|
const missing = [];
|
|
245
|
-
for (const m of
|
|
262
|
+
for (const m of decl.materials ?? []) {
|
|
263
|
+
if (!handled.has(m.key))
|
|
264
|
+
continue;
|
|
246
265
|
if (m.locationType && !have.has(m.locationType))
|
|
247
266
|
missing.push(`material '${m.key}' → '${m.locationType}'`);
|
|
248
267
|
}
|
|
249
|
-
for (const op of
|
|
268
|
+
for (const op of decl.operations ?? []) {
|
|
269
|
+
if (!traversed.has(op.key))
|
|
270
|
+
continue;
|
|
250
271
|
if (op.locationType && !have.has(op.locationType))
|
|
251
272
|
missing.push(`operation '${op.key}' → '${op.locationType}'`);
|
|
252
273
|
}
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -56,6 +56,7 @@ __export(index_exports, {
|
|
|
56
56
|
OPERATION_PROPERTY: () => OPERATION_PROPERTY,
|
|
57
57
|
OP_EVENT: () => OP_EVENT,
|
|
58
58
|
OP_PARAM: () => OP_PARAM,
|
|
59
|
+
ORDER_TERMINAL_STATUS: () => ORDER_TERMINAL_STATUS,
|
|
59
60
|
ObservedReducer: () => ObservedReducer,
|
|
60
61
|
PRIORITY_UNSET: () => PRIORITY_UNSET,
|
|
61
62
|
RETIRED_VOCABULARY: () => RETIRED_VOCABULARY,
|
|
@@ -126,6 +127,7 @@ __export(index_exports, {
|
|
|
126
127
|
isEnergyRecord: () => isEnergyRecord,
|
|
127
128
|
isEquipmentLevel: () => isEquipmentLevel,
|
|
128
129
|
isOperationalRecord: () => isOperationalRecord,
|
|
130
|
+
isOrderTerminal: () => isOrderTerminal,
|
|
129
131
|
isTransformationRecord: () => isTransformationRecord,
|
|
130
132
|
isoDurationHours: () => isoDurationHours,
|
|
131
133
|
itemKeyOf: () => itemKeyOf,
|
|
@@ -179,6 +181,15 @@ __export(index_exports, {
|
|
|
179
181
|
module.exports = __toCommonJS(index_exports);
|
|
180
182
|
|
|
181
183
|
// src/contract.ts
|
|
184
|
+
var ORDER_TERMINAL_STATUS = ["completed", "cancelled"];
|
|
185
|
+
function isOrderTerminal(o) {
|
|
186
|
+
const status = String(o?.status ?? "").trim().toLowerCase();
|
|
187
|
+
if (ORDER_TERMINAL_STATUS.includes(status)) return true;
|
|
188
|
+
const requested = o?.requested;
|
|
189
|
+
const fulfilled = o?.fulfilled;
|
|
190
|
+
if (typeof requested !== "number" || !(requested > 0)) return false;
|
|
191
|
+
return typeof fulfilled === "number" && fulfilled >= requested;
|
|
192
|
+
}
|
|
182
193
|
var LOCATION_SATURATION_NEAR = 0.9;
|
|
183
194
|
function locationStatusOf(n) {
|
|
184
195
|
const cap = n.capacity;
|
|
@@ -3429,7 +3440,7 @@ function deriveAttentions(view, acked, nowIso, thresholds) {
|
|
|
3429
3440
|
});
|
|
3430
3441
|
}
|
|
3431
3442
|
for (const o of view.orders) {
|
|
3432
|
-
if (o
|
|
3443
|
+
if (isOrderTerminal(o)) continue;
|
|
3433
3444
|
if (dueStatusOf(o, nowIso) !== "late") continue;
|
|
3434
3445
|
const overdueMs = Date.parse(nowIso) - Date.parse(o.endTime);
|
|
3435
3446
|
out.push({
|
|
@@ -5033,7 +5044,7 @@ var FlowEngine = class {
|
|
|
5033
5044
|
committedDemands() {
|
|
5034
5045
|
const out = [];
|
|
5035
5046
|
for (const o of this.orders.values()) {
|
|
5036
|
-
if (o
|
|
5047
|
+
if (isOrderTerminal(o)) continue;
|
|
5037
5048
|
const remaining = Math.max(0, (o.requested ?? 0) - (o.fulfilled ?? 0));
|
|
5038
5049
|
if (!remaining) continue;
|
|
5039
5050
|
out.push({ remaining, ...o.endTime ? { dueTime: o.endTime } : {} });
|
|
@@ -7103,10 +7114,21 @@ var MesKernel = class extends FlowEngine {
|
|
|
7103
7114
|
* 그 트윈은 「도는데 아무 일도 안 일어나는」 모습이 된다 — 실제로 `allocateDef` 가 첫 공정의 자리를
|
|
7104
7115
|
* 못 찾으면 조용히 `return` 했고, 오더는 영원히 배정되지 않은 채 화면에는 「running」이라 적혔다.
|
|
7105
7116
|
*
|
|
7106
|
-
* ── 좁히지
|
|
7107
|
-
*
|
|
7108
|
-
*
|
|
7109
|
-
* 선언을
|
|
7117
|
+
* ── 「지나는 것」만 본다 — 좁히지 않았던 것을 되돌렸다 (2026-08-23) ──────────
|
|
7118
|
+
* 처음에는 선언된 **모든** 자재·공정을 검사했다. 근거는 「여러 트윈이 한 선언을 공유하는 사정은 이
|
|
7119
|
+
* 코드에 없다 — 호스트는 트윈마다 자기 모델에서 선언을 만든다(`productionSpecOf(model)`)」였다.
|
|
7120
|
+
* **그 전제가 틀렸다.** 선언을 만드는 두 번째 길이 이미 있었다: 카탈로그다. `trailer-manufacturing`
|
|
7121
|
+
* 은 스스로 「자재 반입→보관→공정 변환→완제품 반출 … 양끝 야드 물류(YMS·WMS·MES)」라 말한다 —
|
|
7122
|
+
* **한 선언이 사이트 전체를 덮고, 도메인마다 다른 트윈이 그 일부를 지난다.**
|
|
7123
|
+
*
|
|
7124
|
+
* 그래서 MES 판(여섯 노드)이 `receive`→`dock-door` · `park`→`yard-slot` · `dispatch`→`gate` 때문에
|
|
7125
|
+
* **아예 열리지 않았다.** 셋 다 이 트윈이 지나지 않는 공정이다. 없는 사정을 상상하지 말라던 규율을
|
|
7126
|
+
* 지키려다, **있는 사정을 못 본 채로** 검사를 넓힌 것이 잘못이었다.
|
|
7127
|
+
*
|
|
7128
|
+
* 좁혀도 이 가드가 지키려던 것은 그대로 남는다. 막으려던 실패는 **「오더가 영원히 배정되지 않는
|
|
7129
|
+
* 것」**이고, 그것은 `allocateDef` 가 지나는 공정의 자리를 못 찾을 때만 생긴다(§`routeOps`). 지나지
|
|
7130
|
+
* 않는 공정은 그 실패를 만들 수 없다. 그러니 **레시피가 쓰는 라우트의 단계**와 **그 레시피가 드나
|
|
7131
|
+
* 내는 자재**만 본다 — 검사의 값은 유지되고, 거짓 실패만 사라진다.
|
|
7110
7132
|
*
|
|
7111
7133
|
* 모아서 한 번에 말한다 — 하나씩 실패하면 사람이 같은 기동을 열 번 반복한다.
|
|
7112
7134
|
*/
|
|
@@ -7118,11 +7140,19 @@ var MesKernel = class extends FlowEngine {
|
|
|
7118
7140
|
);
|
|
7119
7141
|
}
|
|
7120
7142
|
const have = new Set([...this.locations.values()].map((l) => l.type));
|
|
7143
|
+
const decl = this.productionSpec.definition;
|
|
7144
|
+
const routeKeys = new Set(this.distinctRouteKeys());
|
|
7145
|
+
const traversed = new Set((decl.routes ?? []).filter((r) => routeKeys.has(r.key)).flatMap((r) => r.steps ?? []));
|
|
7146
|
+
const handled = new Set(
|
|
7147
|
+
this.recipesDef().flatMap((r) => [...r.inputs ?? [], ...r.outputs ?? []].map((x) => x.material))
|
|
7148
|
+
);
|
|
7121
7149
|
const missing = [];
|
|
7122
|
-
for (const m of
|
|
7150
|
+
for (const m of decl.materials ?? []) {
|
|
7151
|
+
if (!handled.has(m.key)) continue;
|
|
7123
7152
|
if (m.locationType && !have.has(m.locationType)) missing.push(`material '${m.key}' \u2192 '${m.locationType}'`);
|
|
7124
7153
|
}
|
|
7125
|
-
for (const op of
|
|
7154
|
+
for (const op of decl.operations ?? []) {
|
|
7155
|
+
if (!traversed.has(op.key)) continue;
|
|
7126
7156
|
if (op.locationType && !have.has(op.locationType)) missing.push(`operation '${op.key}' \u2192 '${op.locationType}'`);
|
|
7127
7157
|
}
|
|
7128
7158
|
if (missing.length) {
|
|
@@ -9015,6 +9045,7 @@ function retiredVocabularyIn(line) {
|
|
|
9015
9045
|
OPERATION_PROPERTY,
|
|
9016
9046
|
OP_EVENT,
|
|
9017
9047
|
OP_PARAM,
|
|
9048
|
+
ORDER_TERMINAL_STATUS,
|
|
9018
9049
|
ObservedReducer,
|
|
9019
9050
|
PRIORITY_UNSET,
|
|
9020
9051
|
RETIRED_VOCABULARY,
|
|
@@ -9085,6 +9116,7 @@ function retiredVocabularyIn(line) {
|
|
|
9085
9116
|
isEnergyRecord,
|
|
9086
9117
|
isEquipmentLevel,
|
|
9087
9118
|
isOperationalRecord,
|
|
9119
|
+
isOrderTerminal,
|
|
9088
9120
|
isTransformationRecord,
|
|
9089
9121
|
isoDurationHours,
|
|
9090
9122
|
itemKeyOf,
|
package/package.json
CHANGED