@operato/twin-kernel 0.7.62 → 0.7.63
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 +33 -0
- package/dist/contract.js +21 -0
- package/dist/observed-reducer.d.ts +8 -0
- package/dist/observed-reducer.js +93 -4
- package/dist/operational-ingest.d.ts +1 -1
- package/dist/operational-ingest.js +15 -0
- package/dist-cjs/index.cjs +78 -4
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -366,6 +366,18 @@ export interface TestResultFact extends TestResult {
|
|
|
366
366
|
/** 무엇을 시험했나 — 표준 `TestResult.TestableObjectID`. 물품이면 EPC. */
|
|
367
367
|
testableObjectId: string;
|
|
368
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* `OP_EVENT.complete` 의 내용 — 「이 목록이 전부다」.
|
|
371
|
+
*
|
|
372
|
+
* `axis` 를 받아 두는 이유: 지금은 물품만 이 방식이 필요하지만, 다른 목록도 같은 성질을 갖는다
|
|
373
|
+
* (연결된 시스템이 현재 목록을 통째로 말하는 것). 낱말을 닫으면 그때 계약을 또 고친다.
|
|
374
|
+
*/
|
|
375
|
+
export interface AxisCompleteFact {
|
|
376
|
+
/** 어느 목록인가 — 지금은 `'items'` 만 다룬다. */
|
|
377
|
+
completeAxis: string;
|
|
378
|
+
/** 그 주기를 **시작한** 시각(ISO). 이보다 오래된 것은 이 주기에 오지 않은 것이다. */
|
|
379
|
+
since: ISOTime;
|
|
380
|
+
}
|
|
369
381
|
/**
|
|
370
382
|
* 이 시험 결과가 **이 시각에 유효한 합격인가.**
|
|
371
383
|
*
|
|
@@ -2180,6 +2192,27 @@ export declare const OP_EVENT: {
|
|
|
2180
2192
|
* 없고, 미러가 이어받지 못한다(§상태 ⊆ 이벤트).
|
|
2181
2193
|
*/
|
|
2182
2194
|
readonly test: "test.result";
|
|
2195
|
+
/**
|
|
2196
|
+
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
|
|
2197
|
+
*
|
|
2198
|
+
* ── 왜 필요한가 (2026-08-25 실측) ────────────────────────────────────────
|
|
2199
|
+
* 연결된 시스템은 매 주기 현재 재고를 전부 보낸다. 그런데 트윈은 그것을 낱낱의 관측으로 받아
|
|
2200
|
+
* **더하기만 했다.** 「그리고 이것 말고는 없다」를 받는 곳이 없어서, 목록에서 빠진 줄은 아무 말도
|
|
2201
|
+
* 오지 않은 것이 되고 트윈에 영원히 남았다.
|
|
2202
|
+
*
|
|
2203
|
+
* 실측: 실제 재고 1,704건인데 트윈이 10,038건을 갖고 있었다.
|
|
2204
|
+
*
|
|
2205
|
+
* ── 왜 「사라진 것을 알려 주기」가 아니라 이 방식인가 ──────────────────────
|
|
2206
|
+
* 연결 쪽이 앞 주기와 비교해 사라진 줄을 찾아 알리는 방법도 있다. 그런데 그것은 **하나 빠뜨리면
|
|
2207
|
+
* 그 줄이 영원히 남는다.** 이 방식은 주기마다 스스로 바로잡는다 — 이미 잘못 쌓인 것도 다음 주기에
|
|
2208
|
+
* 사라진다.
|
|
2209
|
+
*
|
|
2210
|
+
* ── 보내는 쪽이 지킬 것 ─────────────────────────────────────────────────
|
|
2211
|
+
* **끝까지 읽었을 때만 보낸다.** 읽다가 끊긴 주기에 이것을 보내면 **살아 있는 재고를 지운다.**
|
|
2212
|
+
* 그리고 `since` 는 그 주기를 **시작한** 시각이다(끝낸 시각이 아니다) — 주기 도중에 들어온 관측이
|
|
2213
|
+
* 지워지지 않아야 한다.
|
|
2214
|
+
*/
|
|
2215
|
+
readonly complete: "axis.complete";
|
|
2183
2216
|
/**
|
|
2184
2217
|
* 주목 신호 확인(ack) — **사람이 한 행위**라 파생될 수 없다.
|
|
2185
2218
|
*
|
package/dist/contract.js
CHANGED
|
@@ -944,6 +944,27 @@ export const OP_EVENT = {
|
|
|
944
944
|
* 없고, 미러가 이어받지 못한다(§상태 ⊆ 이벤트).
|
|
945
945
|
*/
|
|
946
946
|
test: 'test.result',
|
|
947
|
+
/**
|
|
948
|
+
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
|
|
949
|
+
*
|
|
950
|
+
* ── 왜 필요한가 (2026-08-25 실측) ────────────────────────────────────────
|
|
951
|
+
* 연결된 시스템은 매 주기 현재 재고를 전부 보낸다. 그런데 트윈은 그것을 낱낱의 관측으로 받아
|
|
952
|
+
* **더하기만 했다.** 「그리고 이것 말고는 없다」를 받는 곳이 없어서, 목록에서 빠진 줄은 아무 말도
|
|
953
|
+
* 오지 않은 것이 되고 트윈에 영원히 남았다.
|
|
954
|
+
*
|
|
955
|
+
* 실측: 실제 재고 1,704건인데 트윈이 10,038건을 갖고 있었다.
|
|
956
|
+
*
|
|
957
|
+
* ── 왜 「사라진 것을 알려 주기」가 아니라 이 방식인가 ──────────────────────
|
|
958
|
+
* 연결 쪽이 앞 주기와 비교해 사라진 줄을 찾아 알리는 방법도 있다. 그런데 그것은 **하나 빠뜨리면
|
|
959
|
+
* 그 줄이 영원히 남는다.** 이 방식은 주기마다 스스로 바로잡는다 — 이미 잘못 쌓인 것도 다음 주기에
|
|
960
|
+
* 사라진다.
|
|
961
|
+
*
|
|
962
|
+
* ── 보내는 쪽이 지킬 것 ─────────────────────────────────────────────────
|
|
963
|
+
* **끝까지 읽었을 때만 보낸다.** 읽다가 끊긴 주기에 이것을 보내면 **살아 있는 재고를 지운다.**
|
|
964
|
+
* 그리고 `since` 는 그 주기를 **시작한** 시각이다(끝낸 시각이 아니다) — 주기 도중에 들어온 관측이
|
|
965
|
+
* 지워지지 않아야 한다.
|
|
966
|
+
*/
|
|
967
|
+
complete: 'axis.complete',
|
|
947
968
|
/**
|
|
948
969
|
* 주목 신호 확인(ack) — **사람이 한 행위**라 파생될 수 없다.
|
|
949
970
|
*
|
|
@@ -18,6 +18,14 @@ interface ProjItem {
|
|
|
18
18
|
expiry?: number;
|
|
19
19
|
/** 이 로트의 시험 결과 — **명세당 최신 하나**(상태가 계측 주기로 자라지 않게). */
|
|
20
20
|
testResults?: TestResult[];
|
|
21
|
+
/**
|
|
22
|
+
* **마지막으로 관측된 시각**(ms) — 「이 목록이 전부다」를 받았을 때 무엇을 지울지 가리는 값.
|
|
23
|
+
*
|
|
24
|
+
* 물품 안에 두는 이유: 순서 판정에 쓰는 시각(`lastAt`)은 **중간 저장본에 들어가지 않는다.**
|
|
25
|
+
* 그것에 기대면 재기동 직후 첫 신호가 재고를 전부 지운다(본 적이 없는 것으로 보이므로).
|
|
26
|
+
* 물품은 중간 저장본에 들어가므로 이 값도 함께 살아남는다.
|
|
27
|
+
*/
|
|
28
|
+
seenAtMs?: number;
|
|
21
29
|
}
|
|
22
30
|
/**
|
|
23
31
|
* 마스터 동기 — 선언적 로케이션 upsert/remove.
|
package/dist/observed-reducer.js
CHANGED
|
@@ -435,6 +435,44 @@ export class ObservedReducer {
|
|
|
435
435
|
item.testResults = [...kept, next];
|
|
436
436
|
break;
|
|
437
437
|
}
|
|
438
|
+
case OP_EVENT.complete: {
|
|
439
|
+
/*
|
|
440
|
+
* ── ★ **이 목록이 전부다** (2026-08-25) ────────────────────────────────
|
|
441
|
+
*
|
|
442
|
+
* 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다. 이 주기에 오지 않은 것은
|
|
443
|
+
* **그 시스템에 더 이상 없는 것**이므로 지운다.
|
|
444
|
+
*
|
|
445
|
+
* 이것이 없던 동안 트윈은 받은 것을 **더하기만 했다** — 목록에서 빠진 줄은 아무 말도 오지 않은
|
|
446
|
+
* 것이 되어 영원히 남았다(실측: 실제 1,704건인데 트윈이 10,038건).
|
|
447
|
+
*
|
|
448
|
+
* ── 아무것도 못 들은 주기에는 지우지 않는다 ────────────────────────────
|
|
449
|
+
* 그 주기에 관측이 **한 건도** 없었다면 그것은 「다 없어졌다」가 아니라 **우리가 못 들은 것**이다.
|
|
450
|
+
* 그때 지우면 살아 있는 재고가 사라진다 — 이 저장소가 여러 번 거절한 모양이다(결측 ≠ 없음).
|
|
451
|
+
*
|
|
452
|
+
* 보내는 쪽의 규율은 계약에 적었다: **끝까지 읽었을 때만 보낸다.** 읽다가 끊긴 주기에 보내면
|
|
453
|
+
* 살아 있는 재고를 지운다. 그 판단은 보내는 쪽만 할 수 있다.
|
|
454
|
+
*
|
|
455
|
+
* 지운 수를 `unhandled` 로 세지 않는다 — 그것은 반영하지 못한 사건을 세는 자리다. 여기서 지운
|
|
456
|
+
* 것은 **정상적으로 반영한 결과**다.
|
|
457
|
+
*/
|
|
458
|
+
const d = e.data;
|
|
459
|
+
if (d?.completeAxis !== 'items')
|
|
460
|
+
break;
|
|
461
|
+
const since = Date.parse(String(d.since ?? ''));
|
|
462
|
+
if (!Number.isFinite(since))
|
|
463
|
+
break;
|
|
464
|
+
let heard = 0;
|
|
465
|
+
for (const it of this.items.values())
|
|
466
|
+
if ((it.seenAtMs ?? -1) >= since)
|
|
467
|
+
heard++;
|
|
468
|
+
if (!heard)
|
|
469
|
+
break;
|
|
470
|
+
for (const [key, it] of [...this.items.entries()]) {
|
|
471
|
+
if ((it.seenAtMs ?? -1) < since)
|
|
472
|
+
this.remove(key);
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
438
476
|
case OP_EVENT.attentionAck: {
|
|
439
477
|
/* 확인한 사실만 담는다 — 그 신호가 지금도 성립하는지는 상태가 답한다(여기서 판단하지 않는다). */
|
|
440
478
|
const d = e.data;
|
|
@@ -632,6 +670,45 @@ export class ObservedReducer {
|
|
|
632
670
|
if (ev.action === 'DELETE') {
|
|
633
671
|
for (const epc of ev.epcList)
|
|
634
672
|
this.remove(epc);
|
|
673
|
+
/*
|
|
674
|
+
* ── ★ **수량으로 온 물품도 없어질 수 있어야 한다** (2026-08-25 실측) ────────
|
|
675
|
+
*
|
|
676
|
+
* 여기는 `epcList` 만 봤다. 그래서 **낱개 번호가 없는 현장에서는 재고를 지울 방법이 하나도
|
|
677
|
+
* 없었다** — 승화푸드는 모든 재고가 수량으로 오는 현장이다.
|
|
678
|
+
*
|
|
679
|
+
* 결과: 한 번 본 재고가 영원히 남았다. 연결된 시스템의 목록에서 그 줄이 없어져도 우리는
|
|
680
|
+
* 「없어졌다」를 들을 길이 없었다(있는 것만 오고, 없어진 것은 아무 말도 오지 않는다).
|
|
681
|
+
* 실측으로 실제 재고 1,704건인데 트윈이 10,038건을 갖고 있었다.
|
|
682
|
+
*
|
|
683
|
+
* ── 무엇을 지우는지는 말한 범위대로 ────────────────────────────────────
|
|
684
|
+
* 자리를 함께 말했다 그 자리의 그 로트만 지운다 (꺼내 가서 그 칸이 빈 경우)
|
|
685
|
+
* 자리를 말하지 않았다 그 로트를 모든 자리에서 지운다 (그 로트가 통째로 없어진 경우)
|
|
686
|
+
*
|
|
687
|
+
* 짐작이 아니라 **진술의 범위를 읽는 것**이다. 자리를 말했으면 그 자리를 말한 것이고,
|
|
688
|
+
* 말하지 않았으면 자리를 가리지 않고 말한 것이다.
|
|
689
|
+
*
|
|
690
|
+
* 순서 판정을 함께 지난다: 늦게 온 옛 「없어졌다」가 그 뒤에 다시 들어온 재고를 지우면 안 된다.
|
|
691
|
+
* 열쇠는 관측과 같은 것을 쓴다 — 같은 대상의 두 방향이므로 한 열쇠로 재야 한다.
|
|
692
|
+
*/
|
|
693
|
+
const goneAt = ev.readPoint?.id;
|
|
694
|
+
for (const qe of ev.quantityList ?? []) {
|
|
695
|
+
if (!qe?.epcClass)
|
|
696
|
+
continue;
|
|
697
|
+
if (goneAt) {
|
|
698
|
+
const key = `${qe.epcClass}@${goneAt}`;
|
|
699
|
+
if (envelope && this.stale(`item:${key}`, envelope))
|
|
700
|
+
continue;
|
|
701
|
+
this.remove(key);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
for (const key of [...this.items.keys()]) {
|
|
705
|
+
if (!key.startsWith(`${qe.epcClass}@`))
|
|
706
|
+
continue;
|
|
707
|
+
if (envelope && this.stale(`item:${key}`, envelope))
|
|
708
|
+
continue;
|
|
709
|
+
this.remove(key);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
635
712
|
return;
|
|
636
713
|
}
|
|
637
714
|
const loc = ev.readPoint?.id;
|
|
@@ -646,7 +723,9 @@ export class ObservedReducer {
|
|
|
646
723
|
/* 물품별 순서 판정 — 늦게 온 옛 관측이 최신 위치를 덮지 않게. */
|
|
647
724
|
if (envelope && this.stale(`item:${epc}`, envelope))
|
|
648
725
|
continue;
|
|
649
|
-
|
|
726
|
+
const seen = this.mergeItem(epc, { location: loc, disposition: ev.disposition, ilmd: ev.ilmd }, q, all);
|
|
727
|
+
const atSeen = Date.parse(String(envelope?.eventTime ?? ''));
|
|
728
|
+
this.items.set(itemKeyOf(seen), Number.isFinite(atSeen) ? { ...seen, seenAtMs: atSeen } : seen);
|
|
650
729
|
}
|
|
651
730
|
/* 개체 없이 수량만 오는 입고(비직렬 자재) — 표준이 허용하고 검증기도 유효로 판정한다.
|
|
652
731
|
* 이 경우 클래스 식별자 자체가 물품의 키다(로트 관리 자재는 LGTIN 이라 로트별로 갈린다). */
|
|
@@ -683,7 +762,9 @@ export class ObservedReducer {
|
|
|
683
762
|
*/
|
|
684
763
|
if (envelope && this.stale(`item:${subLotId}`, envelope))
|
|
685
764
|
continue;
|
|
686
|
-
|
|
765
|
+
const seenQty = this.mergeItem(qe.epcClass, { location: loc, disposition: ev.disposition, ilmd: ev.ilmd }, qe, mine, subLotId);
|
|
766
|
+
const atQty = Date.parse(String(envelope?.eventTime ?? ''));
|
|
767
|
+
this.items.set(subLotId, Number.isFinite(atQty) ? { ...seenQty, seenAtMs: atQty } : seenQty);
|
|
687
768
|
}
|
|
688
769
|
}
|
|
689
770
|
}
|
|
@@ -936,8 +1017,16 @@ export class ObservedReducer {
|
|
|
936
1017
|
origin: n.origin
|
|
937
1018
|
};
|
|
938
1019
|
}),
|
|
939
|
-
/*
|
|
940
|
-
|
|
1020
|
+
/*
|
|
1021
|
+
* 갖고 있는 것을 전부 내보낸다 — 줄이면 그 자리에서 정보가 사라진다.
|
|
1022
|
+
*
|
|
1023
|
+
* **`seenAtMs` 는 뺀다.** 그것은 물품에 대한 사실이 아니라 **우리가 언제 들었나**이고, 「이 목록이
|
|
1024
|
+
* 전부다」를 받았을 때 무엇을 지울지 가리는 데만 쓴다. 화면이 알 필요가 없고, 상태에 내보내면
|
|
1025
|
+
* 자체 구동에는 없는 값이라 두 구동이 어긋난다(적합성 검사가 그것을 잡았다).
|
|
1026
|
+
*
|
|
1027
|
+
* 중간 저장본에는 남는다 — 재기동 뒤에도 무엇을 지울지 가릴 수 있어야 한다.
|
|
1028
|
+
*/
|
|
1029
|
+
items: [...this.items.values()].map(({ seenAtMs: _seen, ...i }) => ({ ...i })),
|
|
941
1030
|
/* 유효 기간 판정은 **저장하지 않고 여기서 낸다** — 시뮬과 **같은 함수**(`effectivityAt`)를 부른다.
|
|
942
1031
|
만료는 이벤트 없이 시각만으로 일어나므로, 델타로 받아 두면 유휴 자원이 영원히 유효하게 남는다. */
|
|
943
1032
|
persons: [...this.persons.values()].map(p => ({ ...p, ...this.effectivityPart(p), ...this.offShiftPart(`person:${p.id}`), ...this.capabilityPart(p, `person:${p.id}`, p.personnelClassIds, this.classDefs.personnel) })),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IngestResult } from './face2-adapter.ts';
|
|
2
2
|
/** 이 문이 받는 여섯 가지 — 리듀서가 접는 것과 같은 목록(주목 확인은 우리 안의 행위라 제외). */
|
|
3
|
-
export type OperationalKind = 'task' | 'equipment' | 'person' | 'asset' | 'order' | 'quality' | 'test' | 'observation';
|
|
3
|
+
export type OperationalKind = 'task' | 'equipment' | 'person' | 'asset' | 'order' | 'quality' | 'test' | 'observation' | 'complete';
|
|
4
4
|
/**
|
|
5
5
|
* 정규 운영 레코드 — **델타의 필드 이름 + 시각(`at`)**.
|
|
6
6
|
*
|
|
@@ -151,6 +151,18 @@ const SPECS = {
|
|
|
151
151
|
* `effectiveTime` 도 요구하지 않는다. 없으면 봉투의 시각이 그 자리를 대신한다(§`resolve`) — 원본이
|
|
152
152
|
* 시각을 말하지 않는 수기 점검이 실재하고, 그때 지어낸 시각보다 폴링 시각이 정직하다.
|
|
153
153
|
*/
|
|
154
|
+
/*
|
|
155
|
+
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 알린다.
|
|
156
|
+
*
|
|
157
|
+
* 둘 다 요구한다: 어느 목록인지와 그 주기를 **시작한** 시각. 시각이 없으면 무엇을 지울지 가릴 수
|
|
158
|
+
* 없고, 그때 지우면 전부 지운다 — 받지 않는 것이 옳다.
|
|
159
|
+
*/
|
|
160
|
+
complete: {
|
|
161
|
+
eventType: OP_EVENT.complete,
|
|
162
|
+
identity: 'completeAxis',
|
|
163
|
+
required: ['completeAxis', 'since'],
|
|
164
|
+
fields: { completeAxis: 'string', since: 'string', recordTime: 'string' }
|
|
165
|
+
},
|
|
154
166
|
observation: {
|
|
155
167
|
eventType: OP_EVENT.observation,
|
|
156
168
|
identity: 'locationId',
|
|
@@ -223,6 +235,9 @@ export function operationalKindOf(record) {
|
|
|
223
235
|
*/
|
|
224
236
|
if (has('locationId') && has('propertyId'))
|
|
225
237
|
return 'observation';
|
|
238
|
+
/* 「이 목록이 전부다」 — 어느 목록인지를 스스로 말하므로 다른 사실과 섞이지 않는다. */
|
|
239
|
+
if (has('completeAxis'))
|
|
240
|
+
return 'complete';
|
|
226
241
|
return undefined;
|
|
227
242
|
}
|
|
228
243
|
/** 이 레코드가 운영 사실인가 — 호스트의 라우팅이 묻는 자리. */
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -614,6 +614,27 @@ var OP_EVENT = {
|
|
|
614
614
|
* 없고, 미러가 이어받지 못한다(§상태 ⊆ 이벤트).
|
|
615
615
|
*/
|
|
616
616
|
test: "test.result",
|
|
617
|
+
/**
|
|
618
|
+
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
|
|
619
|
+
*
|
|
620
|
+
* ── 왜 필요한가 (2026-08-25 실측) ────────────────────────────────────────
|
|
621
|
+
* 연결된 시스템은 매 주기 현재 재고를 전부 보낸다. 그런데 트윈은 그것을 낱낱의 관측으로 받아
|
|
622
|
+
* **더하기만 했다.** 「그리고 이것 말고는 없다」를 받는 곳이 없어서, 목록에서 빠진 줄은 아무 말도
|
|
623
|
+
* 오지 않은 것이 되고 트윈에 영원히 남았다.
|
|
624
|
+
*
|
|
625
|
+
* 실측: 실제 재고 1,704건인데 트윈이 10,038건을 갖고 있었다.
|
|
626
|
+
*
|
|
627
|
+
* ── 왜 「사라진 것을 알려 주기」가 아니라 이 방식인가 ──────────────────────
|
|
628
|
+
* 연결 쪽이 앞 주기와 비교해 사라진 줄을 찾아 알리는 방법도 있다. 그런데 그것은 **하나 빠뜨리면
|
|
629
|
+
* 그 줄이 영원히 남는다.** 이 방식은 주기마다 스스로 바로잡는다 — 이미 잘못 쌓인 것도 다음 주기에
|
|
630
|
+
* 사라진다.
|
|
631
|
+
*
|
|
632
|
+
* ── 보내는 쪽이 지킬 것 ─────────────────────────────────────────────────
|
|
633
|
+
* **끝까지 읽었을 때만 보낸다.** 읽다가 끊긴 주기에 이것을 보내면 **살아 있는 재고를 지운다.**
|
|
634
|
+
* 그리고 `since` 는 그 주기를 **시작한** 시각이다(끝낸 시각이 아니다) — 주기 도중에 들어온 관측이
|
|
635
|
+
* 지워지지 않아야 한다.
|
|
636
|
+
*/
|
|
637
|
+
complete: "axis.complete",
|
|
617
638
|
/**
|
|
618
639
|
* 주목 신호 확인(ack) — **사람이 한 행위**라 파생될 수 없다.
|
|
619
640
|
*
|
|
@@ -1668,6 +1689,19 @@ var ObservedReducer = class {
|
|
|
1668
1689
|
item.testResults = [...kept, next];
|
|
1669
1690
|
break;
|
|
1670
1691
|
}
|
|
1692
|
+
case OP_EVENT.complete: {
|
|
1693
|
+
const d = e.data;
|
|
1694
|
+
if (d?.completeAxis !== "items") break;
|
|
1695
|
+
const since = Date.parse(String(d.since ?? ""));
|
|
1696
|
+
if (!Number.isFinite(since)) break;
|
|
1697
|
+
let heard = 0;
|
|
1698
|
+
for (const it of this.items.values()) if ((it.seenAtMs ?? -1) >= since) heard++;
|
|
1699
|
+
if (!heard) break;
|
|
1700
|
+
for (const [key, it] of [...this.items.entries()]) {
|
|
1701
|
+
if ((it.seenAtMs ?? -1) < since) this.remove(key);
|
|
1702
|
+
}
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1671
1705
|
case OP_EVENT.attentionAck: {
|
|
1672
1706
|
const d = e.data;
|
|
1673
1707
|
if (d?.id) this.acked.add(d.id);
|
|
@@ -1812,6 +1846,21 @@ var ObservedReducer = class {
|
|
|
1812
1846
|
}
|
|
1813
1847
|
if (ev.action === "DELETE") {
|
|
1814
1848
|
for (const epc of ev.epcList) this.remove(epc);
|
|
1849
|
+
const goneAt = ev.readPoint?.id;
|
|
1850
|
+
for (const qe of ev.quantityList ?? []) {
|
|
1851
|
+
if (!qe?.epcClass) continue;
|
|
1852
|
+
if (goneAt) {
|
|
1853
|
+
const key = `${qe.epcClass}@${goneAt}`;
|
|
1854
|
+
if (envelope && this.stale(`item:${key}`, envelope)) continue;
|
|
1855
|
+
this.remove(key);
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
for (const key of [...this.items.keys()]) {
|
|
1859
|
+
if (!key.startsWith(`${qe.epcClass}@`)) continue;
|
|
1860
|
+
if (envelope && this.stale(`item:${key}`, envelope)) continue;
|
|
1861
|
+
this.remove(key);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1815
1864
|
return;
|
|
1816
1865
|
}
|
|
1817
1866
|
const loc = ev.readPoint?.id;
|
|
@@ -1820,7 +1869,9 @@ var ObservedReducer = class {
|
|
|
1820
1869
|
this.touchLocation(loc);
|
|
1821
1870
|
for (const epc of ev.epcList) {
|
|
1822
1871
|
if (envelope && this.stale(`item:${epc}`, envelope)) continue;
|
|
1823
|
-
|
|
1872
|
+
const seen = this.mergeItem(epc, { location: loc, disposition: ev.disposition, ilmd: ev.ilmd }, q, all);
|
|
1873
|
+
const atSeen = Date.parse(String(envelope?.eventTime ?? ""));
|
|
1874
|
+
this.items.set(itemKeyOf(seen), Number.isFinite(atSeen) ? { ...seen, seenAtMs: atSeen } : seen);
|
|
1824
1875
|
}
|
|
1825
1876
|
if (!ev.epcList?.length) {
|
|
1826
1877
|
for (const qe of ev.quantityList ?? []) {
|
|
@@ -1828,7 +1879,9 @@ var ObservedReducer = class {
|
|
|
1828
1879
|
const mine = all.filter((x) => x.epcClass === qe.epcClass);
|
|
1829
1880
|
const subLotId = `${qe.epcClass}@${loc}`;
|
|
1830
1881
|
if (envelope && this.stale(`item:${subLotId}`, envelope)) continue;
|
|
1831
|
-
|
|
1882
|
+
const seenQty = this.mergeItem(qe.epcClass, { location: loc, disposition: ev.disposition, ilmd: ev.ilmd }, qe, mine, subLotId);
|
|
1883
|
+
const atQty = Date.parse(String(envelope?.eventTime ?? ""));
|
|
1884
|
+
this.items.set(subLotId, Number.isFinite(atQty) ? { ...seenQty, seenAtMs: atQty } : seenQty);
|
|
1832
1885
|
}
|
|
1833
1886
|
}
|
|
1834
1887
|
}
|
|
@@ -2065,8 +2118,16 @@ var ObservedReducer = class {
|
|
|
2065
2118
|
origin: n.origin
|
|
2066
2119
|
};
|
|
2067
2120
|
}),
|
|
2068
|
-
/*
|
|
2069
|
-
|
|
2121
|
+
/*
|
|
2122
|
+
* 갖고 있는 것을 전부 내보낸다 — 줄이면 그 자리에서 정보가 사라진다.
|
|
2123
|
+
*
|
|
2124
|
+
* **`seenAtMs` 는 뺀다.** 그것은 물품에 대한 사실이 아니라 **우리가 언제 들었나**이고, 「이 목록이
|
|
2125
|
+
* 전부다」를 받았을 때 무엇을 지울지 가리는 데만 쓴다. 화면이 알 필요가 없고, 상태에 내보내면
|
|
2126
|
+
* 자체 구동에는 없는 값이라 두 구동이 어긋난다(적합성 검사가 그것을 잡았다).
|
|
2127
|
+
*
|
|
2128
|
+
* 중간 저장본에는 남는다 — 재기동 뒤에도 무엇을 지울지 가릴 수 있어야 한다.
|
|
2129
|
+
*/
|
|
2130
|
+
items: [...this.items.values()].map(({ seenAtMs: _seen, ...i }) => ({ ...i })),
|
|
2070
2131
|
/* 유효 기간 판정은 **저장하지 않고 여기서 낸다** — 시뮬과 **같은 함수**(`effectivityAt`)를 부른다.
|
|
2071
2132
|
만료는 이벤트 없이 시각만으로 일어나므로, 델타로 받아 두면 유휴 자원이 영원히 유효하게 남는다. */
|
|
2072
2133
|
persons: [...this.persons.values()].map((p) => ({ ...p, ...this.effectivityPart(p), ...this.offShiftPart(`person:${p.id}`), ...this.capabilityPart(p, `person:${p.id}`, p.personnelClassIds, this.classDefs.personnel) })),
|
|
@@ -9044,6 +9105,18 @@ var SPECS = {
|
|
|
9044
9105
|
* `effectiveTime` 도 요구하지 않는다. 없으면 봉투의 시각이 그 자리를 대신한다(§`resolve`) — 원본이
|
|
9045
9106
|
* 시각을 말하지 않는 수기 점검이 실재하고, 그때 지어낸 시각보다 폴링 시각이 정직하다.
|
|
9046
9107
|
*/
|
|
9108
|
+
/*
|
|
9109
|
+
* **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 알린다.
|
|
9110
|
+
*
|
|
9111
|
+
* 둘 다 요구한다: 어느 목록인지와 그 주기를 **시작한** 시각. 시각이 없으면 무엇을 지울지 가릴 수
|
|
9112
|
+
* 없고, 그때 지우면 전부 지운다 — 받지 않는 것이 옳다.
|
|
9113
|
+
*/
|
|
9114
|
+
complete: {
|
|
9115
|
+
eventType: OP_EVENT.complete,
|
|
9116
|
+
identity: "completeAxis",
|
|
9117
|
+
required: ["completeAxis", "since"],
|
|
9118
|
+
fields: { completeAxis: "string", since: "string", recordTime: "string" }
|
|
9119
|
+
},
|
|
9047
9120
|
observation: {
|
|
9048
9121
|
eventType: OP_EVENT.observation,
|
|
9049
9122
|
identity: "locationId",
|
|
@@ -9075,6 +9148,7 @@ function operationalKindOf(record) {
|
|
|
9075
9148
|
if (has("orderId")) return "order";
|
|
9076
9149
|
if (has("testableObjectId")) return "test";
|
|
9077
9150
|
if (has("locationId") && has("propertyId")) return "observation";
|
|
9151
|
+
if (has("completeAxis")) return "complete";
|
|
9078
9152
|
return void 0;
|
|
9079
9153
|
}
|
|
9080
9154
|
function isOperationalRecord(record) {
|
package/package.json
CHANGED