@enyo-energy/energy-app-sdk 1.3.0 → 1.4.0
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/README.md +87 -57
- package/dist/cjs/energy-app.cjs +12 -6
- package/dist/cjs/energy-app.d.cts +12 -6
- package/dist/cjs/implementations/appliances/appliance-manager.cjs +6 -0
- package/dist/cjs/implementations/pricing/compose-electricity-prices.cjs +47 -10
- package/dist/cjs/implementations/pricing/compose-electricity-prices.d.cts +34 -9
- package/dist/cjs/implementations/pricing/price-component-validators.cjs +0 -57
- package/dist/cjs/implementations/pricing/price-component-validators.d.cts +1 -34
- package/dist/cjs/packages/energy-app-charge.d.cts +8 -81
- package/dist/cjs/packages/energy-app-electricity-tariff.d.cts +148 -69
- package/dist/cjs/packages/energy-app-grid-fee.d.cts +105 -87
- package/dist/cjs/packages/energy-app-modbus.d.cts +12 -0
- package/dist/cjs/types/enyo-charge.d.cts +0 -71
- package/dist/cjs/types/enyo-data-bus-value.d.cts +7 -0
- package/dist/cjs/types/enyo-electricity-tariff.cjs +64 -36
- package/dist/cjs/types/enyo-electricity-tariff.d.cts +192 -139
- package/dist/cjs/types/enyo-energy-prices.d.cts +9 -3
- package/dist/cjs/types/enyo-grid-fee.cjs +14 -3
- package/dist/cjs/types/enyo-grid-fee.d.cts +149 -31
- package/dist/cjs/types/enyo-price-composition.d.cts +35 -14
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/energy-app.d.ts +12 -6
- package/dist/energy-app.js +12 -6
- package/dist/implementations/appliances/appliance-manager.js +6 -0
- package/dist/implementations/pricing/compose-electricity-prices.d.ts +34 -9
- package/dist/implementations/pricing/compose-electricity-prices.js +46 -10
- package/dist/implementations/pricing/price-component-validators.d.ts +1 -34
- package/dist/implementations/pricing/price-component-validators.js +0 -55
- package/dist/packages/energy-app-charge.d.ts +8 -81
- package/dist/packages/energy-app-electricity-tariff.d.ts +148 -69
- package/dist/packages/energy-app-grid-fee.d.ts +105 -87
- package/dist/packages/energy-app-modbus.d.ts +12 -0
- package/dist/types/enyo-charge.d.ts +0 -71
- package/dist/types/enyo-data-bus-value.d.ts +7 -0
- package/dist/types/enyo-electricity-tariff.d.ts +192 -139
- package/dist/types/enyo-electricity-tariff.js +63 -35
- package/dist/types/enyo-energy-prices.d.ts +9 -3
- package/dist/types/enyo-grid-fee.d.ts +149 -31
- package/dist/types/enyo-grid-fee.js +13 -2
- package/dist/types/enyo-price-composition.d.ts +35 -14
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -1,93 +1,20 @@
|
|
|
1
|
-
import { EnyoCharge, EnyoChargeFilter, EnyoDefaultChargeMode
|
|
1
|
+
import { EnyoCharge, EnyoChargeFilter, EnyoDefaultChargeMode } from "../types/enyo-charge.cjs";
|
|
2
2
|
/**
|
|
3
3
|
* Interface for managing charging sessions in enyo packages.
|
|
4
4
|
* Provides operations for charge session tracking and management.
|
|
5
5
|
*/
|
|
6
6
|
export interface EnergyAppCharge {
|
|
7
|
-
/**
|
|
8
|
-
* Opens a charging session for an appliance and returns it.
|
|
9
|
-
*
|
|
10
|
-
* Use this to start a session — not {@link save}. `save` writes whatever
|
|
11
|
-
* object it is given and offers no uniqueness guarantee, so two
|
|
12
|
-
* StartTransactions arriving a few milliseconds apart both pass an app-side
|
|
13
|
-
* "is one already active?" check and create two sessions for one plug-in.
|
|
14
|
-
* This call is handled by the host, which holds the invariant that an
|
|
15
|
-
* appliance has at most one active charge.
|
|
16
|
-
*
|
|
17
|
-
* **Idempotent.** When a session is already active for the same
|
|
18
|
-
* `applianceId` + `transactionId`, that session is returned unchanged
|
|
19
|
-
* rather than a second one being created — a repeated StartTransaction is
|
|
20
|
-
* safe. A session that is active on the appliance under a *different*
|
|
21
|
-
* transaction ID is treated as over: the host finalises it with best-effort
|
|
22
|
-
* end values before opening this one. If it is really the same physical
|
|
23
|
-
* session continuing under a new transaction ID (a reconnect, a
|
|
24
|
-
* re-authorization), use {@link addTransactionIdToActiveCharge} instead.
|
|
25
|
-
*
|
|
26
|
-
* The returned charge carries its `id`, so the session can be updated and
|
|
27
|
-
* later stopped without a {@link findActiveCharge} round-trip first.
|
|
28
|
-
*
|
|
29
|
-
* **Required permission:** `Charge`.
|
|
30
|
-
*
|
|
31
|
-
* @param request - What is known at plug-in time.
|
|
32
|
-
* @returns Promise resolving to the started session — or the session that
|
|
33
|
-
* was already running for this appliance and transaction.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```typescript
|
|
37
|
-
* const charge = await energyApp.useCharge().startCharge({
|
|
38
|
-
* applianceId,
|
|
39
|
-
* transactionId: ocppTransactionId,
|
|
40
|
-
* meterStartValueWh: meterValue,
|
|
41
|
-
* connectorId: 1,
|
|
42
|
-
* });
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
startCharge: (request: EnyoStartChargeRequest) => Promise<EnyoCharge>;
|
|
46
|
-
/**
|
|
47
|
-
* Closes a charging session and returns the final record.
|
|
48
|
-
*
|
|
49
|
-
* The host does the arithmetic, and that is the point of this call: it
|
|
50
|
-
* derives {@link EnyoCharge.totalEnergyKwh} and
|
|
51
|
-
* {@link EnyoCharge.paidPriceEuroCent} from the session's meter readings
|
|
52
|
-
* and the electricity tariff in force while the energy flowed, then moves
|
|
53
|
-
* the session to a terminal status and notifies
|
|
54
|
-
* {@link listenForChargeStopped}. An app must not price a session itself —
|
|
55
|
-
* totals computed app-side would differ between apps, and a customer's bill
|
|
56
|
-
* would depend on which app happened to close the charge.
|
|
57
|
-
*
|
|
58
|
-
* Supply readings, not totals: `meterEndValueWh` and, when the charger
|
|
59
|
-
* reports it, `energyDeliveredWh`. Anything an app writes into
|
|
60
|
-
* `paidPriceEuroCent` via {@link save} is overwritten here.
|
|
61
|
-
*
|
|
62
|
-
* Stopping a session that is already in a terminal status is a no-op and
|
|
63
|
-
* returns the stored record — a duplicate StopTransaction will not re-price
|
|
64
|
-
* a finished charge.
|
|
65
|
-
*
|
|
66
|
-
* **Required permission:** `Charge`.
|
|
67
|
-
*
|
|
68
|
-
* @param chargeId - The session to close, as returned by
|
|
69
|
-
* {@link startCharge} or {@link findActiveCharge}.
|
|
70
|
-
* @param request - How the session ended.
|
|
71
|
-
* @returns Promise resolving to the final charge record, totals included.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```typescript
|
|
75
|
-
* const finished = await energyApp.useCharge().stopCharge(charge.id, {
|
|
76
|
-
* meterEndValueWh: meterValue,
|
|
77
|
-
* energyDeliveredWh: stopTransaction.energyWh,
|
|
78
|
-
* });
|
|
79
|
-
* console.log(`${finished.totalEnergyKwh} kWh for ${finished.paidPriceEuroCent} ct`);
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
stopCharge: (chargeId: string, request: EnyoStopChargeRequest) => Promise<EnyoCharge>;
|
|
83
7
|
/**
|
|
84
8
|
* Save or update a charging session in the system.
|
|
85
9
|
*
|
|
86
10
|
* This is a whole-object write for a session that already exists — a
|
|
87
|
-
* changed vehicle, a corrected reading.
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
11
|
+
* changed vehicle, a corrected reading. Calling it without a `chargeId`
|
|
12
|
+
* creates a row with no uniqueness guarantee, so two StartTransactions
|
|
13
|
+
* arriving a few milliseconds apart both pass an app-side "is one already
|
|
14
|
+
* active?" check and create two sessions for one plug-in. Writing terminal
|
|
15
|
+
* values by hand also skips the host's energy and cost calculation, which
|
|
16
|
+
* is what turns meter readings into {@link EnyoCharge.totalEnergyKwh} and
|
|
17
|
+
* {@link EnyoCharge.paidPriceEuroCent}.
|
|
91
18
|
*/
|
|
92
19
|
save: (charge: Omit<EnyoCharge, 'id'>, chargeId?: string) => Promise<void>;
|
|
93
20
|
/** Get a list of charging sessions with optional filtering */
|
|
@@ -1,96 +1,175 @@
|
|
|
1
|
-
import { EnyoElectricityTariff,
|
|
1
|
+
import { EnyoElectricityTariff, EnyoTariffActivationResult, EnyoTariffChangeEvent, EnyoTariffDirectionEnum, EnyoTariffPricePublication, EnyoTariffPriceSeries } from "../types/enyo-electricity-tariff.cjs";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
* The time range a price query covers.
|
|
4
|
+
*/
|
|
5
|
+
export interface EnyoTariffPriceRange {
|
|
6
|
+
/** Start of the requested range in ISO format (inclusive). */
|
|
7
|
+
fromIso: string;
|
|
8
|
+
/** End of the requested range in ISO format (exclusive). */
|
|
9
|
+
untilIso: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Interface for reading and supplying the site's electricity tariffs.
|
|
13
|
+
*
|
|
14
|
+
* A site has **two tariff slots** — one for consumption, one for feed-in — and
|
|
15
|
+
* everything here addresses them by {@link EnyoTariffDirectionEnum}. There is no
|
|
16
|
+
* list of tariffs, no default flag and no tariff id: occupying a slot is what it
|
|
17
|
+
* means to be the tariff in force.
|
|
18
|
+
*
|
|
19
|
+
* Six methods, split by role. A **consumer** asks {@link getTariff} what applies
|
|
20
|
+
* and {@link getPrices} what it costs, and follows {@link onTariffChanged}. An
|
|
21
|
+
* **owner** — the app integrating a provider like Tibber or Ostrom — answers
|
|
22
|
+
* {@link onTariffSelected} when the user picks it, calls {@link setTariff} once
|
|
23
|
+
* its tariff is usable, and feeds {@link publishPrices} as new prices arrive.
|
|
24
|
+
*
|
|
25
|
+
* A tariff carries no grid fee. A network charge belongs to the site's grid
|
|
26
|
+
* connection, not to the supplier contract — read it from `useGridFee()` and
|
|
27
|
+
* combine the two with `composeElectricityPrices()`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const tariffs = energyApp.useElectricityTariff();
|
|
32
|
+
*
|
|
33
|
+
* // Owner: the user picked us in the app UI.
|
|
34
|
+
* tariffs.onTariffSelected(async direction => {
|
|
35
|
+
* if (!await isAuthenticated()) {
|
|
36
|
+
* return {
|
|
37
|
+
* status: EnyoTariffActivationStatusEnum.AuthenticationRequired,
|
|
38
|
+
* authenticationUrl: buildOAuthUrl(direction),
|
|
39
|
+
* };
|
|
40
|
+
* }
|
|
41
|
+
* await tariffs.setTariff(direction, myTariff);
|
|
42
|
+
* return {status: EnyoTariffActivationStatusEnum.Success};
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
6
45
|
*/
|
|
7
46
|
export interface EnergyAppElectricityTariff {
|
|
8
47
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* it will be updated; otherwise, a new tariff will be created.
|
|
48
|
+
* Returns the tariff currently filling a direction slot, whichever app owns
|
|
49
|
+
* it, or `null` when the slot is empty.
|
|
12
50
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*/
|
|
16
|
-
registerTariff(tariff: Omit<EnyoElectricityTariff, 'id'>): Promise<EnyoElectricityTariffWithDefault>;
|
|
17
|
-
/**
|
|
18
|
-
* Retrieves all registered electricity tariffs.
|
|
19
|
-
* Each tariff includes a defaultTariff boolean indicating whether
|
|
20
|
-
* it is the system default tariff.
|
|
51
|
+
* `null` is a normal answer — a site with no feed-in contract has no feed-in
|
|
52
|
+
* tariff — and not an error.
|
|
21
53
|
*
|
|
22
|
-
* @
|
|
54
|
+
* @param direction - Which slot to read
|
|
55
|
+
* @returns Promise resolving to the tariff in that slot, or `null`
|
|
23
56
|
*/
|
|
24
|
-
|
|
25
|
-
myTariffs?: boolean;
|
|
26
|
-
}): Promise<EnyoElectricityTariffWithDefault[]>;
|
|
57
|
+
getTariff(direction: EnyoTariffDirectionEnum): Promise<EnyoElectricityTariff | null>;
|
|
27
58
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
59
|
+
* Returns prices for one direction over a time range, as a **15-minute
|
|
60
|
+
* series** — the same resolution `useGridFee().getGridFeeValues()` and
|
|
61
|
+
* `useEpexSpotPrices().getPrices()` use, so all three zip index-by-index.
|
|
30
62
|
*
|
|
31
|
-
*
|
|
32
|
-
* @
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
63
|
+
* The host serves this from what the slot's owning app published with
|
|
64
|
+
* {@link publishPrices}, so a consumer never has to know which app is behind
|
|
65
|
+
* the tariff. Returns `null` when the slot is empty or nothing has been
|
|
66
|
+
* published covering the range.
|
|
67
|
+
*
|
|
68
|
+
* Read {@link EnyoTariffPriceSeries.includes} before adding anything to
|
|
69
|
+
* these prices: a series that already contains the grid fee must not have
|
|
70
|
+
* one added on top.
|
|
37
71
|
*
|
|
38
|
-
* @param
|
|
39
|
-
* @
|
|
72
|
+
* @param direction - Which slot to price
|
|
73
|
+
* @param range - The time range to cover
|
|
74
|
+
* @returns Promise resolving to the price series, or `null` when none applies
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const prices = await tariffs.getPrices(EnyoTariffDirectionEnum.Consumption, {
|
|
79
|
+
* fromIso, untilIso,
|
|
80
|
+
* });
|
|
81
|
+
* const needsGridFee = !prices?.includes.includes(EnyoPriceComponentEnum.GridFee);
|
|
82
|
+
* ```
|
|
40
83
|
*/
|
|
41
|
-
|
|
84
|
+
getPrices(direction: EnyoTariffDirectionEnum, range: EnyoTariffPriceRange): Promise<EnyoTariffPriceSeries | null>;
|
|
42
85
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
86
|
+
* Puts this app's tariff into a direction slot, replacing whatever was
|
|
87
|
+
* there.
|
|
88
|
+
*
|
|
89
|
+
* **Calling this is the activation signal.** An app calls it once its tariff
|
|
90
|
+
* is actually usable — credentials valid, contract fetched — whether that is
|
|
91
|
+
* immediately when the user selects it or later, when an OAuth redirect
|
|
92
|
+
* comes back or an onboarding guide completes. There is nothing else to
|
|
93
|
+
* report afterwards.
|
|
94
|
+
*
|
|
95
|
+
* Full replacement is the only write there is: a partial update would let an
|
|
96
|
+
* app half-change a tariff another app owns. Re-setting an unchanged tariff
|
|
97
|
+
* is harmless, so an app that refetches its contract on every start can call
|
|
98
|
+
* this unconditionally.
|
|
99
|
+
*
|
|
100
|
+
* The returned {@link EnyoTariffActivationResult} is the host's answer, not
|
|
101
|
+
* the app's — it reports whether the slot is now live or whether the host
|
|
102
|
+
* still needs something from the user.
|
|
45
103
|
*
|
|
46
|
-
*
|
|
104
|
+
* Requires the `ElectricityTariff` permission.
|
|
105
|
+
*
|
|
106
|
+
* @param direction - Which slot to fill
|
|
107
|
+
* @param tariff - The tariff and its pricing details
|
|
108
|
+
* @returns Promise resolving to the activation outcome
|
|
47
109
|
*/
|
|
48
|
-
|
|
49
|
-
findTariff(filter: {
|
|
50
|
-
applianceId?: string;
|
|
51
|
-
tariffId?: string;
|
|
52
|
-
externalTariffId?: string;
|
|
53
|
-
}): Promise<EnyoElectricityTariffWithDefault | null>;
|
|
110
|
+
setTariff(direction: EnyoTariffDirectionEnum, tariff: EnyoElectricityTariff): Promise<EnyoTariffActivationResult>;
|
|
54
111
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
112
|
+
* Publishes prices for a slot this app owns.
|
|
113
|
+
*
|
|
114
|
+
* Push, not pull: providers hand out prices on their own schedule — day-ahead
|
|
115
|
+
* prices land in the afternoon, and provider APIs are rate-limited — so an
|
|
116
|
+
* app fetches when it makes sense and publishes what it got. The host serves
|
|
117
|
+
* those to every {@link getPrices} caller afterwards.
|
|
118
|
+
*
|
|
119
|
+
* Entries replace previously published entries with the same timestamp and
|
|
120
|
+
* leave the rest alone, so republishing a corrected day does not erase the
|
|
121
|
+
* days around it.
|
|
122
|
+
*
|
|
123
|
+
* Declare in {@link EnyoTariffPricePublication.includes} what the prices
|
|
124
|
+
* already contain. An app that folds the grid fee in before publishing must
|
|
125
|
+
* say so, or every consumer will add it a second time.
|
|
126
|
+
*
|
|
127
|
+
* Fails when this app does not own the slot — only the tariff's owner may
|
|
128
|
+
* price it.
|
|
129
|
+
*
|
|
130
|
+
* Requires the `ElectricityTariff` permission.
|
|
57
131
|
*
|
|
58
|
-
* @param
|
|
59
|
-
* @param
|
|
60
|
-
* @returns Promise that resolves
|
|
132
|
+
* @param direction - Which slot the prices belong to
|
|
133
|
+
* @param prices - The priced intervals and what they already contain
|
|
134
|
+
* @returns Promise that resolves once the prices are stored and listeners dispatched
|
|
61
135
|
*/
|
|
62
|
-
|
|
136
|
+
publishPrices(direction: EnyoTariffDirectionEnum, prices: EnyoTariffPricePublication): Promise<void>;
|
|
63
137
|
/**
|
|
64
|
-
* Registers
|
|
65
|
-
*
|
|
138
|
+
* Registers the handler the host calls when the user picks this app's tariff
|
|
139
|
+
* for a direction — "this should be my consumption tariff".
|
|
66
140
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* link
|
|
71
|
-
*
|
|
141
|
+
* Not a notification: the handler runs the app's activation flow and reports
|
|
142
|
+
* what it found. Returning
|
|
143
|
+
* {@link EnyoTariffActivationStatusEnum.AuthenticationRequired} or
|
|
144
|
+
* {@link EnyoTariffActivationStatusEnum.OnboardingRequired} tells the host to
|
|
145
|
+
* send the user somewhere, so carry the `authenticationUrl` or
|
|
146
|
+
* `onboardingGuideId` that makes it actionable. When the flow later
|
|
147
|
+
* completes, call {@link setTariff} — that is what marks the tariff live.
|
|
72
148
|
*
|
|
73
|
-
*
|
|
149
|
+
* Returning {@link EnyoTariffActivationStatusEnum.Success} without having
|
|
150
|
+
* called {@link setTariff} leaves the slot empty; return it only when the
|
|
151
|
+
* tariff is already set.
|
|
74
152
|
*
|
|
75
|
-
*
|
|
76
|
-
* @returns A unique listener id that can be passed to {@link offTariffChanged}
|
|
153
|
+
* One handler per app. Registering again replaces the previous one.
|
|
77
154
|
*
|
|
78
|
-
* @
|
|
79
|
-
*
|
|
80
|
-
* const listenerId = tariffs.onTariffChanged(async event => {
|
|
81
|
-
* if (event.changeType === EnyoTariffChangeTypeEnum.Removed) return;
|
|
82
|
-
* await recomposePrices(event.tariff);
|
|
83
|
-
* });
|
|
84
|
-
* // later
|
|
85
|
-
* tariffs.offTariffChanged(listenerId);
|
|
86
|
-
* ```
|
|
155
|
+
* @param handler - Called with the direction the user selected this app for
|
|
156
|
+
* @returns A function that removes the handler when called
|
|
87
157
|
*/
|
|
88
|
-
|
|
158
|
+
onTariffSelected(handler: (direction: EnyoTariffDirectionEnum) => Promise<EnyoTariffActivationResult>): () => void;
|
|
89
159
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
160
|
+
* Registers a listener invoked whenever either slot changes — a tariff set,
|
|
161
|
+
* replaced, or cleared.
|
|
162
|
+
*
|
|
163
|
+
* This is the signal to recompose prices. It fires for changes made by every
|
|
164
|
+
* app, so filter on {@link EnyoTariffChangeEvent.direction} when only one
|
|
165
|
+
* slot matters, and compare
|
|
166
|
+
* {@link EnyoElectricityTariff.externalTariffId} against your own to notice
|
|
167
|
+
* that a slot you were publishing for is now owned by someone else.
|
|
168
|
+
*
|
|
169
|
+
* Returns the unsubscribe function directly; calling it twice is a no-op.
|
|
92
170
|
*
|
|
93
|
-
* @param
|
|
171
|
+
* @param listener - Callback invoked with the change event
|
|
172
|
+
* @returns A function that removes this listener when called
|
|
94
173
|
*/
|
|
95
|
-
|
|
174
|
+
onTariffChanged(listener: (event: EnyoTariffChangeEvent) => void | Promise<void>): () => void;
|
|
96
175
|
}
|
|
@@ -1,33 +1,42 @@
|
|
|
1
|
-
import { EnyoDynamicGridFee,
|
|
2
|
-
import { EnyoAbsolutePriceWindow } from '../types/enyo-price-schedule.cjs';
|
|
1
|
+
import { EnyoDynamicGridFee, EnyoDynamicGridFeeRegistration, EnyoGridFeeChangeEvent, EnyoGridFeeInfo, EnyoGridFeeSeries, EnyoGridFeeValuesFilter } from '../types/enyo-grid-fee.cjs';
|
|
3
2
|
/**
|
|
4
|
-
* Interface for publishing and consuming
|
|
3
|
+
* Interface for publishing and consuming grid fees (network charges).
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Five methods, split by role. A **producer** — an app that knows the local grid
|
|
6
|
+
* operator's tariff sheet or fetches a published series — owns
|
|
7
|
+
* {@link registerGridFee} and {@link removeGridFee}. A **consumer** asks
|
|
8
|
+
* {@link getGridFee} what applies and {@link getGridFeeValues} for the numbers
|
|
9
|
+
* over a range, and subscribes with {@link onGridFeeChanged}. Nothing else is
|
|
10
|
+
* needed, so nothing else is offered.
|
|
10
11
|
*
|
|
11
12
|
* Publishing requires the `GridFeeRegister` permission; consuming requires
|
|
12
13
|
* `GridFeeUse`.
|
|
13
14
|
*
|
|
14
15
|
* A grid fee describes the site's **grid connection**, not a supplier contract:
|
|
15
16
|
* it is registered and resolved independently of any tariff, and the same fee
|
|
16
|
-
* applies whichever supplier the site buys from.
|
|
17
|
+
* applies whichever supplier the site buys from. This API is the *only* place a
|
|
18
|
+
* grid fee lives — a tariff declares nothing about how the fee is determined,
|
|
19
|
+
* so there is no second source to reconcile against.
|
|
20
|
+
*
|
|
21
|
+
* **A site has exactly one grid fee.** It has one grid connection, and the
|
|
22
|
+
* operator of that connection publishes one network charge for it. So nothing
|
|
23
|
+
* here takes an identifier: registering replaces whatever was there, and reading
|
|
24
|
+
* needs no selector.
|
|
17
25
|
*
|
|
18
26
|
* **Nothing here is applied to a price automatically.** The host never folds a
|
|
19
27
|
* grid fee into {@link EnergyAppEnergyPrices.getPrices}, because provider APIs
|
|
20
28
|
* differ: some already return grid-fee-inclusive prices, some do not. The app
|
|
21
29
|
* that owns the tariff knows which is the case — it declares it via the
|
|
22
|
-
*
|
|
23
|
-
* `composeElectricityPrices()`.
|
|
30
|
+
* price series' `includes` list and composes the effective price
|
|
31
|
+
* itself with `composeElectricityPrices()`. That flag is the one thing a tariff
|
|
32
|
+
* says about grid fees, and it describes the provider's price feed rather than
|
|
33
|
+
* the fee.
|
|
24
34
|
*
|
|
25
35
|
* @example
|
|
26
36
|
* ```typescript
|
|
27
37
|
* // Producer: a §14a EnWG module 3 HT/NT network charge
|
|
28
38
|
* const gridFee = energyApp.useGridFee();
|
|
29
39
|
* await gridFee.registerGridFee({
|
|
30
|
-
* id: 'dso-hd-2026',
|
|
31
40
|
* name: 'Netzentgelt HT/NT 2026',
|
|
32
41
|
* gridOperator: 'Netze BW',
|
|
33
42
|
* currency: EnyoCurrencyEnum.EUR,
|
|
@@ -44,7 +53,7 @@ import { EnyoAbsolutePriceWindow } from '../types/enyo-price-schedule.cjs';
|
|
|
44
53
|
* });
|
|
45
54
|
*
|
|
46
55
|
* // Consumer: resolve it for the next 24 hours
|
|
47
|
-
* const fees = await gridFee.
|
|
56
|
+
* const fees = await gridFee.getGridFeeValues({
|
|
48
57
|
* fromIso: new Date().toISOString(),
|
|
49
58
|
* untilIso: new Date(Date.now() + 86_400_000).toISOString(),
|
|
50
59
|
* });
|
|
@@ -52,130 +61,139 @@ import { EnyoAbsolutePriceWindow } from '../types/enyo-price-schedule.cjs';
|
|
|
52
61
|
*/
|
|
53
62
|
export interface EnergyAppGridFee {
|
|
54
63
|
/**
|
|
55
|
-
*
|
|
56
|
-
* logic based on `id` — if a fee with the same id already exists it is
|
|
57
|
-
* replaced, otherwise a new one is created.
|
|
64
|
+
* Sets the site's grid fee, replacing whatever was registered before.
|
|
58
65
|
*
|
|
59
|
-
*
|
|
66
|
+
* Full replacement is the only write there is, and it is enough for every
|
|
67
|
+
* case — correcting a name, moving to next year's tariff sheet, or
|
|
68
|
+
* republishing a day-ahead schedule are all "here is the fee now". A partial
|
|
69
|
+
* update would only add a second way to say the same thing, and a
|
|
70
|
+
* schedule-only write would let metadata and windows drift apart.
|
|
60
71
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Partially updates a registered grid fee. Only the provided attributes are
|
|
67
|
-
* modified; all other fields remain unchanged.
|
|
72
|
+
* There is no identifier and no create-versus-update distinction: the site
|
|
73
|
+
* has one grid connection and therefore one grid fee. Two apps that both
|
|
74
|
+
* register one will overwrite each other, so an app should only publish a
|
|
75
|
+
* fee it is actually authoritative for.
|
|
68
76
|
*
|
|
69
77
|
* Requires the `GridFeeRegister` permission.
|
|
70
78
|
*
|
|
71
|
-
* @param
|
|
72
|
-
* @
|
|
73
|
-
*
|
|
79
|
+
* @param registration - The complete grid fee, including its schedule
|
|
80
|
+
* @returns Promise that resolves to the stored grid fee, including the host-assigned `publishedAtIso`
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // Republishing tomorrow's day-ahead windows is the same call.
|
|
85
|
+
* await gridFee.registerGridFee({...fee, schedule: {
|
|
86
|
+
* type: EnyoPriceScheduleTypeEnum.Absolute,
|
|
87
|
+
* windows: tomorrowsWindows,
|
|
88
|
+
* }});
|
|
89
|
+
* ```
|
|
74
90
|
*/
|
|
75
|
-
|
|
91
|
+
registerGridFee(registration: EnyoDynamicGridFeeRegistration): Promise<EnyoDynamicGridFee>;
|
|
76
92
|
/**
|
|
77
|
-
* Removes
|
|
78
|
-
* `null`
|
|
79
|
-
* no-op.
|
|
93
|
+
* Removes the site's grid fee. {@link getGridFee} and
|
|
94
|
+
* {@link getGridFeeValues} resolve to `null` afterwards. If no fee is
|
|
95
|
+
* registered, this operation is a no-op.
|
|
80
96
|
*
|
|
81
97
|
* Requires the `GridFeeRegister` permission.
|
|
82
98
|
*
|
|
83
|
-
* @param id - The unique identifier of the grid fee to remove
|
|
84
99
|
* @returns Promise that resolves when the grid fee has been removed
|
|
85
100
|
*/
|
|
86
|
-
removeGridFee(
|
|
101
|
+
removeGridFee(): Promise<void>;
|
|
87
102
|
/**
|
|
88
|
-
*
|
|
103
|
+
* Answers "what grid fee applies here?" in one call.
|
|
89
104
|
*
|
|
90
|
-
*
|
|
105
|
+
* This is the entry point for consuming a grid fee. The returned
|
|
106
|
+
* {@link EnyoGridFeeInfo} says whether the fee is
|
|
107
|
+
* {@link EnyoGridFeeTypeEnum.Static static} or
|
|
108
|
+
* {@link EnyoGridFeeTypeEnum.Dynamic dynamic}, carries the gross cent per
|
|
109
|
+
* kWh outright when it is static, and reports any additional fixed charges
|
|
110
|
+
* separately. A static fee needs no second call; a dynamic one is fetched
|
|
111
|
+
* interval by interval with {@link getGridFeeValues}.
|
|
91
112
|
*
|
|
92
|
-
*
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Retrieves a single registered grid fee by its id, or `null` when no fee
|
|
97
|
-
* with that id exists.
|
|
113
|
+
* Takes no arguments — the site has one grid fee, so there is nothing to
|
|
114
|
+
* select. Returns `null` when none is registered, which is a normal answer
|
|
115
|
+
* and not an error.
|
|
98
116
|
*
|
|
99
117
|
* Requires the `GridFeeUse` permission.
|
|
100
118
|
*
|
|
101
|
-
* @
|
|
102
|
-
* @returns Promise that resolves to the grid fee, or `null`
|
|
103
|
-
*/
|
|
104
|
-
getGridFee(id: string): Promise<EnyoDynamicGridFee | null>;
|
|
105
|
-
/**
|
|
106
|
-
* Replaces the absolute windows of a grid fee whose schedule is
|
|
107
|
-
* {@link EnyoPriceScheduleTypeEnum.Absolute}. Use this for fees the grid
|
|
108
|
-
* operator publishes as a dated series (for example day-ahead), where the
|
|
109
|
-
* schedule changes far more often than the fee's metadata.
|
|
110
|
-
*
|
|
111
|
-
* Windows must not overlap and are stored sorted ascending by `startIso`.
|
|
112
|
-
* Publishing replaces the previously stored windows in full.
|
|
119
|
+
* @returns Promise resolving to the site's grid fee, or `null` when none is registered
|
|
113
120
|
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const fee = await gridFee.getGridFee();
|
|
124
|
+
* if (fee?.type === EnyoGridFeeTypeEnum.Static) {
|
|
125
|
+
* show(fee.grossCentPerKwh, fee.additionalFeesGrossCentPerKwh);
|
|
126
|
+
* } else if (fee) {
|
|
127
|
+
* const series = await gridFee.getGridFeeValues({fromIso, untilIso});
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
119
130
|
*/
|
|
120
|
-
|
|
131
|
+
getGridFee(): Promise<EnyoGridFeeInfo | null>;
|
|
121
132
|
/**
|
|
122
|
-
* Resolves
|
|
123
|
-
* requested range — the same resolution
|
|
133
|
+
* Resolves the applicable grid fee to a flat **15-minute series** of gross
|
|
134
|
+
* cent per kWh over the requested range — the same resolution
|
|
124
135
|
* {@link EnergyAppEnergyPrices.getPrices} uses, so both series can be
|
|
125
136
|
* combined interval by interval.
|
|
126
137
|
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* Returns `null` when no fee matches the selector.
|
|
138
|
+
* Works for every fee, static or dynamic: a flat charge resolves to a series
|
|
139
|
+
* whose entries all carry the same amount, so a caller that only wants
|
|
140
|
+
* numbers over a range never has to branch on {@link EnyoGridFeeInfo.type}
|
|
141
|
+
* at all.
|
|
132
142
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
143
|
+
* Set `includeAdditionalFees` to fold the fee's fixed charges into every
|
|
144
|
+
* interval and get the full gross charge per kWh; leave it off for the
|
|
145
|
+
* network charge alone. The result reports which of the two you got in
|
|
146
|
+
* {@link EnyoGridFeeSeries.includesAdditionalFees}, so a fee that declares
|
|
147
|
+
* no additional charges is never mistaken for a total.
|
|
148
|
+
*
|
|
149
|
+
* Returns `null` when the site has no grid fee registered. Intervals no
|
|
150
|
+
* window covers resolve to `0`, and intervals outside the fee's
|
|
151
|
+
* `validFromIso` / `validUntilIso` window are omitted.
|
|
135
152
|
*
|
|
136
153
|
* Requires the `GridFeeUse` permission.
|
|
137
154
|
*
|
|
138
|
-
* @param filter - The time range and
|
|
155
|
+
* @param filter - The time range and whether to include additional fees
|
|
139
156
|
* @returns Promise that resolves to the 15-minute fee series, or `null` when no fee applies
|
|
140
157
|
*
|
|
141
158
|
* @example
|
|
142
159
|
* ```typescript
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
160
|
+
* // network charge only
|
|
161
|
+
* const net = await gridFee.getGridFeeValues({fromIso, untilIso});
|
|
162
|
+
*
|
|
163
|
+
* // everything the customer pays per kWh
|
|
164
|
+
* const total = await gridFee.getGridFeeValues({
|
|
165
|
+
* fromIso,
|
|
166
|
+
* untilIso,
|
|
167
|
+
* includeAdditionalFees: true,
|
|
146
168
|
* });
|
|
147
|
-
*
|
|
169
|
+
* total?.entries.forEach(e => console.log(e.timestampIso, e.grossCentPerKwh));
|
|
148
170
|
* ```
|
|
149
171
|
*/
|
|
150
|
-
|
|
172
|
+
getGridFeeValues(filter: EnyoGridFeeValuesFilter): Promise<EnyoGridFeeSeries | null>;
|
|
151
173
|
/**
|
|
152
|
-
* Registers a listener invoked whenever any grid fee is registered,
|
|
153
|
-
* or removed
|
|
174
|
+
* Registers a listener invoked whenever any grid fee is registered, replaced
|
|
175
|
+
* or removed.
|
|
154
176
|
*
|
|
155
177
|
* Use it to recompute composed prices without polling: a grid operator that
|
|
156
178
|
* publishes a new day-ahead series mid-day changes the cost of every future
|
|
157
179
|
* interval.
|
|
158
180
|
*
|
|
181
|
+
* Returns the unsubscribe function directly rather than an id to hand back
|
|
182
|
+
* to a second method — calling it twice is a no-op.
|
|
183
|
+
*
|
|
159
184
|
* Requires the `GridFeeUse` permission.
|
|
160
185
|
*
|
|
161
186
|
* @param listener - Callback invoked with the change event
|
|
162
|
-
* @returns A
|
|
187
|
+
* @returns A function that removes this listener when called
|
|
163
188
|
*
|
|
164
189
|
* @example
|
|
165
190
|
* ```typescript
|
|
166
|
-
* const
|
|
167
|
-
*
|
|
191
|
+
* const unsubscribe = gridFee.onGridFeeChanged(async () => {
|
|
192
|
+
* await recomputePrices();
|
|
168
193
|
* });
|
|
169
194
|
* // later
|
|
170
|
-
*
|
|
195
|
+
* unsubscribe();
|
|
171
196
|
* ```
|
|
172
197
|
*/
|
|
173
|
-
onGridFeeChanged(listener: (event: EnyoGridFeeChangeEvent) => void | Promise<void>):
|
|
174
|
-
/**
|
|
175
|
-
* Removes a previously registered grid fee change listener. If the listener
|
|
176
|
-
* id is unknown, this operation is a no-op.
|
|
177
|
-
*
|
|
178
|
-
* @param listenerId - The id returned from {@link onGridFeeChanged}
|
|
179
|
-
*/
|
|
180
|
-
offGridFeeChanged(listenerId: string): void;
|
|
198
|
+
onGridFeeChanged(listener: (event: EnyoGridFeeChangeEvent) => void | Promise<void>): () => void;
|
|
181
199
|
}
|