@operato/twin-kernel 0.7.51 → 0.7.52
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-cjs/index.cjs +15 -2
- 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-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 } : {} });
|
|
@@ -9015,6 +9026,7 @@ function retiredVocabularyIn(line) {
|
|
|
9015
9026
|
OPERATION_PROPERTY,
|
|
9016
9027
|
OP_EVENT,
|
|
9017
9028
|
OP_PARAM,
|
|
9029
|
+
ORDER_TERMINAL_STATUS,
|
|
9018
9030
|
ObservedReducer,
|
|
9019
9031
|
PRIORITY_UNSET,
|
|
9020
9032
|
RETIRED_VOCABULARY,
|
|
@@ -9085,6 +9097,7 @@ function retiredVocabularyIn(line) {
|
|
|
9085
9097
|
isEnergyRecord,
|
|
9086
9098
|
isEquipmentLevel,
|
|
9087
9099
|
isOperationalRecord,
|
|
9100
|
+
isOrderTerminal,
|
|
9088
9101
|
isTransformationRecord,
|
|
9089
9102
|
isoDurationHours,
|
|
9090
9103
|
itemKeyOf,
|
package/package.json
CHANGED