@operato/twin-kernel 0.7.83 → 0.7.84
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/energy-ingest.d.ts +28 -0
- package/dist/energy-ingest.js +81 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist-cjs/index.cjs +68 -0
- package/package.json +1 -1
package/dist/energy-ingest.d.ts
CHANGED
|
@@ -115,6 +115,34 @@ export interface EnergyUsagePeriodRecord {
|
|
|
115
115
|
basis?: string;
|
|
116
116
|
}
|
|
117
117
|
export declare function isEnergyUsagePeriodRecord(record: unknown): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* 마감된 **발전 기간** — 「이 기간에 이 설비가 이만큼 냈다」.
|
|
120
|
+
*
|
|
121
|
+
* 라이브에서는 커널이 스스로 마감해 낸다(§`closeGenerationPeriod`). 이 문은 **지난 기록을 채울 때**
|
|
122
|
+
* 쓴다 — 원본이 날짜별 발전량을 주는 현장에서, 트윈이 없던 동안의 날들을 뒤늦게 넣는다.
|
|
123
|
+
*
|
|
124
|
+
* 상태를 바꾸지 않는다. 저널에만 적히고, 저널을 읽는 쪽(성과 계산)이 그 사실을 본다.
|
|
125
|
+
*/
|
|
126
|
+
export interface EnergyGenerationPeriodRecord {
|
|
127
|
+
equipmentId: string;
|
|
128
|
+
from: string;
|
|
129
|
+
to: string;
|
|
130
|
+
kWh: number;
|
|
131
|
+
/** 원본이 준 적산의 종류 — 총량을 어떻게 얻었는지가 이것으로 갈린다. */
|
|
132
|
+
accumulation?: string;
|
|
133
|
+
/** 그 기간에서 처음·마지막으로 관측한 시각. 기간 경계와 다르면 그만큼 재지 않았다. */
|
|
134
|
+
observedFrom?: string;
|
|
135
|
+
observedTo?: string;
|
|
136
|
+
}
|
|
137
|
+
export declare function isEnergyGenerationPeriodRecord(record: unknown): boolean;
|
|
138
|
+
/**
|
|
139
|
+
* 마감된 발전 기간을 봉투로 — **사건 시각은 기간의 끝**이다(그때 성립한다).
|
|
140
|
+
*
|
|
141
|
+
* 적산의 문(`ingestEnergyGenerationRecords`)과 겹치지 않는다: 그쪽은 구간이 없는 시점의 값이고
|
|
142
|
+
* 이쪽은 구간이 있는 마감된 사실이다. 겹치면 지난 기록이 「지금 적산」으로 읽혀 트윈의 시계가
|
|
143
|
+
* 과거로 끌린다.
|
|
144
|
+
*/
|
|
145
|
+
export declare function ingestEnergyGenerationPeriodRecords(records: EnergyGenerationPeriodRecord | EnergyGenerationPeriodRecord[] | undefined | null, opts: EnergyIngestOptions): EnergyIngestResult;
|
|
118
146
|
/** 발전 단가 — 이 기간에 낸 전기 1kWh 의 값. 날마다 바뀐다. */
|
|
119
147
|
export interface EnergyGenerationPriceRecord {
|
|
120
148
|
from: string;
|
package/dist/energy-ingest.js
CHANGED
|
@@ -268,6 +268,9 @@ export function ingestEnergyEquipmentRecords(records, opts) {
|
|
|
268
268
|
}
|
|
269
269
|
/** 이 레코드가 발전 적산인가 — 어느 갈래로 보낼지를 한 곳에서 정한다. */
|
|
270
270
|
export function isEnergyGenerationRecord(record) {
|
|
271
|
+
/* 구간이 있으면 마감된 발전 기간이지 시점의 적산이 아니다 — 겹치면 지난 기록이 「지금」으로 읽힌다. */
|
|
272
|
+
if (record?.from !== undefined || record?.to !== undefined)
|
|
273
|
+
return false;
|
|
271
274
|
if (!record || typeof record !== 'object')
|
|
272
275
|
return false;
|
|
273
276
|
const r = record;
|
|
@@ -378,6 +381,84 @@ export function isEnergyUsagePeriodRecord(record) {
|
|
|
378
381
|
const r = record;
|
|
379
382
|
return !!r && typeof r === 'object' && r.meterId !== undefined && r.from !== undefined && r.to !== undefined && r.kWh !== undefined;
|
|
380
383
|
}
|
|
384
|
+
export function isEnergyGenerationPeriodRecord(record) {
|
|
385
|
+
const r = record;
|
|
386
|
+
if (!r || typeof r !== 'object')
|
|
387
|
+
return false;
|
|
388
|
+
if (r.equipmentId === undefined || r.from === undefined || r.to === undefined)
|
|
389
|
+
return false;
|
|
390
|
+
return r.kWh !== undefined;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 마감된 발전 기간을 봉투로 — **사건 시각은 기간의 끝**이다(그때 성립한다).
|
|
394
|
+
*
|
|
395
|
+
* 적산의 문(`ingestEnergyGenerationRecords`)과 겹치지 않는다: 그쪽은 구간이 없는 시점의 값이고
|
|
396
|
+
* 이쪽은 구간이 있는 마감된 사실이다. 겹치면 지난 기록이 「지금 적산」으로 읽혀 트윈의 시계가
|
|
397
|
+
* 과거로 끌린다.
|
|
398
|
+
*/
|
|
399
|
+
export function ingestEnergyGenerationPeriodRecords(records, opts) {
|
|
400
|
+
const list = records === undefined || records === null ? [] : Array.isArray(records) ? records : [records];
|
|
401
|
+
const accepted = [];
|
|
402
|
+
const rejected = [];
|
|
403
|
+
for (const r of list) {
|
|
404
|
+
const errors = [];
|
|
405
|
+
const equipmentId = String(r?.equipmentId ?? '').trim();
|
|
406
|
+
if (!equipmentId)
|
|
407
|
+
errors.push('equipmentId 가 없다 — 어느 설비가 낸 것인지 지어낼 수 없다');
|
|
408
|
+
const span = readSpan(r, errors);
|
|
409
|
+
const kWh = readAmount(r?.kWh, 'kWh', errors);
|
|
410
|
+
if (kWh === undefined && !errors.length)
|
|
411
|
+
errors.push('kWh 가 없다 — 이 문이 받는 값은 그 기간의 발전량이다');
|
|
412
|
+
const ACCUMULATIONS = ['lifetime', 'daily', 'monthly', 'billing', 'unknown'];
|
|
413
|
+
let accumulation;
|
|
414
|
+
const rawAcc = r?.accumulation;
|
|
415
|
+
if (rawAcc !== undefined && rawAcc !== null && String(rawAcc).trim()) {
|
|
416
|
+
const text = String(rawAcc).trim();
|
|
417
|
+
if (!ACCUMULATIONS.includes(text)) {
|
|
418
|
+
errors.push(`accumulation 이 아는 값이 아니다(${ACCUMULATIONS.join('·')}): ${JSON.stringify(rawAcc)}`);
|
|
419
|
+
}
|
|
420
|
+
else
|
|
421
|
+
accumulation = text;
|
|
422
|
+
}
|
|
423
|
+
/* 관측 구간은 기간 안에 있어야 한다 — 밖이면 그 값이 이 기간의 것이 아니다. */
|
|
424
|
+
const readAt = (raw, name) => {
|
|
425
|
+
if (raw === undefined || raw === null || !String(raw).trim())
|
|
426
|
+
return undefined;
|
|
427
|
+
const text = String(raw).trim();
|
|
428
|
+
if (!Number.isFinite(Date.parse(text))) {
|
|
429
|
+
errors.push(`${name} 를 시각으로 읽을 수 없다: ${JSON.stringify(raw)}`);
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
return text;
|
|
433
|
+
};
|
|
434
|
+
const observedFrom = readAt(r?.observedFrom, 'observedFrom');
|
|
435
|
+
const observedTo = readAt(r?.observedTo, 'observedTo');
|
|
436
|
+
if (errors.length || !span || kWh === undefined) {
|
|
437
|
+
rejected.push({ record: r, errors });
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const data = {
|
|
441
|
+
equipmentId,
|
|
442
|
+
kWh,
|
|
443
|
+
periodStart: span.from,
|
|
444
|
+
periodEnd: span.to,
|
|
445
|
+
/* 관측 구간을 말하지 않으면 기간 전체를 잰 것으로 둔다 — 지난 기록은 그 원본이 하루를 마감해 준 값이다. */
|
|
446
|
+
observedFrom: observedFrom ?? span.from,
|
|
447
|
+
observedTo: observedTo ?? span.to,
|
|
448
|
+
accumulation: (accumulation ?? 'daily'),
|
|
449
|
+
/* 밖에서 마감되어 온 것이다 — 우리가 시각 기준으로 닫은 것이 아니다. */
|
|
450
|
+
boundary: 'declared'
|
|
451
|
+
};
|
|
452
|
+
accepted.push({
|
|
453
|
+
eventId: periodFactId(opts.tenantId, ENERGY_EVENT.generationPeriod, equipmentId, span.from, span.to),
|
|
454
|
+
eventType: ENERGY_EVENT.generationPeriod,
|
|
455
|
+
eventTime: span.to,
|
|
456
|
+
tenantId: opts.tenantId,
|
|
457
|
+
data
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
return { accepted, rejected };
|
|
461
|
+
}
|
|
381
462
|
export function isEnergyGenerationPriceRecord(record) {
|
|
382
463
|
const r = record;
|
|
383
464
|
if (!r || typeof r !== 'object')
|
package/dist/index.d.ts
CHANGED
|
@@ -30,10 +30,10 @@ export { WmsKernel } from './kernel.ts';
|
|
|
30
30
|
export { YmsKernel } from './yms-kernel.ts';
|
|
31
31
|
export { MesKernel } from './mes-kernel.ts';
|
|
32
32
|
export { EmsKernel, DEMAND_WINDOW_MS, demandWindowStart } from './ems-kernel.ts';
|
|
33
|
-
export { ingestEnergyRecords, isEnergyRecord, ingestEnergyEquipmentRecords, isEnergyEquipmentRecord, ingestEnergyGenerationRecords, isEnergyGenerationRecord, ingestEnergyUsagePeriodRecords, isEnergyUsagePeriodRecord, ingestEnergyBillRecords, isEnergyBillRecord, ingestEnergyTariffBasisRecords, isEnergyTariffBasisRecord, ingestEnergyGenerationPriceRecords, isEnergyGenerationPriceRecord } from './energy-ingest.ts';
|
|
33
|
+
export { ingestEnergyRecords, isEnergyRecord, ingestEnergyEquipmentRecords, isEnergyEquipmentRecord, ingestEnergyGenerationRecords, isEnergyGenerationRecord, ingestEnergyUsagePeriodRecords, isEnergyUsagePeriodRecord, ingestEnergyBillRecords, isEnergyBillRecord, ingestEnergyTariffBasisRecords, isEnergyTariffBasisRecord, ingestEnergyGenerationPriceRecords, isEnergyGenerationPriceRecord, ingestEnergyGenerationPeriodRecords, isEnergyGenerationPeriodRecord } from './energy-ingest.ts';
|
|
34
34
|
export { ingestOperationalRecords, isOperationalRecord, operationalKindOf } from './operational-ingest.ts';
|
|
35
35
|
export type { OperationalKind, OperationalRecord, OperationalIngestOptions } from './operational-ingest.ts';
|
|
36
36
|
export { attributeEnergy, electricityCost, energyIntensity, energyOfWindows } from './energy-attribution.ts';
|
|
37
37
|
export type { AttributionBasis, AttributionResult, ElectricityCost, EnergyConsumer, EnergyPool, EnergyShare, IntensityInput, IntensityResult, IntensityDenominator, TariffDeclaration, WeightKind, WindowedEnergy } from './energy-attribution.ts';
|
|
38
|
-
export type { EnergyRecord, EnergyEquipmentRecord, EnergyGenerationRecord, EnergyUsagePeriodRecord, EnergyBillRecord, EnergyTariffBasisRecord, EnergyGenerationPriceRecord, EnergyIngestOptions, EnergyIngestResult } from './energy-ingest.ts';
|
|
38
|
+
export type { EnergyRecord, EnergyEquipmentRecord, EnergyGenerationRecord, EnergyUsagePeriodRecord, EnergyBillRecord, EnergyTariffBasisRecord, EnergyGenerationPriceRecord, EnergyGenerationPeriodRecord, EnergyIngestOptions, EnergyIngestResult } from './energy-ingest.ts';
|
|
39
39
|
export * from './vocabulary.ts';
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ export { WmsKernel } from "./kernel.js";
|
|
|
30
30
|
export { YmsKernel } from "./yms-kernel.js";
|
|
31
31
|
export { MesKernel } from "./mes-kernel.js";
|
|
32
32
|
export { EmsKernel, DEMAND_WINDOW_MS, demandWindowStart } from "./ems-kernel.js";
|
|
33
|
-
export { ingestEnergyRecords, isEnergyRecord, ingestEnergyEquipmentRecords, isEnergyEquipmentRecord, ingestEnergyGenerationRecords, isEnergyGenerationRecord, ingestEnergyUsagePeriodRecords, isEnergyUsagePeriodRecord, ingestEnergyBillRecords, isEnergyBillRecord, ingestEnergyTariffBasisRecords, isEnergyTariffBasisRecord, ingestEnergyGenerationPriceRecords, isEnergyGenerationPriceRecord } from "./energy-ingest.js";
|
|
33
|
+
export { ingestEnergyRecords, isEnergyRecord, ingestEnergyEquipmentRecords, isEnergyEquipmentRecord, ingestEnergyGenerationRecords, isEnergyGenerationRecord, ingestEnergyUsagePeriodRecords, isEnergyUsagePeriodRecord, ingestEnergyBillRecords, isEnergyBillRecord, ingestEnergyTariffBasisRecords, isEnergyTariffBasisRecord, ingestEnergyGenerationPriceRecords, isEnergyGenerationPriceRecord, ingestEnergyGenerationPeriodRecords, isEnergyGenerationPeriodRecord } from "./energy-ingest.js";
|
|
34
34
|
/* 운영 사실의 문 — 리듀서가 다루는 여섯이 들어오는 자리(미러가 시뮬보다 가난하지 않게). */
|
|
35
35
|
export { ingestOperationalRecords, isOperationalRecord, operationalKindOf } from "./operational-ingest.js";
|
|
36
36
|
export { attributeEnergy, electricityCost, energyIntensity, energyOfWindows } from "./energy-attribution.js";
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -127,6 +127,7 @@ __export(index_exports, {
|
|
|
127
127
|
ingest: () => ingest,
|
|
128
128
|
ingestEnergyBillRecords: () => ingestEnergyBillRecords,
|
|
129
129
|
ingestEnergyEquipmentRecords: () => ingestEnergyEquipmentRecords,
|
|
130
|
+
ingestEnergyGenerationPeriodRecords: () => ingestEnergyGenerationPeriodRecords,
|
|
130
131
|
ingestEnergyGenerationPriceRecords: () => ingestEnergyGenerationPriceRecords,
|
|
131
132
|
ingestEnergyGenerationRecords: () => ingestEnergyGenerationRecords,
|
|
132
133
|
ingestEnergyRecords: () => ingestEnergyRecords,
|
|
@@ -138,6 +139,7 @@ __export(index_exports, {
|
|
|
138
139
|
isElectricalLocationType: () => isElectricalLocationType,
|
|
139
140
|
isEnergyBillRecord: () => isEnergyBillRecord,
|
|
140
141
|
isEnergyEquipmentRecord: () => isEnergyEquipmentRecord,
|
|
142
|
+
isEnergyGenerationPeriodRecord: () => isEnergyGenerationPeriodRecord,
|
|
141
143
|
isEnergyGenerationPriceRecord: () => isEnergyGenerationPriceRecord,
|
|
142
144
|
isEnergyGenerationRecord: () => isEnergyGenerationRecord,
|
|
143
145
|
isEnergyRecord: () => isEnergyRecord,
|
|
@@ -9970,6 +9972,7 @@ function ingestEnergyEquipmentRecords(records, opts) {
|
|
|
9970
9972
|
return { accepted, rejected };
|
|
9971
9973
|
}
|
|
9972
9974
|
function isEnergyGenerationRecord(record) {
|
|
9975
|
+
if (record?.from !== void 0 || record?.to !== void 0) return false;
|
|
9973
9976
|
if (!record || typeof record !== "object") return false;
|
|
9974
9977
|
const r = record;
|
|
9975
9978
|
return typeof r.equipmentId === "string" && r.equipmentId.trim().length > 0 && r.kWh !== void 0 && r.meterId === void 0 && r.epc === void 0;
|
|
@@ -10043,6 +10046,69 @@ function isEnergyUsagePeriodRecord(record) {
|
|
|
10043
10046
|
const r = record;
|
|
10044
10047
|
return !!r && typeof r === "object" && r.meterId !== void 0 && r.from !== void 0 && r.to !== void 0 && r.kWh !== void 0;
|
|
10045
10048
|
}
|
|
10049
|
+
function isEnergyGenerationPeriodRecord(record) {
|
|
10050
|
+
const r = record;
|
|
10051
|
+
if (!r || typeof r !== "object") return false;
|
|
10052
|
+
if (r.equipmentId === void 0 || r.from === void 0 || r.to === void 0) return false;
|
|
10053
|
+
return r.kWh !== void 0;
|
|
10054
|
+
}
|
|
10055
|
+
function ingestEnergyGenerationPeriodRecords(records, opts) {
|
|
10056
|
+
const list = records === void 0 || records === null ? [] : Array.isArray(records) ? records : [records];
|
|
10057
|
+
const accepted = [];
|
|
10058
|
+
const rejected = [];
|
|
10059
|
+
for (const r of list) {
|
|
10060
|
+
const errors = [];
|
|
10061
|
+
const equipmentId = String(r?.equipmentId ?? "").trim();
|
|
10062
|
+
if (!equipmentId) errors.push("equipmentId \uAC00 \uC5C6\uB2E4 \u2014 \uC5B4\uB290 \uC124\uBE44\uAC00 \uB0B8 \uAC83\uC778\uC9C0 \uC9C0\uC5B4\uB0BC \uC218 \uC5C6\uB2E4");
|
|
10063
|
+
const span = readSpan(r, errors);
|
|
10064
|
+
const kWh = readAmount(r?.kWh, "kWh", errors);
|
|
10065
|
+
if (kWh === void 0 && !errors.length) errors.push("kWh \uAC00 \uC5C6\uB2E4 \u2014 \uC774 \uBB38\uC774 \uBC1B\uB294 \uAC12\uC740 \uADF8 \uAE30\uAC04\uC758 \uBC1C\uC804\uB7C9\uC774\uB2E4");
|
|
10066
|
+
const ACCUMULATIONS = ["lifetime", "daily", "monthly", "billing", "unknown"];
|
|
10067
|
+
let accumulation;
|
|
10068
|
+
const rawAcc = r?.accumulation;
|
|
10069
|
+
if (rawAcc !== void 0 && rawAcc !== null && String(rawAcc).trim()) {
|
|
10070
|
+
const text = String(rawAcc).trim();
|
|
10071
|
+
if (!ACCUMULATIONS.includes(text)) {
|
|
10072
|
+
errors.push(`accumulation \uC774 \uC544\uB294 \uAC12\uC774 \uC544\uB2C8\uB2E4(${ACCUMULATIONS.join("\xB7")}): ${JSON.stringify(rawAcc)}`);
|
|
10073
|
+
} else accumulation = text;
|
|
10074
|
+
}
|
|
10075
|
+
const readAt = (raw, name) => {
|
|
10076
|
+
if (raw === void 0 || raw === null || !String(raw).trim()) return void 0;
|
|
10077
|
+
const text = String(raw).trim();
|
|
10078
|
+
if (!Number.isFinite(Date.parse(text))) {
|
|
10079
|
+
errors.push(`${name} \uB97C \uC2DC\uAC01\uC73C\uB85C \uC77D\uC744 \uC218 \uC5C6\uB2E4: ${JSON.stringify(raw)}`);
|
|
10080
|
+
return void 0;
|
|
10081
|
+
}
|
|
10082
|
+
return text;
|
|
10083
|
+
};
|
|
10084
|
+
const observedFrom = readAt(r?.observedFrom, "observedFrom");
|
|
10085
|
+
const observedTo = readAt(r?.observedTo, "observedTo");
|
|
10086
|
+
if (errors.length || !span || kWh === void 0) {
|
|
10087
|
+
rejected.push({ record: r, errors });
|
|
10088
|
+
continue;
|
|
10089
|
+
}
|
|
10090
|
+
const data = {
|
|
10091
|
+
equipmentId,
|
|
10092
|
+
kWh,
|
|
10093
|
+
periodStart: span.from,
|
|
10094
|
+
periodEnd: span.to,
|
|
10095
|
+
/* 관측 구간을 말하지 않으면 기간 전체를 잰 것으로 둔다 — 지난 기록은 그 원본이 하루를 마감해 준 값이다. */
|
|
10096
|
+
observedFrom: observedFrom ?? span.from,
|
|
10097
|
+
observedTo: observedTo ?? span.to,
|
|
10098
|
+
accumulation: accumulation ?? "daily",
|
|
10099
|
+
/* 밖에서 마감되어 온 것이다 — 우리가 시각 기준으로 닫은 것이 아니다. */
|
|
10100
|
+
boundary: "declared"
|
|
10101
|
+
};
|
|
10102
|
+
accepted.push({
|
|
10103
|
+
eventId: periodFactId(opts.tenantId, ENERGY_EVENT.generationPeriod, equipmentId, span.from, span.to),
|
|
10104
|
+
eventType: ENERGY_EVENT.generationPeriod,
|
|
10105
|
+
eventTime: span.to,
|
|
10106
|
+
tenantId: opts.tenantId,
|
|
10107
|
+
data
|
|
10108
|
+
});
|
|
10109
|
+
}
|
|
10110
|
+
return { accepted, rejected };
|
|
10111
|
+
}
|
|
10046
10112
|
function isEnergyGenerationPriceRecord(record) {
|
|
10047
10113
|
const r = record;
|
|
10048
10114
|
if (!r || typeof r !== "object") return false;
|
|
@@ -10938,6 +11004,7 @@ function retiredVocabularyIn(line) {
|
|
|
10938
11004
|
ingest,
|
|
10939
11005
|
ingestEnergyBillRecords,
|
|
10940
11006
|
ingestEnergyEquipmentRecords,
|
|
11007
|
+
ingestEnergyGenerationPeriodRecords,
|
|
10941
11008
|
ingestEnergyGenerationPriceRecords,
|
|
10942
11009
|
ingestEnergyGenerationRecords,
|
|
10943
11010
|
ingestEnergyRecords,
|
|
@@ -10949,6 +11016,7 @@ function retiredVocabularyIn(line) {
|
|
|
10949
11016
|
isElectricalLocationType,
|
|
10950
11017
|
isEnergyBillRecord,
|
|
10951
11018
|
isEnergyEquipmentRecord,
|
|
11019
|
+
isEnergyGenerationPeriodRecord,
|
|
10952
11020
|
isEnergyGenerationPriceRecord,
|
|
10953
11021
|
isEnergyGenerationRecord,
|
|
10954
11022
|
isEnergyRecord,
|
package/package.json
CHANGED