@operato/twin-kernel 0.7.0 → 0.7.2

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.
@@ -77,6 +77,7 @@ __export(index_exports, {
77
77
  activeShiftOf: () => activeShiftOf,
78
78
  aggregationEvent: () => aggregationEvent,
79
79
  analyzeCapacity: () => analyzeCapacity,
80
+ attributeEnergy: () => attributeEnergy,
80
81
  axesOfSystem: () => axesOfSystem,
81
82
  axisAppliesTo: () => axisAppliesTo,
82
83
  axisInfo: () => axisInfo,
@@ -94,6 +95,8 @@ __export(index_exports, {
94
95
  documentPath: () => documentPath,
95
96
  dueStatusOf: () => dueStatusOf,
96
97
  effectivityAt: () => effectivityAt,
98
+ energyIntensity: () => energyIntensity,
99
+ energyOfWindows: () => energyOfWindows,
97
100
  fefoPolicy: () => fefoPolicy,
98
101
  firstFitPolicy: () => firstFitPolicy,
99
102
  foldJobResponses: () => foldJobResponses,
@@ -104,6 +107,8 @@ __export(index_exports, {
104
107
  inWorkCalendar: () => inWorkCalendar,
105
108
  inWorkCalendarAt: () => inWorkCalendarAt,
106
109
  ingest: () => ingest,
110
+ ingestEnergyRecords: () => ingestEnergyRecords,
111
+ isEnergyRecord: () => isEnergyRecord,
107
112
  isEquipmentLevel: () => isEquipmentLevel,
108
113
  isoDurationHours: () => isoDurationHours,
109
114
  itemKeyOf: () => itemKeyOf,
@@ -1598,7 +1603,7 @@ var WMS_TYPES = [
1598
1603
 
1599
1604
  // src/ems-profile.ts
1600
1605
  var EMS_LOCATION_TYPES = ["incoming", "feeder", "submeter-zone"];
1601
- var EMS_EQUIPMENT_TYPES = ["meter", "breaker", "pv-array", "battery", "curtailable-load"];
1606
+ var EMS_EQUIPMENT_TYPES = ["meter", "breaker", "pv-array", "battery", "utility", "curtailable-load"];
1602
1607
  var EMS_PROPERTY = {
1603
1608
  /** 계약전력(kW) — 수전·분기 자리에 선언한다. 없으면 계약 대비 판정을 하지 않는다. */
1604
1609
  contractKW: "contract.kW"
@@ -1684,6 +1689,28 @@ var EMS_TYPES = [
1684
1689
  identity: { scheme: "kernel:id" },
1685
1690
  capabilities: ["storing", "metered", "operable"]
1686
1691
  },
1692
+ {
1693
+ key: "utility",
1694
+ role: "equipment",
1695
+ label: "twin.type.utility",
1696
+ /*
1697
+ * 공통 설비 — 공조·컴프레서·칠러·조명·폐수처리처럼 **어느 공정에도 귀속되지 않는** 소비처.
1698
+ *
1699
+ * ── 왜 따로 있나 (2026-08-14) ────────────────────────────────────────────
1700
+ * 물류·생산 트윈에서 이런 것들은 **설비가 아니다**(공정에 매핑되지 않으므로 자원 축에 없다).
1701
+ * 그런데 에너지에서는 소비의 절반을 차지하고 감축 후보 1순위다 — 담을 자리가 반드시 있어야 한다.
1702
+ *
1703
+ * 처음에는 `curtailable-load` 하나로 받으려 했다. 그런데 그 이름은 **「줄일 수 있다」고 주장**한다:
1704
+ * 폐수처리·방폭 환기·서버실 냉방은 공통이지만 줄일 수 없고, 그것을 감축 가능으로 두면 트윈이
1705
+ * 「이걸 줄이면 됩니다」라는 거짓 제안을 한다. 공통성과 감축 가능성은 **다른 축**이다.
1706
+ *
1707
+ * 표준: IEC 61850 에 「공통 설비」라는 논리 노드는 없다(설비 종류마다 다른 노드다) — 비운다.
1708
+ * ISO 50001 의 SEU 는 이 부류를 가장 많이 가리킨다(유의 에너지 사용처).
1709
+ */
1710
+ standardClass: { iso50001: "SEU", iso55000: "Asset" },
1711
+ identity: { scheme: "kernel:id" },
1712
+ capabilities: ["metered", "operable"]
1713
+ },
1687
1714
  {
1688
1715
  key: "curtailable-load",
1689
1716
  role: "equipment",
@@ -2030,6 +2057,34 @@ var TWIN_AXES = [
2030
2057
  historical: true,
2031
2058
  standardClass: { isa95: "SegmentResponse", epcis: "TransformationEvent" },
2032
2059
  systems: LOGISTICS
2060
+ },
2061
+ /*
2062
+ * ── 에너지가 더하는 개념은 **하나**다 (2026-08-14, §10 6.5단계) ──────────────
2063
+ *
2064
+ * 에너지 트윈의 개체 대부분은 **이미 있는 축**이 답한다: 전기 구간은 `locations`, 계량기·차단기·
2065
+ * 태양광·축전지·감축 부하는 `equipment` 다(카탈로그가 그 타입들을 그 역할로 선언한다). 그것들을
2066
+ * 새 축으로 다시 세우면 같은 것이 두 곳에서 세어진다 — 개념 지도가 계량기를 두 번 보여 준다.
2067
+ *
2068
+ * 정말로 새로운 것은 **수요 구간**이다: 자원이 아니고, 선언이 아니고, 15분마다 닫히는 **사실**이다.
2069
+ * 그것이 요금의 알갱이이고 피크의 근거다(피크는 마감된 구간의 최대이므로 파생이다 — 축이 아니다).
2070
+ *
2071
+ * ── 표준 칸을 비운다 ──────────────────────────────────────────────────────
2072
+ * 15분 수요 구간은 **요금 제도의 알갱이**다(계약·TOU). ISO 50001 은 경영 체계를, IEC 61850 은 설비
2073
+ * 데이터 모델을 말하고, 둘 다 이 구간을 정의하지 않는다. 가까운 이름을 적으면 적합성 표가 거짓을
2074
+ * 말하므로 비워 둔다 — 「표준에 자리가 없으면 빈 객체」라는 이 선언의 규율 그대로다.
2075
+ *
2076
+ * 아직 세우지 않은 것: **요금 구간**(TariffPeriod)과 **원단위**(EnPI). 둘 다 아직 아무도 만들지
2077
+ * 않는다 — 선언만 하면 개념 지도가 언제나 0 을 보여 주고, 그것은 결손처럼 읽힌다(§10 7단계의 일).
2078
+ */
2079
+ {
2080
+ axis: "demandWindows",
2081
+ path: "energy.closed",
2082
+ label: "twin.axis.demandWindows",
2083
+ kind: "instance",
2084
+ source: "state",
2085
+ historical: true,
2086
+ standardClass: {},
2087
+ systems: ["ems"]
2033
2088
  }
2034
2089
  ];
2035
2090
  var TWIN_RELATIONS = [
@@ -5521,6 +5576,11 @@ var MesKernel = class extends FlowEngine {
5521
5576
  // src/ems-kernel.ts
5522
5577
  var DEMAND_WINDOW_MS = 15 * 60 * 1e3;
5523
5578
  var demandWindowStart = (atMs, windowMs = DEMAND_WINDOW_MS) => Math.floor(atMs / windowMs) * windowMs;
5579
+ function windowMsOf(opts) {
5580
+ const raw = typeof opts === "number" ? opts : opts?.windowMs;
5581
+ const n = Number(raw);
5582
+ return Number.isFinite(n) && n > 0 ? n : DEMAND_WINDOW_MS;
5583
+ }
5524
5584
  var KEEP_CLOSED = 96;
5525
5585
  var EmsKernel = class extends FlowEngine {
5526
5586
  points = /* @__PURE__ */ new Map();
@@ -5531,9 +5591,23 @@ var EmsKernel = class extends FlowEngine {
5531
5591
  /** 이 구간에서 이미 제안을 냈나 — 같은 사실을 되풀어 방송하지 않는다(라이브 브리지의 교훈). */
5532
5592
  suggestedFor;
5533
5593
  windowMs;
5534
- constructor(tenantId, policy = firstFitPolicy, windowMs = DEMAND_WINDOW_MS) {
5594
+ /**
5595
+ * ── 세 번째 인자는 **호스트가 정한 자리**다 (2026-08-14 실측으로 고침) ──────
5596
+ *
5597
+ * 호스트는 모든 커널을 한 모양으로 세운다: `new Kernel(tenantId, undefined, productionSpecOf(model))`.
5598
+ * 즉 세 번째 인자는 **도메인 옵션 슬롯**이고 커널마다 뜻이 다르다(야드는 모드, 생산은 명세).
5599
+ *
5600
+ * 처음에 이 자리를 `windowMs: number` 로 받았다가, 라이브에서 **모든 수요 구간이 깨졌다** —
5601
+ * 생산 명세 객체가 창 길이로 들어와 `startMs: null`·`endMs: NaN` 이 됐다. 내 시험은 커널을
5602
+ * `new EmsKernel('t')` 로 직접 세웠기 때문에 그것을 잡지 못했다(호스트와 다른 방식으로 세운 것이
5603
+ * 그 자체로 결함이었다).
5604
+ *
5605
+ * 그래서 **쓸 수 있는 것만 읽는다**: 수면 창 길이로 쓰고, 객체면 `windowMs` 를 찾고, 없으면 기본값
5606
+ * (15분)이다. 모르는 것을 창 길이로 삼지 않는다.
5607
+ */
5608
+ constructor(tenantId, policy = firstFitPolicy, opts) {
5535
5609
  super(tenantId, policy);
5536
- this.windowMs = windowMs;
5610
+ this.windowMs = windowMsOf(opts);
5537
5611
  }
5538
5612
  /**
5539
5613
  * 계약전력 — **현장이 선언한 자리 속성**에서 읽는다(`EMS_PROPERTY.contractKW`).
@@ -5554,6 +5628,45 @@ var EmsKernel = class extends FlowEngine {
5554
5628
  }
5555
5629
  return max;
5556
5630
  }
5631
+ /**
5632
+ * **뿌리 계량기** — 조상 중에 계량된 자리가 없는 계량 지점들.
5633
+ *
5634
+ * 계층 계량(수전 ⊃ 분기)에서 전부 더하면 이중 계상이 된다. 뿌리만 더하면 현장의 실제 부하가 된다.
5635
+ * 모델이 그 계량기의 자리를 모르면 뿌리로 본다(모르는 것을 빼면 부하가 조용히 작아진다 — 그것이
5636
+ * 「계약 안쪽」이라는 더 위험한 거짓을 만든다).
5637
+ */
5638
+ rootMeterIds() {
5639
+ const locOf = /* @__PURE__ */ new Map();
5640
+ for (const e of this.boardDef?.equipment ?? []) locOf.set(e.id, e.homeLocation);
5641
+ const parentOf = /* @__PURE__ */ new Map();
5642
+ for (const l of this.boardDef?.locations ?? []) parentOf.set(l.id, l.parentId);
5643
+ const meteredLocs = /* @__PURE__ */ new Set();
5644
+ for (const id of this.points.keys()) {
5645
+ const loc = locOf.get(id);
5646
+ if (loc) meteredLocs.add(loc);
5647
+ }
5648
+ const out = /* @__PURE__ */ new Set();
5649
+ for (const id of this.points.keys()) {
5650
+ const loc = locOf.get(id);
5651
+ if (!loc) {
5652
+ out.add(id);
5653
+ continue;
5654
+ }
5655
+ let cur = parentOf.get(loc);
5656
+ let root = true;
5657
+ const seen = /* @__PURE__ */ new Set([loc]);
5658
+ while (cur && !seen.has(cur)) {
5659
+ if (meteredLocs.has(cur)) {
5660
+ root = false;
5661
+ break;
5662
+ }
5663
+ seen.add(cur);
5664
+ cur = parentOf.get(cur);
5665
+ }
5666
+ if (root) out.add(id);
5667
+ }
5668
+ return out;
5669
+ }
5557
5670
  /**
5558
5671
  * 계측 표본을 받는다 — 에너지 사건만 가로채고 나머지는 그대로 상위에 넘긴다.
5559
5672
  *
@@ -5587,7 +5700,8 @@ var EmsKernel = class extends FlowEngine {
5587
5700
  this.points.set(id, point);
5588
5701
  const w = this.open;
5589
5702
  if (kW !== void 0) {
5590
- const total = [...this.points.values()].reduce((sum, p) => sum + (p.kW ?? 0), 0);
5703
+ const roots = this.rootMeterIds();
5704
+ const total = [...this.points.values()].reduce((sum, p) => sum + (roots.has(p.id) ? p.kW ?? 0 : 0), 0);
5591
5705
  if (w.maxKW === void 0 || total > w.maxKW) w.maxKW = total;
5592
5706
  w.samples++;
5593
5707
  w.meanKW = w.meanKW === void 0 ? total : (w.meanKW * (w.samples - 1) + total) / w.samples;
@@ -5733,6 +5847,211 @@ var EmsKernel = class extends FlowEngine {
5733
5847
  }
5734
5848
  };
5735
5849
 
5850
+ // src/energy-ingest.ts
5851
+ function isEnergyRecord(record) {
5852
+ if (!record || typeof record !== "object") return false;
5853
+ const r = record;
5854
+ return typeof r.meterId === "string" && r.meterId.trim().length > 0 && r.epc === void 0;
5855
+ }
5856
+ function ingestEnergyRecords(records, opts) {
5857
+ const arr = Array.isArray(records) ? records : records ? [records] : [];
5858
+ const accepted = [];
5859
+ const rejected = [];
5860
+ let seq = 0;
5861
+ for (const record of arr) {
5862
+ const errors = [];
5863
+ const meterId = String(record?.meterId ?? "").trim();
5864
+ if (!meterId) errors.push("meterId \uC5C6\uC74C \u2014 \uC5B4\uB514\uC758 \uC18C\uBE44\uC778\uC9C0 \uBAA8\uB974\uB294 \uAC12\uC740 \uB204\uC801\uD560 \uC218 \uC5C6\uB2E4");
5865
+ const at = String(record?.at ?? "").trim() || opts.defaultEventTime;
5866
+ const atMs = at ? Date.parse(at) : Number.NaN;
5867
+ if (!Number.isFinite(atMs)) errors.push("at \uC5C6\uC74C/\uD615\uC2DD \uC624\uB958 \u2014 \uC9C0\uAE08 \uC2DC\uAC01\uC73C\uB85C \uBA54\uC6B0\uBA74 \uB0A8\uC758 \uC218\uC694 \uAD6C\uAC04\uC5D0 \uC2E4\uB9B0\uB2E4");
5868
+ const num = (v, name) => {
5869
+ if (v === void 0 || v === null || v === "") return void 0;
5870
+ const n = Number(v);
5871
+ if (!Number.isFinite(n)) {
5872
+ errors.push(`${name} \uAC00 \uC218\uAC00 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
5873
+ return void 0;
5874
+ }
5875
+ return n;
5876
+ };
5877
+ const kW = num(record?.kW, "kW");
5878
+ const kWh = num(record?.kWh, "kWh");
5879
+ const powerFactor = num(record?.powerFactor, "powerFactor");
5880
+ if (errors.length) {
5881
+ rejected.push({ record, errors });
5882
+ continue;
5883
+ }
5884
+ const eventTime = new Date(atMs).toISOString();
5885
+ const data = {
5886
+ meterId,
5887
+ /* 계약은 `kW` 를 필수로 두지만 **못 읽은 표본도 사실**이다 — 그 경우 값을 비우고 보낸다.
5888
+ 커널이 「받았지만 부하를 못 읽었다」로 세고, 구간 마감에 그 이유를 싣는다. */
5889
+ ...kW !== void 0 ? { kW } : {},
5890
+ ...kWh !== void 0 ? { kWh } : {},
5891
+ ...powerFactor !== void 0 ? { powerFactor } : {},
5892
+ at: eventTime
5893
+ };
5894
+ accepted.push({
5895
+ eventId: `${opts.tenantId}-energy-${++seq}`,
5896
+ eventType: ENERGY_EVENT.measured,
5897
+ eventTime,
5898
+ tenantId: opts.tenantId,
5899
+ data
5900
+ });
5901
+ }
5902
+ return { accepted, rejected };
5903
+ }
5904
+
5905
+ // src/energy-attribution.ts
5906
+ var near = (a, b, eps = 1e-9) => Math.abs(a - b) <= eps;
5907
+ function attributeEnergy(opts) {
5908
+ const byId = new Map(opts.consumers.map((c) => [c.id, c]));
5909
+ const dedicated = /* @__PURE__ */ new Map();
5910
+ for (const c of opts.consumers) if (c.meterId) dedicated.set(c.meterId, c);
5911
+ const shares = [];
5912
+ const unattributed = [];
5913
+ const excluded = [];
5914
+ const overhead = [];
5915
+ for (const pool of opts.pools) {
5916
+ const kWh = Number(pool.kWh);
5917
+ if (!Number.isFinite(kWh)) continue;
5918
+ const own = dedicated.get(pool.meterId);
5919
+ if (own) {
5920
+ shares.push({ consumerId: own.id, kWh, basis: "measured", poolMeterId: pool.meterId });
5921
+ continue;
5922
+ }
5923
+ if (pool.overhead) {
5924
+ const alloc = opts.overheadAllocation;
5925
+ const targets = (alloc?.processConsumerIds ?? []).map((id) => byId.get(id)).filter((c) => !!c);
5926
+ const wOf = (c) => alloc?.weightKind === "equal" ? 1 : Number.isFinite(Number(c.weight)) && Number(c.weight) > 0 ? Number(c.weight) : 0;
5927
+ const sharing2 = alloc?.weightKind === "equal" ? targets : targets.filter((c) => wOf(c) > 0);
5928
+ const ws = sharing2.map(wOf);
5929
+ const total = ws.reduce((a, b) => a + b, 0);
5930
+ if (!alloc || total <= 0) {
5931
+ overhead.push({ poolMeterId: pool.meterId, kWh });
5932
+ continue;
5933
+ }
5934
+ let done = 0;
5935
+ sharing2.forEach((c, i) => {
5936
+ const share = ws[i] / total;
5937
+ const amount = i === sharing2.length - 1 ? kWh - done : kWh * share;
5938
+ done += amount;
5939
+ shares.push({
5940
+ consumerId: c.id,
5941
+ kWh: amount,
5942
+ basis: "apportioned",
5943
+ poolMeterId: pool.meterId,
5944
+ weightKind: alloc.weightKind,
5945
+ weightShare: share,
5946
+ overhead: true
5947
+ });
5948
+ });
5949
+ continue;
5950
+ }
5951
+ const covered = (pool.consumerIds ?? []).map((id) => byId.get(id)).filter((c) => !!c);
5952
+ if (!covered.length) {
5953
+ unattributed.push({ poolMeterId: pool.meterId, kWh, reason: "no-consumers" });
5954
+ continue;
5955
+ }
5956
+ if (covered.length === 1) {
5957
+ shares.push({ consumerId: covered[0].id, kWh, basis: "measured", poolMeterId: pool.meterId });
5958
+ continue;
5959
+ }
5960
+ const kind = opts.weightKind;
5961
+ const weightOf = (c) => kind === "equal" ? 1 : Number.isFinite(Number(c.weight)) && Number(c.weight) > 0 ? Number(c.weight) : 0;
5962
+ const sharing = kind === "equal" ? covered : covered.filter((c) => weightOf(c) > 0);
5963
+ if (kind && sharing.length < covered.length) {
5964
+ for (const c of covered) if (weightOf(c) <= 0) excluded.push({ consumerId: c.id, poolMeterId: pool.meterId, reason: "zero-weight" });
5965
+ }
5966
+ const weights = sharing.map(weightOf);
5967
+ const sum = weights.reduce((a, b) => a + b, 0);
5968
+ if (!kind || sum <= 0) {
5969
+ unattributed.push({
5970
+ poolMeterId: pool.meterId,
5971
+ kWh,
5972
+ reason: "no-weights",
5973
+ consumerIds: covered.map((c) => c.id)
5974
+ });
5975
+ continue;
5976
+ }
5977
+ let given = 0;
5978
+ sharing.forEach((c, i) => {
5979
+ const share = weights[i] / sum;
5980
+ const amount = i === sharing.length - 1 ? kWh - given : kWh * share;
5981
+ given += amount;
5982
+ shares.push({
5983
+ consumerId: c.id,
5984
+ kWh: amount,
5985
+ basis: "apportioned",
5986
+ poolMeterId: pool.meterId,
5987
+ weightKind: kind,
5988
+ weightShare: share
5989
+ });
5990
+ });
5991
+ }
5992
+ const measuredKWh = opts.pools.reduce((a, p) => a + (Number.isFinite(Number(p.kWh)) ? Number(p.kWh) : 0), 0);
5993
+ const attributedKWh = shares.reduce((a, s) => a + s.kWh, 0);
5994
+ const unattributedKWh = unattributed.reduce((a, u) => a + u.kWh, 0);
5995
+ const overheadKWh = overhead.reduce((a, o) => a + o.kWh, 0);
5996
+ if (!near(attributedKWh + unattributedKWh + overheadKWh, measuredKWh, 1e-6)) {
5997
+ throw new Error(
5998
+ `energy attribution lost or created energy: measured=${measuredKWh} attributed=${attributedKWh} unattributed=${unattributedKWh} overhead=${overheadKWh}`
5999
+ );
6000
+ }
6001
+ return { shares, unattributed, excluded, overhead, totals: { measuredKWh, attributedKWh, unattributedKWh, overheadKWh } };
6002
+ }
6003
+ var UNIT = {
6004
+ output: "kWh/unit",
6005
+ runtimeHours: "kW",
6006
+ area: "kWh/m2"
6007
+ };
6008
+ function energyIntensity(input) {
6009
+ const kWh = Number(input.kWh);
6010
+ if (!Number.isFinite(kWh)) return { value: null, reason: "no-energy" };
6011
+ const den = Number(input.denominator?.value);
6012
+ if (!Number.isFinite(den)) return { value: null, reason: "no-denominator" };
6013
+ const ew = input.energyWindow;
6014
+ const dw = input.denominator?.window;
6015
+ if (ew && dw && (ew.startMs !== dw.startMs || ew.endMs !== dw.endMs)) return { value: null, reason: "window-mismatch" };
6016
+ if (den === 0) return { value: null, reason: "zero-denominator" };
6017
+ const window = ew ?? dw;
6018
+ return {
6019
+ value: kWh / den,
6020
+ unit: UNIT[input.denominator.kind] ?? "kWh",
6021
+ kWh,
6022
+ denominator: den,
6023
+ ...window ? { window } : { window: { startMs: 0, endMs: 0 } }
6024
+ };
6025
+ }
6026
+ function energyOfWindows(windows, range) {
6027
+ const inRange = (w) => !range || w.startMs >= range.startMs && w.endMs <= range.endMs;
6028
+ let kWh = 0;
6029
+ let counted = 0;
6030
+ let skipped = 0;
6031
+ let first;
6032
+ let last;
6033
+ for (const w of windows ?? []) {
6034
+ if (!inRange(w)) continue;
6035
+ const mean = Number(w.meanKW);
6036
+ const hours = (Number(w.endMs) - Number(w.startMs)) / 36e5;
6037
+ if (!Number.isFinite(mean) || !Number.isFinite(hours) || hours <= 0) {
6038
+ skipped++;
6039
+ continue;
6040
+ }
6041
+ kWh += mean * hours;
6042
+ counted++;
6043
+ if (first === void 0 || w.startMs < first) first = w.startMs;
6044
+ if (last === void 0 || w.endMs > last) last = w.endMs;
6045
+ }
6046
+ return {
6047
+ ...counted > 0 ? { kWh } : {},
6048
+ basis: "mean-kw",
6049
+ counted,
6050
+ skipped,
6051
+ ...first !== void 0 && last !== void 0 ? { window: { startMs: first, endMs: last } } : {}
6052
+ };
6053
+ }
6054
+
5736
6055
  // src/vocabulary.ts
5737
6056
  var RETIRED_VOCABULARY = ["mover", "Mover", "MOVER", "node", "Node", "NODE"];
5738
6057
  var VOCABULARY_EXCEPTIONS = [
@@ -5835,6 +6154,7 @@ function retiredVocabularyIn(line) {
5835
6154
  activeShiftOf,
5836
6155
  aggregationEvent,
5837
6156
  analyzeCapacity,
6157
+ attributeEnergy,
5838
6158
  axesOfSystem,
5839
6159
  axisAppliesTo,
5840
6160
  axisInfo,
@@ -5852,6 +6172,8 @@ function retiredVocabularyIn(line) {
5852
6172
  documentPath,
5853
6173
  dueStatusOf,
5854
6174
  effectivityAt,
6175
+ energyIntensity,
6176
+ energyOfWindows,
5855
6177
  fefoPolicy,
5856
6178
  firstFitPolicy,
5857
6179
  foldJobResponses,
@@ -5862,6 +6184,8 @@ function retiredVocabularyIn(line) {
5862
6184
  inWorkCalendar,
5863
6185
  inWorkCalendarAt,
5864
6186
  ingest,
6187
+ ingestEnergyRecords,
6188
+ isEnergyRecord,
5865
6189
  isEquipmentLevel,
5866
6190
  isoDurationHours,
5867
6191
  itemKeyOf,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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": {