@operato/twin-kernel 0.7.12 → 0.7.14
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/ems-profile.d.ts +6 -0
- package/dist/ems-profile.js +20 -1
- package/dist/energy-attribution.d.ts +44 -0
- package/dist/energy-attribution.js +24 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist-cjs/index.cjs +43 -1
- package/package.json +1 -1
package/dist/ems-profile.d.ts
CHANGED
|
@@ -16,5 +16,11 @@ export declare const EMS_PROPERTY: {
|
|
|
16
16
|
readonly ratedKW: "power.ratedKW";
|
|
17
17
|
/** 멈춰 있을 때의 소비(kW). 없으면 멈춘 동안을 **비운다** — 0 이라고 주장하지 않는다. */
|
|
18
18
|
readonly standbyKW: "power.standbyKW";
|
|
19
|
+
/** 기본요금 단가 — 최대수요 1kW 당(청구 주기 기준). */
|
|
20
|
+
readonly demandChargePerKW: "tariff.demandChargePerKW";
|
|
21
|
+
/** 사용량 단가 — 1kWh 당. */
|
|
22
|
+
readonly energyChargePerKWh: "tariff.energyChargePerKWh";
|
|
23
|
+
/** 통화 — ISO 4217 코드(USD·KRW…). 없으면 금액에 단위를 붙이지 않는다. */
|
|
24
|
+
readonly currency: "tariff.currency";
|
|
19
25
|
};
|
|
20
26
|
export declare const EMS_TYPES: TwinTypeInfo[];
|
package/dist/ems-profile.js
CHANGED
|
@@ -20,7 +20,26 @@ export const EMS_PROPERTY = {
|
|
|
20
20
|
/** 가동 중 소비(kW). */
|
|
21
21
|
ratedKW: 'power.ratedKW',
|
|
22
22
|
/** 멈춰 있을 때의 소비(kW). 없으면 멈춘 동안을 **비운다** — 0 이라고 주장하지 않는다. */
|
|
23
|
-
standbyKW: 'power.standbyKW'
|
|
23
|
+
standbyKW: 'power.standbyKW',
|
|
24
|
+
/*
|
|
25
|
+
* ── 요금 단가 — 수를 금액으로 바꾸는 선언 (2026-08-17) ─────────────────────
|
|
26
|
+
*
|
|
27
|
+
* 그동안 트윈은 「최대수요 328kW」·「전력량 1,547kWh」까지 말하고 멈췄다. 그런데 피크를 깎는 일이
|
|
28
|
+
* 돈이 되는 이유는 **요금이 둘로 나뉘기** 때문이다: 사용량(kWh)에 붙는 요금과 **최대수요(kW)에 붙는
|
|
29
|
+
* 기본요금**. 단가가 없으면 그 절반을 말할 수 없고, 그래서 「피크를 깎아 얼마를 아끼나」에 답하지 못했다.
|
|
30
|
+
*
|
|
31
|
+
* 단가는 **현장의 계약**이다(같은 나라 안에서도 사업자·요금제마다 다르다). 그래서 기본값을 두지
|
|
32
|
+
* 않는다 — 선언하지 않으면 금액을 계산하지 않는다. 짐작한 단가로 낸 금액은 숫자가 있다는 것만으로
|
|
33
|
+
* 사람을 결정으로 밀어붙인다.
|
|
34
|
+
*
|
|
35
|
+
* 계약전력(`contract.kW`)과 같은 자리(수전)에 선언한다.
|
|
36
|
+
*/
|
|
37
|
+
/** 기본요금 단가 — 최대수요 1kW 당(청구 주기 기준). */
|
|
38
|
+
demandChargePerKW: 'tariff.demandChargePerKW',
|
|
39
|
+
/** 사용량 단가 — 1kWh 당. */
|
|
40
|
+
energyChargePerKWh: 'tariff.energyChargePerKWh',
|
|
41
|
+
/** 통화 — ISO 4217 코드(USD·KRW…). 없으면 금액에 단위를 붙이지 않는다. */
|
|
42
|
+
currency: 'tariff.currency'
|
|
24
43
|
};
|
|
25
44
|
export const EMS_TYPES = [
|
|
26
45
|
/* ── 자리: 전기적 구간 ─────────────────────────────────────────────────── */
|
|
@@ -221,3 +221,47 @@ export declare function energyOfWindows(windows: readonly {
|
|
|
221
221
|
startMs: number;
|
|
222
222
|
endMs: number;
|
|
223
223
|
}): WindowedEnergy;
|
|
224
|
+
/** 선언된 단가 — 없는 항목은 계산하지 않는다(0 으로 두지 않는다). */
|
|
225
|
+
export interface TariffDeclaration {
|
|
226
|
+
/** 최대수요 1kW 당 기본요금. */
|
|
227
|
+
demandChargePerKW?: number;
|
|
228
|
+
/** 1kWh 당 사용량 요금. */
|
|
229
|
+
energyChargePerKWh?: number;
|
|
230
|
+
/** ISO 4217 통화 코드. 없으면 금액에 단위를 붙일 수 없다. */
|
|
231
|
+
currency?: string;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* 이 창의 전기요금 — **청구서가 아니다.**
|
|
235
|
+
*
|
|
236
|
+
* ── 무엇을 계산하나 ─────────────────────────────────────────────────────────
|
|
237
|
+
* · 사용량 부분: `kWh × 단가`. 이것은 창에 비례하므로 창을 잘라도 뜻이 유지된다.
|
|
238
|
+
* · 기본요금 부분: `최대수요 × 단가`. **이것은 창의 값이 아니다** — 기본요금은 청구 주기(보통 한 달)의
|
|
239
|
+
* 최고 수요로 매겨진다. 그래서 이 값의 뜻은 「이 창의 최대수요가 그 주기의 최고로 남는다면 기본요금이
|
|
240
|
+
* 이만큼」이고, 그 조건을 `demandBasis` 로 함께 낸다. 조건을 감추면 창을 좁힌 사람이 요금이 줄었다고
|
|
241
|
+
* 읽는다(실제로는 같은 달의 다른 시각에 더 높은 피크가 있을 수 있다).
|
|
242
|
+
*
|
|
243
|
+
* ── 세우지 않은 것 ──────────────────────────────────────────────────────────
|
|
244
|
+
* 시간대 요금(TOU)·계절 요금·래칫(직전 개월의 피크를 끌고 가는 구조)·최소 청구액·역률 요금은
|
|
245
|
+
* **모델에 없다.** 있는 척하지 않는다 — 그것들이 필요한 현장에서는 이 값이 실 청구서와 다르고,
|
|
246
|
+
* 그 사실을 아는 편이 그럴듯한 총액보다 낫다.
|
|
247
|
+
*/
|
|
248
|
+
export type ElectricityCost = {
|
|
249
|
+
/** 사용량 요금 — 선언과 전력량이 다 있을 때만. */
|
|
250
|
+
energyCharge?: number;
|
|
251
|
+
/** 기본요금 — 선언과 최대수요가 다 있을 때만. */
|
|
252
|
+
demandCharge?: number;
|
|
253
|
+
/** 위 둘의 합. 둘 중 하나만 있으면 그 하나다(없는 쪽을 0 으로 세지 않는다). */
|
|
254
|
+
total?: number;
|
|
255
|
+
currency?: string;
|
|
256
|
+
/** 기본요금이 성립하는 조건 — 창의 피크가 청구 주기의 최고일 때. */
|
|
257
|
+
demandBasis?: 'window-peak-as-period-peak';
|
|
258
|
+
/** 세우지 않은 요금 요소 — 소비처가 「이 값이 청구서가 아니다」를 말할 수 있게. */
|
|
259
|
+
notModeled: readonly string[];
|
|
260
|
+
} | {
|
|
261
|
+
reason: 'no-tariff';
|
|
262
|
+
};
|
|
263
|
+
export declare function electricityCost(input: {
|
|
264
|
+
kWh?: number;
|
|
265
|
+
peakKW?: number;
|
|
266
|
+
tariff?: TariffDeclaration;
|
|
267
|
+
}): ElectricityCost;
|
|
@@ -239,3 +239,27 @@ export function energyOfWindows(windows, range) {
|
|
|
239
239
|
...(first !== undefined && last !== undefined ? { window: { startMs: first, endMs: last } } : {})
|
|
240
240
|
};
|
|
241
241
|
}
|
|
242
|
+
const NOT_MODELED = ['tou', 'seasonal', 'ratchet', 'minimum-bill', 'power-factor'];
|
|
243
|
+
export function electricityCost(input) {
|
|
244
|
+
const t = input.tariff;
|
|
245
|
+
const eRate = Number(t?.energyChargePerKWh);
|
|
246
|
+
const dRate = Number(t?.demandChargePerKW);
|
|
247
|
+
const hasE = Number.isFinite(eRate) && eRate > 0;
|
|
248
|
+
const hasD = Number.isFinite(dRate) && dRate > 0;
|
|
249
|
+
/* 단가가 하나도 없으면 금액을 만들지 않는다 — 이유를 낸다(빈 값과 0 원을 구별할 수 있게). */
|
|
250
|
+
if (!hasE && !hasD)
|
|
251
|
+
return { reason: 'no-tariff' };
|
|
252
|
+
const kWh = Number(input.kWh);
|
|
253
|
+
const peakKW = Number(input.peakKW);
|
|
254
|
+
const energyCharge = hasE && Number.isFinite(kWh) ? kWh * eRate : undefined;
|
|
255
|
+
const demandCharge = hasD && Number.isFinite(peakKW) ? peakKW * dRate : undefined;
|
|
256
|
+
/* 반올림하지 않는다 — 표현은 화면의 일이고, 여기서 깎으면 합과 부분이 어긋난다. */
|
|
257
|
+
const total = energyCharge === undefined && demandCharge === undefined ? undefined : (energyCharge ?? 0) + (demandCharge ?? 0);
|
|
258
|
+
return {
|
|
259
|
+
...(energyCharge !== undefined ? { energyCharge } : {}),
|
|
260
|
+
...(demandCharge !== undefined ? { demandCharge, demandBasis: 'window-peak-as-period-peak' } : {}),
|
|
261
|
+
...(total !== undefined ? { total } : {}),
|
|
262
|
+
...(t?.currency ? { currency: String(t.currency) } : {}),
|
|
263
|
+
notModeled: NOT_MODELED
|
|
264
|
+
};
|
|
265
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export { YmsKernel } from './yms-kernel.ts';
|
|
|
30
30
|
export { MesKernel, MES_PART_GTINS, MES_PRODUCT_GTINS, MES_PRODUCTS } from './mes-kernel.ts';
|
|
31
31
|
export { EmsKernel, DEMAND_WINDOW_MS, demandWindowStart } from './ems-kernel.ts';
|
|
32
32
|
export { ingestEnergyRecords, isEnergyRecord, ingestEnergyEquipmentRecords, isEnergyEquipmentRecord } from './energy-ingest.ts';
|
|
33
|
-
export { attributeEnergy, energyIntensity, energyOfWindows } from './energy-attribution.ts';
|
|
34
|
-
export type { AttributionBasis, AttributionResult, EnergyConsumer, EnergyPool, EnergyShare, IntensityInput, IntensityResult, IntensityDenominator, WeightKind, WindowedEnergy } from './energy-attribution.ts';
|
|
33
|
+
export { attributeEnergy, electricityCost, energyIntensity, energyOfWindows } from './energy-attribution.ts';
|
|
34
|
+
export type { AttributionBasis, AttributionResult, ElectricityCost, EnergyConsumer, EnergyPool, EnergyShare, IntensityInput, IntensityResult, IntensityDenominator, TariffDeclaration, WeightKind, WindowedEnergy } from './energy-attribution.ts';
|
|
35
35
|
export type { EnergyRecord, EnergyEquipmentRecord, EnergyIngestOptions, EnergyIngestResult } from './energy-ingest.ts';
|
|
36
36
|
export * from './vocabulary.ts';
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,6 @@ export { YmsKernel } from "./yms-kernel.js";
|
|
|
30
30
|
export { MesKernel, MES_PART_GTINS, MES_PRODUCT_GTINS, MES_PRODUCTS } from "./mes-kernel.js";
|
|
31
31
|
export { EmsKernel, DEMAND_WINDOW_MS, demandWindowStart } from "./ems-kernel.js";
|
|
32
32
|
export { ingestEnergyRecords, isEnergyRecord, ingestEnergyEquipmentRecords, isEnergyEquipmentRecord } from "./energy-ingest.js";
|
|
33
|
-
export { attributeEnergy, energyIntensity, energyOfWindows } from "./energy-attribution.js";
|
|
33
|
+
export { attributeEnergy, electricityCost, energyIntensity, energyOfWindows } from "./energy-attribution.js";
|
|
34
34
|
/* 에너지 상태 타입은 **계약**에 있다(상태의 모양은 계약이다) — contract 의 `export *` 가 이미 낸다. */
|
|
35
35
|
export * from "./vocabulary.js";
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -96,6 +96,7 @@ __export(index_exports, {
|
|
|
96
96
|
documentPath: () => documentPath,
|
|
97
97
|
dueStatusOf: () => dueStatusOf,
|
|
98
98
|
effectivityAt: () => effectivityAt,
|
|
99
|
+
electricityCost: () => electricityCost,
|
|
99
100
|
energyIntensity: () => energyIntensity,
|
|
100
101
|
energyOfWindows: () => energyOfWindows,
|
|
101
102
|
fefoPolicy: () => fefoPolicy,
|
|
@@ -1683,7 +1684,26 @@ var EMS_PROPERTY = {
|
|
|
1683
1684
|
/** 가동 중 소비(kW). */
|
|
1684
1685
|
ratedKW: "power.ratedKW",
|
|
1685
1686
|
/** 멈춰 있을 때의 소비(kW). 없으면 멈춘 동안을 **비운다** — 0 이라고 주장하지 않는다. */
|
|
1686
|
-
standbyKW: "power.standbyKW"
|
|
1687
|
+
standbyKW: "power.standbyKW",
|
|
1688
|
+
/*
|
|
1689
|
+
* ── 요금 단가 — 수를 금액으로 바꾸는 선언 (2026-08-17) ─────────────────────
|
|
1690
|
+
*
|
|
1691
|
+
* 그동안 트윈은 「최대수요 328kW」·「전력량 1,547kWh」까지 말하고 멈췄다. 그런데 피크를 깎는 일이
|
|
1692
|
+
* 돈이 되는 이유는 **요금이 둘로 나뉘기** 때문이다: 사용량(kWh)에 붙는 요금과 **최대수요(kW)에 붙는
|
|
1693
|
+
* 기본요금**. 단가가 없으면 그 절반을 말할 수 없고, 그래서 「피크를 깎아 얼마를 아끼나」에 답하지 못했다.
|
|
1694
|
+
*
|
|
1695
|
+
* 단가는 **현장의 계약**이다(같은 나라 안에서도 사업자·요금제마다 다르다). 그래서 기본값을 두지
|
|
1696
|
+
* 않는다 — 선언하지 않으면 금액을 계산하지 않는다. 짐작한 단가로 낸 금액은 숫자가 있다는 것만으로
|
|
1697
|
+
* 사람을 결정으로 밀어붙인다.
|
|
1698
|
+
*
|
|
1699
|
+
* 계약전력(`contract.kW`)과 같은 자리(수전)에 선언한다.
|
|
1700
|
+
*/
|
|
1701
|
+
/** 기본요금 단가 — 최대수요 1kW 당(청구 주기 기준). */
|
|
1702
|
+
demandChargePerKW: "tariff.demandChargePerKW",
|
|
1703
|
+
/** 사용량 단가 — 1kWh 당. */
|
|
1704
|
+
energyChargePerKWh: "tariff.energyChargePerKWh",
|
|
1705
|
+
/** 통화 — ISO 4217 코드(USD·KRW…). 없으면 금액에 단위를 붙이지 않는다. */
|
|
1706
|
+
currency: "tariff.currency"
|
|
1687
1707
|
};
|
|
1688
1708
|
var EMS_TYPES = [
|
|
1689
1709
|
/* ── 자리: 전기적 구간 ─────────────────────────────────────────────────── */
|
|
@@ -6468,6 +6488,27 @@ function energyOfWindows(windows, range) {
|
|
|
6468
6488
|
...first !== void 0 && last !== void 0 ? { window: { startMs: first, endMs: last } } : {}
|
|
6469
6489
|
};
|
|
6470
6490
|
}
|
|
6491
|
+
var NOT_MODELED = ["tou", "seasonal", "ratchet", "minimum-bill", "power-factor"];
|
|
6492
|
+
function electricityCost(input) {
|
|
6493
|
+
const t = input.tariff;
|
|
6494
|
+
const eRate = Number(t?.energyChargePerKWh);
|
|
6495
|
+
const dRate = Number(t?.demandChargePerKW);
|
|
6496
|
+
const hasE = Number.isFinite(eRate) && eRate > 0;
|
|
6497
|
+
const hasD = Number.isFinite(dRate) && dRate > 0;
|
|
6498
|
+
if (!hasE && !hasD) return { reason: "no-tariff" };
|
|
6499
|
+
const kWh = Number(input.kWh);
|
|
6500
|
+
const peakKW = Number(input.peakKW);
|
|
6501
|
+
const energyCharge = hasE && Number.isFinite(kWh) ? kWh * eRate : void 0;
|
|
6502
|
+
const demandCharge = hasD && Number.isFinite(peakKW) ? peakKW * dRate : void 0;
|
|
6503
|
+
const total = energyCharge === void 0 && demandCharge === void 0 ? void 0 : (energyCharge ?? 0) + (demandCharge ?? 0);
|
|
6504
|
+
return {
|
|
6505
|
+
...energyCharge !== void 0 ? { energyCharge } : {},
|
|
6506
|
+
...demandCharge !== void 0 ? { demandCharge, demandBasis: "window-peak-as-period-peak" } : {},
|
|
6507
|
+
...total !== void 0 ? { total } : {},
|
|
6508
|
+
...t?.currency ? { currency: String(t.currency) } : {},
|
|
6509
|
+
notModeled: NOT_MODELED
|
|
6510
|
+
};
|
|
6511
|
+
}
|
|
6471
6512
|
|
|
6472
6513
|
// src/vocabulary.ts
|
|
6473
6514
|
var RETIRED_VOCABULARY = ["mover", "Mover", "MOVER", "node", "Node", "NODE"];
|
|
@@ -6590,6 +6631,7 @@ function retiredVocabularyIn(line) {
|
|
|
6590
6631
|
documentPath,
|
|
6591
6632
|
dueStatusOf,
|
|
6592
6633
|
effectivityAt,
|
|
6634
|
+
electricityCost,
|
|
6593
6635
|
energyIntensity,
|
|
6594
6636
|
energyOfWindows,
|
|
6595
6637
|
fefoPolicy,
|
package/package.json
CHANGED