@operato/twin-kernel 0.7.76 → 0.7.77
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-kernel.d.ts +19 -0
- package/dist/ems-kernel.js +43 -0
- package/dist/ems-profile.d.ts +15 -1
- package/dist/ems-profile.js +25 -2
- package/dist/energy-ingest.js +10 -0
- package/dist-cjs/index.cjs +65 -2
- package/package.json +1 -1
package/dist/ems-kernel.d.ts
CHANGED
|
@@ -337,6 +337,25 @@ export declare class EmsKernel extends FlowEngine {
|
|
|
337
337
|
* 계기가 잰 방전으로 읽는다(수요 구간의 `derived` 와 같은 규율).
|
|
338
338
|
*/
|
|
339
339
|
private emitEnergyEquipment;
|
|
340
|
+
/**
|
|
341
|
+
* 이 트윈의 **발전 정격** — 이용률의 분모가 될 값과 그 근거.
|
|
342
|
+
*
|
|
343
|
+
* ── 어느 정격으로 나누나 ───────────────────────────────────────────────────
|
|
344
|
+
* 교류가 먼저다. 계통으로 나가는 양은 교류 정격을 넘을 수 없으므로 그 비가 1 을 넘지 않는다.
|
|
345
|
+
* 직류로 나누면 잘 만든 발전소도 100% 에 닿지 않아 늘 낮게 보인다.
|
|
346
|
+
*
|
|
347
|
+
* **다만 시스템마다 관행이 다르다.** 직류로 나눠 보여 주는 화면이 실제로 있다. 그래서 커널은
|
|
348
|
+
* 어느 쪽으로 나눴는지를 값과 함께 낸다 — 두 화면의 수가 다를 때 사람이 맞춰 볼 수 있어야 한다.
|
|
349
|
+
*
|
|
350
|
+
* ── 어디서 읽나 ────────────────────────────────────────────────────────────
|
|
351
|
+
* 설비마다 선언돼 있으면 그 합이다. 없으면 자리(수전)에 선언된 합계를 쓴다. 둘 다 없으면
|
|
352
|
+
* 계산하지 않는다 — 0 이나 짐작을 내지 않는다.
|
|
353
|
+
*/
|
|
354
|
+
generationRated(): {
|
|
355
|
+
kW: number;
|
|
356
|
+
basis: 'ac' | 'dc';
|
|
357
|
+
from: 'equipment' | 'location';
|
|
358
|
+
} | undefined;
|
|
340
359
|
/** 자원 속성에서 수 하나 — 값이 수가 아니면 없는 것으로 본다(짐작하지 않는다). */
|
|
341
360
|
private numberProperty;
|
|
342
361
|
/** 글자로 선언된 값(발전 형상·통화 같은 것) — 비어 있으면 없는 것으로 본다. */
|
package/dist/ems-kernel.js
CHANGED
|
@@ -1410,6 +1410,49 @@ export class EmsKernel extends FlowEngine {
|
|
|
1410
1410
|
emitEnergyEquipment(equipmentId, atMs, fields) {
|
|
1411
1411
|
this.emitOp(ENERGY_EVENT.equipment, { equipmentId, at: new Date(atMs).toISOString(), derived: true, ...fields });
|
|
1412
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* 이 트윈의 **발전 정격** — 이용률의 분모가 될 값과 그 근거.
|
|
1415
|
+
*
|
|
1416
|
+
* ── 어느 정격으로 나누나 ───────────────────────────────────────────────────
|
|
1417
|
+
* 교류가 먼저다. 계통으로 나가는 양은 교류 정격을 넘을 수 없으므로 그 비가 1 을 넘지 않는다.
|
|
1418
|
+
* 직류로 나누면 잘 만든 발전소도 100% 에 닿지 않아 늘 낮게 보인다.
|
|
1419
|
+
*
|
|
1420
|
+
* **다만 시스템마다 관행이 다르다.** 직류로 나눠 보여 주는 화면이 실제로 있다. 그래서 커널은
|
|
1421
|
+
* 어느 쪽으로 나눴는지를 값과 함께 낸다 — 두 화면의 수가 다를 때 사람이 맞춰 볼 수 있어야 한다.
|
|
1422
|
+
*
|
|
1423
|
+
* ── 어디서 읽나 ────────────────────────────────────────────────────────────
|
|
1424
|
+
* 설비마다 선언돼 있으면 그 합이다. 없으면 자리(수전)에 선언된 합계를 쓴다. 둘 다 없으면
|
|
1425
|
+
* 계산하지 않는다 — 0 이나 짐작을 내지 않는다.
|
|
1426
|
+
*/
|
|
1427
|
+
generationRated() {
|
|
1428
|
+
for (const [prop, basis] of [
|
|
1429
|
+
[EMS_PROPERTY.genRatedKW, 'ac'],
|
|
1430
|
+
[EMS_PROPERTY.genRatedKWdc, 'dc']
|
|
1431
|
+
]) {
|
|
1432
|
+
let sum = 0;
|
|
1433
|
+
for (const e of this.boardDef?.equipment ?? []) {
|
|
1434
|
+
const v = this.numberProperty(e, prop);
|
|
1435
|
+
if (v !== undefined && v > 0)
|
|
1436
|
+
sum += v;
|
|
1437
|
+
}
|
|
1438
|
+
if (sum > 0)
|
|
1439
|
+
return { kW: sum, basis, from: 'equipment' };
|
|
1440
|
+
}
|
|
1441
|
+
for (const [prop, basis] of [
|
|
1442
|
+
[EMS_PROPERTY.genRatedKW, 'ac'],
|
|
1443
|
+
[EMS_PROPERTY.genRatedKWdc, 'dc']
|
|
1444
|
+
]) {
|
|
1445
|
+
let sum = 0;
|
|
1446
|
+
for (const loc of this.boardDef?.locations ?? []) {
|
|
1447
|
+
const v = this.numberProperty(loc, prop);
|
|
1448
|
+
if (v !== undefined && v > 0)
|
|
1449
|
+
sum += v;
|
|
1450
|
+
}
|
|
1451
|
+
if (sum > 0)
|
|
1452
|
+
return { kW: sum, basis, from: 'location' };
|
|
1453
|
+
}
|
|
1454
|
+
return undefined;
|
|
1455
|
+
}
|
|
1413
1456
|
/** 자원 속성에서 수 하나 — 값이 수가 아니면 없는 것으로 본다(짐작하지 않는다). */
|
|
1414
1457
|
numberProperty(resource, id) {
|
|
1415
1458
|
for (const p of resource?.properties ?? []) {
|
package/dist/ems-profile.d.ts
CHANGED
|
@@ -70,8 +70,22 @@ export declare const EMS_PROPERTY: {
|
|
|
70
70
|
* 이것은 상태의 씨앗이지 계측이 아니다. 그래서 계측이 들어오는 순간 그것이 이긴다.
|
|
71
71
|
*/
|
|
72
72
|
readonly initialSoc: "storage.initialSoc";
|
|
73
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* 발전 정격(kW) — **교류 쪽 최대 출력**. 계통으로 나가는 값이다.
|
|
75
|
+
*
|
|
76
|
+
* 이용률의 분모가 이 값이다(만든 양 ÷ 정격 × 시간). **직류 쪽 정격(§`genRatedKWdc`)과 다르고 더 작다** —
|
|
77
|
+
* 두 값을 한 이름에 담으면 이용률이 그 차이만큼 틀리고, 틀린 이유가 아무 데도 표시되지 않는다.
|
|
78
|
+
*
|
|
79
|
+
* 설비마다 선언하거나, 설비별 값을 모르는 현장은 **자리(수전)에 합계로** 선언한다.
|
|
80
|
+
*/
|
|
74
81
|
readonly genRatedKW: "generation.ratedKW";
|
|
82
|
+
/**
|
|
83
|
+
* 발전 정격(kW) — **직류 쪽**. 변환기(인버터)의 입력 쪽 정격이고, 태양광이면 패널 정격의 합이다.
|
|
84
|
+
*
|
|
85
|
+
* 이용률의 분모로 쓰지 않는다. 이 값이 교류 정격보다 큰 것은 설계이고(변환기를 그렇게 고른다),
|
|
86
|
+
* 그 차이가 「맑은 정오에도 교류 출력이 더 오르지 않는 이유」를 설명한다.
|
|
87
|
+
*/
|
|
88
|
+
readonly genRatedKWdc: "generation.ratedKWdc";
|
|
75
89
|
/**
|
|
76
90
|
* 하루 형상 — 쉼표로 나눈 **24개 비율**(0~1). 시각(현장 시간대)의 정격 대비 출력이다.
|
|
77
91
|
*
|
package/dist/ems-profile.js
CHANGED
|
@@ -131,8 +131,22 @@ export const EMS_PROPERTY = {
|
|
|
131
131
|
*
|
|
132
132
|
* 정격만 있고 형상이 없으면 발전하지 않는다 — 하루 종일 정격으로 발전하는 태양광은 없다.
|
|
133
133
|
*/
|
|
134
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* 발전 정격(kW) — **교류 쪽 최대 출력**. 계통으로 나가는 값이다.
|
|
136
|
+
*
|
|
137
|
+
* 이용률의 분모가 이 값이다(만든 양 ÷ 정격 × 시간). **직류 쪽 정격(§`genRatedKWdc`)과 다르고 더 작다** —
|
|
138
|
+
* 두 값을 한 이름에 담으면 이용률이 그 차이만큼 틀리고, 틀린 이유가 아무 데도 표시되지 않는다.
|
|
139
|
+
*
|
|
140
|
+
* 설비마다 선언하거나, 설비별 값을 모르는 현장은 **자리(수전)에 합계로** 선언한다.
|
|
141
|
+
*/
|
|
135
142
|
genRatedKW: 'generation.ratedKW',
|
|
143
|
+
/**
|
|
144
|
+
* 발전 정격(kW) — **직류 쪽**. 변환기(인버터)의 입력 쪽 정격이고, 태양광이면 패널 정격의 합이다.
|
|
145
|
+
*
|
|
146
|
+
* 이용률의 분모로 쓰지 않는다. 이 값이 교류 정격보다 큰 것은 설계이고(변환기를 그렇게 고른다),
|
|
147
|
+
* 그 차이가 「맑은 정오에도 교류 출력이 더 오르지 않는 이유」를 설명한다.
|
|
148
|
+
*/
|
|
149
|
+
genRatedKWdc: 'generation.ratedKWdc',
|
|
136
150
|
/**
|
|
137
151
|
* 하루 형상 — 쉼표로 나눈 **24개 비율**(0~1). 시각(현장 시간대)의 정격 대비 출력이다.
|
|
138
152
|
*
|
|
@@ -165,7 +179,16 @@ export const EMS_PROPERTY_SPEC = {
|
|
|
165
179
|
/* 퍼센트다 — 0.8 은 0.8% 이고 80% 가 아니다. 이 한 줄이 없어서 배터리가 조용히 비어 있었다. */
|
|
166
180
|
[EMS_PROPERTY.reserveSoc]: { uom: '%', dataType: 'xs:double', range: [0, 100], note: 'reserve state of charge as a percentage 0-100 (20 means 20%), never discharged below.' },
|
|
167
181
|
[EMS_PROPERTY.initialSoc]: { uom: '%', dataType: 'xs:double', range: [0, 100], note: 'starting state of charge as a percentage 0-100 (80 means 80%, not 0.8).' },
|
|
168
|
-
[EMS_PROPERTY.genRatedKW]: {
|
|
182
|
+
[EMS_PROPERTY.genRatedKW]: {
|
|
183
|
+
uom: 'kW',
|
|
184
|
+
dataType: 'xs:double',
|
|
185
|
+
note: 'rated AC generation output, in kW — the denominator of capacity factor. Declare per equipment, or on the incoming location as a site total.'
|
|
186
|
+
},
|
|
187
|
+
[EMS_PROPERTY.genRatedKWdc]: {
|
|
188
|
+
uom: 'kW',
|
|
189
|
+
dataType: 'xs:double',
|
|
190
|
+
note: 'rated DC generation capacity, in kW (panel rating for PV). Larger than the AC rating by design; not the capacity-factor denominator.'
|
|
191
|
+
},
|
|
169
192
|
[EMS_PROPERTY.genDailyProfile]: {
|
|
170
193
|
dataType: 'xs:string',
|
|
171
194
|
note: 'daily shape as 24 comma-separated fractions of rated output (0-1), one per hour of local time — the site declares its own curve; we do not invent one.'
|
package/dist/energy-ingest.js
CHANGED
|
@@ -384,6 +384,16 @@ export function isEnergyTariffBasisRecord(record) {
|
|
|
384
384
|
return false;
|
|
385
385
|
if (r.from === undefined || r.to === undefined)
|
|
386
386
|
return false;
|
|
387
|
+
/*
|
|
388
|
+
* ── 청구서와 배타로 (2026-08-29) ───────────────────────────────────────────
|
|
389
|
+
*
|
|
390
|
+
* 청구서가 요금적용전력을 함께 실으면 이 판별도 참이 됐다. 그때 이 문으로 가면 「금액은 받지
|
|
391
|
+
* 않는다」로 거부되어 **정산이 통째로 사라진다** — 거부는 되지만 그 사실을 잃는 것은 같다.
|
|
392
|
+
*
|
|
393
|
+
* 금액이 하나라도 있으면 그것은 정산이다. 기준은 금액을 싣지 않는다.
|
|
394
|
+
*/
|
|
395
|
+
if (r.energyCharge !== undefined || r.demandCharge !== undefined || r.total !== undefined)
|
|
396
|
+
return false;
|
|
387
397
|
return r.billingDemandKW !== undefined || r.demandChargePerKW !== undefined;
|
|
388
398
|
}
|
|
389
399
|
/**
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -7567,8 +7567,22 @@ var EMS_PROPERTY = {
|
|
|
7567
7567
|
*
|
|
7568
7568
|
* 정격만 있고 형상이 없으면 발전하지 않는다 — 하루 종일 정격으로 발전하는 태양광은 없다.
|
|
7569
7569
|
*/
|
|
7570
|
-
/**
|
|
7570
|
+
/**
|
|
7571
|
+
* 발전 정격(kW) — **교류 쪽 최대 출력**. 계통으로 나가는 값이다.
|
|
7572
|
+
*
|
|
7573
|
+
* 이용률의 분모가 이 값이다(만든 양 ÷ 정격 × 시간). **직류 쪽 정격(§`genRatedKWdc`)과 다르고 더 작다** —
|
|
7574
|
+
* 두 값을 한 이름에 담으면 이용률이 그 차이만큼 틀리고, 틀린 이유가 아무 데도 표시되지 않는다.
|
|
7575
|
+
*
|
|
7576
|
+
* 설비마다 선언하거나, 설비별 값을 모르는 현장은 **자리(수전)에 합계로** 선언한다.
|
|
7577
|
+
*/
|
|
7571
7578
|
genRatedKW: "generation.ratedKW",
|
|
7579
|
+
/**
|
|
7580
|
+
* 발전 정격(kW) — **직류 쪽**. 변환기(인버터)의 입력 쪽 정격이고, 태양광이면 패널 정격의 합이다.
|
|
7581
|
+
*
|
|
7582
|
+
* 이용률의 분모로 쓰지 않는다. 이 값이 교류 정격보다 큰 것은 설계이고(변환기를 그렇게 고른다),
|
|
7583
|
+
* 그 차이가 「맑은 정오에도 교류 출력이 더 오르지 않는 이유」를 설명한다.
|
|
7584
|
+
*/
|
|
7585
|
+
genRatedKWdc: "generation.ratedKWdc",
|
|
7572
7586
|
/**
|
|
7573
7587
|
* 하루 형상 — 쉼표로 나눈 **24개 비율**(0~1). 시각(현장 시간대)의 정격 대비 출력이다.
|
|
7574
7588
|
*
|
|
@@ -7599,7 +7613,16 @@ var EMS_PROPERTY_SPEC = {
|
|
|
7599
7613
|
/* 퍼센트다 — 0.8 은 0.8% 이고 80% 가 아니다. 이 한 줄이 없어서 배터리가 조용히 비어 있었다. */
|
|
7600
7614
|
[EMS_PROPERTY.reserveSoc]: { uom: "%", dataType: "xs:double", range: [0, 100], note: "reserve state of charge as a percentage 0-100 (20 means 20%), never discharged below." },
|
|
7601
7615
|
[EMS_PROPERTY.initialSoc]: { uom: "%", dataType: "xs:double", range: [0, 100], note: "starting state of charge as a percentage 0-100 (80 means 80%, not 0.8)." },
|
|
7602
|
-
[EMS_PROPERTY.genRatedKW]: {
|
|
7616
|
+
[EMS_PROPERTY.genRatedKW]: {
|
|
7617
|
+
uom: "kW",
|
|
7618
|
+
dataType: "xs:double",
|
|
7619
|
+
note: "rated AC generation output, in kW \u2014 the denominator of capacity factor. Declare per equipment, or on the incoming location as a site total."
|
|
7620
|
+
},
|
|
7621
|
+
[EMS_PROPERTY.genRatedKWdc]: {
|
|
7622
|
+
uom: "kW",
|
|
7623
|
+
dataType: "xs:double",
|
|
7624
|
+
note: "rated DC generation capacity, in kW (panel rating for PV). Larger than the AC rating by design; not the capacity-factor denominator."
|
|
7625
|
+
},
|
|
7603
7626
|
[EMS_PROPERTY.genDailyProfile]: {
|
|
7604
7627
|
dataType: "xs:string",
|
|
7605
7628
|
note: "daily shape as 24 comma-separated fractions of rated output (0-1), one per hour of local time \u2014 the site declares its own curve; we do not invent one."
|
|
@@ -8760,6 +8783,45 @@ var EmsKernel = class extends FlowEngine {
|
|
|
8760
8783
|
emitEnergyEquipment(equipmentId, atMs, fields) {
|
|
8761
8784
|
this.emitOp(ENERGY_EVENT.equipment, { equipmentId, at: new Date(atMs).toISOString(), derived: true, ...fields });
|
|
8762
8785
|
}
|
|
8786
|
+
/**
|
|
8787
|
+
* 이 트윈의 **발전 정격** — 이용률의 분모가 될 값과 그 근거.
|
|
8788
|
+
*
|
|
8789
|
+
* ── 어느 정격으로 나누나 ───────────────────────────────────────────────────
|
|
8790
|
+
* 교류가 먼저다. 계통으로 나가는 양은 교류 정격을 넘을 수 없으므로 그 비가 1 을 넘지 않는다.
|
|
8791
|
+
* 직류로 나누면 잘 만든 발전소도 100% 에 닿지 않아 늘 낮게 보인다.
|
|
8792
|
+
*
|
|
8793
|
+
* **다만 시스템마다 관행이 다르다.** 직류로 나눠 보여 주는 화면이 실제로 있다. 그래서 커널은
|
|
8794
|
+
* 어느 쪽으로 나눴는지를 값과 함께 낸다 — 두 화면의 수가 다를 때 사람이 맞춰 볼 수 있어야 한다.
|
|
8795
|
+
*
|
|
8796
|
+
* ── 어디서 읽나 ────────────────────────────────────────────────────────────
|
|
8797
|
+
* 설비마다 선언돼 있으면 그 합이다. 없으면 자리(수전)에 선언된 합계를 쓴다. 둘 다 없으면
|
|
8798
|
+
* 계산하지 않는다 — 0 이나 짐작을 내지 않는다.
|
|
8799
|
+
*/
|
|
8800
|
+
generationRated() {
|
|
8801
|
+
for (const [prop, basis] of [
|
|
8802
|
+
[EMS_PROPERTY.genRatedKW, "ac"],
|
|
8803
|
+
[EMS_PROPERTY.genRatedKWdc, "dc"]
|
|
8804
|
+
]) {
|
|
8805
|
+
let sum = 0;
|
|
8806
|
+
for (const e of this.boardDef?.equipment ?? []) {
|
|
8807
|
+
const v = this.numberProperty(e, prop);
|
|
8808
|
+
if (v !== void 0 && v > 0) sum += v;
|
|
8809
|
+
}
|
|
8810
|
+
if (sum > 0) return { kW: sum, basis, from: "equipment" };
|
|
8811
|
+
}
|
|
8812
|
+
for (const [prop, basis] of [
|
|
8813
|
+
[EMS_PROPERTY.genRatedKW, "ac"],
|
|
8814
|
+
[EMS_PROPERTY.genRatedKWdc, "dc"]
|
|
8815
|
+
]) {
|
|
8816
|
+
let sum = 0;
|
|
8817
|
+
for (const loc of this.boardDef?.locations ?? []) {
|
|
8818
|
+
const v = this.numberProperty(loc, prop);
|
|
8819
|
+
if (v !== void 0 && v > 0) sum += v;
|
|
8820
|
+
}
|
|
8821
|
+
if (sum > 0) return { kW: sum, basis, from: "location" };
|
|
8822
|
+
}
|
|
8823
|
+
return void 0;
|
|
8824
|
+
}
|
|
8763
8825
|
/** 자원 속성에서 수 하나 — 값이 수가 아니면 없는 것으로 본다(짐작하지 않는다). */
|
|
8764
8826
|
numberProperty(resource, id) {
|
|
8765
8827
|
for (const p of resource?.properties ?? []) {
|
|
@@ -9910,6 +9972,7 @@ function isEnergyTariffBasisRecord(record) {
|
|
|
9910
9972
|
const r = record;
|
|
9911
9973
|
if (!r || typeof r !== "object") return false;
|
|
9912
9974
|
if (r.from === void 0 || r.to === void 0) return false;
|
|
9975
|
+
if (r.energyCharge !== void 0 || r.demandCharge !== void 0 || r.total !== void 0) return false;
|
|
9913
9976
|
return r.billingDemandKW !== void 0 || r.demandChargePerKW !== void 0;
|
|
9914
9977
|
}
|
|
9915
9978
|
function ingestEnergyTariffBasisRecords(records, opts) {
|
package/package.json
CHANGED