@operato/twin-kernel 0.11.25 → 0.11.26

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.
@@ -17,7 +17,7 @@ import { resolveSubject } from '@operato/ops-contract';
17
17
  import { WMS_ORDER_KIND } from '@operato/ops-contract';
18
18
  import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP, objectUri, bizTransactionUri, gdtiUri } from '@operato/ops-contract';
19
19
  import { allocationPolicyOf } from "./allocation-policy.js";
20
- import { ORDER_FOLD_DEFAULT, emptyFoldedOrders, foldOrdersInto, foldedOrderCount, orderFoldPolicyOf, planOrderFold } from '@operato/ops-contract';
20
+ import { ORDER_FOLD_DEFAULT, emptyFoldedOrders, foldOrdersInto, foldedOrderCount, pruneFoldedHours, orderFoldPolicyOf, planOrderFold } from '@operato/ops-contract';
21
21
  import { OP_PARAM } from '@operato/ops-contract';
22
22
  import { parseIsoDuration } from '@operato/ops-contract';
23
23
  import { analyzeCapacity } from '@operato/ops-contract';
@@ -721,7 +721,9 @@ export class FlowEngine {
721
721
  const ids = planOrderFold([...this.orders.values()], nowMs ?? this.nowMs(), this.orderFoldPolicy);
722
722
  if (!ids.length)
723
723
  return 0;
724
- this.foldedOrders = foldOrdersInto(this.foldedOrders, ids.map(id => this.orders.get(id)).filter(Boolean));
724
+ /* 창 밖 시간 칸은 접을 때 버린다 — 칸 수가 T/1h 를 넘지 않게(ADR-0092 결정 2 보탬). */
725
+ const now = nowMs ?? this.nowMs();
726
+ this.foldedOrders = pruneFoldedHours(foldOrdersInto(this.foldedOrders, ids.map(id => this.orders.get(id)).filter(Boolean)), now, this.orderFoldPolicy);
725
727
  for (const id of ids)
726
728
  this.orders.delete(id);
727
729
  this.revision++;
@@ -22,7 +22,7 @@
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
24
  import { ILMD_ATTR, parseEpc } from '@operato/ops-contract';
25
- import { emptyFoldedOrders, foldOrdersInto, foldedOrderCount, isOrderTerminal, orderFoldPolicyOf, planOrderFold, standsNewOrderRow } from '@operato/ops-contract';
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`). */
28
28
  import { ClassMasterTable } from "./class-master-table.js";
@@ -1423,7 +1423,8 @@ export class ObservedReducer {
1423
1423
  if (!ids.length)
1424
1424
  return 0;
1425
1425
  const rows = ids.map(id => this.orders.get(id)).filter(Boolean);
1426
- this.foldedOrders = foldOrdersInto(this.foldedOrders, rows);
1426
+ /* 창 밖 시간 칸은 접을 때 버린다 — 칸 수가 T/1h 를 넘지 않게(ADR-0092 결정 2 보탬). */
1427
+ this.foldedOrders = pruneFoldedHours(foldOrdersInto(this.foldedOrders, rows), nowMs, this.orderFoldPolicy);
1427
1428
  for (const id of ids) {
1428
1429
  this.orders.delete(id);
1429
1430
  this.lastAt.delete(`order:${id}`);
@@ -1910,6 +1910,7 @@ function gs1KeyDigitViolation(uri) {
1910
1910
  }
1911
1911
 
1912
1912
  // ../ops-contract/dist/order-fold.js
1913
+ var ORDER_FOLD_HOUR_MS = 60 * 60 * 1e3;
1913
1914
  var ORDER_FOLD_DEFAULT = Object.freeze({ afterMs: 24 * 60 * 60 * 1e3, maxTerminal: 1e3 });
1914
1915
  function orderFoldPolicyOf(declared) {
1915
1916
  const policy = { ...ORDER_FOLD_DEFAULT };
@@ -1922,6 +1923,9 @@ function orderFoldPolicyOf(declared) {
1922
1923
  }
1923
1924
  policy[key] = key === "maxTerminal" ? Math.floor(v) : v;
1924
1925
  }
1926
+ if (policy.afterMs % ORDER_FOLD_HOUR_MS !== 0) {
1927
+ return { error: `order fold afterMs must be a whole number of hours, got ${policy.afterMs} ms \u2014 the window is counted in hour slots (ADR-0092)` };
1928
+ }
1925
1929
  if (policy.maxTerminal < 1)
1926
1930
  return { error: "order fold maxTerminal must be at least 1 (ADR-0092)" };
1927
1931
  return { policy };
@@ -1949,6 +1953,7 @@ function foldOrdersInto(folded, orders) {
1949
1953
  counts: Object.fromEntries(Object.entries(folded?.counts ?? {}).map(([k, v]) => [k, { ...v }])),
1950
1954
  boundaryMs: folded?.boundaryMs ?? null
1951
1955
  };
1956
+ const hours = Object.fromEntries(Object.entries(folded?.hours ?? {}).map(([h, byKind]) => [h, Object.fromEntries(Object.entries(byKind).map(([k, v]) => [k, { ...v }]))]));
1952
1957
  for (const o of orders) {
1953
1958
  const kind = String(o.kind ?? "");
1954
1959
  const status = String(o.status ?? "");
@@ -1956,8 +1961,12 @@ function foldOrdersInto(folded, orders) {
1956
1961
  byStatus[status] = (byStatus[status] ?? 0) + 1;
1957
1962
  if (typeof o.terminalAtMs === "number" && Number.isFinite(o.terminalAtMs)) {
1958
1963
  out.boundaryMs = out.boundaryMs === null ? o.terminalAtMs : Math.max(out.boundaryMs, o.terminalAtMs);
1964
+ const slot = (hours[String(hourStartOf(o.terminalAtMs))] ??= {})[kind] ??= {};
1965
+ slot[status] = (slot[status] ?? 0) + 1;
1959
1966
  }
1960
1967
  }
1968
+ if (Object.keys(hours).length)
1969
+ out.hours = hours;
1961
1970
  return out;
1962
1971
  }
1963
1972
  function standsNewOrderRow(record, eventTimeMs, folded) {
@@ -1970,6 +1979,22 @@ function standsNewOrderRow(record, eventTimeMs, folded) {
1970
1979
  return true;
1971
1980
  return eventTimeMs > boundary;
1972
1981
  }
1982
+ function hourStartOf(ms2) {
1983
+ return Math.floor(ms2 / ORDER_FOLD_HOUR_MS) * ORDER_FOLD_HOUR_MS;
1984
+ }
1985
+ function finishedWindowStartMs(nowMs, policy = ORDER_FOLD_DEFAULT) {
1986
+ return hourStartOf(nowMs) - policy.afterMs;
1987
+ }
1988
+ function pruneFoldedHours(folded, nowMs, policy = ORDER_FOLD_DEFAULT) {
1989
+ if (!folded.hours)
1990
+ return folded;
1991
+ const start = finishedWindowStartMs(nowMs, policy);
1992
+ const kept = Object.fromEntries(Object.entries(folded.hours).filter(([h]) => Number(h) >= start));
1993
+ const out = { counts: folded.counts, boundaryMs: folded.boundaryMs };
1994
+ if (Object.keys(kept).length)
1995
+ out.hours = kept;
1996
+ return out;
1997
+ }
1973
1998
 
1974
1999
  // ../ops-contract/dist/iso-duration.js
1975
2000
  var RE = /^(-)?P(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
@@ -3852,7 +3877,7 @@ var ObservedReducer = class {
3852
3877
  const ids = planOrderFold([...this.orders.values()], nowMs, this.orderFoldPolicy);
3853
3878
  if (!ids.length) return 0;
3854
3879
  const rows = ids.map((id) => this.orders.get(id)).filter(Boolean);
3855
- this.foldedOrders = foldOrdersInto(this.foldedOrders, rows);
3880
+ this.foldedOrders = pruneFoldedHours(foldOrdersInto(this.foldedOrders, rows), nowMs, this.orderFoldPolicy);
3856
3881
  for (const id of ids) {
3857
3882
  this.orders.delete(id);
3858
3883
  this.lastAt.delete(`order:${id}`);
@@ -4524,7 +4549,8 @@ var FlowEngine = class {
4524
4549
  }
4525
4550
  const ids = planOrderFold([...this.orders.values()], nowMs ?? this.nowMs(), this.orderFoldPolicy);
4526
4551
  if (!ids.length) return 0;
4527
- this.foldedOrders = foldOrdersInto(this.foldedOrders, ids.map((id) => this.orders.get(id)).filter(Boolean));
4552
+ const now = nowMs ?? this.nowMs();
4553
+ this.foldedOrders = pruneFoldedHours(foldOrdersInto(this.foldedOrders, ids.map((id) => this.orders.get(id)).filter(Boolean)), now, this.orderFoldPolicy);
4528
4554
  for (const id of ids) this.orders.delete(id);
4529
4555
  this.revision++;
4530
4556
  return ids.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.11.25",
3
+ "version": "0.11.26",
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.36"
31
+ "@operato/ops-contract": "^0.9.37"
32
32
  }
33
33
  }