@operato/twin-kernel 0.7.57 → 0.7.59
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/face2-adapter.d.ts +16 -0
- package/dist/face2-adapter.js +22 -0
- package/dist/observed-reducer.js +20 -0
- package/dist-cjs/index.cjs +14 -0
- package/package.json +1 -1
package/dist/face2-adapter.d.ts
CHANGED
|
@@ -29,6 +29,22 @@ export interface ObjectEventMapping {
|
|
|
29
29
|
* 사건이 정답을 다시 말해 주고 오차가 쌓이지 않는다(시뮬 `consumeMaterials` 가 택한 것과 같은 규약).
|
|
30
30
|
*/
|
|
31
31
|
quantityList?: MapValue;
|
|
32
|
+
/**
|
|
33
|
+
* **개체·로트의 마스터데이터** → `ilmd`(EPCIS 2.0 Instance/Lot Master Data).
|
|
34
|
+
*
|
|
35
|
+
* ── 왜 뒤늦게 생겼나 (2026-08-24 실측) ─────────────────────────────────────
|
|
36
|
+
* `quantityList` 와 **똑같은 모양의 결함**이었다: 받는 쪽은 처음부터 준비돼 있는데 **문이 없었다.**
|
|
37
|
+
* `ObservedReducer` 는 `ilmd` 를 읽어 로트와 소비기한을 세운다(`expiryOf` · `ILMD_ATTR.lot`), 그리고
|
|
38
|
+
* 이름을 모르는 속성도 버리지 않고 원문으로 든다. 그런데 이 매핑에 자리가 없어서 **커넥터가 그것을
|
|
39
|
+
* 말할 길이 없었다.**
|
|
40
|
+
*
|
|
41
|
+
* 실측(포천): 원본 재고 1,704행 **전부**에 소비기한이 있는데 투영된 상태에는 **0건**이었다. 지난
|
|
42
|
+
* 재고 950건·59,785kg 이 어디에도 나타나지 않았다 — 화면이 비어 있는 것이 「이상 없음」으로 읽혔다.
|
|
43
|
+
*
|
|
44
|
+
* 값은 **객체 그대로** 받는다(`{ 'cbvmda:lotNumber': …, 'cbvmda:itemExpirationDate': … }`). 키 어휘는
|
|
45
|
+
* 표준(CBV MDA)의 것이고 커널이 `ILMD_ATTR` 로 그 이름을 안다 — 여기서 이름을 다시 정하지 않는다.
|
|
46
|
+
*/
|
|
47
|
+
ilmd?: MapValue;
|
|
32
48
|
readPoint?: MapValue;
|
|
33
49
|
bizLocation?: MapValue;
|
|
34
50
|
}
|
package/dist/face2-adapter.js
CHANGED
|
@@ -99,6 +99,26 @@ function resolveQuantityList(v, record, field, errors) {
|
|
|
99
99
|
}
|
|
100
100
|
return r.map(x => ({ ...x }));
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* 객체 하나를 그대로 꺼낸다 — `ilmd` 처럼 **구조가 곧 값**인 자리.
|
|
104
|
+
*
|
|
105
|
+
* `resolve` 는 객체가 오면 오류로 본다(스칼라 자리에 표준 구조를 실은 것이 실제 결함이었다). 여기서는
|
|
106
|
+
* 반대다: 객체가 맞고 스칼라가 틀렸다. 그래서 함수를 갈라 두고 각자 자기 모양을 단정한다.
|
|
107
|
+
*/
|
|
108
|
+
function resolveObject(v, record, field, errors) {
|
|
109
|
+
if (v === undefined)
|
|
110
|
+
return undefined;
|
|
111
|
+
const r = get(record, v.startsWith('$.') ? v.slice(2) : v);
|
|
112
|
+
if (r === undefined || r === null)
|
|
113
|
+
return undefined;
|
|
114
|
+
if (typeof r !== 'object' || Array.isArray(r)) {
|
|
115
|
+
errors.push(`${field} 가 객체가 아니다(${v}): ${JSON.stringify(r).slice(0, 80)}`);
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
const o = { ...r };
|
|
119
|
+
/* 빈 객체는 「말하지 않은 것」이다 — 실으면 소비처가 「마스터데이터가 있는데 비었다」로 읽는다. */
|
|
120
|
+
return Object.keys(o).length ? o : undefined;
|
|
121
|
+
}
|
|
102
122
|
/** 단일 레코드 → 정규 EPCIS 이벤트 + 매핑에서 드러난 문제(검증은 `ingest` 가 이어서 한다). */
|
|
103
123
|
export function mapRecordChecked(record, mapping, eventTime) {
|
|
104
124
|
const errors = [];
|
|
@@ -165,11 +185,13 @@ export function mapRecordChecked(record, mapping, eventTime) {
|
|
|
165
185
|
if (!epc && !quantityList.length) {
|
|
166
186
|
errors.push('epc 와 quantityList 가 둘 다 비었다 — 개체 식별자나 클래스+수량 중 하나는 있어야 한다');
|
|
167
187
|
}
|
|
188
|
+
const ilmd = resolveObject(mapping.ilmd, record, 'ilmd', errors);
|
|
168
189
|
return {
|
|
169
190
|
event: objectEvent({
|
|
170
191
|
eventTime, action, bizStep, disposition,
|
|
171
192
|
epcList: epc ? [epc] : [],
|
|
172
193
|
...(quantityList.length ? { quantityList } : {}),
|
|
194
|
+
...(ilmd ? { ilmd } : {}),
|
|
173
195
|
readPoint, bizLocation
|
|
174
196
|
}),
|
|
175
197
|
errors
|
package/dist/observed-reducer.js
CHANGED
|
@@ -630,6 +630,26 @@ export class ObservedReducer {
|
|
|
630
630
|
* 덮었다**(rack-1 의 100개가 사라지고 합계가 60이 됐다). 로트가 나뉘어 놓이는 것은 일상이다.
|
|
631
631
|
*/
|
|
632
632
|
const subLotId = `${qe.epcClass}@${loc}`;
|
|
633
|
+
/*
|
|
634
|
+
* ── ★ **이 갈래가 문을 지나지 않았다** (2026-08-24 실측) ──────────────
|
|
635
|
+
* 개체 관측은 `stale()` 을 지나는데 **수량 관측은 지나지 않았다.** 둘이 걸려 있었다.
|
|
636
|
+
*
|
|
637
|
+
* ① **순서 판정이 없었다** — 늦게 온 옛 관측이 최신 수량을 덮는다. 실 연동에서 순서는 뒤집힌다
|
|
638
|
+
* (재시도·큐·배치). 비직렬 자재가 주된 경로인 원본(식품 제조)에서는 이것이 재고의 대부분이다.
|
|
639
|
+
*
|
|
640
|
+
* ② **트윈의 시계가 밀리지 않았다** — `stale()` 이 관측 시각을 기억하는 유일한 자리이고
|
|
641
|
+
* (`observedAtMs`), `nowMs` 가 그 값을 「지금」으로 쓴다. 그래서 포천 미러는 재고 관측이
|
|
642
|
+
* 오늘 시각으로 계속 들어오는데도 `nowTime` 이 **2026-04-15 에 멈춰 있었다** — 시계를 밀던
|
|
643
|
+
* 유일한 스트림(공정·로트)이 4월에 멈춘 것이다. 그 결과 주의 신호의 지속시간이 언제나 0 이고,
|
|
644
|
+
* 시각으로 재는 모든 판단이 조용히 틀렸다.
|
|
645
|
+
*
|
|
646
|
+
* 에너지 경로가 같은 문을 우회해 같은 사고가 났고(2026-08-18) 그때 `noteObservedAt` 을 냈다.
|
|
647
|
+
* **세 번째다.** 축을 늘릴 때 이 문을 지나는지 보는 것이 규율이다.
|
|
648
|
+
*
|
|
649
|
+
* 키에 서브로트를 넣는다 — 같은 로트의 다른 자리가 서로를 막지 않아야 한다(§`subLotId`).
|
|
650
|
+
*/
|
|
651
|
+
if (envelope && this.stale(`item:${subLotId}`, envelope))
|
|
652
|
+
continue;
|
|
633
653
|
this.items.set(subLotId, this.mergeItem(qe.epcClass, { location: loc, disposition: ev.disposition, ilmd: ev.ilmd }, qe, mine, subLotId));
|
|
634
654
|
}
|
|
635
655
|
}
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -1812,6 +1812,7 @@ var ObservedReducer = class {
|
|
|
1812
1812
|
if (!qe?.epcClass) continue;
|
|
1813
1813
|
const mine = all.filter((x) => x.epcClass === qe.epcClass);
|
|
1814
1814
|
const subLotId = `${qe.epcClass}@${loc}`;
|
|
1815
|
+
if (envelope && this.stale(`item:${subLotId}`, envelope)) continue;
|
|
1815
1816
|
this.items.set(subLotId, this.mergeItem(qe.epcClass, { location: loc, disposition: ev.disposition, ilmd: ev.ilmd }, qe, mine, subLotId));
|
|
1816
1817
|
}
|
|
1817
1818
|
}
|
|
@@ -3188,6 +3189,17 @@ function resolveQuantityList(v, record, field, errors) {
|
|
|
3188
3189
|
}
|
|
3189
3190
|
return r.map((x) => ({ ...x }));
|
|
3190
3191
|
}
|
|
3192
|
+
function resolveObject(v, record, field, errors) {
|
|
3193
|
+
if (v === void 0) return void 0;
|
|
3194
|
+
const r = get(record, v.startsWith("$.") ? v.slice(2) : v);
|
|
3195
|
+
if (r === void 0 || r === null) return void 0;
|
|
3196
|
+
if (typeof r !== "object" || Array.isArray(r)) {
|
|
3197
|
+
errors.push(`${field} \uAC00 \uAC1D\uCCB4\uAC00 \uC544\uB2C8\uB2E4(${v}): ${JSON.stringify(r).slice(0, 80)}`);
|
|
3198
|
+
return void 0;
|
|
3199
|
+
}
|
|
3200
|
+
const o = { ...r };
|
|
3201
|
+
return Object.keys(o).length ? o : void 0;
|
|
3202
|
+
}
|
|
3191
3203
|
function mapRecordChecked(record, mapping, eventTime) {
|
|
3192
3204
|
const errors = [];
|
|
3193
3205
|
const bizStep = resolve(mapping.bizStep, record, "bizStep", errors) ?? "";
|
|
@@ -3251,6 +3263,7 @@ function mapRecordChecked(record, mapping, eventTime) {
|
|
|
3251
3263
|
if (!epc && !quantityList.length) {
|
|
3252
3264
|
errors.push("epc \uC640 quantityList \uAC00 \uB458 \uB2E4 \uBE44\uC5C8\uB2E4 \u2014 \uAC1C\uCCB4 \uC2DD\uBCC4\uC790\uB098 \uD074\uB798\uC2A4+\uC218\uB7C9 \uC911 \uD558\uB098\uB294 \uC788\uC5B4\uC57C \uD55C\uB2E4");
|
|
3253
3265
|
}
|
|
3266
|
+
const ilmd = resolveObject(mapping.ilmd, record, "ilmd", errors);
|
|
3254
3267
|
return {
|
|
3255
3268
|
event: objectEvent({
|
|
3256
3269
|
eventTime,
|
|
@@ -3259,6 +3272,7 @@ function mapRecordChecked(record, mapping, eventTime) {
|
|
|
3259
3272
|
disposition,
|
|
3260
3273
|
epcList: epc ? [epc] : [],
|
|
3261
3274
|
...quantityList.length ? { quantityList } : {},
|
|
3275
|
+
...ilmd ? { ilmd } : {},
|
|
3262
3276
|
readPoint,
|
|
3263
3277
|
bizLocation
|
|
3264
3278
|
}),
|
package/package.json
CHANGED