@operato/twin-kernel 0.7.77 → 0.7.79
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/contract.d.ts +47 -0
- package/dist/contract.js +17 -0
- package/dist/ems-kernel.d.ts +6 -0
- package/dist/ems-kernel.js +29 -0
- package/dist/energy-ingest.d.ts +15 -0
- package/dist/energy-ingest.js +47 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist-cjs/index.cjs +80 -0
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -2041,6 +2041,23 @@ export interface EnergyState {
|
|
|
2041
2041
|
* 청구서보다 이쪽이 먼저다: 청구서는 끝난 기간의 정산이고 이것은 지금 적용되는 값이다.
|
|
2042
2042
|
*/
|
|
2043
2043
|
tariffBasis?: EnergyTariffBasisData;
|
|
2044
|
+
/**
|
|
2045
|
+
* 이 트윈의 **발전 정격**과 그 근거 — 이용률의 분모(§ `generationRated`).
|
|
2046
|
+
*
|
|
2047
|
+
* 선언에서 고른 값이라 상태에 낸다. 읽는 쪽이 모델을 다시 뒤지면 고르는 규칙이 두 벌이 되고,
|
|
2048
|
+
* 그때부터 두 화면이 다른 이용률을 말한다.
|
|
2049
|
+
*/
|
|
2050
|
+
generationRated?: {
|
|
2051
|
+
kW: number;
|
|
2052
|
+
basis: 'ac' | 'dc';
|
|
2053
|
+
from: 'equipment' | 'location';
|
|
2054
|
+
};
|
|
2055
|
+
/**
|
|
2056
|
+
* 마지막으로 받은 **발전 단가** — 이 기간에 낸 전기 1kWh 의 값.
|
|
2057
|
+
*
|
|
2058
|
+
* 창 밖의 단가도 이 창에 적용될 수 있어(단가는 기간마다 정해진다) 상태로 이어 간다.
|
|
2059
|
+
*/
|
|
2060
|
+
generationPrice?: EnergyGenerationPriceData;
|
|
2044
2061
|
/**
|
|
2045
2062
|
* 이 청구 주기의 **최고 수요** — 기본요금이 이 값으로 다시 정해진다.
|
|
2046
2063
|
*
|
|
@@ -2480,6 +2497,23 @@ export declare const ENERGY_EVENT: {
|
|
|
2480
2497
|
* 쪽인지 말한다.
|
|
2481
2498
|
*/
|
|
2482
2499
|
readonly usagePeriod: "energy.usage.period";
|
|
2500
|
+
/**
|
|
2501
|
+
* **발전에 매겨지는 단가가 정해졌다** — 이 기간에 낸 전기 1kWh 의 값.
|
|
2502
|
+
*
|
|
2503
|
+
* 소비에 요금이 매겨지듯 발전에도 값이 매겨진다. 파는 발전소는 판 값이고, 자가소비형은 아꼈다고
|
|
2504
|
+
* 볼 수 있는 값이다 — 어느 쪽인지는 현장이 정한다.
|
|
2505
|
+
*
|
|
2506
|
+
* **단가는 날마다 바뀐다.** 그래서 선언이 아니라 기간을 가진 사실로 받는다. 선언에 적어 두면
|
|
2507
|
+
* 사람이 매일 갱신해야 하고, 안 하면 낡은 값으로 수익이 조용히 틀린다.
|
|
2508
|
+
*
|
|
2509
|
+
* 사건 시각은 기간의 시작이다 — 「이 시각부터 이 단가가 적용된다」이므로 기간이 끝나기 전에 성립한다.
|
|
2510
|
+
*
|
|
2511
|
+
* ── 담지 않은 것 ────────────────────────────────────────────────────────────
|
|
2512
|
+
* 제도마다 있는 가산·인증서·가중치는 담지 않는다. 그 셈은 제도가 정하고 나라마다 다르므로,
|
|
2513
|
+
* 커넥터가 계산해 **1kWh 당 얼마**로 옮겨 보낸다. 커널이 한 제도의 모양을 안으면 다른 제도에서
|
|
2514
|
+
* 그 자리가 거짓이 된다.
|
|
2515
|
+
*/
|
|
2516
|
+
readonly generationPrice: "energy.generation.price";
|
|
2483
2517
|
/**
|
|
2484
2518
|
* **이 주기에 적용되는 요금 기준이 정해졌다** — 요금적용전력과 기본요금 단가.
|
|
2485
2519
|
*
|
|
@@ -2753,6 +2787,19 @@ export interface EnergyUsagePeriodData {
|
|
|
2753
2787
|
* 사건 시각은 `from` 이다. 「이 시각부터 이 기준이 적용된다」이므로 주기가 끝나기 전에 성립하고,
|
|
2754
2788
|
* `to` 로 시각을 잡으면 아직 오지 않은 시각이 저널에 적힌다.
|
|
2755
2789
|
*/
|
|
2790
|
+
/**
|
|
2791
|
+
* 발전 단가 — `ENERGY_EVENT.generationPrice` 의 데이터.
|
|
2792
|
+
*
|
|
2793
|
+
* 사건 시각은 `from` 이다(그때부터 적용된다). 가산·인증서 같은 제도의 셈은 커넥터가 하고, 여기에는
|
|
2794
|
+
* **1kWh 당 얼마**만 온다.
|
|
2795
|
+
*/
|
|
2796
|
+
export interface EnergyGenerationPriceData {
|
|
2797
|
+
from: ISOTime;
|
|
2798
|
+
to: ISOTime;
|
|
2799
|
+
/** 1kWh 당 값. 통화는 `currency`. */
|
|
2800
|
+
unitPrice: number;
|
|
2801
|
+
currency: string;
|
|
2802
|
+
}
|
|
2756
2803
|
export interface EnergyTariffBasisData {
|
|
2757
2804
|
from: ISOTime;
|
|
2758
2805
|
to: ISOTime;
|
package/dist/contract.js
CHANGED
|
@@ -1046,6 +1046,23 @@ export const ENERGY_EVENT = {
|
|
|
1046
1046
|
* 쪽인지 말한다.
|
|
1047
1047
|
*/
|
|
1048
1048
|
usagePeriod: 'energy.usage.period',
|
|
1049
|
+
/**
|
|
1050
|
+
* **발전에 매겨지는 단가가 정해졌다** — 이 기간에 낸 전기 1kWh 의 값.
|
|
1051
|
+
*
|
|
1052
|
+
* 소비에 요금이 매겨지듯 발전에도 값이 매겨진다. 파는 발전소는 판 값이고, 자가소비형은 아꼈다고
|
|
1053
|
+
* 볼 수 있는 값이다 — 어느 쪽인지는 현장이 정한다.
|
|
1054
|
+
*
|
|
1055
|
+
* **단가는 날마다 바뀐다.** 그래서 선언이 아니라 기간을 가진 사실로 받는다. 선언에 적어 두면
|
|
1056
|
+
* 사람이 매일 갱신해야 하고, 안 하면 낡은 값으로 수익이 조용히 틀린다.
|
|
1057
|
+
*
|
|
1058
|
+
* 사건 시각은 기간의 시작이다 — 「이 시각부터 이 단가가 적용된다」이므로 기간이 끝나기 전에 성립한다.
|
|
1059
|
+
*
|
|
1060
|
+
* ── 담지 않은 것 ────────────────────────────────────────────────────────────
|
|
1061
|
+
* 제도마다 있는 가산·인증서·가중치는 담지 않는다. 그 셈은 제도가 정하고 나라마다 다르므로,
|
|
1062
|
+
* 커넥터가 계산해 **1kWh 당 얼마**로 옮겨 보낸다. 커널이 한 제도의 모양을 안으면 다른 제도에서
|
|
1063
|
+
* 그 자리가 거짓이 된다.
|
|
1064
|
+
*/
|
|
1065
|
+
generationPrice: 'energy.generation.price',
|
|
1049
1066
|
/**
|
|
1050
1067
|
* **이 주기에 적용되는 요금 기준이 정해졌다** — 요금적용전력과 기본요금 단가.
|
|
1051
1068
|
*
|
package/dist/ems-kernel.d.ts
CHANGED
|
@@ -122,6 +122,10 @@ export declare class EmsKernel extends FlowEngine {
|
|
|
122
122
|
* 모르는 지점이면 세어 둔다 — 조용히 버리면 「값이 왜 안 보이지」로만 남는다(계량과 같은 규율).
|
|
123
123
|
*/
|
|
124
124
|
private applyUsagePeriod;
|
|
125
|
+
/**
|
|
126
|
+
* 발전 단가를 상태에 둔다 — 늦게 온 지난 기간의 단가가 최신을 덮지 않게 시작 시각으로 가린다.
|
|
127
|
+
*/
|
|
128
|
+
private applyGenerationPrice;
|
|
125
129
|
/**
|
|
126
130
|
* 이 주기에 적용되는 요금 기준을 상태에 둔다.
|
|
127
131
|
*
|
|
@@ -208,6 +212,8 @@ export declare class EmsKernel extends FlowEngine {
|
|
|
208
212
|
private lastBill?;
|
|
209
213
|
/** 마지막으로 받은 요금 기준 — 이 주기에 적용되는 요금적용전력과 단가. */
|
|
210
214
|
private tariffBasis?;
|
|
215
|
+
/** 마지막으로 받은 발전 단가 — 이 기간에 낸 전기 1kWh 의 값. */
|
|
216
|
+
private generationPrice?;
|
|
211
217
|
/**
|
|
212
218
|
* 이 청구 주기의 **최고 수요** — 기본요금이 이 값으로 다시 정해진다.
|
|
213
219
|
*
|
package/dist/ems-kernel.js
CHANGED
|
@@ -281,6 +281,10 @@ export class EmsKernel extends FlowEngine {
|
|
|
281
281
|
this.applyTariffBasis(envelope);
|
|
282
282
|
return;
|
|
283
283
|
}
|
|
284
|
+
if (envelope.eventType === ENERGY_EVENT.generationPrice) {
|
|
285
|
+
this.applyGenerationPrice(envelope);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
284
288
|
super.apply(envelope);
|
|
285
289
|
}
|
|
286
290
|
/**
|
|
@@ -341,6 +345,22 @@ export class EmsKernel extends FlowEngine {
|
|
|
341
345
|
}
|
|
342
346
|
this.revision++;
|
|
343
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* 발전 단가를 상태에 둔다 — 늦게 온 지난 기간의 단가가 최신을 덮지 않게 시작 시각으로 가린다.
|
|
350
|
+
*/
|
|
351
|
+
applyGenerationPrice(envelope) {
|
|
352
|
+
const d = envelope.data;
|
|
353
|
+
if (!d?.from || !d?.to)
|
|
354
|
+
return;
|
|
355
|
+
const fromMs = Date.parse(String(d.from));
|
|
356
|
+
if (!Number.isFinite(fromMs))
|
|
357
|
+
return;
|
|
358
|
+
const priorFromMs = Date.parse(String(this.generationPrice?.from ?? ''));
|
|
359
|
+
if (Number.isFinite(priorFromMs) && fromMs < priorFromMs)
|
|
360
|
+
return;
|
|
361
|
+
this.generationPrice = { ...d };
|
|
362
|
+
this.revision++;
|
|
363
|
+
}
|
|
344
364
|
/**
|
|
345
365
|
* 이 주기에 적용되는 요금 기준을 상태에 둔다.
|
|
346
366
|
*
|
|
@@ -627,6 +647,8 @@ export class EmsKernel extends FlowEngine {
|
|
|
627
647
|
lastBill;
|
|
628
648
|
/** 마지막으로 받은 요금 기준 — 이 주기에 적용되는 요금적용전력과 단가. */
|
|
629
649
|
tariffBasis;
|
|
650
|
+
/** 마지막으로 받은 발전 단가 — 이 기간에 낸 전기 1kWh 의 값. */
|
|
651
|
+
generationPrice;
|
|
630
652
|
/**
|
|
631
653
|
* 이 청구 주기의 **최고 수요** — 기본요금이 이 값으로 다시 정해진다.
|
|
632
654
|
*
|
|
@@ -1560,6 +1582,8 @@ export class EmsKernel extends FlowEngine {
|
|
|
1560
1582
|
this.lastBill = { ...e.lastBill };
|
|
1561
1583
|
if (e.tariffBasis?.from && e.tariffBasis?.to)
|
|
1562
1584
|
this.tariffBasis = { ...e.tariffBasis };
|
|
1585
|
+
if (e.generationPrice?.from && e.generationPrice?.to)
|
|
1586
|
+
this.generationPrice = { ...e.generationPrice };
|
|
1563
1587
|
/* 이 주기의 최고도 이어받는다 — 재기동마다 0 으로 돌아가면 이미 넘은 것을 못 넘었다고 말한다. */
|
|
1564
1588
|
if (Number.isFinite(Number(e.demandHigh?.kW)) && e.demandHigh?.since)
|
|
1565
1589
|
this.demandHigh = { ...e.demandHigh };
|
|
@@ -1589,6 +1613,11 @@ export class EmsKernel extends FlowEngine {
|
|
|
1589
1613
|
...(this.unclosedGenerationPeriods ? { unclosedGenerationPeriods: this.unclosedGenerationPeriods } : {}),
|
|
1590
1614
|
...(this.lastBill ? { lastBill: { ...this.lastBill } } : {}),
|
|
1591
1615
|
...(this.tariffBasis ? { tariffBasis: { ...this.tariffBasis } } : {}),
|
|
1616
|
+
...(this.generationPrice ? { generationPrice: { ...this.generationPrice } } : {}),
|
|
1617
|
+
...(() => {
|
|
1618
|
+
const r = this.generationRated();
|
|
1619
|
+
return r ? { generationRated: r } : {};
|
|
1620
|
+
})(),
|
|
1592
1621
|
...(this.demandHigh ? { demandHigh: { ...this.demandHigh } } : {}),
|
|
1593
1622
|
...(contractKW !== undefined ? { contractKW } : {}),
|
|
1594
1623
|
/* 물류 흐름 요청을 받은 적이 있나 — 있으면 이 트윈에 엉뚱한 명령이 오고 있다는 사실이다. */
|
package/dist/energy-ingest.d.ts
CHANGED
|
@@ -115,6 +115,21 @@ export interface EnergyUsagePeriodRecord {
|
|
|
115
115
|
basis?: string;
|
|
116
116
|
}
|
|
117
117
|
export declare function isEnergyUsagePeriodRecord(record: unknown): boolean;
|
|
118
|
+
/** 발전 단가 — 이 기간에 낸 전기 1kWh 의 값. 날마다 바뀐다. */
|
|
119
|
+
export interface EnergyGenerationPriceRecord {
|
|
120
|
+
from: string;
|
|
121
|
+
to: string;
|
|
122
|
+
unitPrice: number;
|
|
123
|
+
currency: string;
|
|
124
|
+
}
|
|
125
|
+
export declare function isEnergyGenerationPriceRecord(record: unknown): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* 발전 단가를 봉투로 — **사건 시각은 `from`** 이다(그때부터 적용된다).
|
|
128
|
+
*
|
|
129
|
+
* 제도의 가산·인증서 셈은 커넥터가 하고 여기에는 1kWh 당 얼마만 온다. 커널이 한 제도의 모양을
|
|
130
|
+
* 안으면 다른 제도에서 그 자리가 거짓이 된다.
|
|
131
|
+
*/
|
|
132
|
+
export declare function ingestEnergyGenerationPriceRecords(records: EnergyGenerationPriceRecord | EnergyGenerationPriceRecord[] | undefined | null, opts: EnergyIngestOptions): EnergyIngestResult;
|
|
118
133
|
/** 이 주기에 적용되는 요금 기준 — 공급자가 정하고 매 주기 다시 정해진다. */
|
|
119
134
|
export interface EnergyTariffBasisRecord {
|
|
120
135
|
from: string;
|
package/dist/energy-ingest.js
CHANGED
|
@@ -378,6 +378,53 @@ export function isEnergyUsagePeriodRecord(record) {
|
|
|
378
378
|
const r = record;
|
|
379
379
|
return !!r && typeof r === 'object' && r.meterId !== undefined && r.from !== undefined && r.to !== undefined && r.kWh !== undefined;
|
|
380
380
|
}
|
|
381
|
+
export function isEnergyGenerationPriceRecord(record) {
|
|
382
|
+
const r = record;
|
|
383
|
+
if (!r || typeof r !== 'object')
|
|
384
|
+
return false;
|
|
385
|
+
if (r.from === undefined || r.to === undefined)
|
|
386
|
+
return false;
|
|
387
|
+
/* 계량 지점·설비가 있으면 그 설비의 사실이지 단가가 아니다 — 문이 겹치지 않게. */
|
|
388
|
+
if (r.meterId !== undefined || r.equipmentId !== undefined)
|
|
389
|
+
return false;
|
|
390
|
+
return r.unitPrice !== undefined;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 발전 단가를 봉투로 — **사건 시각은 `from`** 이다(그때부터 적용된다).
|
|
394
|
+
*
|
|
395
|
+
* 제도의 가산·인증서 셈은 커넥터가 하고 여기에는 1kWh 당 얼마만 온다. 커널이 한 제도의 모양을
|
|
396
|
+
* 안으면 다른 제도에서 그 자리가 거짓이 된다.
|
|
397
|
+
*/
|
|
398
|
+
export function ingestEnergyGenerationPriceRecords(records, opts) {
|
|
399
|
+
const list = records === undefined || records === null ? [] : Array.isArray(records) ? records : [records];
|
|
400
|
+
const accepted = [];
|
|
401
|
+
const rejected = [];
|
|
402
|
+
let seq = 0;
|
|
403
|
+
for (const r of list) {
|
|
404
|
+
const errors = [];
|
|
405
|
+
const span = readSpan(r, errors);
|
|
406
|
+
const unitPrice = readAmount(r?.unitPrice, 'unitPrice', errors);
|
|
407
|
+
if (unitPrice === undefined && !errors.length)
|
|
408
|
+
errors.push('unitPrice 가 없다 — 이 문이 받는 값은 1kWh 당 값 하나다');
|
|
409
|
+
/* 단위 없는 값은 다른 금액과 더할 수 없다. */
|
|
410
|
+
const currency = String(r?.currency ?? '').trim();
|
|
411
|
+
if (!currency)
|
|
412
|
+
errors.push('currency 가 없다 — 단위 없는 값은 다른 금액과 더할 수 없다');
|
|
413
|
+
if (errors.length || !span || unitPrice === undefined) {
|
|
414
|
+
rejected.push({ record: r, errors });
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const data = { from: span.from, to: span.to, unitPrice, currency };
|
|
418
|
+
accepted.push({
|
|
419
|
+
eventId: `${opts.tenantId}-generation-price-${++seq}`,
|
|
420
|
+
eventType: ENERGY_EVENT.generationPrice,
|
|
421
|
+
eventTime: span.from,
|
|
422
|
+
tenantId: opts.tenantId,
|
|
423
|
+
data
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return { accepted, rejected };
|
|
427
|
+
}
|
|
381
428
|
export function isEnergyTariffBasisRecord(record) {
|
|
382
429
|
const r = record;
|
|
383
430
|
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 } from './energy-ingest.ts';
|
|
33
|
+
export { ingestEnergyRecords, isEnergyRecord, ingestEnergyEquipmentRecords, isEnergyEquipmentRecord, ingestEnergyGenerationRecords, isEnergyGenerationRecord, ingestEnergyUsagePeriodRecords, isEnergyUsagePeriodRecord, ingestEnergyBillRecords, isEnergyBillRecord, ingestEnergyTariffBasisRecords, isEnergyTariffBasisRecord, ingestEnergyGenerationPriceRecords, isEnergyGenerationPriceRecord } 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, EnergyIngestOptions, EnergyIngestResult } from './energy-ingest.ts';
|
|
38
|
+
export type { EnergyRecord, EnergyEquipmentRecord, EnergyGenerationRecord, EnergyUsagePeriodRecord, EnergyBillRecord, EnergyTariffBasisRecord, EnergyGenerationPriceRecord, 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 } from "./energy-ingest.js";
|
|
33
|
+
export { ingestEnergyRecords, isEnergyRecord, ingestEnergyEquipmentRecords, isEnergyEquipmentRecord, ingestEnergyGenerationRecords, isEnergyGenerationRecord, ingestEnergyUsagePeriodRecords, isEnergyUsagePeriodRecord, ingestEnergyBillRecords, isEnergyBillRecord, ingestEnergyTariffBasisRecords, isEnergyTariffBasisRecord, ingestEnergyGenerationPriceRecords, isEnergyGenerationPriceRecord } 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
|
+
ingestEnergyGenerationPriceRecords: () => ingestEnergyGenerationPriceRecords,
|
|
130
131
|
ingestEnergyGenerationRecords: () => ingestEnergyGenerationRecords,
|
|
131
132
|
ingestEnergyRecords: () => ingestEnergyRecords,
|
|
132
133
|
ingestEnergyTariffBasisRecords: () => ingestEnergyTariffBasisRecords,
|
|
@@ -137,6 +138,7 @@ __export(index_exports, {
|
|
|
137
138
|
isElectricalLocationType: () => isElectricalLocationType,
|
|
138
139
|
isEnergyBillRecord: () => isEnergyBillRecord,
|
|
139
140
|
isEnergyEquipmentRecord: () => isEnergyEquipmentRecord,
|
|
141
|
+
isEnergyGenerationPriceRecord: () => isEnergyGenerationPriceRecord,
|
|
140
142
|
isEnergyGenerationRecord: () => isEnergyGenerationRecord,
|
|
141
143
|
isEnergyRecord: () => isEnergyRecord,
|
|
142
144
|
isEnergyTariffBasisRecord: () => isEnergyTariffBasisRecord,
|
|
@@ -711,6 +713,23 @@ var ENERGY_EVENT = {
|
|
|
711
713
|
* 쪽인지 말한다.
|
|
712
714
|
*/
|
|
713
715
|
usagePeriod: "energy.usage.period",
|
|
716
|
+
/**
|
|
717
|
+
* **발전에 매겨지는 단가가 정해졌다** — 이 기간에 낸 전기 1kWh 의 값.
|
|
718
|
+
*
|
|
719
|
+
* 소비에 요금이 매겨지듯 발전에도 값이 매겨진다. 파는 발전소는 판 값이고, 자가소비형은 아꼈다고
|
|
720
|
+
* 볼 수 있는 값이다 — 어느 쪽인지는 현장이 정한다.
|
|
721
|
+
*
|
|
722
|
+
* **단가는 날마다 바뀐다.** 그래서 선언이 아니라 기간을 가진 사실로 받는다. 선언에 적어 두면
|
|
723
|
+
* 사람이 매일 갱신해야 하고, 안 하면 낡은 값으로 수익이 조용히 틀린다.
|
|
724
|
+
*
|
|
725
|
+
* 사건 시각은 기간의 시작이다 — 「이 시각부터 이 단가가 적용된다」이므로 기간이 끝나기 전에 성립한다.
|
|
726
|
+
*
|
|
727
|
+
* ── 담지 않은 것 ────────────────────────────────────────────────────────────
|
|
728
|
+
* 제도마다 있는 가산·인증서·가중치는 담지 않는다. 그 셈은 제도가 정하고 나라마다 다르므로,
|
|
729
|
+
* 커넥터가 계산해 **1kWh 당 얼마**로 옮겨 보낸다. 커널이 한 제도의 모양을 안으면 다른 제도에서
|
|
730
|
+
* 그 자리가 거짓이 된다.
|
|
731
|
+
*/
|
|
732
|
+
generationPrice: "energy.generation.price",
|
|
714
733
|
/**
|
|
715
734
|
* **이 주기에 적용되는 요금 기준이 정해졌다** — 요금적용전력과 기본요금 단가.
|
|
716
735
|
*
|
|
@@ -7991,6 +8010,10 @@ var EmsKernel = class extends FlowEngine {
|
|
|
7991
8010
|
this.applyTariffBasis(envelope);
|
|
7992
8011
|
return;
|
|
7993
8012
|
}
|
|
8013
|
+
if (envelope.eventType === ENERGY_EVENT.generationPrice) {
|
|
8014
|
+
this.applyGenerationPrice(envelope);
|
|
8015
|
+
return;
|
|
8016
|
+
}
|
|
7994
8017
|
super.apply(envelope);
|
|
7995
8018
|
}
|
|
7996
8019
|
/**
|
|
@@ -8031,6 +8054,19 @@ var EmsKernel = class extends FlowEngine {
|
|
|
8031
8054
|
}
|
|
8032
8055
|
this.revision++;
|
|
8033
8056
|
}
|
|
8057
|
+
/**
|
|
8058
|
+
* 발전 단가를 상태에 둔다 — 늦게 온 지난 기간의 단가가 최신을 덮지 않게 시작 시각으로 가린다.
|
|
8059
|
+
*/
|
|
8060
|
+
applyGenerationPrice(envelope) {
|
|
8061
|
+
const d = envelope.data;
|
|
8062
|
+
if (!d?.from || !d?.to) return;
|
|
8063
|
+
const fromMs = Date.parse(String(d.from));
|
|
8064
|
+
if (!Number.isFinite(fromMs)) return;
|
|
8065
|
+
const priorFromMs = Date.parse(String(this.generationPrice?.from ?? ""));
|
|
8066
|
+
if (Number.isFinite(priorFromMs) && fromMs < priorFromMs) return;
|
|
8067
|
+
this.generationPrice = { ...d };
|
|
8068
|
+
this.revision++;
|
|
8069
|
+
}
|
|
8034
8070
|
/**
|
|
8035
8071
|
* 이 주기에 적용되는 요금 기준을 상태에 둔다.
|
|
8036
8072
|
*
|
|
@@ -8225,6 +8261,8 @@ var EmsKernel = class extends FlowEngine {
|
|
|
8225
8261
|
lastBill;
|
|
8226
8262
|
/** 마지막으로 받은 요금 기준 — 이 주기에 적용되는 요금적용전력과 단가. */
|
|
8227
8263
|
tariffBasis;
|
|
8264
|
+
/** 마지막으로 받은 발전 단가 — 이 기간에 낸 전기 1kWh 의 값. */
|
|
8265
|
+
generationPrice;
|
|
8228
8266
|
/**
|
|
8229
8267
|
* 이 청구 주기의 **최고 수요** — 기본요금이 이 값으로 다시 정해진다.
|
|
8230
8268
|
*
|
|
@@ -8899,6 +8937,7 @@ var EmsKernel = class extends FlowEngine {
|
|
|
8899
8937
|
if (Number.isFinite(e.unclosedGenerationPeriods)) this.unclosedGenerationPeriods = Number(e.unclosedGenerationPeriods);
|
|
8900
8938
|
if (e.lastBill?.from && e.lastBill?.to) this.lastBill = { ...e.lastBill };
|
|
8901
8939
|
if (e.tariffBasis?.from && e.tariffBasis?.to) this.tariffBasis = { ...e.tariffBasis };
|
|
8940
|
+
if (e.generationPrice?.from && e.generationPrice?.to) this.generationPrice = { ...e.generationPrice };
|
|
8902
8941
|
if (Number.isFinite(Number(e.demandHigh?.kW)) && e.demandHigh?.since) this.demandHigh = { ...e.demandHigh };
|
|
8903
8942
|
}
|
|
8904
8943
|
getSnapshot() {
|
|
@@ -8922,6 +8961,11 @@ var EmsKernel = class extends FlowEngine {
|
|
|
8922
8961
|
...this.unclosedGenerationPeriods ? { unclosedGenerationPeriods: this.unclosedGenerationPeriods } : {},
|
|
8923
8962
|
...this.lastBill ? { lastBill: { ...this.lastBill } } : {},
|
|
8924
8963
|
...this.tariffBasis ? { tariffBasis: { ...this.tariffBasis } } : {},
|
|
8964
|
+
...this.generationPrice ? { generationPrice: { ...this.generationPrice } } : {},
|
|
8965
|
+
...(() => {
|
|
8966
|
+
const r = this.generationRated();
|
|
8967
|
+
return r ? { generationRated: r } : {};
|
|
8968
|
+
})(),
|
|
8925
8969
|
...this.demandHigh ? { demandHigh: { ...this.demandHigh } } : {},
|
|
8926
8970
|
...contractKW !== void 0 ? { contractKW } : {},
|
|
8927
8971
|
/* 물류 흐름 요청을 받은 적이 있나 — 있으면 이 트윈에 엉뚱한 명령이 오고 있다는 사실이다. */
|
|
@@ -9968,6 +10012,40 @@ function isEnergyUsagePeriodRecord(record) {
|
|
|
9968
10012
|
const r = record;
|
|
9969
10013
|
return !!r && typeof r === "object" && r.meterId !== void 0 && r.from !== void 0 && r.to !== void 0 && r.kWh !== void 0;
|
|
9970
10014
|
}
|
|
10015
|
+
function isEnergyGenerationPriceRecord(record) {
|
|
10016
|
+
const r = record;
|
|
10017
|
+
if (!r || typeof r !== "object") return false;
|
|
10018
|
+
if (r.from === void 0 || r.to === void 0) return false;
|
|
10019
|
+
if (r.meterId !== void 0 || r.equipmentId !== void 0) return false;
|
|
10020
|
+
return r.unitPrice !== void 0;
|
|
10021
|
+
}
|
|
10022
|
+
function ingestEnergyGenerationPriceRecords(records, opts) {
|
|
10023
|
+
const list = records === void 0 || records === null ? [] : Array.isArray(records) ? records : [records];
|
|
10024
|
+
const accepted = [];
|
|
10025
|
+
const rejected = [];
|
|
10026
|
+
let seq = 0;
|
|
10027
|
+
for (const r of list) {
|
|
10028
|
+
const errors = [];
|
|
10029
|
+
const span = readSpan(r, errors);
|
|
10030
|
+
const unitPrice = readAmount(r?.unitPrice, "unitPrice", errors);
|
|
10031
|
+
if (unitPrice === void 0 && !errors.length) errors.push("unitPrice \uAC00 \uC5C6\uB2E4 \u2014 \uC774 \uBB38\uC774 \uBC1B\uB294 \uAC12\uC740 1kWh \uB2F9 \uAC12 \uD558\uB098\uB2E4");
|
|
10032
|
+
const currency = String(r?.currency ?? "").trim();
|
|
10033
|
+
if (!currency) errors.push("currency \uAC00 \uC5C6\uB2E4 \u2014 \uB2E8\uC704 \uC5C6\uB294 \uAC12\uC740 \uB2E4\uB978 \uAE08\uC561\uACFC \uB354\uD560 \uC218 \uC5C6\uB2E4");
|
|
10034
|
+
if (errors.length || !span || unitPrice === void 0) {
|
|
10035
|
+
rejected.push({ record: r, errors });
|
|
10036
|
+
continue;
|
|
10037
|
+
}
|
|
10038
|
+
const data = { from: span.from, to: span.to, unitPrice, currency };
|
|
10039
|
+
accepted.push({
|
|
10040
|
+
eventId: `${opts.tenantId}-generation-price-${++seq}`,
|
|
10041
|
+
eventType: ENERGY_EVENT.generationPrice,
|
|
10042
|
+
eventTime: span.from,
|
|
10043
|
+
tenantId: opts.tenantId,
|
|
10044
|
+
data
|
|
10045
|
+
});
|
|
10046
|
+
}
|
|
10047
|
+
return { accepted, rejected };
|
|
10048
|
+
}
|
|
9971
10049
|
function isEnergyTariffBasisRecord(record) {
|
|
9972
10050
|
const r = record;
|
|
9973
10051
|
if (!r || typeof r !== "object") return false;
|
|
@@ -10830,6 +10908,7 @@ function retiredVocabularyIn(line) {
|
|
|
10830
10908
|
ingest,
|
|
10831
10909
|
ingestEnergyBillRecords,
|
|
10832
10910
|
ingestEnergyEquipmentRecords,
|
|
10911
|
+
ingestEnergyGenerationPriceRecords,
|
|
10833
10912
|
ingestEnergyGenerationRecords,
|
|
10834
10913
|
ingestEnergyRecords,
|
|
10835
10914
|
ingestEnergyTariffBasisRecords,
|
|
@@ -10840,6 +10919,7 @@ function retiredVocabularyIn(line) {
|
|
|
10840
10919
|
isElectricalLocationType,
|
|
10841
10920
|
isEnergyBillRecord,
|
|
10842
10921
|
isEnergyEquipmentRecord,
|
|
10922
|
+
isEnergyGenerationPriceRecord,
|
|
10843
10923
|
isEnergyGenerationRecord,
|
|
10844
10924
|
isEnergyRecord,
|
|
10845
10925
|
isEnergyTariffBasisRecord,
|
package/package.json
CHANGED