@operato/twin-kernel 0.11.27 → 0.11.29
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/flow-engine.d.ts +5 -0
- package/dist/flow-engine.js +17 -0
- package/dist/observed-reducer.d.ts +12 -0
- package/dist/observed-reducer.js +49 -2
- package/dist-cjs/index.cjs +58 -3
- package/package.json +2 -2
package/dist/flow-engine.d.ts
CHANGED
|
@@ -825,6 +825,11 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
825
825
|
id: string;
|
|
826
826
|
since: string;
|
|
827
827
|
}[];
|
|
828
|
+
/**
|
|
829
|
+
* **접힌 오더**(ADR-0092) — 저널이나 원천을 다시 읽어 세우는 트윈도 이어받아야 한다. 이것이 없으면 세는 수를 잃고,
|
|
830
|
+
* 원천이 옛 종결을 다시 보낼 때 그것을 거를 경계가 없어 접힌 오더가 뜨거운 줄로 되살아난다.
|
|
831
|
+
*/
|
|
832
|
+
foldedOrders?: FoldedOrders;
|
|
828
833
|
}): void;
|
|
829
834
|
/**
|
|
830
835
|
* 씨앗의 확보분 중 **이 트윈에 실제로 있는 것만** 남긴다 — 없는 것은 세고 버린다.
|
package/dist/flow-engine.js
CHANGED
|
@@ -1049,6 +1049,21 @@ export class FlowEngine {
|
|
|
1049
1049
|
for (const e of snap?.attentionSince ?? [])
|
|
1050
1050
|
if (e?.id && e.since)
|
|
1051
1051
|
this._attentionSince.set(e.id, e.since);
|
|
1052
|
+
if (snap?.foldedOrders && foldedOrderCount(snap.foldedOrders)) {
|
|
1053
|
+
/*
|
|
1054
|
+
* 이미 접힌 것이 있으면(재개점이 먼저 되세웠다) 건드리지 않는다 — 같은 값을 또 더하면 세는 수가 두 번 는다.
|
|
1055
|
+
* 미러는 리듀서가 원천이다 — 거기에 넣어야 다시 온 옛 종결을 거른다. 리듀서가 아직 없으면 만든다(§restoreObserved).
|
|
1056
|
+
*/
|
|
1057
|
+
if (this.observeMode) {
|
|
1058
|
+
if (!this.observer)
|
|
1059
|
+
this.observer = new ObservedReducer(this.boardDef ?? { locations: [], equipment: [] });
|
|
1060
|
+
if (this.observer.adoptFoldedOrders(snap.foldedOrders))
|
|
1061
|
+
this.observedDirty = true;
|
|
1062
|
+
}
|
|
1063
|
+
else if (!foldedOrderCount(this.foldedOrders)) {
|
|
1064
|
+
this.foldedOrders = structuredClone(snap.foldedOrders);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1052
1067
|
}
|
|
1053
1068
|
/**
|
|
1054
1069
|
* 씨앗의 확보분 중 **이 트윈에 실제로 있는 것만** 남긴다 — 없는 것은 세고 버린다.
|
|
@@ -1887,6 +1902,8 @@ export class FlowEngine {
|
|
|
1887
1902
|
})),
|
|
1888
1903
|
/* 접힌 오더 — 접은 것이 없으면 싣지 않는다(ADR-0092). */
|
|
1889
1904
|
...(foldedOrderCount(this.foldedOrders) ? { foldedOrders: structuredClone(this.foldedOrders) } : {}),
|
|
1905
|
+
/* 다른 렌즈의 자리 — 모델의 선언 그대로(ADR-0081 보탬). 시뮬과 미러가 같은 칸을 낸다. */
|
|
1906
|
+
...(this.boardDef?.referencedLocations?.length ? { referencedLocations: this.boardDef.referencedLocations.map(r => ({ ...r })) } : {}),
|
|
1890
1907
|
orders: [...this.orders.values()].map(o => ({
|
|
1891
1908
|
id: o.id, kind: o.kind, status: o.status,
|
|
1892
1909
|
...(o.terminalAtMs !== undefined ? { terminalAtMs: o.terminalAtMs } : {}),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AssetState, TestResult, MaterialQuantity, TwinModelDef, CanonicalEnvelope, LocationState, ItemState, LocationObservation, EquipmentState, PersonState, TaskState, OrderState, StructureShift, DispositionFact } from '@operato/ops-contract';
|
|
2
|
+
import { type ReferencedLocation } from '@operato/ops-contract';
|
|
2
3
|
import { type FoldedOrders } from '@operato/ops-contract';
|
|
3
4
|
import type { MaterialLotUse } from '@operato/ops-contract';
|
|
4
5
|
import { type VocabularyElement } from '@operato/ops-contract';
|
|
@@ -96,6 +97,8 @@ export interface ProjectedState {
|
|
|
96
97
|
lastAtMs?: number;
|
|
97
98
|
}[];
|
|
98
99
|
locations: LocationState[];
|
|
100
|
+
/** 다른 렌즈의 자리 — 참조만 한다(§`ReferencedLocation`). 선언이 없으면 칸이 없다. */
|
|
101
|
+
referencedLocations?: ReferencedLocation[];
|
|
99
102
|
items: ItemState[];
|
|
100
103
|
/** 사람 — 등급·교대·투입. 인원을 선언하지 않은 트윈에서는 빈 배열. */
|
|
101
104
|
persons: PersonState[];
|
|
@@ -306,6 +309,8 @@ export declare class ObservedReducer {
|
|
|
306
309
|
private classDefs;
|
|
307
310
|
/** 시각 해석 기준(트윈 모델 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
308
311
|
private utcOffsetMinutes?;
|
|
312
|
+
private referencedDecl;
|
|
313
|
+
private referenced;
|
|
309
314
|
constructor(model: TwinModelDef);
|
|
310
315
|
/**
|
|
311
316
|
* **구조를 전환한다** — 관측된 사실은 지키고 토폴로지만 새 선언으로 바꾼다.
|
|
@@ -525,6 +530,13 @@ export declare class ObservedReducer {
|
|
|
525
530
|
* 모델에서 오는 판정 입력(교대·등급·시간대)은 생성자가 이미 세웠으므로 건드리지 않는다.
|
|
526
531
|
*/
|
|
527
532
|
restore(cp: ReducerCheckpoint): void;
|
|
533
|
+
/**
|
|
534
|
+
* **이어받은 접힌 쪽을 앉힌다**(ADR-0092) — 이어가기 씨앗으로 뜨는 미러가 부른다(§`FlowEngine.hydrateContinuity`).
|
|
535
|
+
*
|
|
536
|
+
* 이미 접힌 것이 있으면 **건드리지 않는다.** 재개점(체크포인트)이 먼저 되세웠다면 그것이 정본이고, 같은 값을 또 더하면 세는
|
|
537
|
+
* 수가 두 번 는다. 비어 있을 때만 이어받는다. 이어받았으면 참을 돌려준다.
|
|
538
|
+
*/
|
|
539
|
+
adoptFoldedOrders(folded: FoldedOrders): boolean;
|
|
528
540
|
/**
|
|
529
541
|
* **끝난 오더를 접는다**(ADR-0092) — 접은 수를 돌려준다.
|
|
530
542
|
*
|
package/dist/observed-reducer.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { computeOee, isWorkShiftExecutionBasis } from '@operato/ops-contract';
|
|
23
23
|
import { OP_EVENT, capabilityOf, itemKeyOf, judgeAgainstSpec, requiredTestsFor, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, effectivityAt, offCalendarAt, offCalendarReasonAt, activeShiftAt } from '@operato/ops-contract';
|
|
24
|
-
import { ILMD_ATTR, parseEpc } from '@operato/ops-contract';
|
|
24
|
+
import { ILMD_ATTR, parseEpc, referencedLocationIds } from '@operato/ops-contract';
|
|
25
25
|
import { emptyFoldedOrders, foldOrdersInto, foldedOrderCount, pruneFoldedHours, isOrderTerminal, orderFoldPolicyOf, planOrderFold, standsNewOrderRow } from '@operato/ops-contract';
|
|
26
26
|
import { expiryFromAttributes, lotFromAttributes } from '@operato/ops-contract';
|
|
27
27
|
/* 클래스 마스터 표는 상태를 들므로 커널에 있다(§`class-master-table`). */
|
|
@@ -125,6 +125,12 @@ export class ObservedReducer {
|
|
|
125
125
|
classDefs = {};
|
|
126
126
|
/** 시각 해석 기준(트윈 모델 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
127
127
|
utcOffsetMinutes;
|
|
128
|
+
/*
|
|
129
|
+
* **다른 렌즈의 자리** — 참조만 한다(ADR-0081 보탬). 관측으로 자리로 올리지 않고, 거기로 간 물품은 이 렌즈의 재고에서 뺀다.
|
|
130
|
+
* 창고가 조립 라인으로 불출한 자재는 창고 재고가 아니다 — 그 자리를 「종류 모르는 자리」로 세우면 재고가 거기 남는다.
|
|
131
|
+
*/
|
|
132
|
+
referencedDecl = [];
|
|
133
|
+
referenced = new Set();
|
|
128
134
|
constructor(model) {
|
|
129
135
|
/* 접지 않는 선언(0 · 무한)은 거절한다 — 기본값으로 조용히 바꾸면 선언한 사람은 자기 값이 쓰이는 줄 안다. */
|
|
130
136
|
const fold = orderFoldPolicyOf(model.orderFold);
|
|
@@ -136,6 +142,8 @@ export class ObservedReducer {
|
|
|
136
142
|
/* 선언된 판정 기준 — 원천이 판정하지 않은 결과를 커널이 판정할 때 쓴다(§`judgeAgainstSpec`). */
|
|
137
143
|
for (const sp of model.testSpecifications ?? [])
|
|
138
144
|
this.testSpecs.set(sp.id, sp);
|
|
145
|
+
this.referencedDecl = [...(model.referencedLocations ?? [])];
|
|
146
|
+
this.referenced = referencedLocationIds(model);
|
|
139
147
|
for (const n of readBoardLocations(model))
|
|
140
148
|
this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, parentId: n.parentId, origin: 'master' });
|
|
141
149
|
// 설비 기준선(마스터) — equipment.status 델타로 갱신됨.
|
|
@@ -177,6 +185,17 @@ export class ObservedReducer {
|
|
|
177
185
|
const declaredLocationIds = new Set(locations.map(n => n.id));
|
|
178
186
|
let locationsAdded = 0;
|
|
179
187
|
let locationsDropped = 0;
|
|
188
|
+
this.referencedDecl = [...(model.referencedLocations ?? [])];
|
|
189
|
+
this.referenced = referencedLocationIds(model);
|
|
190
|
+
/* 새로 참조가 된 자리 — 관측으로 올라 있던 줄은 내리고, 거기 앉아 있던 물품은 이 렌즈의 재고에서 뺀다(참조의 뜻 그대로). */
|
|
191
|
+
for (const [id, cur] of [...this.master]) {
|
|
192
|
+
if (cur.origin === 'observed' && this.referenced.has(id))
|
|
193
|
+
this.master.delete(id);
|
|
194
|
+
}
|
|
195
|
+
for (const [key, it] of [...this.items]) {
|
|
196
|
+
if (it.location && this.referenced.has(it.location))
|
|
197
|
+
this.remove(key);
|
|
198
|
+
}
|
|
180
199
|
for (const [id, cur] of [...this.master]) {
|
|
181
200
|
if (declaredLocationIds.has(id) || cur.origin === 'observed')
|
|
182
201
|
continue;
|
|
@@ -256,7 +275,7 @@ export class ObservedReducer {
|
|
|
256
275
|
* (트윈 모델에 좌표가 없고, 용량을 채워야 계획에 참여한다). 마스터 동기가 오면 `master` 로 승격된다.
|
|
257
276
|
*/
|
|
258
277
|
touchLocation(id) {
|
|
259
|
-
if (!id || this.master.has(id))
|
|
278
|
+
if (!id || this.master.has(id) || this.referenced.has(id))
|
|
260
279
|
return;
|
|
261
280
|
this.master.set(id, { id, type: UNKNOWN_TYPE, origin: 'observed' });
|
|
262
281
|
}
|
|
@@ -935,6 +954,9 @@ export class ObservedReducer {
|
|
|
935
954
|
const loc = ev.readPoint?.id ?? '';
|
|
936
955
|
/* 변환의 마스터데이터는 **출력**에 적용된다(§7.3.8) — 입력에 붙이면 소비되는 것에 태생을 심는 셈. */
|
|
937
956
|
this.touchLocation(loc);
|
|
957
|
+
/* 다른 렌즈의 자리에서 나온 출력은 그 렌즈의 것이다 — 이 렌즈의 재고에 세우지 않는다. */
|
|
958
|
+
if (this.referenced.has(loc))
|
|
959
|
+
return;
|
|
938
960
|
for (const epc of ev.outputEPCList ?? []) {
|
|
939
961
|
this.items.set(epc, this.mergeItem(epc, { location: loc, disposition: ev.disposition, ilmd: ev.ilmd }));
|
|
940
962
|
}
|
|
@@ -993,6 +1015,18 @@ export class ObservedReducer {
|
|
|
993
1015
|
const all = ev.quantityList ?? [];
|
|
994
1016
|
const q = all[0];
|
|
995
1017
|
this.touchLocation(loc);
|
|
1018
|
+
/*
|
|
1019
|
+
* **다른 렌즈의 자리로 갔다** — 그 물품은 이 렌즈를 떠났다. 개체는 지우고, 수량은 거기 앉히지 않는다(떠난 쪽의 줄은
|
|
1020
|
+
* 원본의 「없어졌다」가 줄인다). 순서 판정은 그대로 지난다 — 늦게 온 옛 불출이 그 뒤에 돌아온 물품을 지우지 않게.
|
|
1021
|
+
*/
|
|
1022
|
+
if (loc && this.referenced.has(loc)) {
|
|
1023
|
+
for (const epc of ev.epcList) {
|
|
1024
|
+
if (envelope && this.stale(`item:${epc}`, envelope))
|
|
1025
|
+
continue;
|
|
1026
|
+
this.remove(epc);
|
|
1027
|
+
}
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
996
1030
|
for (const epc of ev.epcList) {
|
|
997
1031
|
/* 물품별 순서 판정 — 늦게 온 옛 관측이 최신 위치를 덮지 않게. */
|
|
998
1032
|
if (envelope && this.stale(`item:${epc}`, envelope))
|
|
@@ -1409,6 +1443,18 @@ export class ObservedReducer {
|
|
|
1409
1443
|
this.corrections = (cp?.corrections ?? []).map(c => ({ ...c }));
|
|
1410
1444
|
this.unhandled = new Map((cp?.unhandled ?? []).map(({ eventType, ...v }) => [eventType, { ...v }]));
|
|
1411
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* **이어받은 접힌 쪽을 앉힌다**(ADR-0092) — 이어가기 씨앗으로 뜨는 미러가 부른다(§`FlowEngine.hydrateContinuity`).
|
|
1448
|
+
*
|
|
1449
|
+
* 이미 접힌 것이 있으면 **건드리지 않는다.** 재개점(체크포인트)이 먼저 되세웠다면 그것이 정본이고, 같은 값을 또 더하면 세는
|
|
1450
|
+
* 수가 두 번 는다. 비어 있을 때만 이어받는다. 이어받았으면 참을 돌려준다.
|
|
1451
|
+
*/
|
|
1452
|
+
adoptFoldedOrders(folded) {
|
|
1453
|
+
if (foldedOrderCount(this.foldedOrders))
|
|
1454
|
+
return false;
|
|
1455
|
+
this.foldedOrders = structuredClone(folded);
|
|
1456
|
+
return true;
|
|
1457
|
+
}
|
|
1412
1458
|
/**
|
|
1413
1459
|
* **끝난 오더를 접는다**(ADR-0092) — 접은 수를 돌려준다.
|
|
1414
1460
|
*
|
|
@@ -1479,6 +1525,7 @@ export class ObservedReducer {
|
|
|
1479
1525
|
equipment: [...this.equipment.values()].map(m => ({ ...m, ...this.oeePart(m.id), ...this.effectivityPart(m), ...this.offShiftPart(`eq:${m.id}`), ...this.capabilityPart(m, `eq:${m.id}`, m.kind ? [m.kind] : [], this.classDefs.equipment) })),
|
|
1480
1526
|
orders: [...this.orders.values()].map(o => ({ ...o })),
|
|
1481
1527
|
...(foldedOrderCount(this.foldedOrders) ? { foldedOrders: structuredClone(this.foldedOrders) } : {}),
|
|
1528
|
+
...(this.referencedDecl.length ? { referencedLocations: this.referencedDecl.map(r => ({ ...r })) } : {}),
|
|
1482
1529
|
acked: [...this.acked]
|
|
1483
1530
|
};
|
|
1484
1531
|
}
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -868,6 +868,7 @@ var ALLOCATION_POLICY = {
|
|
|
868
868
|
/** 먼저 만료되는 것부터(First Expired, First Out). */
|
|
869
869
|
fefo: "fefo"
|
|
870
870
|
};
|
|
871
|
+
var BASIS_UNKNOWN = ["BEFORE_BASELINE"];
|
|
871
872
|
var CMD = {
|
|
872
873
|
orderHold: "order.hold",
|
|
873
874
|
orderResume: "order.resume",
|
|
@@ -2035,6 +2036,11 @@ function pruneFoldedHours(folded, nowMs, policy = ORDER_FOLD_DEFAULT) {
|
|
|
2035
2036
|
return out;
|
|
2036
2037
|
}
|
|
2037
2038
|
|
|
2039
|
+
// ../ops-contract/dist/referenced-location.js
|
|
2040
|
+
function referencedLocationIds(def) {
|
|
2041
|
+
return new Set((def?.referencedLocations ?? []).map((r) => r.id).filter(Boolean));
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2038
2044
|
// ../ops-contract/dist/iso-duration.js
|
|
2039
2045
|
var RE = /^(-)?P(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
|
|
2040
2046
|
function parseIsoDuration(text) {
|
|
@@ -2299,8 +2305,12 @@ var SPECS = {
|
|
|
2299
2305
|
*/
|
|
2300
2306
|
recipeKey: "string",
|
|
2301
2307
|
recipeBasis: "object",
|
|
2302
|
-
materialBasis: "object[]"
|
|
2308
|
+
materialBasis: "object[]",
|
|
2309
|
+
/* 근거 칸이 빈 까닭 — 닫힌 낱말이다(§`BASIS_UNKNOWN`). 투입이 근거 장치보다 먼저였던 오더도 이행은 사실이다. */
|
|
2310
|
+
basisUnknown: "string"
|
|
2303
2311
|
},
|
|
2312
|
+
/* 상태·종류는 도메인이 소유한다 — 닫지 않는다. 근거가 빈 까닭은 커널이 읽으므로 닫는다. */
|
|
2313
|
+
enums: { basisUnknown: BASIS_UNKNOWN },
|
|
2304
2314
|
/*
|
|
2305
2315
|
* 확보분의 안쪽을 본다 — 열쇠와 수 둘이다(§`AllocatedQuantity`). 안 보면 틀린 이름이 조용히
|
|
2306
2316
|
* 지나가고(`epcs`·`quantity` 따위), 미러는 잡힌 수를 0 으로 읽는다.
|
|
@@ -2322,7 +2332,6 @@ var SPECS = {
|
|
|
2322
2332
|
positive: ["qty"]
|
|
2323
2333
|
}
|
|
2324
2334
|
}
|
|
2325
|
-
/* 상태·종류는 도메인이 소유한다 — 닫지 않는다. */
|
|
2326
2335
|
},
|
|
2327
2336
|
quality: {
|
|
2328
2337
|
eventType: OP_EVENT.quality,
|
|
@@ -2997,6 +3006,12 @@ var ObservedReducer = class {
|
|
|
2997
3006
|
classDefs = {};
|
|
2998
3007
|
/** 시각 해석 기준(트윈 모델 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
2999
3008
|
utcOffsetMinutes;
|
|
3009
|
+
/*
|
|
3010
|
+
* **다른 렌즈의 자리** — 참조만 한다(ADR-0081 보탬). 관측으로 자리로 올리지 않고, 거기로 간 물품은 이 렌즈의 재고에서 뺀다.
|
|
3011
|
+
* 창고가 조립 라인으로 불출한 자재는 창고 재고가 아니다 — 그 자리를 「종류 모르는 자리」로 세우면 재고가 거기 남는다.
|
|
3012
|
+
*/
|
|
3013
|
+
referencedDecl = [];
|
|
3014
|
+
referenced = /* @__PURE__ */ new Set();
|
|
3000
3015
|
constructor(model) {
|
|
3001
3016
|
const fold = orderFoldPolicyOf(model.orderFold);
|
|
3002
3017
|
if ("error" in fold) throw new Error(fold.error);
|
|
@@ -3004,6 +3019,8 @@ var ObservedReducer = class {
|
|
|
3004
3019
|
this.utcOffsetMinutes = model.utcOffsetMinutes;
|
|
3005
3020
|
this.classDefs = { personnel: model.personnelClasses, equipment: model.equipmentClasses, asset: model.assetClasses };
|
|
3006
3021
|
for (const sp of model.testSpecifications ?? []) this.testSpecs.set(sp.id, sp);
|
|
3022
|
+
this.referencedDecl = [...model.referencedLocations ?? []];
|
|
3023
|
+
this.referenced = referencedLocationIds(model);
|
|
3007
3024
|
for (const n of readBoardLocations(model)) this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, parentId: n.parentId, origin: "master" });
|
|
3008
3025
|
for (const m of readBoardEquipment(model)) this.equipment.set(m.id, { id: m.id, kind: m.kind, ...m.equipmentClassIds ? { equipmentClassIds: m.equipmentClassIds } : {}, status: "idle", location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...m.testResults ? { testResults: m.testResults } : {}, ...effectiveOf(m), origin: "master" });
|
|
3009
3026
|
for (const p of model.persons ?? []) this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: "idle", ...p.homeLocation ? { location: p.homeLocation } : {}, ...p.properties ? { properties: p.properties } : {}, ...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}, ...p.testResults ? { testResults: p.testResults } : {}, ...effectiveOf(p) });
|
|
@@ -3035,6 +3052,14 @@ var ObservedReducer = class {
|
|
|
3035
3052
|
const declaredLocationIds = new Set(locations.map((n) => n.id));
|
|
3036
3053
|
let locationsAdded = 0;
|
|
3037
3054
|
let locationsDropped = 0;
|
|
3055
|
+
this.referencedDecl = [...model.referencedLocations ?? []];
|
|
3056
|
+
this.referenced = referencedLocationIds(model);
|
|
3057
|
+
for (const [id, cur] of [...this.master]) {
|
|
3058
|
+
if (cur.origin === "observed" && this.referenced.has(id)) this.master.delete(id);
|
|
3059
|
+
}
|
|
3060
|
+
for (const [key, it] of [...this.items]) {
|
|
3061
|
+
if (it.location && this.referenced.has(it.location)) this.remove(key);
|
|
3062
|
+
}
|
|
3038
3063
|
for (const [id, cur] of [...this.master]) {
|
|
3039
3064
|
if (declaredLocationIds.has(id) || cur.origin === "observed") continue;
|
|
3040
3065
|
this.master.delete(id);
|
|
@@ -3105,7 +3130,7 @@ var ObservedReducer = class {
|
|
|
3105
3130
|
* (트윈 모델에 좌표가 없고, 용량을 채워야 계획에 참여한다). 마스터 동기가 오면 `master` 로 승격된다.
|
|
3106
3131
|
*/
|
|
3107
3132
|
touchLocation(id) {
|
|
3108
|
-
if (!id || this.master.has(id)) return;
|
|
3133
|
+
if (!id || this.master.has(id) || this.referenced.has(id)) return;
|
|
3109
3134
|
this.master.set(id, { id, type: UNKNOWN_TYPE, origin: "observed" });
|
|
3110
3135
|
}
|
|
3111
3136
|
/**
|
|
@@ -3552,6 +3577,7 @@ var ObservedReducer = class {
|
|
|
3552
3577
|
for (const epc of ev.inputEPCList ?? []) this.remove(epc);
|
|
3553
3578
|
const loc2 = ev.readPoint?.id ?? "";
|
|
3554
3579
|
this.touchLocation(loc2);
|
|
3580
|
+
if (this.referenced.has(loc2)) return;
|
|
3555
3581
|
for (const epc of ev.outputEPCList ?? []) {
|
|
3556
3582
|
this.items.set(epc, this.mergeItem(epc, { location: loc2, disposition: ev.disposition, ilmd: ev.ilmd }));
|
|
3557
3583
|
}
|
|
@@ -3580,6 +3606,13 @@ var ObservedReducer = class {
|
|
|
3580
3606
|
const all = ev.quantityList ?? [];
|
|
3581
3607
|
const q = all[0];
|
|
3582
3608
|
this.touchLocation(loc);
|
|
3609
|
+
if (loc && this.referenced.has(loc)) {
|
|
3610
|
+
for (const epc of ev.epcList) {
|
|
3611
|
+
if (envelope && this.stale(`item:${epc}`, envelope)) continue;
|
|
3612
|
+
this.remove(epc);
|
|
3613
|
+
}
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3583
3616
|
for (const epc of ev.epcList) {
|
|
3584
3617
|
if (envelope && this.stale(`item:${epc}`, envelope)) continue;
|
|
3585
3618
|
const seen = this.mergeItem(epc, { location: loc, disposition: ev.disposition, ilmd: ev.ilmd }, q, all);
|
|
@@ -3904,6 +3937,17 @@ var ObservedReducer = class {
|
|
|
3904
3937
|
this.corrections = (cp?.corrections ?? []).map((c) => ({ ...c }));
|
|
3905
3938
|
this.unhandled = new Map((cp?.unhandled ?? []).map(({ eventType, ...v }) => [eventType, { ...v }]));
|
|
3906
3939
|
}
|
|
3940
|
+
/**
|
|
3941
|
+
* **이어받은 접힌 쪽을 앉힌다**(ADR-0092) — 이어가기 씨앗으로 뜨는 미러가 부른다(§`FlowEngine.hydrateContinuity`).
|
|
3942
|
+
*
|
|
3943
|
+
* 이미 접힌 것이 있으면 **건드리지 않는다.** 재개점(체크포인트)이 먼저 되세웠다면 그것이 정본이고, 같은 값을 또 더하면 세는
|
|
3944
|
+
* 수가 두 번 는다. 비어 있을 때만 이어받는다. 이어받았으면 참을 돌려준다.
|
|
3945
|
+
*/
|
|
3946
|
+
adoptFoldedOrders(folded) {
|
|
3947
|
+
if (foldedOrderCount(this.foldedOrders)) return false;
|
|
3948
|
+
this.foldedOrders = structuredClone(folded);
|
|
3949
|
+
return true;
|
|
3950
|
+
}
|
|
3907
3951
|
/**
|
|
3908
3952
|
* **끝난 오더를 접는다**(ADR-0092) — 접은 수를 돌려준다.
|
|
3909
3953
|
*
|
|
@@ -3970,6 +4014,7 @@ var ObservedReducer = class {
|
|
|
3970
4014
|
equipment: [...this.equipment.values()].map((m) => ({ ...m, ...this.oeePart(m.id), ...this.effectivityPart(m), ...this.offShiftPart(`eq:${m.id}`), ...this.capabilityPart(m, `eq:${m.id}`, m.kind ? [m.kind] : [], this.classDefs.equipment) })),
|
|
3971
4015
|
orders: [...this.orders.values()].map((o) => ({ ...o })),
|
|
3972
4016
|
...foldedOrderCount(this.foldedOrders) ? { foldedOrders: structuredClone(this.foldedOrders) } : {},
|
|
4017
|
+
...this.referencedDecl.length ? { referencedLocations: this.referencedDecl.map((r) => ({ ...r })) } : {},
|
|
3973
4018
|
acked: [...this.acked]
|
|
3974
4019
|
};
|
|
3975
4020
|
}
|
|
@@ -4878,6 +4923,14 @@ var FlowEngine = class {
|
|
|
4878
4923
|
hydrateContinuity(snap) {
|
|
4879
4924
|
for (const id of snap?.acked ?? []) this._acked.add(id);
|
|
4880
4925
|
for (const e of snap?.attentionSince ?? []) if (e?.id && e.since) this._attentionSince.set(e.id, e.since);
|
|
4926
|
+
if (snap?.foldedOrders && foldedOrderCount(snap.foldedOrders)) {
|
|
4927
|
+
if (this.observeMode) {
|
|
4928
|
+
if (!this.observer) this.observer = new ObservedReducer(this.boardDef ?? { locations: [], equipment: [] });
|
|
4929
|
+
if (this.observer.adoptFoldedOrders(snap.foldedOrders)) this.observedDirty = true;
|
|
4930
|
+
} else if (!foldedOrderCount(this.foldedOrders)) {
|
|
4931
|
+
this.foldedOrders = structuredClone(snap.foldedOrders);
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4881
4934
|
}
|
|
4882
4935
|
/**
|
|
4883
4936
|
* 씨앗의 확보분 중 **이 트윈에 실제로 있는 것만** 남긴다 — 없는 것은 세고 버린다.
|
|
@@ -5530,6 +5583,8 @@ var FlowEngine = class {
|
|
|
5530
5583
|
})),
|
|
5531
5584
|
/* 접힌 오더 — 접은 것이 없으면 싣지 않는다(ADR-0092). */
|
|
5532
5585
|
...foldedOrderCount(this.foldedOrders) ? { foldedOrders: structuredClone(this.foldedOrders) } : {},
|
|
5586
|
+
/* 다른 렌즈의 자리 — 모델의 선언 그대로(ADR-0081 보탬). 시뮬과 미러가 같은 칸을 낸다. */
|
|
5587
|
+
...this.boardDef?.referencedLocations?.length ? { referencedLocations: this.boardDef.referencedLocations.map((r) => ({ ...r })) } : {},
|
|
5533
5588
|
orders: [...this.orders.values()].map((o) => ({
|
|
5534
5589
|
id: o.id,
|
|
5535
5590
|
kind: o.kind,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/twin-kernel",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.29",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Twin Domain Kernel — framework-agnostic, zero-dep (domain + sim + 3-channel contract). WMS/YMS/MES, EPCIS 2.0 · ISA-95.",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
"test": "node --test test/*.test.ts"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@operato/ops-contract": "^0.9.
|
|
31
|
+
"@operato/ops-contract": "^0.9.40"
|
|
32
32
|
}
|
|
33
33
|
}
|