@operato/twin-kernel 0.7.90 → 0.8.1

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.
@@ -21,198 +21,49 @@ var index_exports = {};
21
21
  __export(index_exports, {
22
22
  ATTENTION_DEFAULTS: () => ATTENTION_DEFAULTS,
23
23
  ATTENTION_PROPERTY: () => ATTENTION_PROPERTY,
24
- BIZSTEP: () => BIZSTEP,
25
- BTT: () => BTT,
26
- BTT_DELIVERY: () => BTT_DELIVERY,
27
- BTT_PRODORDER: () => BTT_PRODORDER,
28
- CAPABILITIES: () => CAPABILITIES,
29
- CAPABILITY_KEYS: () => CAPABILITY_KEYS,
30
- CBV_BIZSTEP: () => CBV_BIZSTEP,
31
- CMD: () => CMD,
32
24
  ClassMasterTable: () => ClassMasterTable,
33
25
  DEMAND_WINDOW_MS: () => DEMAND_WINDOW_MS,
34
- DISP: () => DISP,
35
- DOMAIN_CATALOG: () => DOMAIN_CATALOG,
36
- DOMAIN_SYSTEMS: () => DOMAIN_SYSTEMS,
37
- EMS_EQUIPMENT_TYPES: () => EMS_EQUIPMENT_TYPES,
38
- EMS_LOCATION_TYPES: () => EMS_LOCATION_TYPES,
39
- EMS_PROPERTY: () => EMS_PROPERTY,
40
- EMS_PROPERTY_SPEC: () => EMS_PROPERTY_SPEC,
41
- EMS_TYPES: () => EMS_TYPES,
42
- ENERGY_EVENT: () => ENERGY_EVENT,
43
- EPCIS_CONTEXT: () => EPCIS_CONTEXT,
44
- EPCIS_EVENT: () => EPCIS_EVENT,
45
- EQUIPMENT_LEVEL: () => EQUIPMENT_LEVEL,
46
26
  EmsKernel: () => EmsKernel,
47
27
  EventJournal: () => EventJournal,
48
28
  FlowEngine: () => FlowEngine,
49
- GUARD_PRAGMA: () => GUARD_PRAGMA,
50
- ILMD_ATTR: () => ILMD_ATTR,
51
29
  ItemStore: () => ItemStore,
52
- LOCATION_SATURATION_NEAR: () => LOCATION_SATURATION_NEAR,
53
- MATERIAL_PROPERTY: () => MATERIAL_PROPERTY,
54
- MES_BIZSTEP: () => MES_BIZSTEP,
55
- MES_LOCATION_TYPES: () => MES_LOCATION_TYPES,
56
- MES_TYPES: () => MES_TYPES,
57
- METER_DIRECTION: () => METER_DIRECTION,
58
30
  MesKernel: () => MesKernel,
59
- OBSERVATION_BASIS: () => OBSERVATION_BASIS,
60
31
  OPERATION_PROPERTY: () => OPERATION_PROPERTY,
61
- OP_EVENT: () => OP_EVENT,
62
- OP_PARAM: () => OP_PARAM,
63
- ORDER_TERMINAL_STATUS: () => ORDER_TERMINAL_STATUS,
64
32
  ObservedReducer: () => ObservedReducer,
65
- PRIORITY_UNSET: () => PRIORITY_UNSET,
66
- RETIRED_VOCABULARY: () => RETIRED_VOCABULARY,
67
33
  StateProjector: () => ObservedReducer,
68
- TWIN_AXES: () => TWIN_AXES,
69
- TWIN_PROPERTIES: () => TWIN_PROPERTIES,
70
- TWIN_RELATIONS: () => TWIN_RELATIONS,
71
34
  TwinHistory: () => TwinHistory,
72
35
  TwinObserver: () => TwinObserver,
73
36
  TwinRuntime: () => TwinRuntime,
74
- UTC_OFFSET: () => UTC_OFFSET,
75
- VOCABULARY_EXCEPTIONS: () => VOCABULARY_EXCEPTIONS,
76
- VOCABULARY_TYPE: () => VOCABULARY_TYPE,
77
- WMS_LOCATION_TYPES: () => WMS_LOCATION_TYPES,
78
- WMS_TYPES: () => WMS_TYPES,
79
37
  WmsKernel: () => WmsKernel,
80
- YARD_BIZSTEP: () => YARD_BIZSTEP,
81
- YMS_LOCATION_TYPES: () => YMS_LOCATION_TYPES,
82
- YMS_TYPES: () => YMS_TYPES,
83
38
  YmsKernel: () => YmsKernel,
84
- activeShiftAt: () => activeShiftAt,
85
- activeShiftOf: () => activeShiftOf,
86
- aggregationEvent: () => aggregationEvent,
87
- analyzeCapacity: () => analyzeCapacity,
88
39
  attributeEnergy: () => attributeEnergy,
89
- axesOfSystem: () => axesOfSystem,
90
- axisAppliesTo: () => axisAppliesTo,
91
- axisInfo: () => axisInfo,
92
- axisSource: () => axisSource,
93
- bizTransactionUri: () => bizTransactionUri,
94
- capabilitiesForType: () => capabilitiesForType,
95
- capabilityOf: () => capabilityOf,
96
- classClosure: () => classClosure,
97
- classIdentifierViolation: () => classIdentifierViolation,
98
- commandsOf: () => commandsOf,
99
40
  compareStates: () => compareStates,
100
41
  computeOee: () => computeOee,
101
42
  constantDuration: () => constantDuration,
102
- conversionFactorOf: () => conversionFactorOf,
103
43
  counterfactualAt: () => counterfactualAt,
104
- criterionSaysNothing: () => criterionSaysNothing,
105
44
  demandWindowStart: () => demandWindowStart,
106
45
  deriveAttentions: () => deriveAttentions,
107
- documentPath: () => documentPath,
108
- dueStatusOf: () => dueStatusOf,
109
- effectivityAt: () => effectivityAt,
110
- electricalUpstreamOf: () => electricalUpstreamOf,
111
46
  electricityCost: () => electricityCost,
112
47
  energyIntensity: () => energyIntensity,
113
48
  energyOfWindows: () => energyOfWindows,
114
- expiryFromAttributes: () => expiryFromAttributes,
115
49
  fefoPolicy: () => fefoPolicy,
116
50
  firstFitPolicy: () => firstFitPolicy,
117
51
  foldJobResponses: () => foldJobResponses,
118
52
  foldTaskRecords: () => foldTaskRecords,
119
- gdtiUri: () => gdtiUri,
120
- generationFractionAt: () => generationFractionAt,
121
- graiUri: () => graiUri,
122
- gs1KeyDigitViolation: () => gs1KeyDigitViolation,
123
- hierarchyOf: () => hierarchyOf,
124
- identityGroundingOf: () => identityGroundingOf,
125
- inWorkCalendar: () => inWorkCalendar,
126
- inWorkCalendarAt: () => inWorkCalendarAt,
127
- ingest: () => ingest,
128
- ingestEnergyBillRecords: () => ingestEnergyBillRecords,
129
- ingestEnergyEquipmentRecords: () => ingestEnergyEquipmentRecords,
130
- ingestEnergyGenerationPeriodRecords: () => ingestEnergyGenerationPeriodRecords,
131
- ingestEnergyGenerationPriceRecords: () => ingestEnergyGenerationPriceRecords,
132
- ingestEnergyGenerationRecords: () => ingestEnergyGenerationRecords,
133
- ingestEnergyRecords: () => ingestEnergyRecords,
134
- ingestEnergyTariffBasisRecords: () => ingestEnergyTariffBasisRecords,
135
- ingestEnergyUsagePeriodRecords: () => ingestEnergyUsagePeriodRecords,
136
- ingestMasterData: () => ingestMasterData,
137
- ingestOperationalRecords: () => ingestOperationalRecords,
138
- isAggregationRecord: () => isAggregationRecord,
139
- isElectricalLocationType: () => isElectricalLocationType,
140
- isEnergyBillRecord: () => isEnergyBillRecord,
141
- isEnergyEquipmentRecord: () => isEnergyEquipmentRecord,
142
- isEnergyGenerationPeriodRecord: () => isEnergyGenerationPeriodRecord,
143
- isEnergyGenerationPriceRecord: () => isEnergyGenerationPriceRecord,
144
- isEnergyGenerationRecord: () => isEnergyGenerationRecord,
145
- isEnergyRecord: () => isEnergyRecord,
146
- isEnergyTariffBasisRecord: () => isEnergyTariffBasisRecord,
147
- isEnergyUsagePeriodRecord: () => isEnergyUsagePeriodRecord,
148
- isEpcisEventType: () => isEpcisEventType,
149
- isEquipmentLevel: () => isEquipmentLevel,
150
- isMasterDataRecord: () => isMasterDataRecord,
151
- isOperationalRecord: () => isOperationalRecord,
152
- isOrderTerminal: () => isOrderTerminal,
153
- isTransformationRecord: () => isTransformationRecord,
154
- isoDurationHours: () => isoDurationHours,
155
- itemKeyOf: () => itemKeyOf,
156
53
  journalProjector: () => journalProjector,
157
- judgeAgainstSpec: () => judgeAgainstSpec,
158
- levelOfLocationType: () => levelOfLocationType,
159
- lgtinClass: () => lgtinClass,
160
- localDayIndexAt: () => localDayIndexAt,
161
- localDayStartMs: () => localDayStartMs,
162
- locationStatusOf: () => locationStatusOf,
163
- lotFromAttributes: () => lotFromAttributes,
164
- mapRecord: () => mapRecord,
165
- mapRecordChecked: () => mapRecordChecked,
166
- meetsTests: () => meetsTests,
167
- minuteOfDayAt: () => minuteOfDayAt,
168
54
  monteCarloForecast: () => monteCarloForecast,
169
55
  monteCarloForecastAsync: () => monteCarloForecastAsync,
170
- objectEvent: () => objectEvent,
171
- objectUri: () => objectUri,
172
- observationAt: () => observationAt,
173
- offCalendarAt: () => offCalendarAt,
174
- offCalendarReasonAt: () => offCalendarReasonAt,
175
- operationalKindOf: () => operationalKindOf,
176
- operationsCapabilityOf: () => operationsCapabilityOf,
177
- outsideLimit: () => outsideLimit,
178
- parseEpc: () => parseEpc,
179
- parseIsoDuration: () => parseIsoDuration,
180
56
  partialFitPolicy: () => partialFitPolicy,
181
- periodFactId: () => periodFactId,
182
- priorityRank: () => priorityRank,
183
- propertiesOf: () => propertiesOf,
184
- quantityIn: () => quantityIn,
185
- readBoardAssets: () => readBoardAssets,
186
- readBoardEquipment: () => readBoardEquipment,
187
- readBoardLocations: () => readBoardLocations,
188
- relationsFrom: () => relationsFrom,
189
- relationsTo: () => relationsTo,
57
+ planMakeToOrder: () => planMakeToOrder,
58
+ producedByIndex: () => producedByIndex,
190
59
  replay: () => replay,
191
60
  replayFrom: () => replayFrom,
192
61
  replaySegments: () => replaySegments,
193
- replayWithCheckpoint: () => replayWithCheckpoint,
194
- requiredTestsFor: () => requiredTestsFor,
195
- resolveSubject: () => resolveSubject,
196
- retiredVocabularyIn: () => retiredVocabularyIn,
197
- sgtinClass: () => sgtinClass,
198
- sgtinUri: () => sgtinUri,
199
- ssccUri: () => ssccUri,
200
- stateFieldsOf: () => stateFieldsOf,
201
- subLotIdOf: () => subLotIdOf,
202
- testEvidenceGaps: () => testEvidenceGaps,
203
- testPassedAt: () => testPassedAt,
204
- transactionEvent: () => transactionEvent,
205
- transformationEvent: () => transformationEvent,
206
- validateDomainDefinition: () => validateDomainDefinition,
207
- validateEpcisEvent: () => validateEpcisEvent,
208
- validateScenario: () => validateScenario,
209
- weekdayAt: () => weekdayAt,
210
- workingHoursBetween: () => workingHoursBetween,
211
- workingTimeOfWeek: () => workingTimeOfWeek
62
+ replayWithCheckpoint: () => replayWithCheckpoint
212
63
  });
213
64
  module.exports = __toCommonJS(index_exports);
214
65
 
215
- // ../operations-contract/dist/capability.js
66
+ // ../ops-contract/dist/capability.js
216
67
  var CAPABILITIES = {
217
68
  operable: {
218
69
  key: "operable",
@@ -339,7 +190,6 @@ var CAPABILITIES = {
339
190
  results: ["stored", "discharged"]
340
191
  }
341
192
  };
342
- var CAPABILITY_KEYS = ["operable", "storable", "mobile", "processable", "trackable", "switching", "metered", "curtailable", "energyGenerating", "energyStoring"];
343
193
  function stateFieldsOf(caps) {
344
194
  const out = /* @__PURE__ */ new Set();
345
195
  for (const c of caps)
@@ -347,15 +197,8 @@ function stateFieldsOf(caps) {
347
197
  out.add(f);
348
198
  return [...out];
349
199
  }
350
- function commandsOf(caps) {
351
- const out = /* @__PURE__ */ new Set();
352
- for (const c of caps)
353
- for (const cmd of CAPABILITIES[c]?.commands ?? [])
354
- out.add(cmd);
355
- return [...out];
356
- }
357
200
 
358
- // ../operations-contract/dist/contract.js
201
+ // ../ops-contract/dist/contract.js
359
202
  var ORDER_TERMINAL_STATUS = ["completed", "cancelled"];
360
203
  function isOrderTerminal(o) {
361
204
  const status = String(o?.status ?? "").trim().toLowerCase();
@@ -375,92 +218,6 @@ function locationStatusOf(n) {
375
218
  const r = (n.occupancy ?? 0) / cap;
376
219
  return r >= 1 ? "full" : r >= LOCATION_SATURATION_NEAR ? "near-full" : "available";
377
220
  }
378
- var EQUIPMENT_LEVEL = [
379
- "Enterprise",
380
- "Site",
381
- "Area",
382
- "ProcessCell",
383
- "Unit",
384
- "ProductionLine",
385
- "WorkCell",
386
- "ProductionUnit",
387
- "StorageZone",
388
- "StorageUnit",
389
- "WorkCenter",
390
- "WorkUnit",
391
- "EquipmentModule",
392
- "ControlModule",
393
- "Other"
394
- ];
395
- function isEquipmentLevel(v) {
396
- return typeof v === "string" && EQUIPMENT_LEVEL.includes(v);
397
- }
398
- function hierarchyOf(s, levelOfType) {
399
- const push = (m, k, v) => {
400
- const cur = m.get(k);
401
- if (cur)
402
- cur.push(v);
403
- else
404
- m.set(k, [v]);
405
- };
406
- const parent = /* @__PURE__ */ new Map();
407
- const kids = /* @__PURE__ */ new Map();
408
- const known = new Set(s.locations.map((n) => n.id));
409
- for (const n of s.locations) {
410
- if (!n.parentId || n.parentId === n.id)
411
- continue;
412
- parent.set(n.id, n.parentId);
413
- if (known.has(n.parentId))
414
- push(kids, n.parentId, n.id);
415
- }
416
- for (const start of known) {
417
- const seen = [start];
418
- for (let at = parent.get(start); at !== void 0; at = parent.get(at)) {
419
- if (seen.includes(at))
420
- throw new Error(`location hierarchy has a cycle: ${[...seen, at].join(" \u2192 ")}`);
421
- seen.push(at);
422
- if (!known.has(at))
423
- break;
424
- }
425
- }
426
- const homes = /* @__PURE__ */ new Map();
427
- for (const m of s.equipment ?? [])
428
- if (m.homeLocation)
429
- push(homes, m.homeLocation, m.id);
430
- const ancestorsOf = (id) => {
431
- const out = [];
432
- for (let at = parent.get(id); at !== void 0; at = parent.get(at)) {
433
- out.push(at);
434
- if (!known.has(at))
435
- break;
436
- }
437
- return out;
438
- };
439
- const descendantsOf = (id) => {
440
- const out = [];
441
- const stack = [...kids.get(id) ?? []];
442
- while (stack.length) {
443
- const cur = stack.pop();
444
- out.push(cur);
445
- stack.push(...kids.get(cur) ?? []);
446
- }
447
- return out;
448
- };
449
- const typeOf = new Map(s.locations.map((n) => [n.id, n.type]));
450
- const levelOf = new Map(s.locations.map((n) => [n.id, n.level ?? (n.type ? levelOfType?.(n.type) : void 0)]));
451
- return {
452
- childrenOf: (id) => [...kids.get(id) ?? []],
453
- ancestorsOf,
454
- descendantsOf,
455
- rollupOf: (id) => ancestorsOf(id).find((a) => !known.has(a)),
456
- ancestorOfLevel: (id, level) => ancestorsOf(id).find((a) => known.has(a) && levelOf.get(a) === level),
457
- ancestorOfType: (id, type) => ancestorsOf(id).find((a) => known.has(a) && typeOf.get(a) === type),
458
- equipmentOf: (id, opts) => {
459
- const scope = opts?.deep ? [id, ...descendantsOf(id)] : [id];
460
- return scope.flatMap((n) => homes.get(n) ?? []);
461
- }
462
- };
463
- }
464
221
  function testPassedAt(r, at) {
465
222
  if (r.result !== "pass")
466
223
  return false;
@@ -511,16 +268,6 @@ function outsideLimit(criterion, observed) {
511
268
  return true;
512
269
  return false;
513
270
  }
514
- function testEvidenceGaps(spec, result) {
515
- const criteria = spec?.criteria ?? [];
516
- const measurements = result?.propertyMeasurements ?? [];
517
- const wanted = new Set(criteria.map((c) => c.evaluatedPropertyId).filter((k) => !!k));
518
- const got = new Set(measurements.map((m) => m.testableObjectPropertyId).filter((k) => !!k));
519
- return {
520
- unmeasured: criteria.filter((c) => c.evaluatedPropertyId && !got.has(c.evaluatedPropertyId)).map((c) => c.id),
521
- unmatched: measurements.map((m) => m.testableObjectPropertyId).filter((k) => !!k && !wanted.has(k))
522
- };
523
- }
524
271
  function judgeAgainstSpec(spec, result) {
525
272
  const criteria = (spec?.criteria ?? []).filter((c) => !criterionSaysNothing(c));
526
273
  if (!criteria.length)
@@ -613,41 +360,6 @@ function subLotIdOf(classUri, location) {
613
360
  function itemKeyOf(item) {
614
361
  return item.subLotId ?? item.epc;
615
362
  }
616
- function quantityIn(item, uom, definitions) {
617
- const hit = item.quantities?.find((q) => (q.uom ?? void 0) === (uom ?? void 0));
618
- if (hit)
619
- return hit.value;
620
- if ((item.uom ?? void 0) === (uom ?? void 0))
621
- return item.qty;
622
- return convertQuantity(item, uom, definitions);
623
- }
624
- var MATERIAL_PROPERTY = {
625
- /** 기준 단위 하나당 이 단위의 양. `uom` 이 대상 단위. */
626
- perBaseUnit: "perBaseUnit"
627
- };
628
- function conversionFactorOf(def, uom) {
629
- if (!def || !uom)
630
- return void 0;
631
- for (const p of def.properties ?? []) {
632
- if (p.id !== MATERIAL_PROPERTY.perBaseUnit || p.uom !== uom)
633
- continue;
634
- const n = typeof p.value === "number" ? p.value : Number(p.value);
635
- if (Number.isFinite(n) && n > 0)
636
- return n;
637
- }
638
- return void 0;
639
- }
640
- function convertQuantity(item, uom, definitions) {
641
- if (!definitions || !uom)
642
- return void 0;
643
- if (typeof item.qty !== "number" || !Number.isFinite(item.qty))
644
- return void 0;
645
- if (item.uom)
646
- return void 0;
647
- const def = definitions.get(item.definitionId ?? item.gtin ?? "");
648
- const factor = conversionFactorOf(def, uom);
649
- return factor === void 0 ? void 0 : item.qty * factor;
650
- }
651
363
  function minutesOfDay(hhmm) {
652
364
  const m = /^(\d{1,2}):(\d{2})$/.exec(hhmm ?? "");
653
365
  if (!m)
@@ -696,9 +408,6 @@ function coversMinute(e, minuteOfDay) {
696
408
  return false;
697
409
  return from <= to ? minuteOfDay >= from && minuteOfDay < to : minuteOfDay >= from || minuteOfDay < to;
698
410
  }
699
- function inWorkCalendar(entries, minuteOfDay) {
700
- return judgeCalendar(entries, (e) => coversMinute(e, minuteOfDay), false);
701
- }
702
411
  function inWorkCalendarAt(entries, atMs, utcOffsetMinutes) {
703
412
  const minute = minuteOfDayAt(atMs, utcOffsetMinutes);
704
413
  const day = weekdayAt(atMs, utcOffsetMinutes);
@@ -725,19 +434,6 @@ function judgeCalendar(entries, covers, withAbsolute) {
725
434
  return true;
726
435
  return working.some(covers);
727
436
  }
728
- function activeShiftOf(entries, minuteOfDay) {
729
- if (!entries?.length)
730
- return void 0;
731
- if (!inWorkCalendar(entries, minuteOfDay))
732
- return void 0;
733
- for (const e of entries) {
734
- if (needsDate(e) || (e.entryType ?? "working") !== "working" || !e.id)
735
- continue;
736
- if (coversMinute(e, minuteOfDay))
737
- return e.id;
738
- }
739
- return void 0;
740
- }
741
437
  function activeShiftAt(entries, atMs, utcOffsetMinutes) {
742
438
  if (!entries?.length)
743
439
  return void 0;
@@ -838,30 +534,6 @@ function capabilityOf(r, ctx) {
838
534
  return { available: false, reason: "working" };
839
535
  return { available: true, reason: "available" };
840
536
  }
841
- function observationAt(observations, propertyId, at) {
842
- const t = parsedMs(at);
843
- if (!(t >= 0))
844
- return void 0;
845
- let best;
846
- let bestStart = -1;
847
- for (const o of observations ?? []) {
848
- if (o.propertyId !== propertyId)
849
- continue;
850
- const from = parsedMs(o.effectiveTime);
851
- if (!(from >= 0) || from > t)
852
- continue;
853
- if (o.effectiveEndTime) {
854
- const to = parsedMs(o.effectiveEndTime);
855
- if (to >= 0 && to < t)
856
- continue;
857
- }
858
- if (from > bestStart) {
859
- best = o;
860
- bestStart = from;
861
- }
862
- }
863
- return best;
864
- }
865
537
  var OP_EVENT = {
866
538
  task: "task.status",
867
539
  equipment: "equipment.status",
@@ -885,6 +557,24 @@ var OP_EVENT = {
885
557
  * 없고, 미러가 이어받지 못한다(§상태 ⊆ 이벤트).
886
558
  */
887
559
  test: "test.result",
560
+ /**
561
+ * **부적합 처분** — 재고 판정을 받은 것을 어떻게 하기로 정했나(재작업 · 특채 · 폐기 · 반품).
562
+ *
563
+ * ── 판정과 다른 사실이다 (2026-08-30) ─────────────────────────────────────
564
+ * `test.result` 에 필드로 붙이지 않는다. 셋 중 둘을 표현할 수 없게 되기 때문이다.
565
+ *
566
+ * 한 판정에 처분이 여럿 일부는 재작업하고 일부는 폐기한다
567
+ * 처분 없는 판정 기록만 하고 결정은 나중에 한다
568
+ * 판정 없는 처분 현장 재량으로 뺀다
569
+ *
570
+ * ── 왜 커널이 아는가 ──────────────────────────────────────────────────────
571
+ * 처분은 **자원에 효과를 준다.** 재작업은 자재를 다시 공정에 넣고, 폐기는 재고에서 뺀다. 효과를 주는
572
+ * 사실은 커널 1급이다.
573
+ *
574
+ * EPCIS 의 `disposition` 과 헷갈리지 않는다 — 그것은 **개체의 상태**(`damaged`·`destroyed`)이고
575
+ * 이것은 **결정**이다. 누가·언제·왜 그렇게 정했는지의 자리는 그쪽에 없다.
576
+ */
577
+ disposition: "nonconformance.disposition",
888
578
  /**
889
579
  * **이 목록이 전부다** — 연결된 시스템이 현재 목록을 한 바퀴 다 보낸 뒤 그것을 알린다.
890
580
  *
@@ -1029,7 +719,6 @@ var ENERGY_EVENT = {
1029
719
  */
1030
720
  drSuggested: "energy.dr.suggested"
1031
721
  };
1032
- var OBSERVATION_BASIS = ["metered", "provider", "billed", "estimated"];
1033
722
  var CMD = {
1034
723
  orderHold: "order.hold",
1035
724
  orderResume: "order.resume",
@@ -1090,7 +779,7 @@ function gs1Namespaces(prefix) {
1090
779
  return [`urn:epc:idpat:sgtin:${prefix}.`, `urn:epc:class:lgtin:${prefix}.`, `urn:epc:id:sgtin:${prefix}.`];
1091
780
  }
1092
781
 
1093
- // ../operations-contract/dist/capacity.js
782
+ // ../ops-contract/dist/capacity.js
1094
783
  function isoDurationHours(iso2) {
1095
784
  if (!iso2)
1096
785
  return 0;
@@ -1198,7 +887,7 @@ function analyzeCapacity(input) {
1198
887
  };
1199
888
  }
1200
889
 
1201
- // ../operations-contract/dist/wms-profile.js
890
+ // ../ops-contract/dist/wms-profile.js
1202
891
  var BIZSTEP = {
1203
892
  receiving: "urn:epcglobal:cbv:bizstep:receiving",
1204
893
  storing: "urn:epcglobal:cbv:bizstep:storing",
@@ -1228,7 +917,7 @@ var WMS_TYPES = [
1228
917
  { key: "packer", role: "equipment", label: "twin.type.packer", standardClass: { epcis: "object", isa95: "Equipment" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["processable", "operable"] }
1229
918
  ];
1230
919
 
1231
- // ../operations-contract/dist/yms-profile.js
920
+ // ../ops-contract/dist/yms-profile.js
1232
921
  var YARD_BIZSTEP = {
1233
922
  arriving: "urn:epcglobal:cbv:bizstep:arriving",
1234
923
  // 게이트-인
@@ -1242,9 +931,6 @@ var YARD_BIZSTEP = {
1242
931
  // 게이트-아웃
1243
932
  };
1244
933
  var BTT_DELIVERY = "urn:epcglobal:cbv:btt:deliv";
1245
- function graiUri(companyPrefix, assetType, serial) {
1246
- return `urn:epc:id:grai:${companyPrefix}.${assetType}.${String(serial).padStart(6, "0")}`;
1247
- }
1248
934
  var YMS_LOCATION_TYPES = ["gate", "yard-slot", "dock-door", "staging"];
1249
935
  var YMS_LOCATION_LEVEL = {
1250
936
  gate: "Other",
@@ -1257,7 +943,7 @@ var YMS_TYPES = [
1257
943
  { key: "hostler", role: "equipment", label: "twin.type.hostler", standardClass: { epcis: "object", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["mobile", "operable"] }
1258
944
  ];
1259
945
 
1260
- // ../operations-contract/dist/mes-profile.js
946
+ // ../ops-contract/dist/mes-profile.js
1261
947
  var MES_BIZSTEP = {
1262
948
  receiving: "urn:epcglobal:cbv:bizstep:receiving",
1263
949
  // 원자재 수령
@@ -1295,10 +981,9 @@ var MES_TYPES = [
1295
981
  { key: "assembler", role: "equipment", label: "twin.type.assembler", standardClass: { isa95: "Equipment", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["processable", "operable"] }
1296
982
  ];
1297
983
 
1298
- // ../operations-contract/dist/ems-profile.js
984
+ // ../ops-contract/dist/ems-profile.js
1299
985
  var EMS_LOCATION_TYPES = ["incoming", "feeder", "submeter-zone"];
1300
986
  var METER_DIRECTION = ["import", "export", "bidirectional"];
1301
- var EMS_EQUIPMENT_TYPES = ["meter", "breaker", "pv-array", "battery", "utility", "curtailable-load"];
1302
987
  var EMS_PROPERTY = {
1303
988
  /** 계약전력(kW) — 수전·분기 자리에 선언한다. 없으면 계약 대비 판정을 하지 않는다. */
1304
989
  contractKW: "contract.kW",
@@ -1617,7 +1302,7 @@ function isElectricalLocationType(type) {
1617
1302
  return EMS_LOCATION_TYPES.includes(type);
1618
1303
  }
1619
1304
 
1620
- // ../operations-contract/dist/domain-catalog.js
1305
+ // ../ops-contract/dist/domain-catalog.js
1621
1306
  var locationKeys = (types) => types.filter((t) => t.role === "location").map((t) => t.key);
1622
1307
  var DOMAIN_CATALOG = {
1623
1308
  // label 은 언어 중립 i18n 키(twin.system.<code>) — 사람 언어는 표현계층이 렌더(L2).
@@ -1626,397 +1311,8 @@ var DOMAIN_CATALOG = {
1626
1311
  mes: { system: "mes", label: "twin.system.mes", types: MES_TYPES, locationTypes: locationKeys(MES_TYPES) },
1627
1312
  ems: { system: "ems", label: "twin.system.ems", types: EMS_TYPES, locationTypes: locationKeys(EMS_TYPES) }
1628
1313
  };
1629
- var LOGISTICS = ["wms", "yms", "mes"];
1630
- var TWIN_AXES = [
1631
- {
1632
- axis: "locations",
1633
- path: "locations",
1634
- label: "twin.axis.locations",
1635
- kind: "instance",
1636
- typeRole: "location",
1637
- standardClass: { epcis: "SGLN" }
1638
- },
1639
- {
1640
- axis: "equipment",
1641
- path: "equipment",
1642
- label: "twin.axis.equipment",
1643
- kind: "instance",
1644
- typeRole: "equipment",
1645
- standardClass: { isa95: "Equipment", iso55000: "Asset", epcis: "GIAI" }
1646
- },
1647
- {
1648
- axis: "persons",
1649
- path: "persons",
1650
- label: "twin.axis.persons",
1651
- kind: "instance",
1652
- standardClass: { isa95: "Person" },
1653
- systems: LOGISTICS
1654
- },
1655
- {
1656
- axis: "assets",
1657
- path: "assets",
1658
- label: "twin.axis.assets",
1659
- kind: "instance",
1660
- standardClass: { isa95: "PhysicalAsset", iso55000: "Asset" }
1661
- },
1662
- {
1663
- axis: "personnelClasses",
1664
- path: "personnelClasses",
1665
- label: "twin.axis.personnelClasses",
1666
- kind: "class",
1667
- standardClass: { isa95: "PersonnelClass" },
1668
- systems: LOGISTICS
1669
- },
1670
- {
1671
- axis: "equipmentClasses",
1672
- path: "equipmentClasses",
1673
- label: "twin.axis.equipmentClasses",
1674
- kind: "class",
1675
- standardClass: { isa95: "EquipmentClass" }
1676
- },
1677
- {
1678
- axis: "assetClasses",
1679
- path: "assetClasses",
1680
- label: "twin.axis.assetClasses",
1681
- kind: "class",
1682
- standardClass: { isa95: "PhysicalAssetClass" }
1683
- },
1684
- {
1685
- axis: "materialDefinitions",
1686
- path: "materialDefinitions",
1687
- label: "twin.axis.materialDefinitions",
1688
- kind: "class",
1689
- standardClass: { isa95: "MaterialDefinition" },
1690
- systems: LOGISTICS
1691
- },
1692
- {
1693
- axis: "materialClasses",
1694
- path: "materialClasses",
1695
- label: "twin.axis.materialClasses",
1696
- kind: "class",
1697
- standardClass: { isa95: "MaterialClass" },
1698
- systems: LOGISTICS
1699
- },
1700
- {
1701
- axis: "operations",
1702
- path: "operations",
1703
- label: "twin.axis.operations",
1704
- kind: "spec",
1705
- standardClass: { isa95: "OperationsSegment" },
1706
- systems: LOGISTICS
1707
- },
1708
- {
1709
- axis: "productionSpec",
1710
- path: "productionSpec",
1711
- label: "twin.axis.productionSpec",
1712
- kind: "spec",
1713
- standardClass: { isa95: "OperationsSegment" },
1714
- systems: LOGISTICS
1715
- },
1716
- /*
1717
- * 아래 셋은 저장상 `productionSpec.definition` 안에 있지만 **개념으로는 1급**이다.
1718
- * `materials` 가 최상위 `materialDefinitions` 와 **둘 다** 있는 것은 지금 상태 그대로다 —
1719
- * 전자는 도메인 정의의 가벼운 목록(`{key,label}`), 후자는 ISA-95 풍부형이다. 같은 것의 집이
1720
- * 둘이라는 사실을 **선언이 드러낸다**(감추면 화면이 어느 쪽을 보는지 아무도 모른다).
1721
- */
1722
- {
1723
- axis: "recipes",
1724
- path: "productionSpec.definition.recipes",
1725
- label: "twin.axis.recipes",
1726
- kind: "spec",
1727
- standardClass: { isa95: "OperationsSegment" },
1728
- systems: LOGISTICS
1729
- },
1730
- {
1731
- axis: "routes",
1732
- path: "productionSpec.definition.routes",
1733
- label: "twin.axis.routes",
1734
- kind: "spec",
1735
- standardClass: { isa95: "OperationsSegment" },
1736
- systems: LOGISTICS
1737
- },
1738
- {
1739
- axis: "materials",
1740
- path: "productionSpec.definition.materials",
1741
- label: "twin.axis.materials",
1742
- kind: "class",
1743
- standardClass: { isa95: "MaterialDefinition" },
1744
- systems: LOGISTICS
1745
- },
1746
- /*
1747
- * 시험 명세 — **자원 아홉 종류가 가리키는 대상.**
1748
- *
1749
- * 이 축이 없던 동안 `testSpecificationIds` 는 허공을 가리켰다(값은 실리는데 무엇인지 아무도 모른다).
1750
- * 축이 서면 인스펙션의 선 채움이 **끊어진 참조를 센다** — 없는 명세를 가리키면 화면이 말한다.
1751
- *
1752
- * `kind: 'spec'` 이다: 자원도 등급도 아니고 **판정의 정의**다(공정·레시피와 같은 부류).
1753
- */
1754
- {
1755
- axis: "testSpecifications",
1756
- path: "testSpecifications",
1757
- label: "twin.axis.testSpecifications",
1758
- kind: "spec",
1759
- standardClass: { isa95: "TestSpecification" }
1760
- },
1761
- /*
1762
- * ── ISA-95 Part 4 — 일정과 실적 ────────────────────────────────────────────────
1763
- *
1764
- * 여기부터는 **선언이 아니다.** 오더와 작업은 사람이 board 에 적어 두는 값이 아니라 현장이 낳는
1765
- * 사실이고, 그 집은 커널 상태와 저널이다. 그래서 `source` 가 `'state'` 이고 `path` 가 없다.
1766
- *
1767
- * ── 왜 축이어야 하나 ──────────────────────────────────────────────────────────
1768
- * 이 둘이 축이 아니던 동안 "이 트윈이 표준의 어디를 채우나" 라는 질문은 **절반만** 답했다. Part 2
1769
- * 자원 모델은 여덟이 완전한데 Part 4 는 통째로 비어 있었다 — 런타임에는 실재하는데 조회 모델에는
1770
- * 없었기 때문이다. 실적을 성과 화면이 답한다는 것은 사실이지만, 그건 **집계**이고 개념 자체를
1771
- * 가리켜 걸어 들어갈 자리는 없었다(어느 오더가 어느 공정에서 무엇을 먹었나).
1772
- *
1773
- * ── 왜 테이블로 투영하지 않나 ─────────────────────────────────────────────────
1774
- * 정본이 저널이다. 구조 엔티티는 **원본이 낼 수 있는 것만** 담고(ADR-0032-D), 오더는 원본이 낸
1775
- * 것이지만 트윈이 **관측한** 것이다. 투영은 규모에서 성능이 필요해질 때 KPI 폴드와 같은 급의
1776
- * 별도 결정으로 한다.
1777
- */
1778
- {
1779
- axis: "orders",
1780
- label: "twin.axis.orders",
1781
- kind: "instance",
1782
- source: "state",
1783
- historical: true,
1784
- standardClass: { isa95: "OperationsRequest", epcis: "TransactionEvent" },
1785
- systems: LOGISTICS
1786
- },
1787
- {
1788
- axis: "tasks",
1789
- label: "twin.axis.tasks",
1790
- kind: "instance",
1791
- source: "state",
1792
- historical: true,
1793
- standardClass: { isa95: "SegmentResponse", epcis: "TransformationEvent" },
1794
- systems: LOGISTICS
1795
- },
1796
- /*
1797
- * ── 물품이 축이 아니었다 (2026-08-22) ──────────────────────────────────────
1798
- * 트윈에서 **수가 가장 많은 것**이 물품인데(실측: 엔티티 3,611 중 2,400 · hatio-us 는 2,805) 그것을
1799
- * 가리켜 걸어 들어갈 자리가 없었다. 지도와 집약 태그에는 이미 보이는데 「모델 살펴보기」에는 문이
1800
- * 없었다 — 사용자가 트윈에 가장 자주 묻는 것이 「내 물건이 어디 있나」이므로 그것은 접근 장벽이다.
1801
- *
1802
- * 성격은 `orders`·`tasks` 와 같다: 상태에 살고, 저널에 이력이 있고, 원본이 낸 것을 트윈이 관측한다.
1803
- * 그래서 같은 조합(`instance` · `state` · `historical`)이다.
1804
- *
1805
- * ── 표준 대응 ─────────────────────────────────────────────────────────────
1806
- * ISA-95 는 `MaterialLot` 이다 — 물품은 「무슨 품목인가」(정의)가 아니라 「그 품목의 이 덩어리」이고,
1807
- * 위치·수량·부분(`MaterialSubLot`)을 그 자리가 든다. EPCIS 는 `ObjectEvent` 다: 개체가 생기고
1808
- * 관측되고 사라지는 것을 그 사건이 말한다.
1809
- *
1810
- * ── 무엇이 이 항목을 가리키나 ─────────────────────────────────────────────
1811
- * `subLotId ?? epc` 다. 짐작에 맡기면 `gtin` 으로 떨어지고, 그러면 **같은 품목의 물품 전부가 한
1812
- * 식별자로 뭉친다** — 화면이 2,400개를 몇 개로 보인다.
1813
- */
1814
- {
1815
- axis: "items",
1816
- label: "twin.axis.items",
1817
- kind: "instance",
1818
- source: "state",
1819
- historical: true,
1820
- idField: ["subLotId", "epc"],
1821
- standardClass: { isa95: "MaterialLot", epcis: "ObjectEvent" },
1822
- systems: LOGISTICS
1823
- },
1824
- /*
1825
- * ── 에너지가 더하는 개념은 **하나**다 (2026-08-14, §10 6.5단계) ──────────────
1826
- *
1827
- * 에너지 트윈의 개체 대부분은 **이미 있는 축**이 답한다: 전기 구간은 `locations`, 계량기·차단기·
1828
- * 태양광·축전지·감축 부하는 `equipment` 다(카탈로그가 그 타입들을 그 역할로 선언한다). 그것들을
1829
- * 새 축으로 다시 세우면 같은 것이 두 곳에서 세어진다 — 개념 지도가 계량기를 두 번 보여 준다.
1830
- *
1831
- * 정말로 새로운 것은 **수요 구간**이다: 자원이 아니고, 선언이 아니고, 15분마다 닫히는 **사실**이다.
1832
- * 그것이 요금의 알갱이이고 피크의 근거다(피크는 마감된 구간의 최대이므로 파생이다 — 축이 아니다).
1833
- *
1834
- * ── 표준 칸을 비운다 ──────────────────────────────────────────────────────
1835
- * 15분 수요 구간은 **요금 제도의 알갱이**다(계약·TOU). ISO 50001 은 경영 체계를, IEC 61850 은 설비
1836
- * 데이터 모델을 말하고, 둘 다 이 구간을 정의하지 않는다. 가까운 이름을 적으면 적합성 표가 거짓을
1837
- * 말하므로 비워 둔다 — 「표준에 자리가 없으면 빈 객체」라는 이 선언의 규율 그대로다.
1838
- *
1839
- * 아직 세우지 않은 것: **요금 구간**(TariffPeriod)과 **원단위**(EnPI). 둘 다 아직 아무도 만들지
1840
- * 않는다 — 선언만 하면 개념 지도가 언제나 0 을 보여 주고, 그것은 결손처럼 읽힌다(§10 7단계의 일).
1841
- */
1842
- /*
1843
- * **무엇이 이 항목을 가리키나** — `idField` 가 말한다.
1844
- *
1845
- * 수요 구간에는 `id` 가 없다. 그것이 결함은 아니다: 구간의 정체성은 **그 구간이 언제 시작했나**이고
1846
- * (요금의 알갱이가 그 시각으로 정해진다) 커널도 그것으로 키를 만든다(`contract-projected-over:<startMs>`).
1847
- *
1848
- * 그런데 소비처가 `id`·`key`·`gtin` 을 **짐작**하고 있어서, 화면이 여섯 구간 모두를 「식별자 없음 —
1849
- * 참조할 수 없는 항목」으로 보였다. 값은 실재하는데 가리킬 수 없다고 말한 것이다. 짐작을 없애고
1850
- * 선언이 답하게 한다 — 축이 사는 자리(`path`)를 선언하는 것과 같은 이유다.
1851
- */
1852
- {
1853
- axis: "demandWindows",
1854
- path: "energy.closed",
1855
- idField: "startMs",
1856
- label: "twin.axis.demandWindows",
1857
- kind: "instance",
1858
- source: "state",
1859
- historical: true,
1860
- standardClass: {},
1861
- systems: ["ems"]
1862
- }
1863
- ];
1864
- var TWIN_RELATIONS = [
1865
- /* 자리가 속한 구역 — **board 밖**(호스트의 `TwinArea`). 해소는 호스트가 한다. */
1866
- { from: "locations", field: "parentId", target: { kind: "external", entity: "space.area" }, via: "twin.rel.area", optional: true },
1867
- /* 자원이 사는 자리 */
1868
- { from: "equipment", field: "homeLocation", target: { kind: "axis", axis: "locations" }, via: "twin.rel.home", optional: true },
1869
- { from: "persons", field: "homeLocation", target: { kind: "axis", axis: "locations" }, via: "twin.rel.home", optional: true },
1870
- { from: "assets", field: "homeLocation", target: { kind: "axis", axis: "locations" }, via: "twin.rel.home", optional: true },
1871
- /* 자원의 등급 — 설비만 `kind`(타입)로 가고 사람·자산은 등급 목록으로 간다. 표준의 비대칭 그대로. */
1872
- { from: "persons", field: "personnelClassIds[]", target: { kind: "axis", axis: "personnelClasses" }, via: "twin.rel.class", optional: true },
1873
- { from: "assets", field: "assetClassIds[]", target: { kind: "axis", axis: "assetClasses" }, via: "twin.rel.class", optional: true },
1874
- { from: "equipment", field: "kind", target: { kind: "type", role: "equipment" }, via: "twin.rel.type" },
1875
- { from: "locations", field: "type", target: { kind: "type", role: "location" }, via: "twin.rel.type" },
1876
- /* 생산 — 레시피에서 시작해 라우트·공정·품목으로 퍼진다 */
1877
- { from: "recipes", field: "route", target: { kind: "axis", axis: "routes" }, via: "twin.rel.route", optional: true },
1878
- { from: "recipes", field: "inputs[].material", target: { kind: "axis", axis: "materials" }, via: "twin.rel.input" },
1879
- { from: "recipes", field: "outputs[].material", target: { kind: "axis", axis: "materials" }, via: "twin.rel.output" },
1880
- { from: "routes", field: "steps[]", target: { kind: "axis", axis: "operations" }, via: "twin.rel.step" },
1881
- /*
1882
- * 일정·실적의 관계 — **관측 관계다.** 선언 관계는 "그렇게 만들기로 했다" 이고, 이것은 "그렇게
1883
- * 일어났다" 다. 관계가 없으면 이 두 축은 지도에서 연결되지 않은 카드가 된다(그러면 걸어 들어갈 수 없다).
1884
- */
1885
- /*
1886
- * 오더가 무엇을 만드나 — **자재 키가 아니라 GS1 품목 참조다**(`lines[].gtin`). 자재와 이어 주는
1887
- * 것은 `productionSpec.binding` 이고 그 해소는 호스트가 한다 — 자리·구역과 같은 `external` 이다.
1888
- * 축을 직접 가리키게 적으면 없는 필드를 가리키는 선언이 된다(계약을 보고 고쳤다).
1889
- */
1890
- { from: "orders", field: "lines[].gtin", target: { kind: "external", entity: "gs1.itemRef" }, via: "twin.rel.item", optional: true },
1891
- { from: "tasks", field: "orderId", target: { kind: "axis", axis: "orders" }, via: "twin.rel.order", optional: true },
1892
- /* 작업의 `kind` 는 그 공정의 키다(라우트의 단계 이름 그대로 — `mes-kernel` 의 공정 목록이 그 증거). */
1893
- { from: "tasks", field: "kind", target: { kind: "axis", axis: "operations" }, via: "twin.rel.operation", optional: true },
1894
- { from: "tasks", field: "fromNode", target: { kind: "axis", axis: "locations" }, via: "twin.rel.from", optional: true },
1895
- { from: "tasks", field: "toNode", target: { kind: "axis", axis: "locations" }, via: "twin.rel.at", optional: true },
1896
- { from: "tasks", field: "resourceRef", target: { kind: "axis", axis: "equipment" }, via: "twin.rel.by", optional: true },
1897
- { from: "tasks", field: "personnel[]", target: { kind: "axis", axis: "persons" }, via: "twin.rel.crew", optional: true },
1898
- /*
1899
- * 물품의 관계 (2026-08-22) — 없으면 축이 **걸어 들어갈 수 없는 목록**이 된다.
1900
- *
1901
- * `location` 은 필수다 — 물품은 언제나 어딘가에 있다(그것이 물품의 뜻이다). 나머지는 선택이다:
1902
- * 물류단위에 담기지 않은 물품, 자산에 실리지 않은 팔레트가 정상이다.
1903
- *
1904
- * `parent` 는 **물품 축을 자기 자신으로** 가리킨다(팔레트에 담긴 상자 — EPCIS `AggregationEvent`).
1905
- * `carriedBy` 는 다른 축이다 — 반복사용 자산(GRAI)이 물류단위를 실어 나른다(§`FlowItem.carriedBy`).
1906
- *
1907
- * 관계 이름은 **소문자 한 낱말**이다(`twin.rel.<name>`) — 기존 열여덟 개가 그 규율이고 시험이 지킨다.
1908
- *
1909
- * 품목(`gtin`)은 오더와 **같은 규율**이다: 자재 키가 아니라 GS1 품목 참조이므로 축을 직접 가리키지
1910
- * 않고 `external` 로 둔다. 축을 가리키게 적으면 없는 필드를 가리키는 선언이 된다.
1911
- */
1912
- { from: "items", field: "location", target: { kind: "axis", axis: "locations" }, via: "twin.rel.at" },
1913
- { from: "items", field: "parent", target: { kind: "axis", axis: "items" }, via: "twin.rel.parent", optional: true },
1914
- { from: "items", field: "carriedBy", target: { kind: "axis", axis: "assets" }, via: "twin.rel.asset", optional: true },
1915
- { from: "items", field: "gtin", target: { kind: "external", entity: "gs1.itemRef" }, via: "twin.rel.item", optional: true },
1916
- /*
1917
- * 자격을 검증한 시험 — **여덟 갈래.** 자원(개체)과 등급 양쪽이 가리킨다: 표준이 그 둘 모두에 이
1918
- * 참조를 두었기 때문이다(개체는 "이 사람이 통과했다", 등급은 "이 자격은 이 시험을 요구한다").
1919
- *
1920
- * **자리(`locations`)에는 없다** — 표준이 자리를 시험 대상으로 두지 않았고 그 비대칭은 표준의
1921
- * 판단이다(`contract.ts` 가 "우리도 자리에 넣지 않는다" 고 적었다).
1922
- */
1923
- { from: "persons", field: "testSpecificationIds[]", target: { kind: "axis", axis: "testSpecifications" }, via: "twin.rel.test", optional: true },
1924
- { from: "personnelClasses", field: "testSpecificationIds[]", target: { kind: "axis", axis: "testSpecifications" }, via: "twin.rel.test", optional: true },
1925
- { from: "equipment", field: "testSpecificationIds[]", target: { kind: "axis", axis: "testSpecifications" }, via: "twin.rel.test", optional: true },
1926
- { from: "equipmentClasses", field: "testSpecificationIds[]", target: { kind: "axis", axis: "testSpecifications" }, via: "twin.rel.test", optional: true },
1927
- { from: "assets", field: "testSpecificationIds[]", target: { kind: "axis", axis: "testSpecifications" }, via: "twin.rel.test", optional: true },
1928
- { from: "assetClasses", field: "testSpecificationIds[]", target: { kind: "axis", axis: "testSpecifications" }, via: "twin.rel.test", optional: true },
1929
- { from: "materialDefinitions", field: "testSpecificationIds[]", target: { kind: "axis", axis: "testSpecifications" }, via: "twin.rel.test", optional: true },
1930
- { from: "materialClasses", field: "testSpecificationIds[]", target: { kind: "axis", axis: "testSpecifications" }, via: "twin.rel.test", optional: true },
1931
- /* 공정이 도는 자리·자원 — 항목이 아니라 **타입**을 가리킨다 */
1932
- { from: "operations", field: "locationType", target: { kind: "type", role: "location" }, via: "twin.rel.at", optional: true },
1933
- { from: "operations", field: "resourceType", target: { kind: "type", role: "equipment" }, via: "twin.rel.by", optional: true }
1934
- ];
1935
- var TWIN_PROPERTIES = [
1936
- /* 공정 — 선언(ISO 8601 기간)과 관측(저널에서 계산한 분포)이 둘 다 있는 유일한 짝. */
1937
- {
1938
- axis: "operations",
1939
- key: "duration",
1940
- label: "twin.prop.duration",
1941
- kind: "distribution",
1942
- declaredField: "duration",
1943
- observedBy: "kpi-fold.workTime",
1944
- uom: "ms"
1945
- },
1946
- /* 자리 — 용량은 선언, 점유는 관측. 단위가 다른 것이 아니라 **같은 축의 두 값**이다. */
1947
- {
1948
- axis: "locations",
1949
- key: "capacity",
1950
- label: "twin.prop.capacity",
1951
- kind: "level",
1952
- declaredField: "capacity",
1953
- observedBy: "state.location.occupancy"
1954
- },
1955
- /* 설비 — 고장 모델은 선언, 가동은 관측(OEE). 선언 없이도 관측은 선다. */
1956
- {
1957
- axis: "equipment",
1958
- key: "mtbf",
1959
- label: "twin.prop.mtbf",
1960
- kind: "distribution",
1961
- declaredField: "mtbfMs",
1962
- observedBy: "oee.availability",
1963
- uom: "ms"
1964
- },
1965
- {
1966
- axis: "equipment",
1967
- key: "mttr",
1968
- label: "twin.prop.mttr",
1969
- kind: "distribution",
1970
- declaredField: "mttrMs",
1971
- observedBy: "oee.availability",
1972
- uom: "ms"
1973
- },
1974
- {
1975
- axis: "equipment",
1976
- key: "availability",
1977
- label: "twin.prop.availability",
1978
- kind: "rate",
1979
- observedBy: "oee.availability",
1980
- uom: "ratio"
1981
- }
1982
- ];
1983
- var propertiesOf = (axis) => TWIN_PROPERTIES.filter((p) => p.axis === axis);
1984
- var axisSource = (axis) => TWIN_AXES.find((a) => a.axis === axis)?.source ?? "document";
1985
- function documentPath(axis) {
1986
- const info = TWIN_AXES.find((a) => a.axis === axis);
1987
- if (!info)
1988
- throw new Error(`unknown twin axis: "${axis}"`);
1989
- if (axisSource(axis) !== "document" || !info.path)
1990
- throw new Error(`axis "${axis}" is not read from the document (source: ${axisSource(axis)}) \u2014 read it from the ${axisSource(axis)}`);
1991
- return info.path;
1992
- }
1993
- var relationsFrom = (axis) => TWIN_RELATIONS.filter((r) => r.from === axis);
1994
- var relationsTo = (axis) => TWIN_RELATIONS.filter((r) => r.target.kind === "axis" && r.target.axis === axis);
1995
- function axisInfo(axis) {
1996
- return TWIN_AXES.find((a) => a.axis === axis);
1997
- }
1998
- var DOMAIN_SYSTEMS = ["wms", "yms", "mes", "ems"];
1999
- var axisAppliesTo = (axis, system) => {
2000
- const declared = TWIN_AXES.find((a) => a.axis === axis)?.systems;
2001
- if (!declared || !system)
2002
- return true;
2003
- if (!DOMAIN_SYSTEMS.includes(system))
2004
- return true;
2005
- return declared.includes(system);
2006
- };
2007
- var levelOfLocationType = (typeKey, system) => {
2008
- const pick = (sys) => DOMAIN_CATALOG[sys]?.types.find((t) => t.role === "location" && t.key === typeKey)?.level;
2009
- if (system && DOMAIN_SYSTEMS.includes(system))
2010
- return pick(system);
2011
- const found = [...new Set(DOMAIN_SYSTEMS.map(pick).filter(Boolean))];
2012
- return found.length === 1 ? found[0] : void 0;
2013
- };
2014
- var axesOfSystem = (system) => TWIN_AXES.filter((a) => axisAppliesTo(a.axis, system));
2015
- function capabilitiesForType(system, typeKey) {
2016
- return DOMAIN_CATALOG[system]?.types.find((t) => t.key === typeKey)?.capabilities ?? [];
2017
- }
2018
1314
 
2019
- // ../operations-contract/dist/domain-definition.js
1315
+ // ../ops-contract/dist/domain-definition.js
2020
1316
  var OP_PARAM = {
2021
1317
  /** 양품률(0..1, 무차원). 없으면 커널 기본값 — 기본값을 쓴 사실은 `specCoverage()` 가 밝힌다. */
2022
1318
  yield: "yield",
@@ -2024,528 +1320,8 @@ var OP_PARAM = {
2024
1320
  * 현재 커널은 작업에 붙는 셋업으로 다루므로 모수로 받는다. */
2025
1321
  setupDuration: "setupDuration"
2026
1322
  };
2027
- var INTENTS = ["transport", "process", "dwell"];
2028
- function dupes(keys) {
2029
- const seen = /* @__PURE__ */ new Set();
2030
- const dup = /* @__PURE__ */ new Set();
2031
- for (const k of keys) {
2032
- if (seen.has(k))
2033
- dup.add(k);
2034
- else
2035
- seen.add(k);
2036
- }
2037
- return [...dup];
2038
- }
2039
- function validateDomainDefinition(def) {
2040
- const v = [];
2041
- if (!def || typeof def !== "object")
2042
- return ["domain definition \uC5C6\uC74C/\uAC1D\uCCB4 \uC544\uB2D8"];
2043
- if (typeof def.id !== "string" || !def.id)
2044
- v.push("id \uB204\uB77D");
2045
- if (typeof def.label !== "string" || !def.label)
2046
- v.push("label \uB204\uB77D");
2047
- if (!Array.isArray(def.locationTypes) || def.locationTypes.length === 0)
2048
- v.push("locationTypes \uBE44\uC5B4\uC788\uC74C");
2049
- if (!Array.isArray(def.resourceTypes))
2050
- v.push("resourceTypes \uBC30\uC5F4 \uC544\uB2D8");
2051
- const locationKeys2 = new Set((def.locationTypes || []).map((n) => n.key));
2052
- const resKeys = new Set((def.resourceTypes || []).map((r) => r.key));
2053
- const matKeys = new Set((def.materials || []).map((m) => m.key));
2054
- const matByKey = new Map((def.materials || []).map((m) => [m.key, m]));
2055
- const opKeys = new Set((def.operations || []).map((o) => o.key));
2056
- const routeKeys = new Set((def.routes || []).map((r) => r.key));
2057
- for (const [name, arr] of [["locationTypes", def.locationTypes], ["resourceTypes", def.resourceTypes], ["materials", def.materials], ["operations", def.operations], ["routes", def.routes], ["recipes", def.recipes]]) {
2058
- for (const d of dupes((arr || []).map((x) => x.key)))
2059
- v.push(`${name} \uD0A4 \uC911\uBCF5: ${d}`);
2060
- }
2061
- for (const o of def.operations || []) {
2062
- if (!INTENTS.includes(o.intent))
2063
- v.push(`operation '${o.key}' intent \uBD80\uC815: ${o.intent}`);
2064
- if (o.locationType && !locationKeys2.has(o.locationType))
2065
- v.push(`operation '${o.key}' locationType '${o.locationType}' \uBBF8\uC815\uC758`);
2066
- if (o.resourceType && !resKeys.has(o.resourceType))
2067
- v.push(`operation '${o.key}' resourceType '${o.resourceType}' \uBBF8\uC815\uC758`);
2068
- if (o.intent === "dwell" && o.resourceType)
2069
- v.push(`operation '${o.key}' dwell \uC778\uB370 resourceType \uC9C0\uC815\uB428(\uBB34\uC790\uC6D0\uC774\uC5B4\uC57C)`);
2070
- }
2071
- for (const r of def.routes || []) {
2072
- if (!Array.isArray(r.steps) || r.steps.length === 0)
2073
- v.push(`route '${r.key}' steps \uBE44\uC5B4\uC788\uC74C`);
2074
- for (const s of r.steps || [])
2075
- if (!opKeys.has(s))
2076
- v.push(`route '${r.key}' step '${s}' \uBBF8\uC815\uC758 operation`);
2077
- }
2078
- for (const rc of def.recipes || []) {
2079
- if (rc.route && !routeKeys.has(rc.route))
2080
- v.push(`recipe '${rc.key}' route '${rc.route}' \uBBF8\uC815\uC758`);
2081
- if (!rc.outputs?.length)
2082
- v.push(`recipe '${rc.key}' outputs \uBE44\uC5B4\uC788\uC74C`);
2083
- for (const p of [...rc.inputs || [], ...rc.outputs || []]) {
2084
- if (!matKeys.has(p.material))
2085
- v.push(`recipe '${rc.key}' material '${p.material}' \uBBF8\uC815\uC758`);
2086
- if (typeof p.qty !== "number" || p.qty <= 0)
2087
- v.push(`recipe '${rc.key}' material '${p.material}' qty \uBD80\uC815`);
2088
- const mat = matByKey.get(p.material);
2089
- if (mat?.locationType && !locationKeys2.has(mat.locationType))
2090
- v.push(`recipe '${rc.key}' material '${p.material}' locationType '${mat.locationType}' \uBBF8\uC815\uC758`);
2091
- }
2092
- }
2093
- return v;
2094
- }
2095
1323
 
2096
- // ../operations-contract/dist/energy-ingest.js
2097
- function isEnergyRecord(record) {
2098
- if (!record || typeof record !== "object")
2099
- return false;
2100
- const r = record;
2101
- if (!(typeof r.meterId === "string" && r.meterId.trim().length > 0 && r.epc === void 0))
2102
- return false;
2103
- return r.from === void 0 && r.to === void 0;
2104
- }
2105
- function ingestEnergyRecords(records, opts) {
2106
- const arr = Array.isArray(records) ? records : records ? [records] : [];
2107
- const accepted = [];
2108
- const rejected = [];
2109
- let seq = 0;
2110
- for (const record of arr) {
2111
- const errors = [];
2112
- const meterId = String(record?.meterId ?? "").trim();
2113
- if (!meterId)
2114
- errors.push("meterId \uC5C6\uC74C \u2014 \uC5B4\uB514\uC758 \uC18C\uBE44\uC778\uC9C0 \uBAA8\uB974\uB294 \uAC12\uC740 \uB204\uC801\uD560 \uC218 \uC5C6\uB2E4");
2115
- const at = String(record?.at ?? "").trim() || opts.defaultEventTime;
2116
- const atMs = at ? Date.parse(at) : Number.NaN;
2117
- if (!Number.isFinite(atMs))
2118
- 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");
2119
- const num = (v, name) => {
2120
- if (v === void 0 || v === null || v === "")
2121
- return void 0;
2122
- const n = Number(v);
2123
- if (!Number.isFinite(n)) {
2124
- errors.push(`${name} \uAC00 \uC218\uAC00 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
2125
- return void 0;
2126
- }
2127
- return n;
2128
- };
2129
- const kW = num(record?.kW, "kW");
2130
- const kWh = num(record?.kWh, "kWh");
2131
- const powerFactor = num(record?.powerFactor, "powerFactor");
2132
- if (errors.length) {
2133
- rejected.push({ record, errors });
2134
- continue;
2135
- }
2136
- const eventTime = new Date(atMs).toISOString();
2137
- const data = {
2138
- meterId,
2139
- /* 계약은 `kW` 를 필수로 두지만 **못 읽은 표본도 사실**이다 — 그 경우 값을 비우고 보낸다.
2140
- 커널이 「받았지만 부하를 못 읽었다」로 세고, 구간 마감에 그 이유를 싣는다. */
2141
- ...kW !== void 0 ? { kW } : {},
2142
- ...kWh !== void 0 ? { kWh } : {},
2143
- ...powerFactor !== void 0 ? { powerFactor } : {},
2144
- at: eventTime
2145
- };
2146
- accepted.push({
2147
- eventId: `${opts.tenantId}-energy-${++seq}`,
2148
- eventType: ENERGY_EVENT.measured,
2149
- eventTime,
2150
- tenantId: opts.tenantId,
2151
- data
2152
- });
2153
- }
2154
- return { accepted, rejected };
2155
- }
2156
- var POSITIONS = /* @__PURE__ */ new Set(["open", "closed", "intermediate", "bad"]);
2157
- function isEnergyEquipmentRecord(record) {
2158
- if (!record || typeof record !== "object")
2159
- return false;
2160
- const r = record;
2161
- return typeof r.equipmentId === "string" && r.equipmentId.trim().length > 0 && r.epc === void 0 && r.meterId === void 0 && /* 적산을 실었으면 발전이다 — 이 문의 값은 상태이고, 적산은 자기 문이 있다. */
2162
- r.kWh === void 0;
2163
- }
2164
- function ingestEnergyEquipmentRecords(records, opts) {
2165
- const arr = Array.isArray(records) ? records : records ? [records] : [];
2166
- const accepted = [];
2167
- const rejected = [];
2168
- let seq = 0;
2169
- for (const record of arr) {
2170
- const errors = [];
2171
- const r = record;
2172
- const equipmentId = String(r?.equipmentId ?? "").trim();
2173
- if (!equipmentId)
2174
- errors.push("equipmentId \uC5C6\uC74C \u2014 \uC5B4\uB290 \uC124\uBE44\uC758 \uC0C1\uD0DC\uC778\uC9C0 \uBAA8\uB974\uB294 \uAC12\uC740 \uC2E4\uC744 \uC218 \uC5C6\uB2E4");
2175
- const at = String(r?.at ?? "").trim() || opts.defaultEventTime;
2176
- const atMs = at ? Date.parse(at) : Number.NaN;
2177
- if (!Number.isFinite(atMs))
2178
- errors.push("at \uC5C6\uC74C/\uD615\uC2DD \uC624\uB958 \u2014 \uC9C0\uAE08 \uC2DC\uAC01\uC73C\uB85C \uBA54\uC6B0\uBA74 \uC5B8\uC81C\uC758 \uC0C1\uD0DC\uC778\uC9C0 \uC54C \uC218 \uC5C6\uB2E4");
2179
- const num = (v, name, min, max) => {
2180
- if (v === void 0 || v === null || v === "")
2181
- return void 0;
2182
- const n = Number(v);
2183
- if (!Number.isFinite(n)) {
2184
- errors.push(`${name} \uAC00 \uC218\uAC00 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
2185
- return void 0;
2186
- }
2187
- if (min !== void 0 && n < min) {
2188
- errors.push(`${name} \uAC00 ${min} \uBCF4\uB2E4 \uC791\uB2E4: ${n}`);
2189
- return void 0;
2190
- }
2191
- if (max !== void 0 && n > max) {
2192
- errors.push(`${name} \uAC00 ${max} \uBCF4\uB2E4 \uD06C\uB2E4: ${n}`);
2193
- return void 0;
2194
- }
2195
- return n;
2196
- };
2197
- const generatedKW = num(r?.generatedKW, "generatedKW", 0);
2198
- const exportKW = num(r?.exportKW, "exportKW");
2199
- const soc = num(r?.soc, "soc", 0, 100);
2200
- const chargeKW = num(r?.chargeKW, "chargeKW", 0);
2201
- const dischargeKW = num(r?.dischargeKW, "dischargeKW", 0);
2202
- const minKW = num(r?.minKW, "minKW", 0);
2203
- let curtailable;
2204
- if (r?.curtailable !== void 0 && r?.curtailable !== null) {
2205
- if (typeof r.curtailable !== "boolean")
2206
- errors.push(`curtailable \uAC00 \uCC38/\uAC70\uC9D3\uC774 \uC544\uB2C8\uB2E4: ${JSON.stringify(r.curtailable)}`);
2207
- else
2208
- curtailable = r.curtailable;
2209
- }
2210
- const dcVoltage = num(r?.dcVoltage, "dcVoltage", 0);
2211
- const dcCurrent = num(r?.dcCurrent, "dcCurrent", 0);
2212
- const phases = (raw, name) => {
2213
- if (raw === void 0 || raw === null)
2214
- return void 0;
2215
- if (!Array.isArray(raw)) {
2216
- errors.push(`${name} \uAC00 \uBC30\uC5F4\uC774 \uC544\uB2C8\uB2E4 \u2014 \uC0C1\uBCC4 \uAC12\uC774\uB77C \uBC30\uC5F4\uB85C \uBC1B\uB294\uB2E4(\uB2E8\uC0C1\uC774\uBA74 \uC6D0\uC18C \uD558\uB098): ${JSON.stringify(raw)}`);
2217
- return void 0;
2218
- }
2219
- if (!raw.length) {
2220
- errors.push(`${name} \uAC00 \uBE48 \uBC30\uC5F4\uC774\uB2E4 \u2014 \uC7B0 \uC0C1\uC774 \uC5C6\uC73C\uBA74 \uADF8 \uCE78\uC744 \uBCF4\uB0B4\uC9C0 \uC54A\uB294\uB2E4`);
2221
- return void 0;
2222
- }
2223
- const out = [];
2224
- for (const [i, v] of raw.entries()) {
2225
- const n = Number(v);
2226
- if (!Number.isFinite(n)) {
2227
- errors.push(`${name}[${i}] \uB97C \uC218\uB85C \uC77D\uC744 \uC218 \uC5C6\uB2E4: ${JSON.stringify(v)}`);
2228
- return void 0;
2229
- }
2230
- if (n < 0) {
2231
- errors.push(`${name}[${i}] \uAC00 \uC74C\uC218\uB2E4(${n}) \u2014 \uD06C\uAE30\uB294 \uC74C\uC218\uAC00 \uB418\uC9C0 \uC54A\uB294\uB2E4(\uBC29\uD5A5\uC740 \uB2E4\uB978 \uCD95\uC774 \uB9D0\uD55C\uB2E4)`);
2232
- return void 0;
2233
- }
2234
- out.push(n);
2235
- }
2236
- return out;
2237
- };
2238
- const acVoltage = phases(r?.acVoltage, "acVoltage");
2239
- const acCurrent = phases(r?.acCurrent, "acCurrent");
2240
- if (acVoltage && acCurrent && acVoltage.length !== acCurrent.length) {
2241
- errors.push(`acVoltage(${acVoltage.length}\uC0C1)\uC640 acCurrent(${acCurrent.length}\uC0C1)\uC758 \uC0C1 \uC218\uAC00 \uB2E4\uB974\uB2E4`);
2242
- }
2243
- let position;
2244
- if (r?.position !== void 0 && r?.position !== null && r?.position !== "") {
2245
- const p = String(r.position);
2246
- if (!POSITIONS.has(p))
2247
- errors.push(`position \uC774 \uD45C\uC900 \uAC12\uC774 \uC544\uB2C8\uB2E4(open\xB7closed\xB7intermediate\xB7bad): ${JSON.stringify(r.position)}`);
2248
- else
2249
- position = p;
2250
- }
2251
- const values = { generatedKW, exportKW, soc, chargeKW, dischargeKW, curtailable, minKW, position, dcVoltage, dcCurrent, acVoltage, acCurrent };
2252
- if (!errors.length && Object.values(values).every((v) => v === void 0)) {
2253
- errors.push("\uBC14\uAFC0 \uC0C1\uD0DC\uAC00 \uD558\uB098\uB3C4 \uC5C6\uB2E4 \u2014 \uAC12 \uC5C6\uB294 \uBCF4\uACE0\uB294 \uC0AC\uC2E4\uB85C \uC801\uC744 \uAC83\uC774 \uC5C6\uB2E4");
2254
- }
2255
- if (errors.length) {
2256
- rejected.push({ record, errors });
2257
- continue;
2258
- }
2259
- const eventTime = new Date(atMs).toISOString();
2260
- const data = {
2261
- equipmentId,
2262
- at: eventTime,
2263
- ...generatedKW !== void 0 ? { generatedKW } : {},
2264
- ...exportKW !== void 0 ? { exportKW } : {},
2265
- ...soc !== void 0 ? { soc } : {},
2266
- ...chargeKW !== void 0 ? { chargeKW } : {},
2267
- ...dischargeKW !== void 0 ? { dischargeKW } : {},
2268
- ...curtailable !== void 0 ? { curtailable } : {},
2269
- ...minKW !== void 0 ? { minKW } : {},
2270
- ...position !== void 0 ? { position } : {},
2271
- ...dcVoltage !== void 0 ? { dcVoltage } : {},
2272
- ...dcCurrent !== void 0 ? { dcCurrent } : {},
2273
- ...acVoltage !== void 0 ? { acVoltage } : {},
2274
- ...acCurrent !== void 0 ? { acCurrent } : {}
2275
- };
2276
- accepted.push({
2277
- eventId: `${opts.tenantId}-energy-eq-${++seq}`,
2278
- eventType: ENERGY_EVENT.equipment,
2279
- eventTime,
2280
- tenantId: opts.tenantId,
2281
- data
2282
- });
2283
- }
2284
- return { accepted, rejected };
2285
- }
2286
- function isEnergyGenerationRecord(record) {
2287
- if (record?.from !== void 0 || record?.to !== void 0)
2288
- return false;
2289
- if (!record || typeof record !== "object")
2290
- return false;
2291
- const r = record;
2292
- return typeof r.equipmentId === "string" && r.equipmentId.trim().length > 0 && r.kWh !== void 0 && r.meterId === void 0 && r.epc === void 0;
2293
- }
2294
- function ingestEnergyGenerationRecords(records, opts) {
2295
- const list = records === void 0 || records === null ? [] : Array.isArray(records) ? records : [records];
2296
- const accepted = [];
2297
- const rejected = [];
2298
- let seq = 0;
2299
- for (const r of list) {
2300
- const errors = [];
2301
- const equipmentId = String(r?.equipmentId ?? "").trim();
2302
- if (!equipmentId)
2303
- errors.push("equipmentId \uAC00 \uC5C6\uB2E4 \u2014 \uC5B4\uB290 \uC124\uBE44\uAC00 \uB9CC\uB4E0 \uAC83\uC778\uC9C0 \uC9C0\uC5B4\uB0BC \uC218 \uC5C6\uB2E4");
2304
- const STATE_FIELDS = ["generatedKW", "exportKW", "soc", "chargeKW", "dischargeKW", "curtailable", "minKW", "position"];
2305
- const alsoState = STATE_FIELDS.filter((f) => r?.[f] !== void 0);
2306
- if (alsoState.length) {
2307
- errors.push(`\uC801\uC0B0\uACFC \uC124\uBE44 \uC0C1\uD0DC\uB97C \uD55C \uB808\uCF54\uB4DC\uC5D0 \uC2E4\uC5C8\uB2E4(${alsoState.join("\xB7")}) \u2014 \uC774 \uBB38\uC740 \uC801\uC0B0\uB9CC \uC77D\uC73C\uBBC0\uB85C \uADF8 \uAC12\uC774 \uC0AC\uB77C\uC9C4\uB2E4. \uAC08\uB77C \uBCF4\uB0BC \uAC83`);
2308
- }
2309
- const raw = r?.kWh;
2310
- const kWh = Number(raw);
2311
- if (raw === void 0 || raw === null || !Number.isFinite(kWh)) {
2312
- errors.push("kWh \uAC00 \uC5C6\uB2E4 \u2014 \uC774 \uBB38\uC774 \uBC1B\uB294 \uAC12\uC740 \uC801\uC0B0 \uD558\uB098\uB2E4(\uC21C\uC2DC \uCD9C\uB825\uC740 \uC124\uBE44 \uC0C1\uD0DC\uB85C \uBCF4\uB0B8\uB2E4)");
2313
- } else if (kWh < 0) {
2314
- errors.push(`kWh \uAC00 \uC74C\uC218\uB2E4(${kWh}) \u2014 \uC801\uC0B0\uC740 \uB4A4\uB85C \uAC00\uB354\uB77C\uB3C4 \uC74C\uC218\uAC00 \uB418\uC9C0 \uC54A\uB294\uB2E4`);
2315
- }
2316
- const eventTime = String(r?.at ?? "").trim() || opts.defaultEventTime || "";
2317
- if (!eventTime)
2318
- errors.push("at \uC774 \uC5C6\uACE0 \uAE30\uBCF8 \uC2DC\uAC01\uB3C4 \uC8FC\uC9C0 \uC54A\uC558\uB2E4 \u2014 \uC5B8\uC81C \uC7B0 \uAC83\uC778\uC9C0 \uC9C0\uC5B4\uB0BC \uC218 \uC5C6\uB2E4");
2319
- let since;
2320
- const rawSince = r?.kWhSince;
2321
- if (rawSince !== void 0 && rawSince !== null && String(rawSince).trim()) {
2322
- const text = String(rawSince).trim();
2323
- const at = Date.parse(text);
2324
- if (!Number.isFinite(at))
2325
- errors.push(`kWhSince \uB97C \uC2DC\uAC01\uC73C\uB85C \uC77D\uC744 \uC218 \uC5C6\uB2E4: ${JSON.stringify(rawSince)}`);
2326
- else if (Number.isFinite(Date.parse(eventTime)) && at > Date.parse(eventTime)) {
2327
- errors.push(`kWhSince(${text}) \uAC00 \uC7B0 \uC2DC\uAC01(${eventTime})\uBCF4\uB2E4 \uB4A4\uB2E4 \u2014 \uB458 \uC911 \uD558\uB098\uAC00 \uD2C0\uB838\uB2E4`);
2328
- } else
2329
- since = text;
2330
- }
2331
- const ACCUMULATIONS = ["lifetime", "daily", "monthly", "billing", "unknown"];
2332
- let accumulation;
2333
- const rawAcc = r?.kWhAccumulation;
2334
- if (rawAcc !== void 0 && rawAcc !== null && String(rawAcc).trim()) {
2335
- const text = String(rawAcc).trim();
2336
- if (!ACCUMULATIONS.includes(text)) {
2337
- errors.push(`kWhAccumulation \uC774 \uC544\uB294 \uAC12\uC774 \uC544\uB2C8\uB2E4(${ACCUMULATIONS.join("\xB7")}): ${JSON.stringify(rawAcc)}`);
2338
- } else
2339
- accumulation = text;
2340
- }
2341
- if (errors.length) {
2342
- rejected.push({ record: r, errors });
2343
- continue;
2344
- }
2345
- const data = {
2346
- equipmentId,
2347
- kWh,
2348
- at: eventTime,
2349
- ...since ? { since } : {},
2350
- ...accumulation ? { accumulation } : {}
2351
- };
2352
- accepted.push({
2353
- eventId: `${opts.tenantId}-generated-${++seq}`,
2354
- eventType: ENERGY_EVENT.generated,
2355
- eventTime,
2356
- tenantId: opts.tenantId,
2357
- data
2358
- });
2359
- }
2360
- return { accepted, rejected };
2361
- }
2362
- function isEnergyUsagePeriodRecord(record) {
2363
- const r = record;
2364
- return !!r && typeof r === "object" && r.meterId !== void 0 && r.from !== void 0 && r.to !== void 0 && r.kWh !== void 0;
2365
- }
2366
- function isEnergyGenerationPeriodRecord(record) {
2367
- const r = record;
2368
- if (!r || typeof r !== "object")
2369
- return false;
2370
- if (r.equipmentId === void 0 || r.from === void 0 || r.to === void 0)
2371
- return false;
2372
- return r.kWh !== void 0;
2373
- }
2374
- function ingestEnergyGenerationPeriodRecords(records, opts) {
2375
- const list = records === void 0 || records === null ? [] : Array.isArray(records) ? records : [records];
2376
- const accepted = [];
2377
- const rejected = [];
2378
- for (const r of list) {
2379
- const errors = [];
2380
- const equipmentId = String(r?.equipmentId ?? "").trim();
2381
- if (!equipmentId)
2382
- errors.push("equipmentId \uAC00 \uC5C6\uB2E4 \u2014 \uC5B4\uB290 \uC124\uBE44\uAC00 \uB0B8 \uAC83\uC778\uC9C0 \uC9C0\uC5B4\uB0BC \uC218 \uC5C6\uB2E4");
2383
- const span = readSpan(r, errors);
2384
- const kWh = readAmount(r?.kWh, "kWh", errors);
2385
- if (kWh === void 0 && !errors.length)
2386
- errors.push("kWh \uAC00 \uC5C6\uB2E4 \u2014 \uC774 \uBB38\uC774 \uBC1B\uB294 \uAC12\uC740 \uADF8 \uAE30\uAC04\uC758 \uBC1C\uC804\uB7C9\uC774\uB2E4");
2387
- const ACCUMULATIONS = ["lifetime", "daily", "monthly", "billing", "unknown"];
2388
- let accumulation;
2389
- const rawAcc = r?.accumulation;
2390
- if (rawAcc !== void 0 && rawAcc !== null && String(rawAcc).trim()) {
2391
- const text = String(rawAcc).trim();
2392
- if (!ACCUMULATIONS.includes(text)) {
2393
- errors.push(`accumulation \uC774 \uC544\uB294 \uAC12\uC774 \uC544\uB2C8\uB2E4(${ACCUMULATIONS.join("\xB7")}): ${JSON.stringify(rawAcc)}`);
2394
- } else
2395
- accumulation = text;
2396
- }
2397
- const readAt = (raw, name) => {
2398
- if (raw === void 0 || raw === null || !String(raw).trim())
2399
- return void 0;
2400
- const text = String(raw).trim();
2401
- if (!Number.isFinite(Date.parse(text))) {
2402
- errors.push(`${name} \uB97C \uC2DC\uAC01\uC73C\uB85C \uC77D\uC744 \uC218 \uC5C6\uB2E4: ${JSON.stringify(raw)}`);
2403
- return void 0;
2404
- }
2405
- return text;
2406
- };
2407
- const observedFrom = readAt(r?.observedFrom, "observedFrom");
2408
- const observedTo = readAt(r?.observedTo, "observedTo");
2409
- if (errors.length || !span || kWh === void 0) {
2410
- rejected.push({ record: r, errors });
2411
- continue;
2412
- }
2413
- const data = {
2414
- equipmentId,
2415
- kWh,
2416
- periodStart: span.from,
2417
- periodEnd: span.to,
2418
- /* 관측 구간을 말하지 않으면 기간 전체를 잰 것으로 둔다 — 지난 기록은 그 원본이 하루를 마감해 준 값이다. */
2419
- observedFrom: observedFrom ?? span.from,
2420
- observedTo: observedTo ?? span.to,
2421
- accumulation: accumulation ?? "daily",
2422
- /* 밖에서 마감되어 온 것이다 — 우리가 시각 기준으로 닫은 것이 아니다. */
2423
- boundary: "declared"
2424
- };
2425
- const subj = resolveSubject("equipment", equipmentId, opts);
2426
- data.subject = subj.subject;
2427
- data.subjectBasis = subj.basis;
2428
- const futureError = futureFactError(span.to, opts);
2429
- if (futureError) {
2430
- rejected.push({ record: r, errors: [futureError] });
2431
- continue;
2432
- }
2433
- accepted.push({
2434
- eventId: periodFactId(opts.tenantId, ENERGY_EVENT.generationPeriod, subj.subject, span.from, span.to),
2435
- eventType: ENERGY_EVENT.generationPeriod,
2436
- eventTime: span.to,
2437
- tenantId: opts.tenantId,
2438
- data
2439
- });
2440
- }
2441
- return { accepted, rejected };
2442
- }
2443
- function isEnergyGenerationPriceRecord(record) {
2444
- const r = record;
2445
- if (!r || typeof r !== "object")
2446
- return false;
2447
- if (r.from === void 0 || r.to === void 0)
2448
- return false;
2449
- if (r.meterId !== void 0 || r.equipmentId !== void 0)
2450
- return false;
2451
- return r.unitPrice !== void 0;
2452
- }
2453
- function ingestEnergyGenerationPriceRecords(records, opts) {
2454
- const list = records === void 0 || records === null ? [] : Array.isArray(records) ? records : [records];
2455
- const accepted = [];
2456
- const rejected = [];
2457
- for (const r of list) {
2458
- const errors = [];
2459
- const span = readSpan(r, errors);
2460
- const unitPrice = readAmount(r?.unitPrice, "unitPrice", errors);
2461
- if (unitPrice === void 0 && !errors.length)
2462
- errors.push("unitPrice \uAC00 \uC5C6\uB2E4 \u2014 \uC774 \uBB38\uC774 \uBC1B\uB294 \uAC12\uC740 1kWh \uB2F9 \uAC12 \uD558\uB098\uB2E4");
2463
- const currency = String(r?.currency ?? "").trim();
2464
- if (!currency)
2465
- errors.push("currency \uAC00 \uC5C6\uB2E4 \u2014 \uB2E8\uC704 \uC5C6\uB294 \uAC12\uC740 \uB2E4\uB978 \uAE08\uC561\uACFC \uB354\uD560 \uC218 \uC5C6\uB2E4");
2466
- if (errors.length || !span || unitPrice === void 0) {
2467
- rejected.push({ record: r, errors });
2468
- continue;
2469
- }
2470
- const data = { from: span.from, to: span.to, unitPrice, currency };
2471
- accepted.push({
2472
- eventId: periodFactId(opts.tenantId, ENERGY_EVENT.generationPrice, "", span.from, span.to),
2473
- eventType: ENERGY_EVENT.generationPrice,
2474
- eventTime: span.from,
2475
- tenantId: opts.tenantId,
2476
- data
2477
- });
2478
- }
2479
- return { accepted, rejected };
2480
- }
2481
- function isEnergyTariffBasisRecord(record) {
2482
- const r = record;
2483
- if (!r || typeof r !== "object")
2484
- return false;
2485
- if (r.from === void 0 || r.to === void 0)
2486
- return false;
2487
- if (r.energyCharge !== void 0 || r.demandCharge !== void 0 || r.total !== void 0)
2488
- return false;
2489
- return r.billingDemandKW !== void 0 || r.demandChargePerKW !== void 0;
2490
- }
2491
- function ingestEnergyTariffBasisRecords(records, opts) {
2492
- const list = records === void 0 || records === null ? [] : Array.isArray(records) ? records : [records];
2493
- const accepted = [];
2494
- const rejected = [];
2495
- for (const r of list) {
2496
- const errors = [];
2497
- const span = readSpan(r, errors);
2498
- const billingDemandKW = readAmount(r?.billingDemandKW, "billingDemandKW", errors);
2499
- const demandChargePerKW = readAmount(r?.demandChargePerKW, "demandChargePerKW", errors);
2500
- if (billingDemandKW === void 0 && demandChargePerKW === void 0) {
2501
- errors.push("\uC694\uAE08 \uAE30\uC900\uC774 \uD558\uB098\uB3C4 \uC5C6\uB2E4 \u2014 \uC801\uC744 \uC0AC\uC2E4\uC774 \uC5C6\uB294 \uAE30\uC900\uC740 \uBC1B\uC9C0 \uC54A\uB294\uB2E4");
2502
- }
2503
- const currency = String(r?.currency ?? "").trim() || void 0;
2504
- if (demandChargePerKW !== void 0 && !currency)
2505
- errors.push("demandChargePerKW \uB97C \uC2E4\uC5C8\uB294\uB370 currency \uAC00 \uC5C6\uB2E4");
2506
- if (r?.demandCharge !== void 0 || r?.total !== void 0) {
2507
- errors.push("\uAE08\uC561(demandCharge\xB7total)\uC740 \uC774 \uBB38\uC774 \uBC1B\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uC815\uC0B0 \uAE08\uC561\uC740 \uCCAD\uAD6C\uC11C \uBB38\uC73C\uB85C \uBCF4\uB0B8\uB2E4");
2508
- }
2509
- if (errors.length || !span) {
2510
- rejected.push({ record: r, errors });
2511
- continue;
2512
- }
2513
- const data = {
2514
- from: span.from,
2515
- to: span.to,
2516
- ...billingDemandKW !== void 0 ? { billingDemandKW } : {},
2517
- ...demandChargePerKW !== void 0 ? { demandChargePerKW } : {},
2518
- ...currency ? { currency } : {}
2519
- };
2520
- accepted.push({
2521
- eventId: periodFactId(opts.tenantId, ENERGY_EVENT.tariffBasis, "", span.from, span.to),
2522
- eventType: ENERGY_EVENT.tariffBasis,
2523
- /* **시작 시각**이다 — 그때부터 유효하다(§ 위 주석). */
2524
- eventTime: span.from,
2525
- tenantId: opts.tenantId,
2526
- data
2527
- });
2528
- }
2529
- return { accepted, rejected };
2530
- }
2531
- function isEnergyBillRecord(record) {
2532
- const r = record;
2533
- if (!r || typeof r !== "object")
2534
- return false;
2535
- if (r.from === void 0 || r.to === void 0)
2536
- return false;
2537
- return r.energyCharge !== void 0 || r.demandCharge !== void 0 || r.total !== void 0;
2538
- }
2539
- function futureFactError(eventTime, opts) {
2540
- const nowText = opts.nowISO ?? opts.defaultEventTime;
2541
- if (!nowText)
2542
- return void 0;
2543
- const nowMs = Date.parse(nowText);
2544
- const atMs = Date.parse(eventTime);
2545
- if (!Number.isFinite(nowMs) || !Number.isFinite(atMs) || atMs <= nowMs)
2546
- return void 0;
2547
- return `\uC544\uC9C1 \uC624\uC9C0 \uC54A\uC740 \uC2DC\uAC01\uC758 \uC0AC\uC2E4\uC774\uB2E4(${eventTime}) \u2014 \uB05D\uB098\uC9C0 \uC54A\uC740 \uAE30\uAC04\uC740 \uB9C8\uAC10\uB41C \uC0AC\uC2E4\uC774 \uC544\uB2C8\uB2E4`;
2548
- }
1324
+ // ../ops-contract/dist/energy-ingest.js
2549
1325
  function resolveSubject(kind, localId, opts) {
2550
1326
  const declared = opts.identityOf?.(kind, localId);
2551
1327
  if (declared)
@@ -2555,145 +1331,8 @@ function resolveSubject(kind, localId, opts) {
2555
1331
  function periodFactId(tenantId, kind, subject, from, to) {
2556
1332
  return [tenantId, kind, subject, from, to].join("|");
2557
1333
  }
2558
- function readSpan(r, errors) {
2559
- const from = String(r?.from ?? "").trim();
2560
- const to = String(r?.to ?? "").trim();
2561
- const fromMs = Date.parse(from);
2562
- const toMs = Date.parse(to);
2563
- if (!from || !Number.isFinite(fromMs))
2564
- errors.push(`from \uC744 \uC2DC\uAC01\uC73C\uB85C \uC77D\uC744 \uC218 \uC5C6\uB2E4: ${JSON.stringify(r?.from)}`);
2565
- if (!to || !Number.isFinite(toMs))
2566
- errors.push(`to \uB97C \uC2DC\uAC01\uC73C\uB85C \uC77D\uC744 \uC218 \uC5C6\uB2E4: ${JSON.stringify(r?.to)}`);
2567
- if (Number.isFinite(fromMs) && Number.isFinite(toMs) && !(toMs > fromMs)) {
2568
- errors.push(`\uAD6C\uAC04\uC774 \uB4A4\uC9D1\uD614\uAC70\uB098 \uAE38\uC774\uAC00 \uC5C6\uB2E4(${from} \u2192 ${to}) \u2014 \uC5B4\uB290 \uCABD\uC774 \uD2C0\uB838\uB294\uC9C0 \uC6B0\uB9AC\uAC00 \uC815\uD560 \uC218 \uC5C6\uB2E4`);
2569
- }
2570
- return errors.length ? void 0 : { from, to };
2571
- }
2572
- function readAmount(raw, name, errors) {
2573
- if (raw === void 0 || raw === null || String(raw).trim() === "")
2574
- return void 0;
2575
- const v = Number(raw);
2576
- if (!Number.isFinite(v)) {
2577
- errors.push(`${name} \uB97C \uC218\uB85C \uC77D\uC744 \uC218 \uC5C6\uB2E4: ${JSON.stringify(raw)}`);
2578
- return void 0;
2579
- }
2580
- if (v < 0) {
2581
- errors.push(`${name} \uAC00 \uC74C\uC218\uB2E4(${v})`);
2582
- return void 0;
2583
- }
2584
- return v;
2585
- }
2586
- function ingestEnergyUsagePeriodRecords(records, opts) {
2587
- const list = records === void 0 || records === null ? [] : Array.isArray(records) ? records : [records];
2588
- const accepted = [];
2589
- const rejected = [];
2590
- for (const r of list) {
2591
- const errors = [];
2592
- const meterId = String(r?.meterId ?? "").trim();
2593
- if (!meterId)
2594
- errors.push("meterId \uAC00 \uC5C6\uB2E4 \u2014 \uC5B4\uB290 \uC9C0\uC810\uC774 \uC4F4 \uAC83\uC778\uC9C0 \uC9C0\uC5B4\uB0BC \uC218 \uC5C6\uB2E4");
2595
- const span = readSpan(r, errors);
2596
- const rawKWh = r?.kWh;
2597
- let kWh;
2598
- if (rawKWh === void 0 || rawKWh === null || String(rawKWh).trim() === "") {
2599
- errors.push("kWh \uAC00 \uC5C6\uB2E4 \u2014 \uC774 \uBB38\uC774 \uBC1B\uB294 \uAC12\uC740 \uADF8 \uAD6C\uAC04\uC5D0 \uC4F4 \uC591\uC774\uB2E4");
2600
- } else {
2601
- kWh = readAmount(rawKWh, "kWh", errors);
2602
- }
2603
- const maxKW = readAmount(r?.maxKW, "maxKW", errors);
2604
- const unitPrice = readAmount(r?.unitPrice, "unitPrice", errors);
2605
- const currency = String(r?.currency ?? "").trim() || void 0;
2606
- if (unitPrice !== void 0 && !currency)
2607
- errors.push("unitPrice \uB97C \uC2E4\uC5C8\uB294\uB370 currency \uAC00 \uC5C6\uB2E4 \u2014 \uB2E8\uC704 \uC5C6\uB294 \uAE08\uC561\uC740 \uB354\uD560 \uC218 \uC5C6\uB2E4");
2608
- let basis;
2609
- const rawBasis = r?.basis;
2610
- if (rawBasis !== void 0 && rawBasis !== null && String(rawBasis).trim()) {
2611
- const text = String(rawBasis).trim();
2612
- if (!OBSERVATION_BASIS.includes(text)) {
2613
- errors.push(`basis \uAC00 \uC544\uB294 \uAC12\uC774 \uC544\uB2C8\uB2E4(${OBSERVATION_BASIS.join("\xB7")}): ${JSON.stringify(rawBasis)}`);
2614
- } else
2615
- basis = text;
2616
- }
2617
- if (errors.length || !span || kWh === void 0) {
2618
- rejected.push({ record: r, errors });
2619
- continue;
2620
- }
2621
- const subj = resolveSubject("meter", meterId, opts);
2622
- const data = {
2623
- meterId,
2624
- subject: subj.subject,
2625
- subjectBasis: subj.basis,
2626
- from: span.from,
2627
- to: span.to,
2628
- kWh,
2629
- ...maxKW !== void 0 ? { maxKW } : {},
2630
- ...unitPrice !== void 0 ? { unitPrice } : {},
2631
- ...currency ? { currency } : {},
2632
- ...basis ? { basis } : {}
2633
- };
2634
- const futureError = futureFactError(span.to, opts);
2635
- if (futureError) {
2636
- rejected.push({ record: r, errors: [futureError] });
2637
- continue;
2638
- }
2639
- accepted.push({
2640
- eventId: periodFactId(opts.tenantId, ENERGY_EVENT.usagePeriod, subj.subject, span.from, span.to),
2641
- eventType: ENERGY_EVENT.usagePeriod,
2642
- /* 구간의 **끝**이 이 사실이 성립한 시각이다 — 시작으로 달면 저널이 그 구간을 미리 안 것이 된다. */
2643
- eventTime: span.to,
2644
- tenantId: opts.tenantId,
2645
- data
2646
- });
2647
- }
2648
- return { accepted, rejected };
2649
- }
2650
- function ingestEnergyBillRecords(records, opts) {
2651
- const list = records === void 0 || records === null ? [] : Array.isArray(records) ? records : [records];
2652
- const accepted = [];
2653
- const rejected = [];
2654
- for (const r of list) {
2655
- const errors = [];
2656
- const span = readSpan(r, errors);
2657
- const energyCharge = readAmount(r?.energyCharge, "energyCharge", errors);
2658
- const demandCharge = readAmount(r?.demandCharge, "demandCharge", errors);
2659
- const total = readAmount(r?.total, "total", errors);
2660
- const billingDemandKW = readAmount(r?.billingDemandKW, "billingDemandKW", errors);
2661
- if (energyCharge === void 0 && demandCharge === void 0 && total === void 0) {
2662
- errors.push("\uAE08\uC561\uC774 \uD558\uB098\uB3C4 \uC5C6\uB2E4 \u2014 \uC801\uC744 \uC0AC\uC2E4\uC774 \uC5C6\uB294 \uCCAD\uAD6C\uC11C\uB294 \uBC1B\uC9C0 \uC54A\uB294\uB2E4");
2663
- }
2664
- const currency = String(r?.currency ?? "").trim() || void 0;
2665
- if (!currency)
2666
- errors.push("currency \uAC00 \uC5C6\uB2E4 \u2014 \uB2E8\uC704 \uC5C6\uB294 \uAE08\uC561\uC740 \uB2E4\uB978 \uAE08\uC561\uACFC \uB354\uD560 \uC218 \uC5C6\uB2E4");
2667
- if (errors.length || !span) {
2668
- rejected.push({ record: r, errors });
2669
- continue;
2670
- }
2671
- const data = {
2672
- from: span.from,
2673
- to: span.to,
2674
- ...energyCharge !== void 0 ? { energyCharge } : {},
2675
- ...demandCharge !== void 0 ? { demandCharge } : {},
2676
- ...total !== void 0 ? { total } : {},
2677
- ...currency ? { currency } : {},
2678
- ...billingDemandKW !== void 0 ? { billingDemandKW } : {}
2679
- };
2680
- const futureError = futureFactError(span.to, opts);
2681
- if (futureError) {
2682
- rejected.push({ record: r, errors: [futureError] });
2683
- continue;
2684
- }
2685
- accepted.push({
2686
- eventId: periodFactId(opts.tenantId, ENERGY_EVENT.bill, "", span.from, span.to),
2687
- eventType: ENERGY_EVENT.bill,
2688
- eventTime: span.to,
2689
- tenantId: opts.tenantId,
2690
- data
2691
- });
2692
- }
2693
- return { accepted, rejected };
2694
- }
2695
1334
 
2696
- // ../operations-contract/dist/epcis.js
1335
+ // ../ops-contract/dist/epcis.js
2697
1336
  var EPCIS_CONTEXT = "https://ref.gs1.org/standards/epcis/2.0.0/epcis-context.jsonld";
2698
1337
  var UTC_OFFSET = "+00:00";
2699
1338
  var DISP = {
@@ -2781,18 +1420,6 @@ var CBV_BIZSTEP = {
2781
1420
  */
2782
1421
  other: "urn:epcglobal:cbv:bizstep:other"
2783
1422
  };
2784
- var EPCIS_EVENT = {
2785
- object: "epcis.ObjectEvent",
2786
- aggregation: "epcis.AggregationEvent",
2787
- transaction: "epcis.TransactionEvent",
2788
- transformation: "epcis.TransformationEvent"
2789
- };
2790
- function isEpcisEventType(eventType) {
2791
- return typeof eventType === "string" && eventType.startsWith("epcis.");
2792
- }
2793
- function ssccUri(companyPrefix, serial) {
2794
- return `urn:epc:id:sscc:${companyPrefix}.${String(serial).padStart(10, "0")}`;
2795
- }
2796
1423
  function sgtinClass(companyPrefix, itemRef) {
2797
1424
  return `urn:epc:idpat:sgtin:${companyPrefix}.${itemRef}.*`;
2798
1425
  }
@@ -2802,9 +1429,6 @@ var ILMD_ATTR = {
2802
1429
  /** 로트·배치 번호(직렬 개체에 로트를 붙일 때). */
2803
1430
  lot: "cbvmda:lotNumber"
2804
1431
  };
2805
- function lgtinClass(companyPrefix, itemRefAndIndicator, lot) {
2806
- return `urn:epc:class:lgtin:${companyPrefix}.${itemRefAndIndicator}.${encodeURIComponent(lot)}`;
2807
- }
2808
1432
  function parseEpc(uri) {
2809
1433
  const raw = String(uri ?? "");
2810
1434
  if (/(?:\/obj\/|:obj:)[^/:\s]+$/.test(raw))
@@ -2966,382 +1590,8 @@ function transformationEvent(p) {
2966
1590
  e.bizTransactionList = p.bizTransactionList;
2967
1591
  return e;
2968
1592
  }
2969
- var ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
2970
- var TZ_RE = /^[+-]\d{2}:\d{2}$/;
2971
- var ACTIONS = ["ADD", "OBSERVE", "DELETE"];
2972
- var EPC_CLASS_PREFIXES = ["urn:epc:idpat:", "urn:epc:class:"];
2973
- var DL_CANONICAL = "https://id.gs1.org/";
2974
- var CBV_URL_CLASS = /^https?:\/\/[^/\s]+\/(?:[^/\s]+\/)*class\/[^/\s]+$/;
2975
- var CBV_URN_CLASS = /^urn:[^:\s]+:(?:[^:\s]+:)*class:[^:\s]+$/;
2976
- var GS1_KEY_DIGITS = { sgtin: 13, sscc: 17, grai: 12, gdti: 12 };
2977
- function gs1KeyDigitViolation(uri) {
2978
- if (!uri)
2979
- return void 0;
2980
- const m = /^urn:epc:(?:id|idpat|class):([a-z]+):([^:]+)$/.exec(uri);
2981
- if (!m)
2982
- return void 0;
2983
- const want = GS1_KEY_DIGITS[m[1]];
2984
- if (!want)
2985
- return void 0;
2986
- const parts = m[2].split(".");
2987
- if (parts.length < 2)
2988
- return `${m[1]} \uD615\uC2DD \uC624\uB958: ${uri} \u2014 \uD68C\uC0AC \uD504\uB9AC\uD53D\uC2A4\uC640 \uCC38\uC870\uAC00 '.' \uB85C \uAC08\uB824\uC57C \uD55C\uB2E4`;
2989
- const [prefix, ref] = parts;
2990
- if (ref === "*")
2991
- return void 0;
2992
- if (!/^\d+$/.test(prefix) || !/^\d+$/.test(ref)) {
2993
- return `${m[1]} \uD615\uC2DD \uC624\uB958: ${uri} \u2014 \uD68C\uC0AC \uD504\uB9AC\uD53D\uC2A4\uC640 \uCC38\uC870\uB294 \uC22B\uC790\uB2E4`;
2994
- }
2995
- const got = prefix.length + ref.length;
2996
- if (got === want)
2997
- return void 0;
2998
- return `${m[1]} \uC790\uB9AC \uC218 \uC624\uB958: ${uri} \u2014 \uD68C\uC0AC \uD504\uB9AC\uD53D\uC2A4(${prefix.length}) + \uCC38\uC870(${ref.length}) = ${got} \uC774\uC9C0\uB9CC ${want} \uC5EC\uC57C \uD55C\uB2E4(GS1 TDS). \uD504\uB9AC\uD53D\uC2A4\uB97C \uBC14\uAFB8\uBA74 \uCC38\uC870 \uC790\uB9AC \uC218\uB97C \uD568\uAED8 \uB9DE\uCDB0\uC57C \uD55C\uB2E4.`;
2999
- }
3000
- function classIdentifierViolation(epcClass) {
3001
- if (!epcClass)
3002
- return `quantity epcClass \uBD80\uC815: ${epcClass}`;
3003
- const digits = gs1KeyDigitViolation(epcClass);
3004
- if (digits)
3005
- return `quantity epcClass \uBD80\uC815: ${digits}`;
3006
- if (EPC_CLASS_PREFIXES.some((p) => epcClass.startsWith(p)))
3007
- return void 0;
3008
- if (epcClass.startsWith(DL_CANONICAL))
3009
- return void 0;
3010
- if (CBV_URL_CLASS.test(epcClass) || CBV_URN_CLASS.test(epcClass))
3011
- return void 0;
3012
- return `quantity epcClass \uBD80\uC815: ${epcClass} \u2014 \uD074\uB798\uC2A4 \uC2DD\uBCC4\uC790\uB294 \uC18C\uC720 \uAD8C\uD55C\uC744 \uB9D0\uD560 \uC218 \uC788\uB294 \uBAA8\uC591\uC774\uC5B4\uC57C \uD55C\uB2E4 (EPC \uD074\uB798\uC2A4 urn:epc:idpat:/urn:epc:class: \xB7 CBV \xA78.3.4 http(s)://<\uB3C4\uBA54\uC778>/**/class/<id> \xB7 CBV \xA78.3.3 urn:<\uC774\uB984\uACF5\uAC04>:**:class:<id>)`;
3013
- }
3014
- function validateEpcisEvent(e) {
3015
- const v = [];
3016
- for (const [field, list] of [
3017
- ["epcList", e.epcList],
3018
- ["childEPCs", e.childEPCs],
3019
- ["inputEPCList", e.inputEPCList],
3020
- ["outputEPCList", e.outputEPCList],
3021
- ["parentID", e.parentID ? [e.parentID] : void 0]
3022
- ]) {
3023
- for (const epc of list ?? []) {
3024
- const bad2 = gs1KeyDigitViolation(epc);
3025
- if (bad2)
3026
- v.push(`${field}: ${bad2}`);
3027
- }
3028
- }
3029
- if (e["@context"] !== EPCIS_CONTEXT)
3030
- v.push("@context \uB204\uB77D/\uBD88\uC77C\uCE58");
3031
- if (!["ObjectEvent", "AggregationEvent", "TransactionEvent", "TransformationEvent"].includes(e.type))
3032
- v.push(`\uC54C \uC218 \uC5C6\uB294 type: ${e.type}`);
3033
- if (typeof e.eventTime !== "string" || !ISO_RE.test(e.eventTime))
3034
- v.push("eventTime ISO8601 \uC544\uB2D8");
3035
- if (typeof e.eventTimeZoneOffset !== "string" || !TZ_RE.test(e.eventTimeZoneOffset))
3036
- v.push("eventTimeZoneOffset \uD615\uC2DD \uC624\uB958");
3037
- if (typeof e.bizStep !== "string" || !e.bizStep)
3038
- v.push("bizStep \uB204\uB77D");
3039
- if (e.eventID !== void 0 && (typeof e.eventID !== "string" || !e.eventID))
3040
- v.push("eventID \uAC00 \uBE48 \uBB38\uC790\uC5F4");
3041
- if (e.recordTime !== void 0 && (typeof e.recordTime !== "string" || !ISO_RE.test(e.recordTime))) {
3042
- v.push("recordTime ISO8601 \uC544\uB2D8");
3043
- }
3044
- if (e.ilmd !== void 0) {
3045
- const allowed = e.type === "ObjectEvent" && e.action === "ADD" || e.type === "TransformationEvent";
3046
- if (!allowed)
3047
- v.push("ilmd \uB294 ObjectEvent(action=ADD) \uB610\uB294 TransformationEvent \uC5D0\uB9CC \uC2E4\uC744 \uC218 \uC788\uB2E4");
3048
- }
3049
- for (const sd of e.sourceList ?? []) {
3050
- if (!sd?.type || !sd?.source)
3051
- v.push("sourceList \uD56D\uBAA9\uC5D0 type \uB610\uB294 source \uB204\uB77D");
3052
- }
3053
- for (const sd of e.destinationList ?? []) {
3054
- if (!sd?.type || !sd?.destination)
3055
- v.push("destinationList \uD56D\uBAA9\uC5D0 type \uB610\uB294 destination \uB204\uB77D");
3056
- }
3057
- if (e.persistentDisposition !== void 0) {
3058
- const set = e.persistentDisposition.set ?? [];
3059
- const unset = e.persistentDisposition.unset ?? [];
3060
- if (!set.length && !unset.length)
3061
- v.push("persistentDisposition \uC774 set\xB7unset \uB458 \uB2E4 \uBE44\uC5B4\uC788\uC74C");
3062
- const both = set.filter((x) => unset.includes(x));
3063
- if (both.length)
3064
- v.push(`persistentDisposition \uC774 \uAC19\uC740 \uAC12\uC744 set\xB7unset \uB3D9\uC2DC \uC9C0\uC815: ${both.join(", ")}`);
3065
- }
3066
- for (const se of e.sensorElementList ?? []) {
3067
- if (!Array.isArray(se?.sensorReport) || se.sensorReport.length === 0) {
3068
- v.push("sensorElement \uC5D0 sensorReport \uAC00 \uD558\uB098\uB3C4 \uC5C6\uC74C");
3069
- }
3070
- }
3071
- if (e.errorDeclaration !== void 0) {
3072
- const d = e.errorDeclaration;
3073
- if (typeof d?.declarationTime !== "string" || !ISO_RE.test(d.declarationTime)) {
3074
- v.push("errorDeclaration.declarationTime ISO8601 \uC544\uB2D8/\uB204\uB77D");
3075
- }
3076
- if (d?.correctiveEventIDs !== void 0) {
3077
- if (!Array.isArray(d.correctiveEventIDs))
3078
- v.push("errorDeclaration.correctiveEventIDs \uBC30\uC5F4 \uC544\uB2D8");
3079
- else if (d.correctiveEventIDs.some((x) => typeof x !== "string" || !x)) {
3080
- v.push("errorDeclaration.correctiveEventIDs \uC5D0 \uBE48 \uAC12");
3081
- }
3082
- }
3083
- }
3084
- if (e.type === "ObjectEvent") {
3085
- if (!ACTIONS.includes(e.action))
3086
- v.push(`action \uBD80\uC815: ${e.action}`);
3087
- if (!Array.isArray(e.epcList))
3088
- v.push("ObjectEvent.epcList \uB204\uB77D");
3089
- else if (e.epcList.length === 0 && !e.quantityList?.length)
3090
- v.push("ObjectEvent: epcList/quantityList \uB458 \uB2E4 \uBE44\uC5B4\uC788\uC74C");
3091
- } else if (e.type === "AggregationEvent") {
3092
- if (!ACTIONS.includes(e.action))
3093
- v.push(`action \uBD80\uC815: ${e.action}`);
3094
- if (!e.parentID)
3095
- v.push("AggregationEvent.parentID \uB204\uB77D");
3096
- if (!e.childEPCs?.length && !e.childQuantityList?.length)
3097
- v.push("AggregationEvent: childEPCs/childQuantityList \uB458 \uB2E4 \uBE44\uC5B4\uC788\uC74C");
3098
- } else if (e.type === "TransactionEvent") {
3099
- if (!ACTIONS.includes(e.action))
3100
- v.push(`action \uBD80\uC815: ${e.action}`);
3101
- if (!Array.isArray(e.bizTransactionList) || e.bizTransactionList.length === 0)
3102
- v.push("TransactionEvent.bizTransactionList \uB204\uB77D");
3103
- } else if (e.type === "TransformationEvent") {
3104
- if (!e.inputEPCList?.length && !e.inputQuantityList?.length)
3105
- v.push("TransformationEvent: input \uBE44\uC5B4\uC788\uC74C");
3106
- if (!e.outputEPCList?.length && !e.outputQuantityList?.length)
3107
- v.push("TransformationEvent: output \uBE44\uC5B4\uC788\uC74C");
3108
- }
3109
- const qtyLists = [
3110
- "quantityList" in e ? e.quantityList : void 0,
3111
- "childQuantityList" in e ? e.childQuantityList : void 0,
3112
- "inputQuantityList" in e ? e.inputQuantityList : void 0,
3113
- "outputQuantityList" in e ? e.outputQuantityList : void 0
3114
- ];
3115
- for (const list of qtyLists)
3116
- for (const q of list ?? []) {
3117
- const bad2 = classIdentifierViolation(q.epcClass);
3118
- if (bad2)
3119
- v.push(bad2);
3120
- const hasQty = q.quantity !== void 0 && q.quantity !== null;
3121
- if (!hasQty) {
3122
- if (q.uom !== void 0)
3123
- v.push("quantity \uC5C6\uC73C\uBA74 uom \uB3C4 \uC5C6\uC5B4\uC57C \uD55C\uB2E4(\uC218\uB7C9 \uBBF8\uC9C0\uC815)");
3124
- continue;
3125
- }
3126
- if (typeof q.quantity !== "number" || !Number.isFinite(q.quantity) || q.quantity <= 0) {
3127
- v.push("quantity \uB294 \uC591\uC218\uC5EC\uC57C \uD55C\uB2E4(\uBAA8\uB974\uBA74 \uC0DD\uB7B5)");
3128
- continue;
3129
- }
3130
- if (q.uom === void 0 && !Number.isInteger(q.quantity))
3131
- v.push("uom \uC5C6\uB294 quantity \uB294 \uC815\uC218(\uAC1C\uC218)\uC5EC\uC57C \uD55C\uB2E4");
3132
- if (q.uom !== void 0 && !/^[A-Z0-9]{2,3}$/.test(q.uom))
3133
- v.push(`uom \uD615\uC2DD \uBD80\uC815(UN/CEFACT \uAD8C\uACE0 20 \uC758 2~3\uC790 \uCF54\uB4DC): ${q.uom}`);
3134
- }
3135
- return v;
3136
- }
3137
-
3138
- // ../operations-contract/dist/face2-adapter.js
3139
- function isAggregationRecord(record) {
3140
- if (!record || typeof record !== "object")
3141
- return false;
3142
- const r = record;
3143
- return typeof r.parentID === "string" && r.parentID.trim().length > 0 && r.epc === void 0;
3144
- }
3145
- function isTransformationRecord(record) {
3146
- if (!record || typeof record !== "object")
3147
- return false;
3148
- const r = record;
3149
- if (r.epc !== void 0 || r.parentID !== void 0)
3150
- return false;
3151
- const has = (k) => Array.isArray(r[k]) && r[k].length > 0;
3152
- return has("inputEPCList") || has("inputQuantityList") || has("outputEPCList") || has("outputQuantityList");
3153
- }
3154
- function get(obj, path) {
3155
- return path.split(".").reduce((o, k) => o == null ? o : o[k], obj);
3156
- }
3157
- function resolve(v, record, field, errors) {
3158
- if (v === void 0)
3159
- return void 0;
3160
- if (!v.startsWith("$."))
3161
- return v;
3162
- const r = get(record, v.slice(2));
3163
- if (r === void 0 || r === null)
3164
- return void 0;
3165
- if (typeof r === "object") {
3166
- errors.push(`${field} \uAC00 \uAC12\uC774 \uC544\uB2C8\uB77C ${Array.isArray(r) ? "\uBC30\uC5F4" : "\uAC1D\uCCB4"}\uB85C \uC654\uB2E4(${v}) \u2014 \uCEE4\uB125\uD130\uAC00 \uD45C\uC900 \uAD6C\uC870\uB97C \uADF8\uB300\uB85C \uC2E4\uC5C8\uB2E4: ${JSON.stringify(r).slice(0, 80)}`);
3167
- return void 0;
3168
- }
3169
- return String(r);
3170
- }
3171
- function resolveList(v, record, field, errors) {
3172
- if (v === void 0)
3173
- return [];
3174
- const r = get(record, v.startsWith("$.") ? v.slice(2) : v);
3175
- if (r === void 0 || r === null)
3176
- return [];
3177
- if (!Array.isArray(r) || r.some((x) => typeof x !== "string")) {
3178
- errors.push(`${field} \uAC00 \uBB38\uC790\uC5F4 \uBC30\uC5F4\uC774 \uC544\uB2C8\uB2E4(${v}): ${JSON.stringify(r).slice(0, 80)}`);
3179
- return [];
3180
- }
3181
- return r.slice();
3182
- }
3183
- function resolveQuantityList(v, record, field, errors) {
3184
- if (v === void 0)
3185
- return [];
3186
- const r = get(record, v.startsWith("$.") ? v.slice(2) : v);
3187
- if (r === void 0 || r === null)
3188
- return [];
3189
- if (!Array.isArray(r) || r.some((x) => !x || typeof x !== "object" || Array.isArray(x))) {
3190
- errors.push(`${field} \uAC00 \uAC1D\uCCB4 \uBC30\uC5F4\uC774 \uC544\uB2C8\uB2E4(${v}): ${JSON.stringify(r).slice(0, 80)}`);
3191
- return [];
3192
- }
3193
- return r.map((x) => ({ ...x }));
3194
- }
3195
- function resolveObject(v, record, field, errors) {
3196
- if (v === void 0)
3197
- return void 0;
3198
- const r = get(record, v.startsWith("$.") ? v.slice(2) : v);
3199
- if (r === void 0 || r === null)
3200
- return void 0;
3201
- if (typeof r !== "object" || Array.isArray(r)) {
3202
- errors.push(`${field} \uAC00 \uAC1D\uCCB4\uAC00 \uC544\uB2C8\uB2E4(${v}): ${JSON.stringify(r).slice(0, 80)}`);
3203
- return void 0;
3204
- }
3205
- const o = { ...r };
3206
- return Object.keys(o).length ? o : void 0;
3207
- }
3208
- function resolveErrorDeclaration(spec, record, errors) {
3209
- if (!spec)
3210
- return void 0;
3211
- const declarationTime = resolve(spec.declarationTime, record, "errorDeclaration.declarationTime", errors);
3212
- const reason = resolve(spec.reason, record, "errorDeclaration.reason", errors);
3213
- const correctiveEventIDs = resolveList(spec.correctiveEventIDs, record, "errorDeclaration.correctiveEventIDs", errors);
3214
- if (!declarationTime) {
3215
- if (reason || correctiveEventIDs.length) {
3216
- errors.push("errorDeclaration \uC774 \uBC18\uCBE4 \uC654\uB2E4 \u2014 \uC774\uC720\xB7\uC815\uC815 \uB300\uC0C1\uC740 \uC788\uB294\uB370 declarationTime \uC774 \uC5C6\uB2E4(\uC815\uC815 \uC2DC\uAC01\uC740 \uC9C0\uC5B4\uB0BC \uC218 \uC5C6\uB2E4)");
3217
- }
3218
- return void 0;
3219
- }
3220
- return {
3221
- declarationTime,
3222
- ...reason ? { reason } : {},
3223
- ...correctiveEventIDs.length ? { correctiveEventIDs } : {}
3224
- };
3225
- }
3226
- function mapRecordChecked(record, mapping, eventTime) {
3227
- const errors = [];
3228
- const bizStep = resolve(mapping.bizStep, record, "bizStep", errors) ?? "";
3229
- const disposition = resolve(mapping.disposition, record, "disposition", errors);
3230
- const readPoint = resolve(mapping.readPoint, record, "readPoint", errors);
3231
- const bizLocation = resolve(mapping.bizLocation, record, "bizLocation", errors);
3232
- const errorDeclaration = resolveErrorDeclaration(mapping.errorDeclaration, record, errors);
3233
- if (mapping.type === "TransformationEvent") {
3234
- const inputEPCList = resolveList(mapping.inputEPCList, record, "inputEPCList", errors);
3235
- const inputQuantityList = resolveQuantityList(mapping.inputQuantityList, record, "inputQuantityList", errors);
3236
- const outputEPCList = resolveList(mapping.outputEPCList, record, "outputEPCList", errors);
3237
- const outputQuantityList = resolveQuantityList(mapping.outputQuantityList, record, "outputQuantityList", errors);
3238
- const transformationID = resolve(mapping.transformationID, record, "transformationID", errors);
3239
- if (!inputEPCList.length && !inputQuantityList.length) {
3240
- errors.push("\uC785\uB825\uC774 \uBE44\uC5C8\uB2E4 \u2014 \uBCC0\uD658\uC740 \uBB34\uC5C7\uC774 \uB4E4\uC5B4\uAC14\uB294\uC9C0 \uB9D0\uD574\uC57C \uD55C\uB2E4(EPCIS 2.0 \xA77.4.5)");
3241
- }
3242
- if (!outputEPCList.length && !outputQuantityList.length) {
3243
- errors.push("\uCD9C\uB825\uC774 \uBE44\uC5C8\uB2E4 \u2014 \uBCC0\uD658\uC740 \uBB34\uC5C7\uC774 \uB098\uC654\uB294\uC9C0 \uB9D0\uD574\uC57C \uD55C\uB2E4(EPCIS 2.0 \xA77.4.5)");
3244
- }
3245
- return {
3246
- event: transformationEvent({
3247
- eventTime,
3248
- bizStep,
3249
- disposition,
3250
- /* 없는 쪽은 **필드를 만들지 않는다** — 빈 배열을 실으면 「없다」고 말하는 것이 된다.
3251
- 개체로 말한 쪽과 수량으로 말한 쪽 중 하나만 쓰는 것이 정상이다. */
3252
- ...inputEPCList.length ? { inputEPCList } : {},
3253
- ...inputQuantityList.length ? { inputQuantityList } : {},
3254
- ...outputEPCList.length ? { outputEPCList } : {},
3255
- ...outputQuantityList.length ? { outputQuantityList } : {},
3256
- ...transformationID ? { transformationID } : {},
3257
- ...errorDeclaration ? { errorDeclaration } : {},
3258
- readPoint,
3259
- bizLocation
3260
- }),
3261
- errors
3262
- };
3263
- }
3264
- if (mapping.type === "AggregationEvent") {
3265
- const action2 = resolve(mapping.action, record, "action", errors) ?? "";
3266
- const parentID = resolve(mapping.parentID, record, "parentID", errors) ?? "";
3267
- const childEPCs = resolveList(mapping.childEPCs, record, "childEPCs", errors);
3268
- const childQuantityList = resolveQuantityList(mapping.childQuantityList, record, "childQuantityList", errors);
3269
- return {
3270
- event: aggregationEvent({
3271
- eventTime,
3272
- action: action2,
3273
- bizStep,
3274
- disposition,
3275
- parentID,
3276
- /* 없는 쪽은 **필드를 만들지 않는다** — 빈 배열을 실으면 「자식이 없다」고 말하는 것이 된다. */
3277
- ...childEPCs.length ? { childEPCs } : {},
3278
- ...childQuantityList.length ? { childQuantityList } : {},
3279
- ...errorDeclaration ? { errorDeclaration } : {},
3280
- readPoint,
3281
- bizLocation
3282
- }),
3283
- errors
3284
- };
3285
- }
3286
- const action = resolve(mapping.action, record, "action", errors) ?? "";
3287
- const epc = resolve(mapping.epc, record, "epc", errors);
3288
- const quantityList = resolveQuantityList(mapping.quantityList, record, "quantityList", errors);
3289
- if (!epc && !quantityList.length) {
3290
- 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");
3291
- }
3292
- const ilmd = resolveObject(mapping.ilmd, record, "ilmd", errors);
3293
- return {
3294
- event: objectEvent({
3295
- eventTime,
3296
- action,
3297
- bizStep,
3298
- disposition,
3299
- epcList: epc ? [epc] : [],
3300
- ...quantityList.length ? { quantityList } : {},
3301
- ...ilmd ? { ilmd } : {},
3302
- ...errorDeclaration ? { errorDeclaration } : {},
3303
- readPoint,
3304
- bizLocation
3305
- }),
3306
- errors
3307
- };
3308
- }
3309
- function mapRecord(record, mapping, eventTime) {
3310
- return mapRecordChecked(record, mapping, eventTime).event;
3311
- }
3312
- function ingest(records, rules, opts) {
3313
- const ruleByType = new Map(rules.map((r) => [r.sourceType, r]));
3314
- const accepted = [];
3315
- const rejected = [];
3316
- let seq = 0;
3317
- for (const record of records) {
3318
- const rule = ruleByType.get(String(record["sourceType"]));
3319
- if (!rule) {
3320
- rejected.push({ record, errors: [`\uB9E4\uCE6D \uB8F0 \uC5C6\uC74C: sourceType=${record["sourceType"]}`] });
3321
- continue;
3322
- }
3323
- const timeErrors = [];
3324
- const eventTime = (opts.eventTimePath ? resolve("$." + opts.eventTimePath, record, "eventTime", timeErrors) : void 0) ?? opts.defaultEventTime;
3325
- const { event: ev, errors: mapErrors } = mapRecordChecked(record, rule.mapping, eventTime);
3326
- const errors = [...timeErrors, ...mapErrors, ...validateEpcisEvent(ev)];
3327
- if (errors.length) {
3328
- rejected.push({ record, errors });
3329
- continue;
3330
- }
3331
- const note = typeof record["description"] === "string" ? String(record["description"]).trim() : "";
3332
- accepted.push({
3333
- eventId: `${opts.tenantId}-ingest-${++seq}`,
3334
- eventType: `epcis.${ev.type}`,
3335
- eventTime: ev.eventTime,
3336
- tenantId: opts.tenantId,
3337
- ...note ? { description: note } : {},
3338
- data: ev
3339
- });
3340
- }
3341
- return { accepted, rejected };
3342
- }
3343
1593
 
3344
- // ../operations-contract/dist/iso-duration.js
1594
+ // ../ops-contract/dist/iso-duration.js
3345
1595
  var RE = /^(-)?P(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
3346
1596
  function parseIsoDuration(text) {
3347
1597
  if (typeof text !== "string")
@@ -3367,41 +1617,7 @@ function parseIsoDuration(text) {
3367
1617
  return sign ? -ms2 : ms2;
3368
1618
  }
3369
1619
 
3370
- // ../operations-contract/dist/master-data.js
3371
- var VOCABULARY_TYPE = {
3372
- /** 품목·로트 클래스의 속성 — 유통기한·로트번호가 여기 온다. */
3373
- epcClass: "urn:epcglobal:epcis:vtype:EPCClass",
3374
- /** 관측 지점의 속성. */
3375
- readPoint: "urn:epcglobal:epcis:vtype:ReadPoint",
3376
- /** 업무 자리의 속성. */
3377
- bizLocation: "urn:epcglobal:epcis:vtype:BizLocation"
3378
- };
3379
- function isMasterDataRecord(record) {
3380
- if (!record || typeof record !== "object")
3381
- return false;
3382
- const r = record;
3383
- return typeof r.vocabularyType === "string" && !!r.vocabularyType && typeof r.id === "string" && !!r.id && isPlainObject(r.attributes);
3384
- }
3385
- function isPlainObject(v) {
3386
- return !!v && typeof v === "object" && !Array.isArray(v);
3387
- }
3388
- function ingestMasterData(records) {
3389
- const arr = Array.isArray(records) ? records : records ? [records] : [];
3390
- const accepted = [];
3391
- const rejected = [];
3392
- for (const r of arr) {
3393
- const errors = [];
3394
- if (!isMasterDataRecord(r))
3395
- errors.push("vocabularyType \xB7 id \xB7 attributes \uAC00 \uC788\uC5B4\uC57C \uD55C\uB2E4");
3396
- else if (!Object.keys(r.attributes).length)
3397
- errors.push("attributes \uAC00 \uBE44\uC5C8\uB2E4 \u2014 \uC544\uBB34 \uB73B\uC774 \uC5C6\uB294 \uC9C4\uC220\uC774\uB2E4");
3398
- if (errors.length)
3399
- rejected.push({ record: r, errors });
3400
- else
3401
- accepted.push({ vocabularyType: r.vocabularyType, id: r.id, attributes: { ...r.attributes } });
3402
- }
3403
- return { accepted, rejected };
3404
- }
1620
+ // ../ops-contract/dist/master-data.js
3405
1621
  function expiryFromAttributes(attrs) {
3406
1622
  return readEpochMs(attrs?.[ILMD_ATTR.expiry]);
3407
1623
  }
@@ -3418,7 +1634,7 @@ function readEpochMs(raw) {
3418
1634
  return Number.isNaN(at) ? void 0 : at;
3419
1635
  }
3420
1636
 
3421
- // ../operations-contract/dist/operational-ingest.js
1637
+ // ../ops-contract/dist/operational-ingest.js
3422
1638
  var TASK_STATUS = ["created", "assigned", "in-progress", "completed"];
3423
1639
  var EQUIPMENT_STATUS = ["idle", "busy", "down"];
3424
1640
  var PERSON_STATUS = ["idle", "busy"];
@@ -3625,154 +1841,8 @@ var SPECS = {
3625
1841
  }
3626
1842
  }
3627
1843
  };
3628
- function operationalKindOf(record) {
3629
- if (!record || typeof record !== "object")
3630
- return void 0;
3631
- const r = record;
3632
- if (r.epc !== void 0 || r.meterId !== void 0 || r.equipmentId !== void 0)
3633
- return void 0;
3634
- const has = (k) => typeof r[k] === "string" && r[k].trim().length > 0;
3635
- if (has("moverId"))
3636
- return r.good !== void 0 ? "quality" : "equipment";
3637
- if (has("personId"))
3638
- return "person";
3639
- if (has("assetId"))
3640
- return "asset";
3641
- if (has("taskId"))
3642
- return "task";
3643
- if (has("orderId"))
3644
- return "order";
3645
- if (has("testableObjectId"))
3646
- return "test";
3647
- if (has("locationId") && has("propertyId"))
3648
- return "observation";
3649
- if (has("completeAxis"))
3650
- return "complete";
3651
- return void 0;
3652
- }
3653
- function isOperationalRecord(record) {
3654
- return operationalKindOf(record) !== void 0;
3655
- }
3656
- function ingestOperationalRecords(records, opts) {
3657
- const arr = Array.isArray(records) ? records : records ? [records] : [];
3658
- const accepted = [];
3659
- const rejected = [];
3660
- let seq = 0;
3661
- for (const record of arr) {
3662
- const kind = operationalKindOf(record);
3663
- if (!kind) {
3664
- rejected.push({
3665
- record,
3666
- errors: ["\uC5B4\uB290 \uC6B4\uC601 \uC0AC\uC2E4\uC778\uC9C0 \uBAA8\uB978\uB2E4 \u2014 \uC815\uCCB4 \uD544\uB4DC\uAC00 \uD544\uC694\uD558\uB2E4(taskId \xB7 moverId(+good=\uD488\uC9C8) \xB7 personId \xB7 assetId \xB7 orderId)"]
3667
- // vocabulary-guard: allow 거부 이유가 계약 필드 이름을 말한다
3668
- });
3669
- continue;
3670
- }
3671
- const spec = SPECS[kind];
3672
- const r = record;
3673
- const errors = [];
3674
- const ENVELOPE_FIELDS = ["at", "description"];
3675
- const unknown = Object.keys(r).filter((k) => !ENVELOPE_FIELDS.includes(k) && spec.fields[k] === void 0);
3676
- if (unknown.length)
3677
- errors.push(`${kind}: \uACC4\uC57D\uC5D0 \uC5C6\uB294 \uD544\uB4DC \u2014 ${unknown.join(", ")}`);
3678
- for (const name of spec.required) {
3679
- const v = r[name];
3680
- if (v === void 0 || v === null || v === "")
3681
- errors.push(`${kind}: ${name} \uC5C6\uC74C \u2014 \uC9C0\uC5B4\uB0BC \uC218 \uC5C6\uB294 \uAC12\uC774\uB2E4`);
3682
- }
3683
- const data = {};
3684
- for (const [name, type] of Object.entries(spec.fields)) {
3685
- const v = r[name];
3686
- if (v === void 0 || v === null || v === "")
3687
- continue;
3688
- switch (type) {
3689
- case "string": {
3690
- if (typeof v !== "string") {
3691
- errors.push(`${kind}.${name} \uC774 \uBB38\uC790\uC5F4\uC774 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
3692
- break;
3693
- }
3694
- const allowed = spec.enums?.[name];
3695
- if (allowed && !allowed.includes(v)) {
3696
- errors.push(`${kind}.${name} \uC774 \uCEE4\uB110\uC774 \uB2E4\uB8E8\uB294 \uB0B1\uB9D0\uC774 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)} \u2014 \uBC1B\uB294 \uAC12\uC740 ${allowed.join(" \xB7 ")}`);
3697
- break;
3698
- }
3699
- data[name] = v;
3700
- break;
3701
- }
3702
- case "number": {
3703
- const n = Number(v);
3704
- if (typeof v === "boolean" || !Number.isFinite(n)) {
3705
- errors.push(`${kind}.${name} \uAC00 \uC218\uAC00 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
3706
- break;
3707
- }
3708
- if (name === "progress" && (n < 0 || n > 1)) {
3709
- errors.push(`${kind}.progress \uB294 0~1 \uBE44\uC728\uC774\uB2E4: ${n}`);
3710
- break;
3711
- }
3712
- if ((name === "requested" || name === "fulfilled" || name === "goodCount" || name === "scrapCount") && n < 0) {
3713
- errors.push(`${kind}.${name} \uAC00 \uC74C\uC218\uB2E4: ${n}`);
3714
- break;
3715
- }
3716
- data[name] = n;
3717
- break;
3718
- }
3719
- case "boolean": {
3720
- if (typeof v !== "boolean") {
3721
- errors.push(`${kind}.${name} \uAC00 \uCC38/\uAC70\uC9D3\uC774 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
3722
- break;
3723
- }
3724
- data[name] = v;
3725
- break;
3726
- }
3727
- case "string[]": {
3728
- if (!Array.isArray(v) || v.some((x) => typeof x !== "string")) {
3729
- errors.push(`${kind}.${name} \uAC00 \uBB38\uC790\uC5F4 \uBC30\uC5F4\uC774 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
3730
- break;
3731
- }
3732
- data[name] = v.slice();
3733
- break;
3734
- }
3735
- case "object": {
3736
- if (Array.isArray(v) || typeof v !== "object") {
3737
- errors.push(`${kind}.${name} \uAC00 \uAC1D\uCCB4\uAC00 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
3738
- break;
3739
- }
3740
- data[name] = { ...v };
3741
- break;
3742
- }
3743
- case "object[]": {
3744
- if (!Array.isArray(v) || v.some((x) => !x || typeof x !== "object")) {
3745
- errors.push(`${kind}.${name} \uAC00 \uAC1D\uCCB4 \uBC30\uC5F4\uC774 \uC544\uB2C8\uB2E4: ${JSON.stringify(v)}`);
3746
- break;
3747
- }
3748
- data[name] = v.map((x) => ({ ...x }));
3749
- break;
3750
- }
3751
- }
3752
- }
3753
- const at = String(r.at ?? "").trim() || opts.defaultEventTime;
3754
- const atMs = at ? Date.parse(at) : Number.NaN;
3755
- if (!Number.isFinite(atMs)) {
3756
- errors.push(`${kind}: at \uC5C6\uC74C/\uD615\uC2DD \uC624\uB958 \u2014 \uC2DC\uAC01 \uC5C6\uC774\uB294 \uB2A6\uAC8C \uC628 \uC61B \uC0AC\uC2E4\uC744 \uAC78\uB7EC\uB0BC \uC218 \uC5C6\uB2E4`);
3757
- }
3758
- if (errors.length) {
3759
- rejected.push({ record, errors });
3760
- continue;
3761
- }
3762
- const note = typeof r.description === "string" ? String(r.description).trim() : "";
3763
- accepted.push({
3764
- eventId: `${opts.tenantId}-op-${kind}-${++seq}`,
3765
- eventType: spec.eventType,
3766
- eventTime: new Date(atMs).toISOString(),
3767
- tenantId: opts.tenantId,
3768
- ...note ? { description: note } : {},
3769
- data
3770
- });
3771
- }
3772
- return { accepted, rejected };
3773
- }
3774
1844
 
3775
- // ../operations-contract/dist/operations-capability.js
1845
+ // ../ops-contract/dist/operations-capability.js
3776
1846
  function workingHoursBetween(calendar, fromMs, toMs, utcOffsetMinutes) {
3777
1847
  if (!(toMs > fromMs))
3778
1848
  return 0;
@@ -3842,116 +1912,6 @@ function operationsCapabilityOf(input) {
3842
1912
  };
3843
1913
  }
3844
1914
 
3845
- // ../operations-contract/dist/scenario-validate.js
3846
- var DISTRIBUTIONS = /* @__PURE__ */ new Set(["poisson", "uniform", "constant", "profile"]);
3847
- var isNum = (v) => typeof v === "number" && Number.isFinite(v);
3848
- var bad = (errorCode, errorParams) => ({ ok: false, errorCode, errorParams });
3849
- function validateGenerator(g, at) {
3850
- if (!g || typeof g !== "object")
3851
- return bad("scenario-generator-invalid", { at });
3852
- if (!g.kind || typeof g.kind !== "string")
3853
- return bad("scenario-generator-kind-required", { at });
3854
- const rate = g.rate;
3855
- if (!rate || typeof rate !== "object")
3856
- return bad("scenario-rate-required", { at, kind: g.kind });
3857
- if (!isNum(rate.meanPerHour) || rate.meanPerHour < 0)
3858
- return bad("scenario-rate-mean-invalid", { at, kind: g.kind });
3859
- if (!DISTRIBUTIONS.has(rate.distribution))
3860
- return bad("scenario-rate-distribution-invalid", { at, kind: g.kind, distribution: String(rate.distribution ?? "") });
3861
- if (rate.distribution === "profile" && !Array.isArray(rate.profile))
3862
- return bad("scenario-rate-profile-required", { at, kind: g.kind });
3863
- const content = g.content;
3864
- if (!content || typeof content !== "object")
3865
- return bad("scenario-content-required", { at, kind: g.kind });
3866
- if (!Array.isArray(content.skuMix) || content.skuMix.length === 0)
3867
- return bad("scenario-sku-mix-required", { at, kind: g.kind });
3868
- for (const s of content.skuMix) {
3869
- if (!s?.gtin || typeof s.gtin !== "string")
3870
- return bad("scenario-sku-gtin-required", { at, kind: g.kind });
3871
- if (!isNum(s.weight) || s.weight <= 0)
3872
- return bad("scenario-sku-weight-invalid", { at, kind: g.kind, gtin: String(s.gtin) });
3873
- }
3874
- const q = content.qtyPerLine;
3875
- if (!q || !isNum(q.min) || !isNum(q.max))
3876
- return bad("scenario-qty-required", { at, kind: g.kind });
3877
- if (q.min < 0 || q.max < q.min)
3878
- return bad("scenario-qty-range-invalid", { at, kind: g.kind, min: q.min, max: q.max });
3879
- const l = content.linesPerOrder;
3880
- if (l && (!isNum(l.min) || !isNum(l.max) || l.min < 1 || l.max < l.min))
3881
- return bad("scenario-lines-range-invalid", { at, kind: g.kind });
3882
- if (g.stimulus !== void 0 && g.stimulus !== "arrival" && g.stimulus !== "order") {
3883
- return bad("scenario-stimulus-invalid", { at, kind: g.kind, stimulus: String(g.stimulus) });
3884
- }
3885
- return { ok: true };
3886
- }
3887
- function validateScenario(def) {
3888
- if (!def || typeof def !== "object")
3889
- return bad("scenario-invalid");
3890
- if (def.seed !== void 0 && !isNum(def.seed))
3891
- return bad("scenario-seed-invalid");
3892
- if (def.speed !== void 0 && (!isNum(def.speed) || def.speed <= 0))
3893
- return bad("scenario-speed-invalid");
3894
- if (def.horizon !== void 0 && (!isNum(def.horizon) || def.horizon < 0))
3895
- return bad("scenario-horizon-invalid");
3896
- const gens = def.generators;
3897
- if (gens === void 0)
3898
- return { ok: true };
3899
- if (!Array.isArray(gens))
3900
- return bad("scenario-generators-invalid");
3901
- for (let i = 0; i < gens.length; i++) {
3902
- const r = validateGenerator(gens[i], i);
3903
- if (!r.ok)
3904
- return r;
3905
- }
3906
- return { ok: true };
3907
- }
3908
-
3909
- // ../operations-contract/dist/vocabulary.js
3910
- var RETIRED_VOCABULARY = ["mover", "Mover", "MOVER", "node", "Node", "NODE", "BoardDef", "loadBoard"];
3911
- var VOCABULARY_EXCEPTIONS = [
3912
- /* ── 저널 와이어 필드 — append-only 역사이므로 이름을 바꾸지 않는다 ───────
3913
- * 같은 사실이 시점에 따라 다른 키로 들어가면 낡은 이름보다 나쁘다. 개명은 이벤트 스키마
3914
- * 버저닝과 함께 다룬다. (개명 시점 실측: moverId 8,965행 · fromNode/toNode 9,679행) */
3915
- { token: "moverId", why: "journal wire field \u2014 renaming would mix two keys for one fact across history" },
3916
- { token: "fromNode", why: "journal wire field (task.status payload)" },
3917
- { token: "toNode", why: "journal wire field (task.status payload)" },
3918
- /* ── 트윈 모델의 옛 세대 키 — 저장된 데이터라 읽어는 줘야 한다 ────────────────
3919
- * 설비 배열의 이름은 세 세대를 거쳤다(movers → equipmentList → equipment). 저장된 보드에는
3920
- * 셋이 섞여 있고(실측 23개 중 13개가 `movers`), 하나라도 안 읽으면 그 트윈 모델은 **설비가 0인 공장**
3921
- * 으로 조용히 읽힌다 — 화면의 설비 수가 0이 되고 용량 판정에서 자원이 사라진다. 쓰는 곳은
3922
- * `readBoardEquipment` 한 곳뿐이고, 거기서 새 이름으로 정규화해 내보낸다. */
3923
- { token: "movers", why: "legacy board key (movers \u2192 equipmentList \u2192 equipment); read-only normalization in readBoardEquipment, 13 stored boards still use it" },
3924
- /* ── 씬 컴포넌트 타입 — 트윈 모델에 저장된 값이고, 뜻이 어긋나지도 않는다 ──────
3925
- * 보드 7개가 이 타입으로 컴포넌트를 담고 있어 개명하면 그 컴포넌트가 조용히 안 그려진다.
3926
- * 그리고 씬에서 이 이름은 자원이 아니라 **움직임 표현**을 가리킨다(표준과 충돌 아님). */
3927
- { token: "twin-mover", why: "scene component type persisted in boards; names a motion representation, not a resource" },
3928
- { token: "TwinMover", why: "class backing the persisted scene component type above" },
3929
- /* ── 플랫폼·언어 어휘 — 우리 도메인 낱말이 아니다 ─────────────────────── */
3930
- { token: "node_modules", why: "npm path" },
3931
- { token: "node:", why: "Node.js builtin module specifier" },
3932
- { token: "nodemon", why: "tool name" },
3933
- { token: "Node.js", why: "platform name" },
3934
- { token: "NodeJS", why: "TypeScript global namespace" },
3935
- { token: "nodeName", why: "DOM API" },
3936
- { token: "childNodes", why: "DOM API" },
3937
- { token: "appendChild", why: "DOM API (no match, kept for clarity)" }
3938
- ];
3939
- var GUARD_PRAGMA = "vocabulary-guard: allow";
3940
- function retiredVocabularyIn(line) {
3941
- if (line.includes(GUARD_PRAGMA))
3942
- return [];
3943
- let rest = line;
3944
- for (const { token } of VOCABULARY_EXCEPTIONS)
3945
- rest = rest.split(token).join(" ");
3946
- const hits = [];
3947
- for (const word of RETIRED_VOCABULARY) {
3948
- const re = new RegExp(`[A-Za-z_]*${word}[A-Za-z_]*`, "g");
3949
- for (const m of rest.matchAll(re))
3950
- hits.push(m[0]);
3951
- }
3952
- return hits;
3953
- }
3954
-
3955
1915
  // src/divergence.ts
3956
1916
  function diffBy(predicted, actual, idOf, valOf) {
3957
1917
  const p = new Map(predicted.map((e) => [idOf(e), valOf(e)]));
@@ -4121,7 +2081,7 @@ var TwinObserver = class {
4121
2081
  };
4122
2082
 
4123
2083
  // src/class-master-table.ts
4124
- function isPlainObject2(v) {
2084
+ function isPlainObject(v) {
4125
2085
  return !!v && typeof v === "object" && !Array.isArray(v);
4126
2086
  }
4127
2087
  var ClassMasterTable = class {
@@ -4172,8 +2132,8 @@ var ClassMasterTable = class {
4172
2132
  }
4173
2133
  restore(saved) {
4174
2134
  this.byId.clear();
4175
- if (!isPlainObject2(saved)) return;
4176
- for (const [id, attrs] of Object.entries(saved)) if (isPlainObject2(attrs)) this.byId.set(id, { ...attrs });
2135
+ if (!isPlainObject(saved)) return;
2136
+ for (const [id, attrs] of Object.entries(saved)) if (isPlainObject(attrs)) this.byId.set(id, { ...attrs });
4177
2137
  }
4178
2138
  clear() {
4179
2139
  this.byId.clear();
@@ -9050,14 +7010,14 @@ var MesKernel = class extends FlowEngine {
9050
7010
  assertNoDoubleProduction(ops) {
9051
7011
  const finals = new Set((this.productionSpec?.definition?.recipes ?? []).flatMap((r) => (r.outputs ?? []).map((o) => o.material)));
9052
7012
  if (!finals.size) return;
9053
- const bad2 = ops.filter(
7013
+ const bad = ops.filter(
9054
7014
  (o) => (o.materialSpecification ?? []).some(
9055
7015
  (m) => m.use === "produced" && m.materialDefinition && finals.has(this.materialKeyOfClass(m.materialDefinition, o.key))
9056
7016
  )
9057
7017
  ).map((o) => o.key);
9058
- if (!bad2.length) return;
7018
+ if (!bad.length) return;
9059
7019
  throw new Error(
9060
- `MES recipe already produces its output \u2014 operations [${bad2.join(", ")}] must not also declare that same material as materialSpecification use:'produced' (that would create the same output twice). Declare intermediate outputs freely; the final output belongs to the recipe.`
7020
+ `MES recipe already produces its output \u2014 operations [${bad.join(", ")}] must not also declare that same material as materialSpecification use:'produced' (that would create the same output twice). Declare intermediate outputs freely; the final output belongs to the recipe.`
9061
7021
  );
9062
7022
  }
9063
7023
  /**
@@ -9079,17 +7039,17 @@ var MesKernel = class extends FlowEngine {
9079
7039
  const def = this.productionSpec?.definition;
9080
7040
  if (!def?.recipes?.length) return;
9081
7041
  const stepsOf = new Map((def.routes ?? []).map((r) => [r.key, new Set(r.steps ?? [])]));
9082
- const bad2 = [];
7042
+ const bad = [];
9083
7043
  for (const rc of def.recipes) {
9084
7044
  const tagged = rc.inputs.filter((i) => i.operation);
9085
7045
  if (tagged.length) {
9086
7046
  const steps = rc.route ? stepsOf.get(rc.route) : void 0;
9087
7047
  if (!steps) {
9088
- bad2.push(`recipe '${rc.key}' tags inputs with an operation but declares no route \u2014 the tag has nothing to match`);
7048
+ bad.push(`recipe '${rc.key}' tags inputs with an operation but declares no route \u2014 the tag has nothing to match`);
9089
7049
  } else {
9090
7050
  for (const line of tagged) {
9091
7051
  if (!steps.has(line.operation)) {
9092
- bad2.push(
7052
+ bad.push(
9093
7053
  `recipe '${rc.key}' input '${line.material}' is tagged for operation '${line.operation}', which is not a step of its route '${rc.route}' (steps: ${[...steps].join(" > ") || "(none)"}) \u2014 that material would never be claimed and the order would wait forever with no reason on screen`
9094
7054
  );
9095
7055
  }
@@ -9099,13 +7059,13 @@ var MesKernel = class extends FlowEngine {
9099
7059
  const withTag = new Set(tagged.map((i) => i.material));
9100
7060
  for (const line of rc.inputs) {
9101
7061
  if (!line.operation && withTag.has(line.material)) {
9102
- bad2.push(
7062
+ bad.push(
9103
7063
  `recipe '${rc.key}' declares material '${line.material}' both with and without an operation tag \u2014 the untagged line is claimed at order start and the tagged one at its step, so it would be consumed twice`
9104
7064
  );
9105
7065
  }
9106
7066
  }
9107
7067
  }
9108
- if (bad2.length) throw new Error(bad2.join("; "));
7068
+ if (bad.length) throw new Error(bad.join("; "));
9109
7069
  }
9110
7070
  /**
9111
7071
  * **레시피 모드에서는 MES 가 산출을 소유한다** — 코어는 비켜선다(§`producesOwnOutputs`).
@@ -11384,192 +9344,43 @@ function electricityCost(input) {
11384
9344
  0 && (module.exports = {
11385
9345
  ATTENTION_DEFAULTS,
11386
9346
  ATTENTION_PROPERTY,
11387
- BIZSTEP,
11388
- BTT,
11389
- BTT_DELIVERY,
11390
- BTT_PRODORDER,
11391
- CAPABILITIES,
11392
- CAPABILITY_KEYS,
11393
- CBV_BIZSTEP,
11394
- CMD,
11395
9347
  ClassMasterTable,
11396
9348
  DEMAND_WINDOW_MS,
11397
- DISP,
11398
- DOMAIN_CATALOG,
11399
- DOMAIN_SYSTEMS,
11400
- EMS_EQUIPMENT_TYPES,
11401
- EMS_LOCATION_TYPES,
11402
- EMS_PROPERTY,
11403
- EMS_PROPERTY_SPEC,
11404
- EMS_TYPES,
11405
- ENERGY_EVENT,
11406
- EPCIS_CONTEXT,
11407
- EPCIS_EVENT,
11408
- EQUIPMENT_LEVEL,
11409
9349
  EmsKernel,
11410
9350
  EventJournal,
11411
9351
  FlowEngine,
11412
- GUARD_PRAGMA,
11413
- ILMD_ATTR,
11414
9352
  ItemStore,
11415
- LOCATION_SATURATION_NEAR,
11416
- MATERIAL_PROPERTY,
11417
- MES_BIZSTEP,
11418
- MES_LOCATION_TYPES,
11419
- MES_TYPES,
11420
- METER_DIRECTION,
11421
9353
  MesKernel,
11422
- OBSERVATION_BASIS,
11423
9354
  OPERATION_PROPERTY,
11424
- OP_EVENT,
11425
- OP_PARAM,
11426
- ORDER_TERMINAL_STATUS,
11427
9355
  ObservedReducer,
11428
- PRIORITY_UNSET,
11429
- RETIRED_VOCABULARY,
11430
9356
  StateProjector,
11431
- TWIN_AXES,
11432
- TWIN_PROPERTIES,
11433
- TWIN_RELATIONS,
11434
9357
  TwinHistory,
11435
9358
  TwinObserver,
11436
9359
  TwinRuntime,
11437
- UTC_OFFSET,
11438
- VOCABULARY_EXCEPTIONS,
11439
- VOCABULARY_TYPE,
11440
- WMS_LOCATION_TYPES,
11441
- WMS_TYPES,
11442
9360
  WmsKernel,
11443
- YARD_BIZSTEP,
11444
- YMS_LOCATION_TYPES,
11445
- YMS_TYPES,
11446
9361
  YmsKernel,
11447
- activeShiftAt,
11448
- activeShiftOf,
11449
- aggregationEvent,
11450
- analyzeCapacity,
11451
9362
  attributeEnergy,
11452
- axesOfSystem,
11453
- axisAppliesTo,
11454
- axisInfo,
11455
- axisSource,
11456
- bizTransactionUri,
11457
- capabilitiesForType,
11458
- capabilityOf,
11459
- classClosure,
11460
- classIdentifierViolation,
11461
- commandsOf,
11462
9363
  compareStates,
11463
9364
  computeOee,
11464
9365
  constantDuration,
11465
- conversionFactorOf,
11466
9366
  counterfactualAt,
11467
- criterionSaysNothing,
11468
9367
  demandWindowStart,
11469
9368
  deriveAttentions,
11470
- documentPath,
11471
- dueStatusOf,
11472
- effectivityAt,
11473
- electricalUpstreamOf,
11474
9369
  electricityCost,
11475
9370
  energyIntensity,
11476
9371
  energyOfWindows,
11477
- expiryFromAttributes,
11478
9372
  fefoPolicy,
11479
9373
  firstFitPolicy,
11480
9374
  foldJobResponses,
11481
9375
  foldTaskRecords,
11482
- gdtiUri,
11483
- generationFractionAt,
11484
- graiUri,
11485
- gs1KeyDigitViolation,
11486
- hierarchyOf,
11487
- identityGroundingOf,
11488
- inWorkCalendar,
11489
- inWorkCalendarAt,
11490
- ingest,
11491
- ingestEnergyBillRecords,
11492
- ingestEnergyEquipmentRecords,
11493
- ingestEnergyGenerationPeriodRecords,
11494
- ingestEnergyGenerationPriceRecords,
11495
- ingestEnergyGenerationRecords,
11496
- ingestEnergyRecords,
11497
- ingestEnergyTariffBasisRecords,
11498
- ingestEnergyUsagePeriodRecords,
11499
- ingestMasterData,
11500
- ingestOperationalRecords,
11501
- isAggregationRecord,
11502
- isElectricalLocationType,
11503
- isEnergyBillRecord,
11504
- isEnergyEquipmentRecord,
11505
- isEnergyGenerationPeriodRecord,
11506
- isEnergyGenerationPriceRecord,
11507
- isEnergyGenerationRecord,
11508
- isEnergyRecord,
11509
- isEnergyTariffBasisRecord,
11510
- isEnergyUsagePeriodRecord,
11511
- isEpcisEventType,
11512
- isEquipmentLevel,
11513
- isMasterDataRecord,
11514
- isOperationalRecord,
11515
- isOrderTerminal,
11516
- isTransformationRecord,
11517
- isoDurationHours,
11518
- itemKeyOf,
11519
9376
  journalProjector,
11520
- judgeAgainstSpec,
11521
- levelOfLocationType,
11522
- lgtinClass,
11523
- localDayIndexAt,
11524
- localDayStartMs,
11525
- locationStatusOf,
11526
- lotFromAttributes,
11527
- mapRecord,
11528
- mapRecordChecked,
11529
- meetsTests,
11530
- minuteOfDayAt,
11531
9377
  monteCarloForecast,
11532
9378
  monteCarloForecastAsync,
11533
- objectEvent,
11534
- objectUri,
11535
- observationAt,
11536
- offCalendarAt,
11537
- offCalendarReasonAt,
11538
- operationalKindOf,
11539
- operationsCapabilityOf,
11540
- outsideLimit,
11541
- parseEpc,
11542
- parseIsoDuration,
11543
9379
  partialFitPolicy,
11544
- periodFactId,
11545
- priorityRank,
11546
- propertiesOf,
11547
- quantityIn,
11548
- readBoardAssets,
11549
- readBoardEquipment,
11550
- readBoardLocations,
11551
- relationsFrom,
11552
- relationsTo,
9380
+ planMakeToOrder,
9381
+ producedByIndex,
11553
9382
  replay,
11554
9383
  replayFrom,
11555
9384
  replaySegments,
11556
- replayWithCheckpoint,
11557
- requiredTestsFor,
11558
- resolveSubject,
11559
- retiredVocabularyIn,
11560
- sgtinClass,
11561
- sgtinUri,
11562
- ssccUri,
11563
- stateFieldsOf,
11564
- subLotIdOf,
11565
- testEvidenceGaps,
11566
- testPassedAt,
11567
- transactionEvent,
11568
- transformationEvent,
11569
- validateDomainDefinition,
11570
- validateEpcisEvent,
11571
- validateScenario,
11572
- weekdayAt,
11573
- workingHoursBetween,
11574
- workingTimeOfWeek
9385
+ replayWithCheckpoint
11575
9386
  });