@operato/twin-kernel 0.7.78 → 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.
@@ -2052,6 +2052,12 @@ export interface EnergyState {
2052
2052
  basis: 'ac' | 'dc';
2053
2053
  from: 'equipment' | 'location';
2054
2054
  };
2055
+ /**
2056
+ * 마지막으로 받은 **발전 단가** — 이 기간에 낸 전기 1kWh 의 값.
2057
+ *
2058
+ * 창 밖의 단가도 이 창에 적용될 수 있어(단가는 기간마다 정해진다) 상태로 이어 간다.
2059
+ */
2060
+ generationPrice?: EnergyGenerationPriceData;
2055
2061
  /**
2056
2062
  * 이 청구 주기의 **최고 수요** — 기본요금이 이 값으로 다시 정해진다.
2057
2063
  *
@@ -2491,6 +2497,23 @@ export declare const ENERGY_EVENT: {
2491
2497
  * 쪽인지 말한다.
2492
2498
  */
2493
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";
2494
2517
  /**
2495
2518
  * **이 주기에 적용되는 요금 기준이 정해졌다** — 요금적용전력과 기본요금 단가.
2496
2519
  *
@@ -2764,6 +2787,19 @@ export interface EnergyUsagePeriodData {
2764
2787
  * 사건 시각은 `from` 이다. 「이 시각부터 이 기준이 적용된다」이므로 주기가 끝나기 전에 성립하고,
2765
2788
  * `to` 로 시각을 잡으면 아직 오지 않은 시각이 저널에 적힌다.
2766
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
+ }
2767
2803
  export interface EnergyTariffBasisData {
2768
2804
  from: ISOTime;
2769
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
  *
@@ -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
  *
@@ -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,7 @@ 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 } } : {}),
1592
1617
  ...(() => {
1593
1618
  const r = this.generationRated();
1594
1619
  return r ? { generationRated: r } : {};
@@ -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;
@@ -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";
@@ -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,7 @@ 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 } } : {},
8925
8965
  ...(() => {
8926
8966
  const r = this.generationRated();
8927
8967
  return r ? { generationRated: r } : {};
@@ -9972,6 +10012,40 @@ function isEnergyUsagePeriodRecord(record) {
9972
10012
  const r = record;
9973
10013
  return !!r && typeof r === "object" && r.meterId !== void 0 && r.from !== void 0 && r.to !== void 0 && r.kWh !== void 0;
9974
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
+ }
9975
10049
  function isEnergyTariffBasisRecord(record) {
9976
10050
  const r = record;
9977
10051
  if (!r || typeof r !== "object") return false;
@@ -10834,6 +10908,7 @@ function retiredVocabularyIn(line) {
10834
10908
  ingest,
10835
10909
  ingestEnergyBillRecords,
10836
10910
  ingestEnergyEquipmentRecords,
10911
+ ingestEnergyGenerationPriceRecords,
10837
10912
  ingestEnergyGenerationRecords,
10838
10913
  ingestEnergyRecords,
10839
10914
  ingestEnergyTariffBasisRecords,
@@ -10844,6 +10919,7 @@ function retiredVocabularyIn(line) {
10844
10919
  isElectricalLocationType,
10845
10920
  isEnergyBillRecord,
10846
10921
  isEnergyEquipmentRecord,
10922
+ isEnergyGenerationPriceRecord,
10847
10923
  isEnergyGenerationRecord,
10848
10924
  isEnergyRecord,
10849
10925
  isEnergyTariffBasisRecord,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@operato/twin-kernel",
3
- "version": "0.7.78",
3
+ "version": "0.7.79",
4
4
  "type": "module",
5
5
  "description": "Twin Domain Kernel — framework-agnostic, zero-dep (domain + sim + 3-channel contract). WMS/YMS/MES, EPCIS 2.0 · ISA-95.",
6
6
  "publishConfig": {