Install
powercost is a dependency-free ES module that runs in the browser and in Node 18+. Tariff data lives in lazy-loaded packs.
npm i powercost
Or use it straight from a static site / GitHub Pages with no build step:
<script type="module">
import { price } from 'https://esm.sh/powercost';
</script>
Quick start
import { price } from 'powercost';
import { swedenDb } from 'powercost/data/se';
const cost = await price({
consumption: [
{ start: '2026-04-15T18:00:00+02:00', end: '2026-04-15T18:15:00+02:00', kwh: 0.40 },
],
config: {
zone: 'SE3',
providerPlanId: 'tibber-kvartspris',
gridOperatorId: 'vattenfall-e4-25a',
reportCurrency: 'USD',
},
db: swedenDb,
live: true, // fetch spot + FX live (client-side)
});
cost.total.format(); // "1.23 SEK"
cost.marginalInclVat.format() // "0.92 SEK" — what these kWh actually cost
cost.presentation.total // { amount: 0.13, currency: 'USD' }
The cost model
A Swedish bill is two companies plus the state, modelled as composable tariff components:
| Piece | Billed by | What it is |
|---|---|---|
| Spot energy | Provider (elhandel) | Day-ahead market price per bidding zone, 15-min. |
| Provider påslag / fees | Provider | Markup + variable costs + monthly fee. |
| Grid transfer + fixed | Grid operator (elnät) | Per-kWh transfer + a fixed subscription per fuse. |
| Energy tax | State, via the grid invoice | Energiskatt per kWh. |
| VAT (moms) | State | 25% on top of everything — including the tax. |
Provider and grid are billed on separate invoices; powercost returns one combined, itemised breakdown.
Marginal vs. total
marginalInclVat— cost of the kWh that scale with consumption (spot + per-kWh fees + grid transfer + tax + VAT). What a load-shifting / net-profit decision needs.total— the full bill including amortised fixed fees (subscription, fuse). What you actually pay.
Live data & CORS
With live: true, prices are fetched directly from the browser — no proxy:
- Spot from elprisetjustnu (sends
access-control-allow-origin: *). - Presentation FX from the ECB Data Portal API (also CORS-open).
Prefer to supply your own prices? Pass spot and/or fx sources instead of live. FX is presentation-only; the cost itself is computed in the tariff's native currency with exact integer money.
Data packs
Tariff data is separate from the engine and loaded on demand, so you only ship what you use:
const { swedenDb } = await import('powercost/data/se');
A pack exposes a TariffDb (provider plans, grid operators, national tax + VAT). New countries are new packs — see contributing.
price(options)
| Option | Type | Notes |
|---|---|---|
consumption | { start, end, kwh }[] | ISO-8601 timestamps; any interval length. |
config | Config | { zone, providerPlanId, gridOperatorId, fuse?, reportCurrency? } |
db | TariffDb | From a data pack. |
live | boolean | Fetch spot (+ FX) live. |
spot / fx | sources | Bring your own prices instead of live. |
Returns a Promise<CostBreakdown>.
CostBreakdown
{
period, currency, totalKwh,
total, marginalInclVat, variableExVat, fixedExVat, // Money
byCategory: { energy, grid, tax, subscription, vat }, // Money
lines: [{ id, label, category, basis, amount }],
presentation?: { currency, rate, total, marginalInclVat },
}
Every money value is a Money with .format(), .toMajor(), .toJSON(). Use serializeBreakdown(b) for plain JSON.
reconcile()
Compare a computed breakdown to a real invoice total to measure accuracy:
import { reconcile } from 'powercost';
const r = reconcile(cost, { actualTotalMajor: 412.50, currency: 'SEK' }, 0.02);
r.withinTolerance; r.relDiffPct;
Low-level
The pieces behind price() are all exported: priceConsumption, resolveRateCard, loadElprisetSpot, loadEcbFx, StaticSpotSource, StaticFxSource, plus the tariff component types. Build your own flow if you need to.
Config values
The Sweden pack (powercost/data/se):
| Field | Values |
|---|---|
zone | SE1, SE2, SE3, SE4 |
providerPlanId | tibber-kvartspris, tibber-rorligt |
gridOperatorId | vattenfall-e4-25a, vattenfall-se3 |
Contributing a pack
A data pack is just a module exporting a TariffDb built from the component grammar (fixed, per_kwh incl. spot-linked, tiered, demand, vat) — all temporal. Add a country or operator, send a PR. See the repo and the Sweden pack as a template.