@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.
- package/dist/class-master-table.d.ts +1 -1
- package/dist/counterfactual.d.ts +1 -1
- package/dist/divergence.d.ts +1 -1
- package/dist/divergence.js +1 -1
- package/dist/ems-kernel.d.ts +1 -1
- package/dist/ems-kernel.js +4 -4
- package/dist/event-journal.d.ts +1 -1
- package/dist/flow-engine.d.ts +6 -6
- package/dist/flow-engine.js +8 -8
- package/dist/forecast.d.ts +1 -1
- package/dist/index.d.ts +2 -22
- package/dist/index.js +17 -21
- package/dist/job-response.d.ts +1 -1
- package/dist/kernel.d.ts +1 -1
- package/dist/kernel.js +4 -4
- package/dist/make-to-order.d.ts +1 -1
- package/dist/mes-kernel.d.ts +3 -3
- package/dist/mes-kernel.js +4 -4
- package/dist/observed-reducer.d.ts +2 -2
- package/dist/observed-reducer.js +3 -3
- package/dist/runtime.d.ts +1 -1
- package/dist/task-fold.d.ts +1 -1
- package/dist/twin-observer.d.ts +1 -1
- package/dist/yms-kernel.d.ts +1 -1
- package/dist/yms-kernel.js +3 -3
- package/dist-cjs/index.cjs +50 -2239
- package/package.json +2 -2
package/dist-cjs/index.cjs
CHANGED
|
@@ -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
|
-
|
|
182
|
-
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
-
// ../
|
|
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
|
|
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 (!
|
|
4176
|
-
for (const [id, attrs] of Object.entries(saved)) if (
|
|
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
|
|
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 (!
|
|
7018
|
+
if (!bad.length) return;
|
|
9059
7019
|
throw new Error(
|
|
9060
|
-
`MES recipe already produces its output \u2014 operations [${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
11545
|
-
|
|
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
|
});
|