@operato/twin-kernel 0.11.19 → 0.11.21
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/README.md +1 -1
- package/dist/observed-reducer.js +22 -0
- package/dist-cjs/index.cjs +40 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -221,6 +221,6 @@ Host binding (transport, persistence, connectors) · board binding (component
|
|
|
221
221
|
|
|
222
222
|
- [`fold-and-resume.md`](../../design/fold-and-resume.md) — folding and resume points (the wrong turns, the principles, what is implemented)
|
|
223
223
|
- [`runtime-state-model.md`](../../design/runtime-state-model.md) — live = authority / history = derived, the time axis, identity
|
|
224
|
-
-
|
|
224
|
+
- `operato-application/adr/` — ADRs
|
|
225
225
|
- `plans/simulation-spec.md` (specs, estimators, forecast standing) · `plans/four-resources-and-conformance.md` (four resources, invariants) · `plans/kernel-unification-live-observe.md` (driver unification)
|
|
226
226
|
- `profiles/ems.md` — the energy profile's standard anchors
|
package/dist/observed-reducer.js
CHANGED
|
@@ -970,6 +970,28 @@ export class ObservedReducer {
|
|
|
970
970
|
/* 개체 없이 수량만 오는 입고(비직렬 자재) — 표준이 허용하고 검증기도 유효로 판정한다.
|
|
971
971
|
* 이 경우 클래스 식별자 자체가 물품의 키다(로트 관리 자재는 LGTIN 이라 로트별로 갈린다). */
|
|
972
972
|
if (!ev.epcList?.length) {
|
|
973
|
+
/*
|
|
974
|
+
* ── 자리를 말하지 않은 수량 관측은 **앉히지 않는다** (2026-09-19, ADR-0082) ──
|
|
975
|
+
*
|
|
976
|
+
* 열쇠가 `${epcClass}@${자리}` 이므로 자리가 없으면 `@undefined` 라는 줄이 선다. 그 줄은 어느
|
|
977
|
+
* 자리의 재고도 아닌데 합에는 든다 — 실측: 자리 있는 100 + 자리 없는 40 = 140, 그리고 아무 데도
|
|
978
|
+
* 세어지지 않았다.
|
|
979
|
+
*
|
|
980
|
+
* **전수로 세어 보니 일부러 그렇게 보내는 원본이 없다** — 수량 관측을 만드는 여덟 자리(chef v1
|
|
981
|
+
* 셋 · v2 하나 · warehouse 입고 둘 · 출고 둘)가 전부 `readPoint` 를 싣고, sap-ewm · oracle-wms ·
|
|
982
|
+
* ppms · ems 는 수량 관측을 내지 않는다. 그래서 잃는 원본 없이 닫을 수 있다.
|
|
983
|
+
*
|
|
984
|
+
* 「없어졌다」(`DELETE`)는 **건드리지 않는다** — 그쪽에서 자리를 말하지 않은 것은 「그 로트가
|
|
985
|
+
* 통째로 없어졌다」는 진술이고 위에서 그 범위대로 지운다.
|
|
986
|
+
*
|
|
987
|
+
* 순서 판정(`stale`)을 지나지 않는다. 그 함수가 트윈의 「지금」을 미는 유일한 자리인데, 받아들이지
|
|
988
|
+
* 않은 사실로 시계를 움직이면 이 트윈이 「방금 들었다」고 말하면서 아무것도 앉히지 않은 것이 된다.
|
|
989
|
+
*/
|
|
990
|
+
if (!loc && (ev.quantityList ?? []).some(qe => qe?.epcClass)) {
|
|
991
|
+
if (envelope)
|
|
992
|
+
this.noteUnhandled(envelope, 'epcis.ObjectEvent:quantity-without-readpoint');
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
973
995
|
for (const qe of ev.quantityList ?? []) {
|
|
974
996
|
if (!qe?.epcClass)
|
|
975
997
|
continue;
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -328,8 +328,36 @@ function outsideLimit(criterion, observed) {
|
|
|
328
328
|
return true;
|
|
329
329
|
return false;
|
|
330
330
|
}
|
|
331
|
+
function isTestSpecificationBasis(value) {
|
|
332
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
333
|
+
return false;
|
|
334
|
+
const basis = value;
|
|
335
|
+
if (!["id", "revisionId", "planId", "contentHash", "businessTime"].every((key) => typeof basis[key] === "string" && basis[key].trim()))
|
|
336
|
+
return false;
|
|
337
|
+
if (!Number.isFinite(Date.parse(basis.businessTime)))
|
|
338
|
+
return false;
|
|
339
|
+
return Array.isArray(basis.criteria) && basis.criteria.every((c) => {
|
|
340
|
+
if (!c || typeof c !== "object" || Array.isArray(c) || typeof c.id !== "string" || !c.id.trim())
|
|
341
|
+
return false;
|
|
342
|
+
if (["description", "expression", "evaluatedPropertyId"].some((key) => c[key] !== void 0 && typeof c[key] !== "string"))
|
|
343
|
+
return false;
|
|
344
|
+
if (c.limit === void 0)
|
|
345
|
+
return true;
|
|
346
|
+
const limit = c.limit;
|
|
347
|
+
if (!limit || typeof limit !== "object" || Array.isArray(limit))
|
|
348
|
+
return false;
|
|
349
|
+
if (["minimum", "maximum"].some((key) => limit[key] !== void 0 && (typeof limit[key] !== "number" || !Number.isFinite(limit[key]))))
|
|
350
|
+
return false;
|
|
351
|
+
if (limit.uom !== void 0 && typeof limit.uom !== "string")
|
|
352
|
+
return false;
|
|
353
|
+
return !(limit.minimum !== void 0 && limit.maximum !== void 0 && limit.minimum > limit.maximum);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
331
356
|
function judgeAgainstSpec(spec, result) {
|
|
332
|
-
const
|
|
357
|
+
const basis = result?.specificationBasis;
|
|
358
|
+
if (basis !== void 0 && !isTestSpecificationBasis(basis))
|
|
359
|
+
return void 0;
|
|
360
|
+
const criteria = (basis ? basis.criteria : spec?.criteria ?? []).filter((c) => !criterionSaysNothing(c));
|
|
333
361
|
if (!criteria.length)
|
|
334
362
|
return void 0;
|
|
335
363
|
const measurements = result?.propertyMeasurements ?? [];
|
|
@@ -2156,8 +2184,15 @@ var SPECS = {
|
|
|
2156
2184
|
expiresAt: "string",
|
|
2157
2185
|
derived: "boolean",
|
|
2158
2186
|
propertyMeasurements: "object[]",
|
|
2187
|
+
specificationBasis: "object",
|
|
2159
2188
|
recordTime: "string"
|
|
2160
2189
|
},
|
|
2190
|
+
shapes: {
|
|
2191
|
+
specificationBasis: {
|
|
2192
|
+
required: ["id", "revisionId", "planId", "contentHash", "businessTime", "criteria"],
|
|
2193
|
+
fields: { id: "string", revisionId: "string", planId: "string", contentHash: "string", businessTime: "string" }
|
|
2194
|
+
}
|
|
2195
|
+
},
|
|
2161
2196
|
enums: { result: ["pass", "fail"] }
|
|
2162
2197
|
},
|
|
2163
2198
|
/*
|
|
@@ -3227,6 +3262,10 @@ var ObservedReducer = class {
|
|
|
3227
3262
|
this.items.set(itemKeyOf(seen), Number.isFinite(atSeen) ? { ...seen, seenAtMs: atSeen } : seen);
|
|
3228
3263
|
}
|
|
3229
3264
|
if (!ev.epcList?.length) {
|
|
3265
|
+
if (!loc && (ev.quantityList ?? []).some((qe) => qe?.epcClass)) {
|
|
3266
|
+
if (envelope) this.noteUnhandled(envelope, "epcis.ObjectEvent:quantity-without-readpoint");
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
3230
3269
|
for (const qe of ev.quantityList ?? []) {
|
|
3231
3270
|
if (!qe?.epcClass) continue;
|
|
3232
3271
|
const mine = all.filter((x) => x.epcClass === qe.epcClass);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/twin-kernel",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.21",
|
|
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.31"
|
|
32
32
|
}
|
|
33
33
|
}
|