Skip to content

About

Home Assistant integration for Belgian all-in electricity prices: energy, network tariffs and levies parsed from the suppliers' own tariff cards, day-ahead spot from ENTSO-E, with year-to-date cost, contract comparison and solar injection credits.

Topics

Resources

Stars

63 stars

Watchers

4 watching

Forks

Repository files navigation

BE electricity - real-time prices

Latest release GitHub release downloads Validate Tests Home Assistant HACS License PayPal


Home Assistant integration that exposes the all-in real EUR/kWh paid for Belgian electricity, taking into account every component of a Belgian bill (energy + transport + distribution + levies + VAT) plus the Flanders capacity tariff billed on the monthly peak.

Energy prices are fetched live from each supplier's own published tariff card. No supplier EUR value is hardcoded in the source: the only EUR rate in it is a regulated one no live source prints, the protected customer's excise before August 2026, which the law's current text no longer states. The household excise is read from the law itself, and the Flemish network ceiling from Fluvius's yearly tariff sheet. A supplier is one Python module, split into a few helpers for the larger ones, that knows where to find that supplier's publication and how to read it. If yours is missing, open an issue asking for it rather than writing the module: a tariff card carries a dozen things that are easy to read almost right -- which column a meter type bills on, whether a figure is per year or per month, whether a levy carries VAT -- and getting those wrong mis-prices a bill quietly. Reviewing that costs more than writing it. There is a supplier request form that asks for the four things it takes: the supplier, the product, a link to the card, and the regions it is sold in.

Targets Home Assistant 2026.2.3 or newer (the minimum declared in hacs.json).

Highlights

Each of these has a section of its own further down; this is the scan.

Prices

  • Live tariff cards — every supplier figure comes from the supplier's own published card. No supplier EUR value is hardcoded.
  • Whole-bill view — energy, distribution, transport, levies and VAT in one EUR/kWh, not just the commodity.
  • Dynamic contracts — factor x spot + base against the Belgian day-ahead price, per hour or per quarter-hour depending on how the supplier bills. See What the integration computes.
  • Monthly-indexed contracts — factor x this month's mean spot + base, for cards that settle on a month rather than an hour.
  • Time-of-Use and Tarif Impact — peak / off-peak / super-off-peak cards, and the Walloon three-band CWaPE tariff, each priced on its own schedule.
  • Flanders capacity tariff — billed the way Fluvius bills it, on the mean of your last twelve monthly peaks, floored per month at 2,5 kW. Worth 52 to 60 EUR per kW per year and invisible on the price graph, so it lands in the cost sensors and capacity_cost rather than in the per-kWh price.
  • Solar — compensation regime, injection tariff, or neither.

Costs over time

  • Year-to-date cost — your running bill since 1 January, each kWh priced at the tariff that applied when you used it. See Sensors and When the year-to-date looks too low.
  • Month-to-date cost — the same for the month in progress.
  • Rolling year cost — roughly what a year on this contract costs: the volume your meter recorded over the last 365 days, priced once at today's tariffs. An indication, not a forecast.
  • Projected year-end cost — what this calendar year's bill will stand at on 31 December: the year so far as billed, and the rest of it on last year's same days at today's card. An indication, not a forecast.
  • Statistics backfill — on first install the integration fills Home Assistant's long-term statistics from your recorder history, so the energy dashboard is not blank. See the backfill service.

Choosing a contract

  • Ranked comparison of every alternative — prices every contract sold in your region against your own settings and sorts them cheapest first, with your own row badged and every gap signed. Optionally once a day in the background, publishing the best saving as a sensor.
  • One-off contract comparison — quotes one supplier and contract against your settings, including your own contract (except on the social tariff or the expert custom supplier, neither of which can be a comparison target), which answers what would this cost me on a bi-hourly meter and what would it cost off the compensation regime. Your own contract is quoted at the rates you signed and with what is left of your own welcome credit, never as a new signer on today's card. Both live under Reconfiguring later.
  • Signing-cohort pricing — set a contract start date and past months bill at the rate you actually signed, not at today's card, for the suppliers listed under current_year_cost in Sensors, and for every supplier through the project's card archive for a signing month it holds, from August 2026 on (May 2026 for Ecofix), within the 36 months it keeps. The months of the year before the start date or the tariff card month, whichever is later, which the year-to-date counts unless you bill it from the start date, bill on their own month's card. A variable contract's settled months bill the formula you signed even without an ENTSO-E key. The months still open bill it on the month's own spot mean with a key, and without one keep the card's printed rate with the standing charge you signed; Engie's Easy Variable, indexed on an ENDEX101 its card names in advance, bills the formulas you signed at each month's printed value, key or not. The feed-in credit follows the formula you signed, and where the card fixes its feed-in price for the term too (Mega's fixed range, Trevion Groene Energie Vast, EnergyVision's fixed-injection card) the price you signed at. See Configuration.
  • Renewal reminder — set your contract's end date and a timestamp sensor carries it, so an automation can remind you before the contract rolls over.

Running it

  • Cheapest / most-expensive window services — ask for the best N-hour block of the day from an automation.
  • Tomorrow-available trigger — a binary sensor that flips when tomorrow's prices land.
  • Keyless day-ahead prices — ENTSO-E is down: prices keep coming from energy-charts.info, and from SMARD where energy-charts cannot answer either, until it recovers. Both need no key and publish the same prices (CC BY 4.0, Bundesnetzagentur | SMARD.de, credited on the sensor while either is in use). They also fill a day ENTSO-E has not published yet, typically tomorrow's curve when ENTSO-E is late, and the hours ENTSO-E did serve stay on ENTSO-E. The spot_source attribute on current_price names every source behind the curve: entsoe, energy-charts, smard, or a mix such as entsoe+energy-charts. With no key at all, tick Use keyless day-ahead prices at the key step and the entry prices off these two alone (a key is still recommended, since they are free services every installation shares as its backup), a dynamic contract included, exactly as it would with a key: wherever this README says what an ENTSO-E key does, the keyless setting does the same. With neither, a monthly-indexed card bills its printed figure until a next card printing the closed month's index settles it, while a feed-in formula settled per slot stays uncredited and its price unknown. See Getting an ENTSO-E API key.
  • Self-healing — last-known prices keep serving through an outage, and Repairs cards explain anything that needs you. See Failure mode.
  • Catalog drift detection — a daily check that tells the maintainer when a supplier changes its lineup.
  • Expert custom formula — type a card in by hand when a supplier is not covered.
  • ENTSO-E key validated at setup, and a translated UI (EN, NL, FR, DE).

Supported providers

Supplier Contracts Source
Aspiravi Energy Eco Plus Flex (monthly Belpex mean, mono / bi / excl. night, for members of its partner cooperatives) · a closed month settles on the mean the next card prints for it, energy and feed-in Flanders only · aspiravi.py · notes
Bolt Bolt Fixe · Bolt Plenty Fixe · Bolt Variable · Bolt Plenty Variable · Bolt Online · Bolt Plenty Online · all six as pro contracts · the variable cards index quarterly on the RLP-weighted Belpex, re-priced on the delivery quarter with an ENTSO-E key, or settled per quarter-hour on request, and a closed quarter's months, key or not, settle on the indices the next quarter's card prints, and a card whose quarterly index table does not give back its own monthly price is refused, so the card held before keeps billing · the fixed cards credit the feed-in per quarter-hour, and so does a variable card on its quarter-hourly settlement; on its variable one it is credited at the SPP-weighted figures the card prints per meter register (the single one on a Walloon day/night meter, whose night register runs 11:00-17:00 as well, and the one per CWaPE band on the Walloon Impact tariff), settled like the consumption on the next quarter's card, and a month before the card archive holds the variable cards (September 2026) is credited its own quarter's, read off the fixed card of the quarter after · the Plenty cards run an offer for contracts signed in their month, a lump with a feed-in bonus until September 2026 and a cut per kWh with one since October (9 c€/kWh on the residential cards and 10 c€/kWh before VAT on the professional Plenty Fixe card, whose VAT basis is read off its Dutch edition, Plenty Fixe in Flanders only), credited at the first year's settlement off the card of the month you signed and to no contract signed in another bolt.py · notes
Cociter Tarif Variable (BELIX) · Tarif Variable Trihoraire (BELIX on the CWaPE 3-band schedule) · Tarif Dynamique (quarter-hourly BELPEX) Wallonia only · cociter.py · notes
DATS 24 (withdrawn 2026-08-31) Elektriciteit Groen Variabel (BE_spotRLP-indexed monthly) Flanders + Wallonia · dats24.py · notes
EBEM Groen Variabel (BelpexRLP0 monthly, mono / bi / excl. night) · Groen B@sic+ (BelpexRLP0 monthly, single rate, online-only) · Groen Dyn@mic (Belpex 15-min, SMR3) Flanders only · ebem.py · notes
Ecofix ⚠️ (cards are page images since August 2026, read in CI) Motion (quarter-hourly Belpex 15M) · Motion Online (same index, own coefficients and a 10 €/yr standing charge, online-only) · Flexy (BELPEX-RLP-M monthly variable) · Flexy Online (same index, own coefficients and the same 10 €/yr charge, online-only) Flanders + Wallonia · ecofix.py · notes · see the note below the table
Ecopower Groene Burgerstroom (50% fixed + 50% Belpex DA, indexed monthly) · Dynamische Burgerstroom (quarter-hourly EPEX DA) Flanders cooperative, Flanders only · ecopower.py · notes
Elegant Flex (open-ended) · ComfortFlex (one year), both indexed monthly on EPEXDAMREK, the plain Belpex mean, mono / bi / excl. night · Zeker & Vast (fixed for a year) · the feed-in credit of all three is indexed monthly on EPEXDAMSPP, the SPP-weighted Belpex mean · a closed month settles on the indices the next card prints for it, energy and feed-in, back to November 2025 Flanders only · elegant.py · notes
Eneco Zon & Wind Vast · Zon & Wind Fix One · Zon & Wind Flex · Zon & Wind Flex One · Zon & Wind Dynamisch Flanders + Wallonia (Dynamisch since its October 2026 card) · eneco.py · notes
energie.be Dynamisch (quarter-hourly EPEX) · Variabel (monthly Belpex_RLP) · Vast Flanders only · energiebe.py · notes
Energy Knights Agilior Online (quarter-hourly Belpex_15) · Agilis Online (hourly Belpex_h) · Essentia Online (monthly Belpex_RLP) · all three as Green · withdrawn on 2026-10-01, marked † in the picker for households still on them: Optima Online and Optima Online Green (quarter-hourly Belpex_15, for an EMS trading on the imbalance market; what the trading earns is not modelled) Flanders only · energyknights.py · notes
EnergyVision Dynamisch (quarter-hourly Belpex) · 3 jaar vast · 1 an fixe (Wallonia) · 1.800 kWh vast (all three regions) · vaste injectieprijs 3 jaar · Laadpunt · Groene stroom (Brussels, monthly Belpex-RLP-M) Flanders unless marked. The 1.800 kWh card is sold in all three regions and prices the same energy leg in each, but the Walloon card prints its own standing charge, which has moved between none and 70 EUR/yr in 2026 where the other two charge 50 EUR/yr. In Brussels EnergyVision trades as Brusol and publishes on its own site, and there the card is sold only to households with EnergyVision/Brusol panels on the roof; Groene stroom is Brusol's own product, any Brussels household can take it, and its cards up to September 2026 charged 250 EUR/yr, 230 on domiciliëring, which the setup flow asks about because those months still bill on it; the October 2026 card no longer prints the reduction · energyvision.py · notes
Engie Easy Fixed · Easy Variable · Direct Online · Basic Online · Dynamic · Empower Fixed · Empower Variable · Empower Flextime (TOU) · Flow · Empty House · the same eight as pro contracts, minus Direct Online and Basic Online All three regions, Basic Online is Flanders + Wallonia only · engie.py · notes
Frank Energie Dynamisch · Dynamisch HV · Dynamisch VT (titled Korting until September 2026) · Dynamisch JN · Dynamisch Slim · Dynamisch Plus (since October 2026) · each settled per hour, or per quarter-hour when switched to it in Frank's app Flanders only · frank.py · notes
Luminus Comfy · Comfy+ · ComfyFlex · ComfyFlex+ (indexed on the delivery quarter's Belpex, re-priced on the quarter with an ENTSO-E key, and a closed quarter's months, key or not, settle on the index the next quarter's card names) · MaxxFix · MaxxFlex · BasicFix · BasicFlex · SmartFlex (TOU, with a Happy Sunday band on summer Sundays from October 2026) · Dynamic · most months run a new-customer campaign tied to the month you sign, either a share of the energy cost (33% on the September 2026 Comfy card) or a volume of free energy (750 kWh as a cashback after 12 months, valued at the mono-hourly rate the card's terms name: its printed rate, or from October 2026 on MaxxFlex and ComfyFlex+ the card's own annual estimate), and it is billed from the card of your contract start month and to no contract signed in another, which the archive can only supply from September 2026 on: Luminus's own tariff archive serves a past month without its campaign, so a contract signed before then is priced with no campaign at all Flanders + Wallonia only · luminus.py · notes
Mega Smart Fixed/Flex · Zen Fixed · Online Fixed/Flex · Cosy Fixed/Flex · Off-peak Fixed · Off-peak Flex · Off-peak Impact (Wallonia, CWaPE 3-band) · Dynamic · the Flex and Impact cards index monthly on the RLP-weighted Belpex, the SME cards on the plain mean; most of the range grants a first-year ristourne, credited at the twelve-month anniversary (fourteen on Zen Fixed and its pro twin, and on Smart Flex and its pro twin in the months their card says so), or by the day from the first invoice on the residential cards since October 2026, and nineteen cards price it on whether the household pays by direct debit; every card that grants the ristourne adds a first-year bonus on the export, credited with the ristourne to a household on the injection regime once a year of its feed-in is measured · pro: SME Fixed/Flex · Smart Fixed/Flex · Online Fixed · Cosy Fixed/Flex · Off-peak Fixed · Dynamic · Zen Fixed All three regions · mega.py · notes
OCTA+ Boost Fix · Boost Fix Impact (Wallonia, CWaPE 3-band) · Eco Boost Fix · Eco Boost Fix Impact (Wallonia) · Boost Flex · Eco Boost Flex · Basic Online · Smart Variable · Dynamic · Eco Dynamic · withdrawn on 2026-10-01, marked † in the picker for households still on them: Fixed · Fixed Impact (Wallonia) · Eco Fixed · Flux · Eco Flux · the June to September 2026 Fixed, Fixed Impact, Flux and Dynamic cards grant a credit note after a year on the card (65 to 180 EUR), credited at the first anniversary of a contract signed on one of them Flanders + Wallonia only · octaplus.py · notes
Social tariff (CREG) (protected customers only) Supplied by Engie (feed-in from Engie's social card) · Supplied by Luminus (feed-in from Luminus's social card) · Supplied by another supplier or the network operator (no feed-in credit) All three regions · social.py · notes
TotalEnergies Electricité Fixe/Variable · Impact (Wallonia) · myComfort · myComfort Fixe · myDrive · myDynamic · myEssential · myEssential Fixe · since October 2026 the feed-in price is on a card of its own beside each product card (INJECTION_<product>), read with it: the monthly Belpex formula on every product but myDynamic, which credits the hourly one · a card served in Dutch is read like its French edition, and the Dutch card stands in when the French address serves the wrong document · a closed month settles on the rates the next cards print: the energy of a variable card, where another card of the range is printed at the same index, and the feed-in of every product but myDynamic, the fixed ones included All three regions · totalenergies.py · notes
Trevion Groene Energie Vast · Groene Stroom Flex (monthly Belpex_RLP_VL) · Groene Energie Dynamisch · Groene Energie Dynamisch Plus · Energreen · FlexiO Max (monthly Belpex_RLP_VL, for a FlexiO EMS+; the flexibility fee the EMS+ earns is not modelled) · withdrawn on 2026-10-01, marked † in the picker for households still on them: LifePowr (monthly Belpex_RLP_VL since June 2026, quarter-hourly Belpex 15 MTU before; replaced by FlexiO Max) Flanders only · trevion.py · notes
Expert: custom formula (no public card) Dynamic (factor × spot + base) · Monthly average (factor × monthly-mean spot + base) · Fixed / manual rate All three regions; the tax step shows only the boxes your region bills: the green levy box takes GSC + WKK in Flanders and the green-energy contribution in Wallonia and Brussels, and the connection-fee box (the Walloon redevance de raccordement, VAT-exempt) appears on Walloon entries only · custom.py

The social tariff

A household granted protected status (client protégé, beschermde afnemer) pays the social tariff whoever supplies it: one price per meter register for the whole country, set each quarter by the CREG and published at creg.be. Pick Social tariff (CREG, protected customers only) as the supplier and the contract naming who supplies you. The price is read from the CREG's quarterly card and replaces the energy, the network charges and the standing charge altogether, so there is no capacity term, data management fee or prosumer tariff. On top of it you pay the special excise at the protected customer's rate, which the law sets (1 € per MWh before VAT since August 2026, read from Justel like the household rate), and in Wallonia the connection fee; you are exempt from the energy contribution and the Flemish energy fund. Under Wallonia's Impact tariff the day price bills the PIC and MEDIUM bands and the night price the ECO band, as Engie's social card states. Past months back to October 2022 bill on their own quarter's card.

Feed-in is the part no rule sets: each supplier pays its own or none. Engie and Luminus print it on their social card and those two contracts credit it; Fluvius, as social supplier, says it pays none, and no other supplier or network operator publishes a rate, so the third contract credits nothing rather than a guess. Engie's rates, single and day/night alike, follow the delivery month's EPEXDAM where you have an ENTSO-E key, as its card's formulas say; Luminus's are credited as its card prints them, which is the previous quarter's index.

Until the law has been read once (Justel unreachable on the very first start), a social tariff entry publishes nothing and a Repairs card says why, since leaving the excise out or billing the household rate would both be wrong.

Not supported, and why:

  • Belvus, Dots, Sparki and the six Energy Together brands (HOA Energy, Servolt Energie, Evident Energie, Power2You, Prijspunten Energie, Smappee Smiles) publish electricity tariff cards, and the gas integration reads their gas cards, but no reader for their electricity cards exists yet. Ask for one with the supplier request form below.
  • Yuso prices the energy commodity alone, with network tariffs and taxes passed through; see the question about business-only suppliers below.

Warning

Ecofix publishes its cards as page images, and has since August 2026. Nothing in the integration can parse a document with no text in it, so the prices come from this project's own reading of the card's pixels: the card archive runs a reader built for these cards once a day, files what it read, and your entry uses that. It stops by itself when Ecofix publishes a readable card again, because what makes a card unreadable is measured on every fetch rather than held as a flag against the supplier.

The comparison pages use that reading too, and say so. A supplier whose card cannot be read used to show up as an error on the ranking, which is the one screen that exists to tell you whether to switch to it. Its row is now priced from the same reading your own entry uses and tagged OCR, and the one-off quote adds a line naming the supplier and saying where the figure came from. A price you might act on should never hide that it was read off a picture.

Check the taxes against your own card. The energy formula and the standing charge survive as live text and are read exactly, and the DSO tables read off the image are right: those tariffs are set for the calendar year, so a picture taken in July still prints September's. The tax block is the one that has moved. Ecofix's September 2026 card still carries the federal scheme that ended on 1 August, an excise of 0,0503288 with a 0,0020417 contribution beside it, where most suppliers' cards for the same month carry a flat 0,04876 and no contribution. Neither is billed. Both are federal levies set by law rather than by your supplier, so the integration bills what the law sets for the month being billed: the contribution was abolished on 1 August and is dropped, and the excise is billed at the flat 0,04876. That is the 0,0036105 €/kWh, about 12,64 € a year on 3.500 kWh, the stale block used to cost you. The daily live check still compares every supplier's federal block against the rest and reports a card that drifts. A figure the reader could not read whole is left out rather than guessed. If you would rather type the numbers in yourself, the Expert: custom formula supplier takes them, though its federal levies follow the law the same way, unless the entry is marked a business.

The full story — what changed in their generator, what Ecofix said about it, the measurements, and the current formulas to copy — is in docs/providers/ecofix.md.

Missing a supplier? Ask for it with the supplier request form rather than sending a module — see the note at the top of this file for why. One module, its registration and a fixture-based test is all it takes to add one; what takes the time is reading the card correctly.

Why isn't a business-only supplier like Yuso listed? Not because it is business-only — professional tariffs are supported, see below — but because Yuso's cards price the energy commodity alone (platform fee plus green/CHP certificates) and state that network tariffs and taxes are passed through one-for-one, billed separately by the grid operator. There is no all-in price to assemble from such a card, for a household or a business alike. The same applies to any supplier that publishes commodity-only pricing. If you know your own formula and grid/tax rates, the Expert: custom formula supplier lets you enter them by hand (see below).

Professional (B2B) tariffs. Bolt, Engie and Mega publish full professional tariff cards carrying the same DSO and tax tables as their residential ones, printed excluding VAT, and the integration reads them. Pick the supplier, then a Pro contract on the contract step, and two extra settings appear:

  • Prices include VAT — professional electricity is taxed at 21%. Leave this on if your business cannot deduct VAT; turn it off if it can, and every price, fee and cost sensor reports the ex-VAT amount you actually bear. Injection follows the same choice (unlike residential injection, which is VAT-exempt outright). A residential contract is always priced with its VAT, whether its card prints prices with it or, like Ecopower's, without, so the setting is hidden for them.
  • Estimated yearly consumption — the Engie and Mega professional cards print the federal special excise as a degressive schedule (bands at 20 000, 50 000 and 1 000 000 kWh/year) billed per tranche, so the rate applied is the blend of every band your year's volume spans rather than the band it lands in: at 30 000 kWh that is 0.013503 EUR/kWh, not 0.012090. Bolt's professional cards print a single flat excise, so the setting does not change what they bill per kWh. The household excise was degressive too until July 2026 (lower above 20 000 kWh/year), and the residential cards that print the table bill a month before August 2026 the same way. Once a full year of your own meter is recorded, that measured volume is used instead of this figure.

Scope limits, taken from the cards themselves: they price low-voltage (basse tension / laagspanning) connections only, with injection up to 10 kVA and consumption up to 1 000 000 kWh/year. Medium- and high-voltage connections, contracted-power demand charges and reactive-power billing are out of scope — they depend on per-site contract terms no public card lists.

Expert: custom formula (no public card). Some products can't be scraped because the supplier publishes no public, machine-resolvable tariff card — the Yuso day-ahead offer, or a one-off group-purchase deal like the Mega iChoosr / Samen Overstappen groepsaankoop. For those, the last entry in the supplier dropdown lets a knowledgeable user type the pricing themselves: a dynamic factor × spot + base formula, a monthly-average variant that bills a flat rate equal to factor × the delivery month's mean spot + base, or a plain fixed rate, each with an optional never-negative injection floor (on by default for the monthly average) — plus the regulated DSO and tax values, which are identical for every supplier on your grid. The two federal levies among them follow the law as they do on every card (see The federal levies come from the law): from August 2026 the energy contribution is dropped whatever you type, and the excise is billed at the rate the law sets for the month. Those are the residential figures: a business (a Yuso customer, say) still owes the professional excise and the contribution, so tick Business contract on the tax step and the two levies you type are billed as typed. The box also puts the entry in the professional segment, so the comparisons quote it against professional cards, excluding VAT when the VAT rate you typed is 0 and including it otherwise. At 0 the figures read from the regulators, the Flemish network ceiling and Brugel's power term, are billed excluding VAT too, like the ones you typed. Coefficients are entered excluding VAT and the VAT rate grosses them up; its box starts at the month's residential rate. The spot and the base are both in EUR/kWh, so a formula printed in c€/kWh on a Belpex in EUR/MWh has to be converted: (0.1068 × Belpex + 1.5) c€/kWh is a factor of 1.068 and a base of 0.015. An injection formula works the same way and keeps the sign of its base: (0.07 × Belpex − 2) c€/kWh is a factor of 0.7 and a base of −0.02. A fixed contract takes a flat feed-in rate only. Every per-kWh box refuses a figure no real one reaches, which is how a figure typed in c€/kWh shows up. This trades away the whole point of the live-extractor model: there is no card to refresh and no drift check, so the numbers are a static snapshot you must keep current yourself, and a monthly-average rate is a running estimate until the month closes. For injection, the monthly-average mode offers an optional SPP-weighted setting: it fetches Synergrid's national solar production profile and weights the monthly day-ahead mean by it (as SPP-indexed contracts do) instead of a plain average — much closer for a solar prosumer, since the plain mean over-credits injection by weighting the cheap midday hours the same as the rest. It uses the published ex-ante profile, which is the one the suppliers' own settled indices are computed on: measured over January to August 2026 this reproduces the Belpex-SPP-M Energy Knights publishes to 0,007%. Three suppliers (EBEM, Trevion and Ecopower) weight the quarter-hour prices instead of the hourly ones and so publish a value about 1 EUR/MWh lower; their closed months are settled on the figure their own card prints rather than on this mean. Elegant's EPEXDAMSPP is the series EBEM and Trevion publish, and its closed months are settled the same way, on the figure its next card prints. It falls back to the plain mean if the profile can't be fetched.

How often the integration polls

The coordinator ticks once an hour. On each tick it runs the supplier's probe() — a cheap freshness check that returns a key (Last-Modified, ETag, or the resolved PDF URL) — and only re-runs the full PDF fetch when that key changes from what we last fetched. This catches a supplier publication within an hour at near-zero ongoing bandwidth instead of a fixed 24-hour schedule. Suppliers that have no usable probe (energie.be, Engie, Luminus and the social tariff, where no cheap freshness key is exposed) keep the time-based 24-hour TTL, except that a card past the last day it states, or past the month its title names when it states no day, is asked for again at the next hourly tick, so the new month's card is picked up on the 1st rather than up to a day later. Mega's professional cards and EnergyVision's Brussels cards take the same TTL, since their probe has no key to give. DATS 24, which left the market on 31 August 2026, is no longer polled; its past months still come from its archive.

What the integration computes

For every hour, an all-in EUR/kWh built up as

all_in = (energy + distribution + transport + levies) × (1 + VAT) + VAT-exempt levies

Each component comes from the supplier's tariff card and the configured DSO. For dynamic contracts the energy term is factor × spot + base, where spot is the Belgian day-ahead price from the ENTSO-E Transparency Platform — published at 15-minute resolution since the SDAC switch of Oct 2025. The integration aggregates it to hourly except for suppliers that bill per quarter-hour (Cociter, EBEM, Ecofix, Ecopower Dynamische Burgerstroom, energie.be, Energy Knights Agilior Online, Optima Online and their Green twins, EnergyVision, Engie, OCTA+ and Trevion, plus Bolt, Frank Energie and the custom supplier's dynamic formula with the quarter-hour box ticked), which keep the native 15-minute slots. A contract start date on an older card that named the hourly index (OCTA+ before 2026, Engie Dynamic and Ecopower Dynamische Burgerstroom before October 2025) keeps that card's formula, settled on the grid of the current card.

VAT is applied to each component and a VAT-exempt levy (the Walloon connection fee) is added at face value, so energy_component + network_component + taxes_component always equals current_price to the cent.

The VAT rate

Residential electricity carries 6% VAT. Most cards print their prices with it included and say so ("Prix 6% TVA comprise", "inclusief 6% btw"), and those cards are priced at the rate they state. Some do not state one (Bolt, energie.be, OCTA+ since June 2026, and TotalEnergies' Brussels variable cards since September 2026, whose text lost the digit), so their formula is priced at the rate of the month being billed, as are the regulated figures published without VAT (the flat excise, the Flemish network ceiling and the Brussels power term). That month's rate is the one the cards themselves agree on, read from the project's card archive (see The card archive): if the law changes the rate, the cards change with it, and so does every price they leave without one. A contract that locks its signing-month formula is billed at the VAT of the month delivered, not the month it was signed in. Professional contracts use the standard 21%.

The federal levies come from the law, not from your card

Two items in that levies term are set by federal law rather than by your supplier: the special excise and the energy contribution. One rate applies to every residential customer in the country in a given month (a protected customer on the social tariff owes a rate of its own, see The social tariff), so the month being billed decides them, not the month your card was printed in.

Since 1 August 2026 the law sets a flat excise of 4,876 c€/kWh including VAT (4,6 excluding) and abolished the energy contribution, folding it into that rate. The integration reads the excise from the law itself, article 419 of the programme law of 27 December 2004 as consolidated on Justel (ejustice.just.fgov.be), once a day, and bills that for every month from then on. A card that still prints the old pair is not billed as printed. Four suppliers were still printing some of it in September 2026: Ecofix, whose card is an image of its July card, and Aspiravi printed the whole old pair, Cociter the contribution beside the new excise, and TotalEnergies a rounded excise with the contribution. Together the two corrections are worth about 12,64 € a year at 3.500 kWh on an Ecofix or Aspiravi contract.

Two limits worth knowing. Professional contracts are untouched, because that scheme bands the excise by annual volume and is a genuinely different rate. And the correction covers the months the law's current text sets: the same measure steps the excise down each January, to 43, 40 and then 38 € per MWh excluding VAT from 2027 to 2029, and each step is billed from its own month because the law states it. Your card's own excise is used for the months before August 2026, which the correction leaves alone, except where a later card stands in for a month whose own card no archive kept (TotalEnergies before September 2026, Ecofix before August 2026 but for May): that month is billed the excise table and the energy contribution the law set then, read once from Justel's archived copy of the same article, from July 2023, since the law that wrote that table set another rate on its first band until June 2023 (read from that law too); an earlier month keeps the card's. Where the card prints the degressive table of the time, a household above 20 000 kWh a year is billed the blend over its volume; Cociter's card prints only the first tranche and energie.be's and Frank's say the excise is degressive without the other rates, so those bill the first tranche on every kWh. The card's excise is also used for as long as the law has never been read. Once read it is kept across restarts, so that only happens while Justel (ejustice.just.fgov.be) cannot be reached from your Home Assistant at all, and a warning in the log says so every six hours until it can. The contribution stays dropped: it was abolished, not set for a period.

The daily live check compares every supplier's federal block against the rest and reports a card that drifts (the social tariff is left out of this and of the network comparison below: it bills the protected rate and no network rows), which is how a supplier that corrects itself gets noticed: Bolt and Trevion both did between August and September.

The network rows are compared the same way, per DSO: distribution, transport, metering, capacity and prosumer figures are set by the regulators, so a card printing another figure than the rest is reported. Unlike the federal levies, network figures are billed as each card prints them, with two exceptions taken from the regulator instead: the VREG network ceiling in Flanders, read off Fluvius's tariff sheet of the year, and Sibelga's power term in Brussels where a card leaves it out. The report names the cards whose households pay a figure the others do not.

Sensors

All sensors share one device per config entry.

Always created

Sensor Description
current_price All-in EUR/kWh now. Attributes carry today's and tomorrow's slot-by-slot prices, the four cheapest and four most expensive hours of today (not necessarily consecutive), the snapshot's publication month and age, and card_read_by_ocr while a card is being read off its pixels. See docs/entities.md.
next_hour_price All-in EUR/kWh for the next hour.
today_average Daily average all-in EUR/kWh.
today_min / today_max Daily extremes.
tomorrow_average Average all-in EUR/kWh for tomorrow. Empty until ENTSO-E publishes the next-day curve (~13:00 CET) for dynamic contracts; available all day for fixed, variable and monthly-indexed contracts, except on the last day covered by a monthly card, where next month's rates are not published yet. Tracks tomorrow_prices_available exactly: the sensor has a value when that binary sensor is on.
tomorrow_min / tomorrow_max Tomorrow's extremes. Same availability as tomorrow_average.
energy_component Energy-only EUR/kWh now (VAT-inclusive).
network_component Distribution + transport EUR/kWh now (VAT-inclusive).
taxes_component Levies EUR/kWh now (VAT-inclusive).
fixed_fee_eur_per_year Supplier's flat annual subscription fee (EUR/year), parsed from the tariff card.
energy_fund_eur_per_month Flemish Energiefonds in EUR/month, as the card prints it for your contract (€0 outside Flanders, €0 for a domiciled residential customer in Flanders, and the non-residential figure on a professional contract).
current_year_cost Running bill since 1 January, or since your contract start date if you tick that option. Every kWh is priced at the tariff that applied when you used it: past months bill on their own card where the supplier archives historical cards (Aspiravi / Bolt fix / Cociter / DATS 24 / EBEM / Ecopower / Elegant / Eneco / energie.be / Energy Knights / EnergyVision / Engie / Frank / Luminus / Mega / OCTA+ / social tariff / Trevion), and for every supplier from August 2026 on (May 2026 for Ecofix) through the project's card archive, on the current one as a stand-in where neither has it (with that month's own federal levies), dynamic contracts replay each hour's actual spot, and annual fees pro-rate across the year. Under the Walloon compensation regime injection nets against consumption and the energy term is floored at zero, so a value that stops moving while you keep injecting is that floor rather than a stalled sensor. Changed supplier during the year? Record the switch and each contract is billed on its own supplier's cards for its own days, listed in the previous_contracts attribute: see Switching supplier during the year. Configured in the Energy meters step. A refresh the recorder does not answer keeps the last figure rather than publish a year priced at fees alone. Coverage and cost attributes (hours_seen / hours_priced / hours_elapsed, days_seen / days_priced / days_elapsed, injection_hours_uncredited, capacity_ytd_eur, fees_ytd_eur and the rest) say how complete the figure is — read them with When the year-to-date looks too low, and see docs/entities.md for the full list.
current_month_cost The same bill as current_year_cost over the running month, which is the period a household budgets in and the one an invoice covers. Priced as its own window rather than sliced off the year, so under the Walloon compensation regime it nets that month's registers and twelve of these do not add up to the yearly figure; on every other regime they do, a first-year welcome credit included: the month takes the year's credit to date less the year's to the day before the month. Resets on the 1st. See docs/entities.md.
tomorrow_prices_available Binary sensor. ON when the price table covers at least one hour with tomorrow's local date and the supplier's published validity still covers tomorrow. Useful as a trigger for dynamic-tariff automations that should only fire after ENTSO-E publishes the next-day curve (~13:00 CET). For fixed/variable contracts it is ON throughout the month, but flips OFF on the last day of a month whose card stops at month-end, since next month's rates are not published yet. A dynamic contract whose card stops at month-end stays OFF on that day too, even after ENTSO-E publishes: the spot is out, but next month's card may change the formula around it. A card that states no validity date is taken to end with the month its title names (Bolt, TotalEnergies). A product the supplier has withdrawn stays ON on its last card, which is the one every day is priced off.
projected_year_consumption kWh your meter will have recorded by 31 December: what it recorded from 1 January to yesterday, plus what it recorded over the same remaining days last year, so the rest of the year follows your own season rather than an average day. Without last year's history, an entry that already loads Synergrid's residential load profile (an RLP-indexed card, or the compensation regime) extrapolates this year's days on that profile once 90 of them are recorded; the profile is never downloaded just for this. Otherwise unknown. Moves once a day. The volume_basis, ytd_kwh and remaining_kwh attributes say which method was used and how the figure splits.
projected_year_cost Shown as Rolling year cost. Roughly what a year on this contract costs in EUR, priced once at today's tariffs against the volume your meter recorded over the last 365 days (rolling_year_consumption), or, short of a year of readings, against the yearly volume you entered or an estimate; the volume_basis attribute says which. It covers the last 365 days, not the calendar year that current_year_cost and the projected_year_* kWh sensors cover. An indication for ranking contracts, not a forecast of your settlement: tariffs move and your usage will not repeat exactly. A card that settles on a monthly Belpex index, or one your ENTSO-E key or signing cohort re-prices on it, holds this month's index for the year (Bolt's variable cards and Luminus ComfyFlex and ComfyFlex+ the quarter's), the way a variable card holds its printed rate. Unknown on a dynamic contract, early in a month whose index is not known yet, and under the compensation regime, with a feed-in meter wired, until a full year of your feed-in is recorded, since a shorter window cannot be netted against a year; with no feed-in meter wired the year is billed gross, as current_year_cost bills it. A feed-in credit that follows the spot price per slot is credited on the day-ahead prices of the past year's closed days, each hour weighted by what you exported in it, as the comparison page credits it, so the figure does not move with each day's prices. It needs a full year of both: the day-ahead is fetched from 1 January (or from the contract start, if you bill from it) and then kept for a year, so until a full year of day-ahead prices and of your own feed-in is held (in an installation's first year, and after a change of contract) the credit is left out and the attributes say so; the comparison page and the daily ranking leave it out too, the page's note says so and the ranking tags the row. See docs/entities.md.
projected_year_end_cost What this calendar year's bill will stand at on 31 December, in EUR: the bill current_year_cost walks, run on to 31 December over last year's same remaining days (the days projected_year_consumption and projected_year_injection read them from), each month to date on its own card and the months after this one on today's card. Fees, the welcome credit and any earlier contracts after a supplier switch are in it, and under the Walloon compensation regime the year is netted once, so a summer surplus pays for the winter after it rather than being forfeit. A month-indexed card holds this month's index to 31 December (Bolt's variable cards and Luminus ComfyFlex and ComfyFlex+ the quarter's). Unknown on a dynamic contract, on a feed-in credit that follows the spot price per slot, early in a month whose index is not known yet (energy or feed-in), and when your meter's history does not cover last year's remaining days; the energy_basis and volume_basis attributes say which. The days_seen, days_priced and days_elapsed attributes (hours_* on an hourly-billed contract) count the year's days as current_year_cost does, so a stretch the recorder lost, which both bill without its energy, shows here too. Moves once a day, and when the card or this month's index does.
rolling_year_consumption kWh your meter recorded over the last 365 days, today's live reading included: the volume projected_year_cost prices. Up to 15 missing recorder days are scaled across; with more missing it is unknown rather than part of a year. The volume_basis attribute says how many days were measured.

Conditional

Sensor Created when Description
capacity_cost Region = Flanders, not on an exclusive-night circuit Current monthly capacity cost in EUR (billed_peak_kw × DSO_capacity_rate / 12). It is capped at the VREG network ceiling of the year, read off Fluvius's tariff sheet: capacity plus the per-kWh network term never costs more than that ceiling times your yearly volume, though the capacity charge never drops below its 2.5 kW minimum. billed_peak_kw is the mean of your last twelve monthly peaks, each floored at 2.5 kW first, which is what Fluvius charges on, so this stays steady through the year rather than tracking whichever month you are in. This charge also accrues into current_year_cost, so the two are consistent rather than the capacity term being invisible in the running bill. Carries billed_peak_kw and months_counted attributes; months_counted reaches 12 after a full year of history, and until then the mean covers only the months measured so far. With a fixed peak, billed_peak_kw is the value you entered (at least 2.5 kW) and months_counted reads 0, since no mean is taken. On an analog meter it is the flat charge Fluvius bills on 2.5 kW, with no ceiling, and months_counted reads 0.
monthly_peak_kw Region = Flanders, not on an exclusive-night circuit Running monthly peak power in kW (resets the 1st), reported as measured, so it reads 0 with a fixed peak or an analog meter, which measure nothing and add no month to the history. With a fixed peak the months measured before stay, and count again when you return to the sensor; ticking Analog meter drops them, so the meter Fluvius later puts in its place starts a mean of its own, measured from the day the box is unticked; the days before the replacement day asked then are billed on the flat 2,5 kW. Version 0.28.5 and earlier banked the fixed value into each month as if measured; the first start after upgrading drops the months holding exactly your fixed value. If you changed the fixed value since, the months at the older one stay: press Reset monthly peak once to clear them. The 2.5 kW regulated minimum is a billing rule and is applied to capacity_cost instead, so a quiet household now reads its true peak here rather than 2.5. State class is MEASUREMENT (mandated by HA for the POWER device class), so the long-term-statistics graph defaults to the mean aggregation. To see the true monthly peaks, switch the statistic-graph card to Max under Developer Tools → Statistics. The month's last day gets one more reading a minute before midnight, so a peak set in its final hour is still banked. A diagnostic Reset monthly peak button on the device page drops the rolling max so the next tick rebuilds it, and clears the monthly peaks banked so far, so the twelve-month mean behind capacity_cost starts over (use after a misconfigured sensor inflated the peak).
prosumer_cost Wallonia, compensation regime + solar_kva > 0 Monthly compensation fee in EUR (solar_kva × (DSO_prosumer_rate + supplier_forfait) / 12). Most suppliers bill only the regulated DSO rate; Cociter Tarif Variable and Variable Trihoraire, Mega and OCTA+ add a supplier-side PV forfait (already TVAC) on top. Only valid for Walloon installations certified before 2024-01-01; ends 2030-12-31.
price_peak / price_offpeak Bi-hourly meter, or digital (smart) meter on a fixed, variable or monthly-indexed card The contract's constant day and night all-in rates, both readable at once. current_price follows the clock and holds whichever band applies now, which the Home Assistant energy dashboard cannot use: configuring a grid source with two tariffs asks for one price entity per tariff, permanently. These are those two. A card priced on the month's index has one rate per band for the whole month, so these show it at the month's mean. Unavailable on a contract with no constant band rate, which is every dynamic and time-of-use one. Not created on the Walloon Tarif Impact DSO tariff, whose distribution follows the CWaPE bands and has no day or night rate, except on an Expert: custom formula entry.
injection_price_peak / injection_price_offpeak Bi-hourly or dynamic (smart) meter, injection regime The feed-in credit of each register. A card that prints a day and a night injection rate (Trevion Groene Energie Vast, the Engie and Luminus social tariff cards) gives each its own, on the delivery month's index where the card gives a formula per register; every other card credits both registers on one formula or rate, so both show the current credit, the same figure as injection_price, which is what lets the Energy dashboard price each return register off its own sensor. Both or neither: a card carrying only one of the pair is read as having none.
injection_price Injection regime EUR/kWh paid for energy fed back to the grid. A card that indexes the credit per settlement slot follows the spot slot by slot, per quarter-hour on Bolt's fixed cards although their price table is hourly; one that indexes it on the delivery month is resolved against that month's own mean; a flat card shows its printed figure. Without an ENTSO-E key a card that indexes the credit on the month shows the figure it prints, where it prints one; a credit settled per slot is unknown. Carries today/tomorrow arrays of the credit per slot on every card that prices the feed-in, flat ones included, for the injection chart below. See docs/entities.md.
projected_year_injection Compensation or injection regime The same for feed-in, read off your injection meter. Its fallback is Synergrid's solar production profile, used only where the entry already loads it (a feed-in indexed on Belpex SPP, or a custom entry weighting its feed-in on SPP).
rolling_year_injection Compensation or injection regime The same as rolling_year_consumption for feed-in, read off your injection meter.
contract_end_date A contract end date is set Timestamp of your contract's end date (device_class: timestamp), so an automation can remind you to renew before it rolls over. Changes no billed rate. It does bound the projections: projected_year_cost reads it to report how much of the 365 days it prices today's contract still covers, and projected_year_end_cost how much of the rest of this year. Stays available even when a supplier fetch fails.
potential_saving Compare every supplier daily is ticked, and your contract has an alternative of its kind in your region to rank against EUR a year the cheapest alternative would save you against your own contract, from the nightly ranking. Negative means nothing on the market beats what you have. Attributes carry the whole ranking, each row with its annual cost and, where the same months could be replayed for it, a year-to-date figure. A row whose feed-in credit is left out for want of a full year of day-ahead and of your export (a feed-in that follows the spot price per slot) carries feed_in_uncredited: true, and the ranking page tags it NO FEED-IN: its figure is high by a year of that credit. A row whose card rewards paying by direct debit, when your own card never asked how you pay, is priced as not paying by direct debit, carries direct_debit_assumed: true and is tagged NO DIRECT DEBIT; the one-off comparison asks instead. A row whose card weights an index by a Synergrid profile the installation does not hold is priced on the plain monthly mean for an energy index, or on the figure the card prints for a feed-in one, carries profile_missing: true and is tagged NO PROFILE; without an ENTSO-E key and the keyless setting, every feed-in that settles on a month of the day-ahead price is credited at the figure its card prints, your own contract's too when its feed-in is one of them, which the ranking page says once under the table. Survives a restart, unless you changed a setting of the entry since the ranking ran (the supplier, the meter, the solar regime or any other): it is then discarded until the next nightly ranking. A night on which Home Assistant's recorder does not answer keeps the previous ranking, and one on which it fails only for the year-to-date figures publishes the ranking without them. See docs/entities.md.
ev_home_charging_rate Publish the CREG home charging rate for a company car is ticked For a company car charged at home: the most an employer may reimburse per metered kWh, free of tax and social contributions, when it pays a flat rate (circular 2024/C/77), in EUR/kWh for your region and the running quarter. Computed from the CREG's monthly prices the way the SPF Finances does in the circular's quarterly addenda, and read once a quarter. It is a ceiling, not what every employer pays: an employer may pay less, must pay the lowest of the three regions if it ignores where its staff live, or may reimburse your actual cost instead. Attributes carry the quarter and every quarter the CREG's file covers, so last quarter's kWh can be settled at last quarter's rate. Not part of current_price.

Installation

HACS (recommended)

Open this repository in HACS.

That button opens HACS on your own Home Assistant, already on this integration: download it there, then restart Home Assistant and add it with

Add the integration.

Both links go through my.home-assistant.io, which redirects to whatever address you use for Home Assistant; the first time, it asks you for it once and remembers.

By hand, if you would rather: open HACS, search for Belgian Electricity Prices — it ships in the HACS default store, so no custom repository is needed — download it, restart, then Settings → Devices & services → Add integration → Belgian Electricity Prices.

Manual

Download the latest release zip, extract it under <config>/custom_components/be_electricity_prices/, and restart Home Assistant.

pypdf, pdfplumber, defusedxml and pyxlsb are the only extra runtime dependencies; Home Assistant installs them automatically from the manifest.

Configuration

The UI walks up to eleven steps, twelve with the Expert: custom formula supplier, depending on contract type and region. Apart from two paths no EUR values are asked, since energy, DSO and tax rates all come from the supplier's tariff card. The exceptions are the optional signing-rate step and the Expert: custom formula supplier, which has no card and asks for the whole set.

The signing-rate step appears on a fixed, dynamic or monthly-indexed contract (a Bolt variable card billed per quarter-hour counts as dynamic) when you set a contract start date or a tariff card month; variable, time-of-use and Impact contracts never ask for one. It is only for a contract whose figures differ from the supplier's published card. Many offers, promotions sold through comparison sites among them, have a card of their own, which is already read: leave the step empty then. A spot factor multiplies the market price in EUR/kWh and a base is in EUR/kWh, so a formula printed in c€/kWh on a Belpex in EUR/MWh has to be converted: (0.1068 × Belpex + 1.5) × 1.06 c€/kWh is a factor of 1.13208 and a base of 0.0159. Per-kWh values include VAT on a residential contract and exclude it on a professional contract and on Ecopower's cards; the yearly fee includes VAT. When you edit an existing entry, each box also shows what the current card gives it, so you can see whether your contract says anything different, as long as the edit keeps the same supplier, contract and region (recording a supplier switch never does). An edit that changes any of the three starts the boxes empty: a rate typed for the contract being left is dropped rather than billed on the new card.

  1. Supplier + Region — Flanders / Wallonia / Brussels. Suppliers that have announced their exit from the residential market are dropped from the list, though an entry already on one keeps showing it so it stays editable. The region does not filter the list: pick a supplier that sells nothing in the chosen region and the step is re-shown with an error rather than the supplier being hidden.
  2. Contract — filtered by supplier and region (e.g., TotalEnergies Impact only appears in Wallonia). Bolt's variable cards and Frank Energie then ask whether you are billed per quarter-hour; on Bolt the answer moves the contract to dynamic settlement. The same step takes the optional Contract start date, Tariff card month and Contract end date, and the Bill the year-to-date from the contract start date box. Left unticked, the year-to-date counts the months before the start date too. Those and any before the tariff card month (a renewal re-signs onto a later card) each bill on their own month's card. A product the supplier no longer sells is still listed, marked †, for a household whose contract still runs on it: it bills on the last card published for it, or with a start date on the card of the month signed. The settlement question and the signing-rate step, where they apply, come right after it, before the DSO.
  3. DSO — filtered by region.
  4. Meter type — mono (single rate), bi (peak / off-peak), dynamic (smart meter), or exclusive-night circuit (a separate meter; see the section below). Dynamic, TOU (Engie Empower Flextime, Luminus SmartFlex) and Impact contracts (Cociter Tarif Variable Trihoraire, Mega Off-peak Impact, OCTA+ Boost Fix Impact, Eco Boost Fix Impact and the withdrawn Fixed Impact) lock the picker to dynamic — the SMR3 meter is required to bill by hour-of-day. A Flemish entry is also asked its Day tariff hours. Fluvius bills the day rate on weekdays from 07:00 to 22:00, except in the municipalities on its list, where the day register runs from 06:00 to 21:00. Where the list marks a municipality with an asterisk, the switch time was set when the meter was installed, so check yours. The answer moves the day/night split of the prices, the running bill and the comparison on a bi-hourly or digital meter, and changes nothing on a mono one. It belongs to the meter, so the contracts held before a recorded supplier switch are billed on it too. Hours already written to the long-term statistics keep the schedule they were priced on until the backfill service is run again. A Flemish mono, bi-hourly or exclusive-night meter is also asked whether it is still an Analog meter (off by default). On the Expert: custom formula supplier the network figures you type are billed as typed: there the box bills the capacity tariff on 2,5 kW and quotes the cards the comparison page sets against yours on the analog rates. The social tariff has no capacity tariff and keeps the CREG's network component: there the box changes nothing on your own bill and only quotes the cards compared with yours on the analog rates and 2,5 kW. A social-tariff entry that recorded its switch from a commercial contract under 0.43.0, which did not ask it there, takes that contract's answer until you answer the box, a later switch to another supplier included. Fluvius cannot measure an analog meter's peak: it bills the capacity tariff on a flat 2,5 kW (the "vaste term" of its tariff sheet), applies no maximum tariff, and charges every kWh, exclusive-night ones included, its analog kWh rate instead of the digital one, 2,73 c/kWh more with VAT on Fluvius Antwerpen in 2026. That rate is read off your area's own Fluvius tariff sheet for each year billed: a year Fluvius has not published yet is billed on the year before. Until a sheet has been read, and for 2023 and 2024 outside Antwerpen, Limburg and West, which Fluvius published per merged operator then, the kWh are billed the digital rate, though the capacity charge stays the flat one. An analog meter has no peak source to set, so the capacity step is skipped. The answer belongs to the meter: a contract kept at a supplier switch recorded before the question existed takes the entry's answer, one recorded since keeps its own. Once Fluvius has replaced the meter, untick the box in Edit settings, or pick the smart meter or a contract that needs one there or in a supplier switch: it then asks the Day the digital meter was placed (never in the future, and left empty if the box was ticked by mistake), and the days before it stay billed as analog while the days from it are billed on the digital meter, the capacity tariff on the peak it measures from then on, the contract kept at a switch included. Edit settings shows the day again on every edit while it falls in this year or the last, so a wrong one can be changed. Ticking the box again drops the day, and so does a move out of Flanders. Hours already written to the long-term statistics keep the meter they were priced on until the backfill service is run again.
  5. Professional contract (pro contracts only) — Prices include VAT (21%) and Estimated yearly consumption; see Professional (B2B) tariffs above.
  6. Direct debit (only where the card prices it) — whether you pay your supplier by direct debit. Some cards charge a lower yearly standing charge when the invoice is settled that way: Brusol Groene stroom's cards up to September 2026 charge 250 € a year and 230 € on domiciliëring, and its October card no longer prints the reduction. On nineteen Mega cards it is the first-year ristourne that depends on it instead, either as a larger credit (42,40 € more on Cosy Fixed) or, on Cosy Flex, Smart Fixed and both their pro twins, as the whole credit, which those cards grant to a direct-debit payer and to nobody else. The amounts are read off the card, not typed, and nothing else on the bill changes. The box is hidden on every contract whose card grants none, and an answer given on one contract is dropped when you switch to a contract that does not ask for it, so it cannot come back into force later.
  7. DSO billing mode (Wallonia only, and skipped for the contracts sold on the CWaPE bands — Cociter Tarif Variable Trihoraire, Mega Off-peak Impact and OCTA+ Boost Fix Impact, Eco Boost Fix Impact and the withdrawn Fixed Impact, which are locked to Tarif Impact) — Simple / Bi-hourly / Tarif Impact. Tarif Impact uses the CWaPE 3-band hour-of-day rates and requires a smart meter; Simple and Bi-hourly follow the existing meter convention. A card that prices the same product on the bands too (Bolt's variable cards, OCTA+ Boost Flex, Eco Boost Flex and Basic Online in Wallonia) bills its energy on them under Tarif Impact. Otherwise a day/night meter under Tarif Impact pays the night rate in the ECO band and the day rate in PIC and MEDIUM, except on TotalEnergies' Walloon cards, which say their day/night split does not apply under Impact and bill the single rate in every band.
  8. ENTSO-E API key or keyless day-ahead prices (dynamic and monthly-indexed contracts, both of which price the commodity off spot; also offered, skippable, right after the Solar panels step, to every contract whose energy is indexed on the delivery month's mean and whose card prints an earlier month's figure, on any solar regime — Aspiravi Eco Plus Flex, Cociter Variable and Trihoraire, Ecofix Flexy and Flexy Online, Ecopower Groene Burgerstroom, Elegant Flex and ComfortFlex, Engie's EPEXDAM cards, Luminus MaxxFlex, SmartFlex and BasicFlex, OCTA+ Smart Variable, Boost Flex, Eco Boost Flex, Basic Online and the withdrawn Flux and Eco Flux, Eneco Flex and Flex One, EBEM Groen Variabel and B@sic+, the withdrawn DATS 24 Groen Variabel, TotalEnergies Electricité Variable, Impact, myComfort, myDrive and myEssential, every Mega Flex and Off-peak Impact card, and Bolt's variable cards and Luminus ComfyFlex and ComfyFlex+ on the delivery quarter's mean — and on the injection regime for a contract whose injection is itself index-linked, which is most static cards and not the handful it once was — every Bolt fixed card, a Bolt variable card billed per quarter-hour and both Cociter variable cards index it per slot, while energie.be Vast and most of the rest index it on a monthly mean) — validated against the real ENTSO-E endpoint at submission; bad keys are rejected before the entry is saved. If ENTSO-E is unreachable rather than rejecting the key, setup no longer dead-ends: the wizard says so and offers to check again or to continue without verifying. ENTSO-E has outages lasting a day or more, and while one is running there is no way to tell a good key from a bad one, so blocking setup would only punish you for their downtime. A key accepted that way is checked for real on the first price refresh, and the usual repair notice appears if it turns out to be rejected. In place of a key you can tick Use keyless day-ahead prices: the entry then fetches the day-ahead from energy-charts.info and SMARD and never asks ENTSO-E, and is priced as one with a key. A blank key with the box unticked is rejected on the spot, without asking ENTSO-E, because a dynamic contract has no price at all without one of the two. A key is the recommended choice: see Getting an ENTSO-E API key for why. For the injection and monthly-indexed cases both are optional and skippable: leave them empty to skip, and a feed-in credit settled per slot stays unavailable until you add a key or tick the box through Configure → Edit settings, while a leg indexed on the month keeps the card's printed figure, an earlier month's (the previous one on most cards, up to three back on Ecofix's, and on Bolt and ComfyFlex the previous quarter's), until a later card settles the closed month or quarter where the supplier prints the index it closed at.
  9. Capacity tariff peak source (Flanders only, not asked on an exclusive-night circuit, whose peak is billed on the main entry: see Exclusive-night meter circuit, nor on an analog meter, billed on 2,5 kW) — a sensor, or a fixed kW value (default the VREG regulated minimum, 2.5 kW, read off Fluvius's tariff sheet like the network ceiling). Leaving it at that default when your real peak is higher understates the bill by the difference times the per-kW rate, which is the largest single thing that can be wrong about current_year_cost without anything looking wrong on the price graph. Fluvius bills the highest quarter-hour average offtake of the month, and a DSMR 5B meter computes exactly that and publishes it on the P1 port; Home Assistant's dsmr integration exposes it as Maximum demand current month. Point the field at that entity when you have it and the figure matches the meter. Any other power sensor (W, kW, VA, or kVA; the unit is honoured so a Riemann-source sensor in W is not misread as kW) reports your live draw, which the integration samples once an hour and keeps the maximum of: that is an estimate, not the billed quantity, and it can miss a peak between samples or read a momentary spike as a quarter-hour one. The picker is restricted to power / apparent-power sensors so a kWh / temperature / unitless sensor cannot be selected. The field is auto-filled with the meter's monthly-peak entity when one exists, otherwise with the power input of any Riemann integration helper that feeds the Energy dashboard's grid source, so users with the typical P1-power → kWh-Riemann → dashboard chain don't have to pick the same sensor twice; the auto-pick refuses non-power sources. It runs at install, or when an edit moves the entry into Flanders; a later edit leaves a field you cleared empty.
  10. Connection power (Brussels only) — the contractual connection power tier (≤ 1.44 / 1.44-6 / 6-9.6 / 9.6-13 / 13-18 / 18-36 / 36-56 / > 56 kVA). Brussels bills a Brugel OSP (Obligations de Service Public) annual fee scaled by this tier, and a connection above 13 kVA is billed Sibelga's own higher power term in place of the data-management charge; existing entries default to the 1.44-6 kVA tier.
  11. Solar panels — inverter capacity in kVA + the regime that applies:
  • No solar panels (default) — no extra sensors.
  • Compensation regime — Wallonia only, installations certified before 2024-01-01, valid until 2030-12-31. Needs the inverter capacity, which cannot be left at 0. Creates prosumer_cost, projected_year_injection and rolling_year_injection.
  • Meter counts draw and injection apart (Wallonia only, off by default) — tick it for a double-flow or communicating meter. CWaPE then bills distribution and transport on your gross draws instead of the prosumer tariff, and caps that at what the prosumer tariff plus those charges on your net draws would cost (article 81 of the 2025-2029 tariff methodology). The running bill, the backfilled series and the compare page bill the lower of the two; energy and the levies stay netted either way. Days your meter has no readings for keep the prosumer tariff: only the days it reported are compared. Leave it off for a meter that runs backwards. prosumer_cost keeps showing the tariff, which is the cap; current_year_cost carries net_network_ytd_eur, gross_network_ytd_eur and network_cap_rebate_eur, the difference taken off.
  • Injection tariff — post-2024 Walloon installations, Flemish smart meters and Brussels. Creates injection_price, ready for HA Energy, projected_year_injection and rolling_year_injection, plus the day and night injection price sensors on a bi-hourly or dynamic meter.
  1. Energy meters (optional, all four / two fields are skippable) — feeds the current_year_cost sensor. Whichever way you wire it, every field wants a cumulative kWh reading, one that only ever climbs. A sensor that resets, such as a "this year" or "this month" total, is read right only when its reset can be told from a fall: a total_increasing sensor, whose drop below 90% of the reading before it is a new cycle as Home Assistant's statistics count one, or a sensor that publishes last_reset. Any other fall reads as zero, so a climbing reading is the safe choice. A net register that counts down while you export (a utility_meter with net_consumption, a bidirectional meter) is not supported either: a fall reads as zero, so wire consumption and injection as two separate climbing sensors. If you fill both wirings for the same side, the day/night registers win; the totals field is billed instead only when the pair cannot bill the whole window (a register records nothing, stopped or started late) or records clearly less than the total (a register frozen at its last reading), and never when the total itself records clearly less than the pair. Without a solar regime the injection fields are not read at all, so wiring them only for the Energy dashboard changes nothing. A sensor that only updates once a day, such as a supplier portal poller, is detected once it has a week of such days, counting the four weeks before 1 January or a supplier switch only while the year or the contract holds fewer than seven days of its own, and each day is spread evenly over its hours, so a contract priced by the hour bills it at the day's average rather than at the hour the reading arrived. A day/night register pair read that way is spread register by register, the day register over the day's peak hours and the night register over its off-peak ones on your day/night schedule (in Flanders, the Day tariff hours you picked), so neither lands on the other's rate. The log says so once and the diagnostics flag it. Two ways to wire it:
  • Day/night register sensors (4 fields): point at the cumulative kWh registers from your meter. The integration reads each day's delta from HA's long-term statistics, so the sensor reflects metered totals exactly and resets cleanly on Jan 1.
  • Cumulative total sensors (2 fields): point at a single running consumption sensor and a single running injection sensor. The integration reads daily kWh from the recorder and recovers the day/night split per past day from the recorder's hourly statistics binned via the bi-hourly schedule (no in-process buckets). Useful when your P1 / digital-meter integration only exposes totals (the standard HA case).
  • Mix and match: each side (consumption, injection) is resolved independently. You can wire registers for consumption and a single total for injection, or vice-versa. A half-wired register pair is rejected unless a totals sensor covers that side, in which case the odd register is ignored, so a missing band can't silently undercount. Without a solar regime the injection meters are not read at all, so a half-wired injection pair is let through. One sensor picked in two of the six fields is rejected too: it would be counted twice.
  • When both wirings are filled for the same side the day/night registers win while they report whole; the total stands in for a pair with a dead, frozen or late half, or halves reporting different periods. Missing inputs collapse to the fees-only floor, so the sensor is unknown only while a recorded supplier switch still has an earlier contract to price, and in the minutes after setup when no figure from before it applies (a new entry, a year or month that turned over while Home Assistant was down, or a setting edited since) until the background meter read lands.
  • Auto-fill from the Energy dashboard: if you've already configured a grid source in HA's Energy dashboard when you add the integration, the cumulative consumption / injection fields are pre-selected from the dashboard's grid meters so you don't pick the same sensor twice. A side the dashboard lists as more than one meter (tariff 1 and tariff 2 registers) is left blank: one register is not the total. When a utility_meter helper rooted at that grid source splits it into peak / offpeak (or jour / nuit, dag / nacht, piek / dal — case-insensitive, separator-tolerant) child tariffs, the four day/night registers are pre-selected too. Tariffs whose names don't map unambiguously to a day/night slot are left blank so a misnamed helper can't silently mis-bill. Whatever is pre-filled stays editable; an existing manual pick is never overwritten. Edit settings fills nothing in, so a sensor you cleared stays cleared.
  • The step ends with three boxes: Compare every supplier daily (off by default), Read past cards from the project's archive (on) and Publish the CREG home charging rate for a company car (off).

Getting an ENTSO-E API key

Required for dynamic and monthly-indexed contracts (energie.be Variabel, Energy Knights Essentia Online and Essentia Online Green, EnergyVision's 1.800 kWh vast, vaste injectieprijs 3 jaar, Laadpunt and Groene stroom, Trevion Groene Stroom Flex, FlexiO Max and the withdrawn LifePowr, and the custom supplier's monthly-average formula), which is where the setup flow asks for it as a validated field, or for the keyless box in its place. It is optional everywhere else, but two features use it when present: an injection tariff that is itself index-linked — the hourly-spot shape (Cociter Variable and Variable Trihoraire, every Bolt fixed card, and a Bolt variable card billed per quarter-hour) and the monthly-mean shape (energie.be Vast on Belpex_SPP, and most other static cards), 80 contracts across 17 suppliers between them, and the re-price of a month-indexed contract on the delivery month's own mean, cohort or not, for which the flow offers the key on every solar regime (Aspiravi Eco Plus Flex, Cociter Variable and Trihoraire, Ecofix Flexy and Flexy Online, Ecopower Groene Burgerstroom, Elegant Flex and ComfortFlex, Engie's EPEXDAM cards, Luminus MaxxFlex, SmartFlex and BasicFlex, OCTA+ Smart Variable, Boost Flex, Eco Boost Flex, Basic Online and the withdrawn Flux and Eco Flux, Eneco Flex and Flex One, EBEM Groen Variabel and B@sic+, the withdrawn DATS 24 Groen Variabel, TotalEnergies Electricité Variable, Impact, myComfort, myDrive and myEssential, every Mega Flex and Off-peak Impact card), and of Bolt's variable cards (Variable, Plenty, Online and Plenty Online, residential and pro) and Luminus ComfyFlex and ComfyFlex+ on the delivery quarter's, which is what those cards are indexed on. Both stay off without a key or the keyless setting rather than failing the entry: a feed-in credit settled per slot goes unavailable, while a monthly one and the re-price keep the card's printed figure, which is an earlier month's (the previous one on most cards, up to three back on Ecofix's), or on Bolt and ComfyFlex the previous quarter's, until a later card settles the closed month or quarter where the supplier prints the index it closed at. A settled month needs no key at all, so with a contract start date it bills on the formula you signed, key or not; without a key or the keyless setting, the months not settled yet keep the printed rate, still with the standing charge you signed.

No key? Tick Use keyless day-ahead prices on the key step instead. The entry then fetches the Belgian day-ahead from energy-charts.info and, where it cannot answer, from SMARD, the German regulator's portal it takes the series from, and is priced exactly as an entry with a key. The difference is only where the prices come from: with a key ENTSO-E is the source and those two back it up, while without one they are the only sources, so an outage of both leaves the entry on its cached curve. The setting can be turned on or off at any time through Configure → Edit settings. Removing the key, or unticking the box with no key left, also drops the day-ahead history the entry gathered, since nothing would bring it up to date any more: the entry is then priced exactly as one that never had either, and adding one again fetches the year anew.

Please use a key if you can get one. energy-charts and SMARD are free public services, and every installation of this integration shares them as its backup; were most installations to use them as their main source, the load could get them restricted for everyone. The box is there for those who cannot get a key.

The token is free but ENTSO-E does not auto-grant it — you have to request access explicitly:

  1. Register an account on the ENTSO-E Transparency Platform and confirm the verification email.
  2. Email transparency@entsoe.eu from that address with the subject Restful API access and a one-line body asking to enable API access for the account. Allow 1–3 business days for the confirmation reply.
  3. Once granted, on the Transparency Platform open My Account Settings → Web API Security Token and generate (or copy) the token. Paste it into the integration's ENTSO-E API key field — the config flow validates it against the real endpoint before saving the entry.

The token does not expire unless you regenerate it. If transparency.entsoe.eu later rejects it with 401, the entsoe_auth_failed_<entry> repair issue fires; paste a fresh token in the entry's options to clear it. The key step shows on every edit of a contract that reads a key, with the stored token filled in; submitting it unchanged saves without asking ENTSO-E again, so an edit still goes through while ENTSO-E is down or out of quota.

Reconfiguring later

Settings → Devices & services → Belgian Electricity Prices → Configure opens a menu of four options, five once a supplier switch has been recorded this year:

  • Edit settings — walks the same chain of steps, pre-filled with the current values. Change supplier, contract, region, DSO, meter, DSO billing mode, ENTSO-E API key, capacity peak source, or solar parameters — anything. Moving to another supplier or region offers the contract's start date, tariff card month, end date and the box billing the year from the start date empty, since they described the contract you left; a supply that began on 1 January, which cannot be recorded as a switch, is set up this way with its own dates. A start date on the day of a supplier switch recorded this year stays, since it is when whichever contract followed the switch began, so correcting the supplier picked at the switch keeps it. The settlement question of Bolt's variable cards and Frank starts unticked too, as on a new setup, since your answer was an arrangement with the supplier you left. Picking another contract of the same supplier keeps the dates shown but asks the tariff card month again, empty, because the month shown was the old contract's and it wins over the start date: enter it again if the new contract was signed on the same card. The integration reloads automatically when you finish, picking the new tariff card on the next refresh. A sensor your new settings no longer create (the EV rate after unticking its box, the band sensors after leaving a bi-hourly meter, the capacity sensors after leaving Flanders or moving to an exclusive-night circuit) is removed rather than left unavailable; its history stays. Turning the setting back on creates it afresh, under its default entity ID, name and area, so a rename or area you gave it has to be set again.
  • Record a supplier switch — for a household that changed supplier during the year. Asks for the first day of the new contract, keeps your current settings as the contract you held until the day before, then walks the edit steps for the new one. See Switching supplier during the year.
  • Remove the last supplier switch — shown once a switch is recorded this year. Puts the entry back as it stood before the last switch was recorded, the contract you left becoming the current one again.
  • Compare another supplier (one-off quote) — a price quote against any supplier and contract, your own included (unless it is the social tariff or the expert custom supplier, which are never offered), with your region / DSO / peak settings held fixed for an apples-to-apples comparison. Static ↔ dynamic crossings are allowed: the flow prompts for an ENTSO-E API key when a side needs spot data (a dynamic or monthly-indexed contract, or an index-linked-injection target like the Cociter variable cards or energie.be Vast on the injection regime) and your current entry carries neither a key nor the keyless setting. The prompt offers the keyless box too, for that quote only. It is skippable: a quote is a one-off, so leaving it blank still shows you every other line of the comparison rather than stopping you on a page you may have no token for. Static contracts also let you override the meter type (mono, bi-hourly, dynamic or exclusive-night) so you can quote what if I were on bi-hourly billing under supplier X, and a target that can settle per quarter-hour (Bolt's variable cards, Frank) asks which settlement to quote it on. The result page lists per-kWh price now, a projected yearly bill computed from your measured rolling-year kWh (recorder data from the consumption sensor configured in the meters step, scaled up when the window is short, or a fallback volume when there is too little history), and a year-to-date what-if that re-prices your actual YTD kWh month by month on each supplier's archived cards (today's rate for a supplier that keeps no archive, or a spot-priced side whose day-ahead prices are missing) with pro-rated annual fees, plus unicode bar charts so the difference reads at a glance. Your own year-to-date row is the figure your current_year_cost sensor bills, on the day-ahead prices your entry holds: an ENTSO-E token typed, or the keyless box ticked, on this page prices the alternative only. The yearly bill is an indication of the order of magnitude and will not match your settlement: it holds today's tariffs for twelve months and assumes your past consumption repeats, neither of which is a forecast. It is meant for ranking one supplier against another, and both sides are quoted on the same volume so the comparison stays fair even where the absolute figure is off. Solar regimes are honoured: compensation nets consumption against injection, injection regime credits each supplier's own injection price against the bill. A solar step lets you quote the whole thing under a different regime ("what would I pay off the compensation regime?"): it moves both sides, drops or adds the Walloon prosumer fee accordingly, and prints your own contract priced both ways. Without an injection meter it asks for your gross yearly consumption and injection first, because a meter that runs backwards reports a netted figure that the injection tariff does not bill; the year-to-date rows are then left blank, since they replay meter history recorded under your configured regime. Submit closes the dialog without changing anything; nothing is saved.
  • Compare every contract of your kind (ranked) — a separate menu entry from the one-off quote above, and a different question. It prices every contract of your own kind sold in your region against your own settings and sorts them, cheapest first. Your own contract is in the table under a YOUR CONTRACT badge, priced from the card your entry already holds rather than re-fetched — so it keeps the signing rate and cohort splice you are actually billed on — and every other row states its gap against yours, signed, so a minus is money you would save. Same-kind on purpose: a fixed rate is a contracted price while a dynamic row is a projection of one year of spot prices onto the next, and sorting the two together puts the least certain number on top and calls it the cheapest. The one-off quote above is the place to cross that line, because it explains one pair at a time and has room to say why. Each row is priced on the index its own card names. Several Flemish cards settle on a monthly Belpex weighted by the residential load profile, and they do not all weight it the same way: Eneco averages the three regional curves, energie.be, Ecopower, Mega, Luminus and TotalEnergies weight every DSO column, Energy Knights, Trevion, EBEM and EnergyVision read the Flemish curve alone, and Bolt's variable cards read the Walloon curve in every region. Those are four different indices, about 2 €/MWh apart, and all four can sit in one table, so the ranking resolves each card against its own rather than against yours. The profiles come from Synergrid and are kept for the whole installation: the comparison pages use whatever any of your entries downloaded and never download one themselves, while the daily ranking below downloads them, at most once a month, for an entry with an ENTSO-E key or the keyless setting. A card priced without its profile says so: an energy index is then priced on the plain monthly mean and a feed-in one on the figure its card prints, the one-off quote's note names it, and the ranking tags the row NO PROFILE. Without either source no profile would help, since there is no day-ahead to weigh: every feed-in that settles on a month of the day-ahead price is credited at the figure its card prints, your own contract's too when its feed-in is one of them, as your sensors credit it, and the note and the ranking say so once. Your own contract reads only the entry's own source, as your sensors do: a key typed on the comparison page prices the others on the month and leaves yours at its printed figure, and the note then says that of your own contract alone. The sweep is bounded by a clock, not a timeout — a tariff card cannot be parsed halfway and abandoned — so it fetches the cheapest cards first, and the table is on screen while it fills, rows appearing and reordering as each card lands. A card that would not fit in the time left is skipped rather than started, so the page stops cleanly instead of freezing for most of a minute on one slow supplier; reopening finishes the rest from what it already downloaded. Measured on a Raspberry Pi on 2026-08-30, the Flanders static cell, the largest, priced about 15 rows in the first ten seconds, 34 in the first minute and 39 within the two-minute budget, the tail being Bolt and TotalEnergies at 13 to 45 seconds a card. Where two products share one tariff card the sweep reads it once and reuses the text, since the parse, not the download, is what the budget is spent on. Rows that could not be priced are shown, not dropped, saying whether the card was unreadable or the supplier unreachable, because a missing row reads as not competitive and that is the one thing it does not mean. Suppliers that publish nothing for your region and segment are not offered, and if your own contract is the only one of its kind where you live, the page says so instead of showing an empty table. A year-to-date column is offered as a second, slower pass: it fetches each past month's card, and a row prints a figure only when it replayed the same real archived months your own side did — a supplier that keeps no month-addressable card would otherwise reuse today's card for January and print a confident number that is up to 23% out. A contract whose feed-in tariff is indexed on the spot price likewise prints a figure only where the day-ahead history to credit it is on hand, since that credit is dropped whole rather than estimated. Nothing is saved. A tick box on the last setup step, off by default, runs the whole ranking once a day in the background instead of on demand. It publishes a Potential yearly saving sensor — the euro a year the cheapest alternative would save you, with the full ranking in its attributes — and the comparison page then opens on the stored answer instead of making you watch the sweep, with the table saying when it ran and a box to price it again on the spot. Off by default because it fetches tariff cards from suppliers you have no relationship with, which is a decision to take rather than one an update makes for you. It is cheap once running: fifteen of the eighteen suppliers publish a freshness check, including the two slowest cards, so a day on which nothing was republished costs a handful of conditional requests rather than the minutes of fetching a cold sweep costs, and tariff cards move about monthly. With a day-ahead source it also downloads the Synergrid load profile the cards' indices are weighted by, and on the injection tariff the solar one, at most once a month for the whole installation. A negative reading on the sensor is a real answer: nothing on the market beats what you already have. Unlike the dialog the scheduled run has no time budget and skips nothing, because the budget only exists to keep a progress bar honest, and a ranking stopped early would call the cheapest row that fitted the cheapest there is.

Switching supplier during the year

An entry prices one contract at a time, so a household that changes supplier during the year records the switch: Configure → Record a supplier switch, then the first day the new supplier supplied, as on its welcome letter or on the old supplier's final bill. Your current settings are kept as the contract you held until the day before, and the edit steps that follow set up the new one, starting from what you have now. From then on:

  • current_year_cost is the bill across both contracts: the old one priced on its own supplier's cards for its own days (its own archive, or the project's card archive, month by month, and a month neither holds on the card it publishes today, with that month's federal levies, as for your own contract), the new one from its first day. The previous_contracts attribute lists each earlier contract with its dates and cost, and previous_contracts_eur their total.
  • Under the Walloon compensation regime each contract nets its own injection, as the regulator rules for a switch during the year (CWaPE communication CD-14d03, section 5.1.2): a surplus banked before the switch does not offset consumption after it.
  • The earlier contracts are priced once a day in the background, because that means fetching the old supplier's cards, and again as soon as the billed capacity peak moves in Flanders, so the old contract's capacity term follows your peak as your current contract's does. A contract held on an analog meter is billed the flat 2,5 kW charge and the analog kWh rate instead, up to the day Fluvius replaced the meter where you entered one, and one on an exclusive-night circuit no capacity charge, as each was. An earlier contract that settled on the day-ahead is priced on it with the key it was set up with, while your current contract without a key or the keyless setting keeps the figures its card prints, as it would had it never had one. After you record a switch, current_year_cost reads unknown until that pricing lands, usually within minutes, rather than a year missing a whole contract. It stays unknown while an earlier contract cannot be priced at all, and is tried again every hour: a contract the integration can never price (a supplier it no longer knows, or a card that does not cover your network operator) keeps it unknown, and the log names the contract. A pricing made while Home Assistant's database did not answer, which reads as a contract that used nothing, is not kept: the last one stands, or the figure stays unknown when there is none, and it is tried again the next hour. A contract whose own cards cannot be had is priced on your current card instead, as the next point says.
  • current_month_cost includes the old contract's days in the month of the switch, the comparison pages price your contract the same way, and the statistics backfill prices each hour on the contract that supplied it.
  • A supplier that publishes its card as page images (Ecofix since August 2026) has its days priced on the card archive's reading of that card, as your own contract would be, and the row says so (card_read_by_ocr). When the old supplier's card cannot be had for the day, the last pricing on its own cards is kept. A supplier that can no longer be reached, and whose cards no archive kept, has its days priced on your current card, without the welcome credit that card offers new customers: the contract's row in previous_contracts says so (priced_on_current_card), and the statistics backfill imports those days the same way and says so. It is tried again every hour while the old supplier's site or the card archive does not answer, and once a day when no archive kept its cards.

Record a switch as soon as you can. Until then the new contract's days are priced as the old contract's, and recording it later corrects the figure, which the long-term statistics show as one step on the day you record it. A second switch in the same year is recorded the same way. Recording a switch unticks Bill the year-to-date from the contract start date; ticking it again leaves the earlier contracts out of the year. A contract you left that billed its year from its own start date keeps doing so: the days before it began stay out of the year, the contracts it left out included, so a later switch moves neither the figure nor its long-term statistics, and the comparison pages measure both sides from that day too. The new contract's start date cannot be set before the switch. A new contract that began on 1 January is not a switch during the year, since the contract you left supplied none of it: set it up with Edit settings, which offers its dates empty.

The switch step keeps your settings as they stand as the contract you left. If you already set the entry up for the new contract before recording the switch, which is the case for anyone who switched before version 0.28.1, do it in this order: Edit settings back to the contract you left, then Record a supplier switch with the first day of the new one, and pick the new contract again in the steps that follow.

Each earlier contract keeps the settings it had when the switch was recorded, the meter type, DSO billing mode, solar regime, direct debit answer and meter sensors included, since a switch is often when those change too. Three facts about the house are taken from the entry where the earlier contract's copy has none: the inverter's capacity in kVA (a copy holding 0 counts as none), the ENTSO-E API key or the keyless setting, and the connection's kVA tier. One you enter after recording the switch, for instance when a Repairs card asks for it, then reaches the months before it as well; one the earlier contract already held is kept. When the meter sensors changed at the switch, the comparison pages leave out the year to date of the contracts they quote, and the compare page says so: those are replayed on the entry's sensors, which hold nothing for the earlier contract's days. They do the same when the meter went from analog to digital at the switch, or back: a quoted contract would bill the earlier days on today's meter, where your own figure bills them on the one they ran on. A replacement day entered when unticking Analog meter is no such case: the quoted contracts bill the days before it as analog too.

The meter checks run on each earlier contract's own sensors over its own days, and on the entry's sensors from the switch on, so a register rewired at the switch is not named for the months before it. A sensor of an earlier contract that recorded nothing, or stopped while its pair or the other side carried on, is named on the "stopped recording" Repairs card with the supplier and dates of the contract it belongs to; one whose history in Home Assistant only begins after that contract ended is not, since no sensor holds those days. Renaming a sensor moves its history to the new name and leaves the old one empty. To point an earlier contract at another sensor, remove the switch, correct the Energy meters step and record the switch again.

A switch recorded with the wrong date, or by mistake, is undone with Configure → Remove the last supplier switch, which puts the entry back as it stood before that switch was recorded: the contract you left becomes the current one again, with the settings it had then, and anything changed since is undone, except two answers that belong to the meter: the Flemish day hours, which every contract reads from the entry, and the answer to a question added after the switch was recorded (whether the meter counts draw and injection apart), which is kept. To correct the date, remove the switch, record it again with the right date and pick the new contract again. A switch recorded in an earlier year is not offered: it no longer prices anything, and removing it would bring back a contract you left before the year began.

Daily operation

Refresh cadence

  • Supplier snapshot — the coordinator runs a cheap probe() every hour and only re-fetches the full PDF when the probe key changes (see How often the integration polls above). Suppliers without a probe (energie.be, Engie, Luminus, the social tariff), and products whose probe has no key to give (Mega's professional cards, EnergyVision's Brussels cards), fall back to a 24 h time-based TTL, shortened to an hour once the card's own validity has run out. Multiple entries pointing at the same (supplier, contract, region) tuple share their fetched snapshot through an in-memory cache, so the same PDF is never polled twice.
  • Spot prices (dynamic and monthly-indexed contracts) — fetched from ENTSO-E at hourly resolution, or at the native 15-minute resolution for suppliers that bill per quarter-hour. Tomorrow's curve is picked up on the first tick after publication, around 13:00 CET. Past hours are backfilled lazily into a per-entry cache on the grid the contract settles on, so current_year_cost replays each one at its actual rate without refetching the same window every tick; a window neither source could answer is retried three hours later rather than on the next tick, and the missing hours forfeit only their energy term until it fills. See docs/data-sources.md.
  • Monthly capacity peak (Flanders, not on an exclusive-night circuit or an analog meter, which measure nothing) — tracked continuously, resets on the 1st of each local month.
  • current_year_cost — recomputed every coordinator tick from HA's recorder; no in-process counters that could drift across restarts. Past days come from the recorder's long-term statistics: daily statistics on the static per-day path (where a bi-hourly totals meter recovers its day/night split from that period's hourly statistics), hourly statistics for the contracts billed hour by hour (TOU, dynamic, monthly-indexed, Impact, exclusive-night). Today is read live off the meter instead, its cumulative reading now minus the reading at local midnight, so the figure keeps moving even when statistics compilation lags. Per-month tariff cards live in a cache keyed by (supplier, contract, region, YYYY-MM), kept in the entry's store across restarts for the calendar year so far plus the signing month, and looked up once per month the year-to-date walk or a short month's area search touches. Annual fees are pro-rated to the elapsed fraction of the year, so on Jan 1 the sensor sits at ~0 and grows day by day instead of jumping to the full annual upfront.
  • Startup reads no meter. Home Assistant waits on each integration's first refresh, with 300 s for all of them together, and the meter reads behind the costs each cover a year of hours, which on a database on a NAS can take minutes. So after a restart the cost and volume sensors show what they showed before it, and the refresh that reads the meters runs right after startup instead of inside it. A figure for a month or a year that has ended since, or after a setting was edited, reads unknown until then, as every cost does on a new entry. The yearly volume and the meter check behind the register Repairs card are read once a day and kept across restarts; a database too busy to answer leaves the day's figures as they were and is asked again on the next refresh. Every refresh reads each meter once, where it used to ask for each figure's window apart, and an ordinary one reads only the days since the day before yesterday and keeps the rest from earlier refreshes. The whole year is read again on the first refresh of each day, after a restart, when a read fails, when those last days hold no statistics and when the statistics no longer join the kept ones. An older hour corrected, or a gap filled in later, shows at the next day's full read: until then the totals are right, but the energy of a filled gap sits on the hour after it.

Failure mode

If a refresh fails, the coordinator keeps serving the last known snapshot and exposes snapshot_age_hours, snapshot_stale and last_error as attributes on sensor.<...>_current_price. last_error always names the failing exception, so a CDN timeout reads network error fetching <url>: TimeoutError rather than trailing off after the colon. Twenty repair issues surface under Settings → System → Repairs so problems are visible without inspecting attributes; the fetch-related ones auto-clear on the next successful refresh:

  • snapshot_stale_<entry> — the card in use was last refreshed more than 7 days ago, or is more than 7 days past its validity (the date it states, or the end of the month its title names), which is what a supplier still serving an old month's card looks like. While it is stale the project's card archive is asked for its newest card, this month's first and back up to a year, and that card is priced on if it is a later one. The card offers to fetch the card again: submitting it refreshes the entry at once, and the card closes only if that cleared it. Not raised once the supplier has left the market (its deprecated_until has passed): the final card stays stale for good, the deprecation notice below already says so, and the entry stops asking the supplier for a card that is gone. Nor on a product its supplier withdrew, for the same reason: its last card never moves again, and the withdrawn-product notice says so.

  • extractor_failed_<entry> — the supplier extractor could not parse the tariff card (typically a layout drift on the supplier's PDF/HTML). Raised on the first failure, since a parse error will not self-heal; cached prices keep serving, including a card stored by an earlier version that an upgrade would otherwise drop. With no card at all, the newest card the project's card archive holds stands in, and only with the archive box unticked or nothing there do the sensors stay unavailable.

  • extractor_card_missing_<entry> — the supplier's site answered that there is no tariff card at the address the integration reads (HTTP 404 or 410, or a web page where the card should be): the card for the month is not out yet, the product was withdrawn, or the supplier moved its cards. Raised on the first failure instead of extractor_failed, since nothing on the card changed; cached prices keep serving, and with no card at all the archive's newest card stands in as for extractor_failed. Worth an issue only if it lasts while the supplier still offers the product.

  • extractor_unreachable_<entry> — the tariff card could not be downloaded (network timeout, reset, a transient server error, or the supplier's own file store refusing the download). Raised only after two consecutive failed refreshes, since a single CDN hiccup usually clears on the next tick; cached prices keep serving, and with no card at all the archive's newest card stands in as for extractor_failed.

  • entsoe_auth_failed_<entry> (dynamic and monthly-indexed contracts, and a contract whose feed-in credit alone follows the market) — ENTSO-E returned 401 for the configured API key. Edit the entry's options and replace the key with a fresh token from transparency.entsoe.eu.

  • supplier_deprecated_<entry> — the supplier is leaving, or has left, the residential market, and the card names the successor and the transfer date (DATS 24 → EnergyVision on 2026-08-31). Before that date prices stay correct until the supplier stops publishing its card. After it the card says the transfer has happened and that the entry keeps showing the last prices it read, which are no longer what you are billed. Once the successor supplies you, record it with Record a supplier switch, dated the first day it supplied you, so the months before stay priced on the old supplier's cards; Edit settings would price the whole year on the successor's, and is the right choice only when that day was 1 January of this year or earlier. Unlike the ones above, this one is not a failure and does not clear on a refresh — it clears when the entry points at a supplier that is still selling. The successor is only named when this integration can actually price it in your region; otherwise the card says the entry will stop updating and asks you to check the letter your supplier sends. Naming the successor is not a promise that your product is in its list: a withdrawal announces a supplier, not the product each customer is moved to, and DATS 24's Flemish customers were moved to one EnergyVision publishes no card for. So the card tells you to pick the product named on your letter, and what to do when it is not there: the Expert: custom formula supplier prices it from your own card in the meantime.

  • contract_withdrawn_<entry> — the supplier has stopped selling the product the entry is on (OCTA+ Fixed, Fixed Impact, Eco Fixed, Flux and Eco Flux, and Trevion LifePowr, since 2026-10-01). The entry keeps pricing on the last card the supplier published for it, which is right for as long as your contract runs on those terms, but that card never changes again, so nothing else would tell it apart from prices that simply did not move. Once the supplier takes that card down, the entry keeps the copy it holds, or reads the project's card archive for the last month the product was sold, and raises no card of its own about the missing file. Only when none of those is at hand (nothing stored, and the archive box unticked or the archive out of reach) do the sensors go unavailable, and extractor_card_missing then says why until a card is found. If your supplier moves you to another product, record it with Record a supplier switch, dated the first day of the new product. The product is no longer offered when setting up an entry or as a comparison target. Not a failure: it clears when the entry points at a product still on sale.

  • extractor_unreadable_<entry> — the card downloaded fine but its pages carry no text layer, so no parser change here can read it (Ecofix since the August 2026 card). Cached prices keep serving, and it clears by itself the moment the supplier publishes a readable card, or once the project's card archive files its daily reading of this one while the archive box is on.

  • extractor_unreadable_no_prices_<entry> — the same unreadable card on an entry with no cached one to stand in: a brand-new entry, or one whose cache predates the card-as-parsed change. Every sensor on it reads unavailable until the supplier publishes a readable card, or until the project's card archive files its daily reading of this one, which the entry reads while the archive box is on, so the card points at the Expert: custom formula supplier rather than warning about drift.

  • card_read_by_ocr_<entry> — the same unreadable card, being priced anyway off the reading this project's daily card archive makes of its pixels. Not a failure and not a drift warning: the figures are the ones the engine read whole or did not read at all. It says so because a price read off a picture of a card is worth checking against your own, and it clears by itself the moment the supplier publishes a readable card.

  • exclusive_night_rate_missing_<entry> — the entry is on an exclusive-night meter but the supplier's DSO table prints neither an exclusive-night nor an off-peak distribution rate, so the night circuit is billed at the day distribution rate (TotalEnergies' Flemish cards).

  • impact_rates_missing_<entry> — the entry is on the Walloon Tarif Impact distribution mode but the card omits the CWaPE PIC / MEDIUM / ECO bands, so it falls back to the bi-hourly split.

  • connection_fee_missing_<entry> — a Walloon card stopped printing the connection fee, so that term is left out of the bill rather than guessed.

  • prosumer_tariff_missing_<entry> — the entry is a Walloon compensation install but its card omits the DSO prosumer tariff, so only the supplier's own PV forfait is billed and the network half is left out rather than borrowed from another supplier's card.

  • compensation_kva_missing_<entry> — the entry is on the Walloon compensation regime with an inverter capacity of 0, so no prosumer fee is billed at all. The solar step refuses that now; an entry saved before it gets this notice until the capacity is filled in. With a double-flow or communicating meter CWaPE bills distribution and transport on the gross draws instead, capped at the per-kVA fee plus the network charges on the net draws; the integration bills the lower of the two once the solar step says the meter counts draw and injection apart, and the cap needs the capacity too.

  • direct_debit_unanswered_<entry> — the card prices a direct-debit payer differently and this entry has no stored answer, which an entry created before the question existed does not. The difference is left out rather than guessed, so answer it in the options to have it billed. On a card whose direct-debit part is a welcome credit it is raised only while that credit can still reach this year's bill: it needs the contract start date and a card of the month you signed whose credit depends on direct debit, since that card's credit is the one billed, and it stays quiet once the first year's credit was paid out before 1 January (or before the day the year is billed from), on the wait that card states. A reduction off the standing charge raises it every year.

  • brussels_power_term_missing_<entry> — the card prints only the metering half of Sibelga's fixed charge and Brugel's sheet, which supplies the power half, could not be read. About 50 EUR a year is left out rather than guessed; the sheet is retried every six hours. While Brugel has not published a year's final sheet, the power half is taken from its 2025-2027 tariff grid, which gives its later years, 2026 and 2027, as indicative figures, and the same card says so in its own wording until the final sheet is read.

  • excise_law_unread_<entry> — a social tariff entry whose protected customer's excise is not known yet, because the law has not been read from Justel (unreachable on the very first start). Leaving the excise out or billing the household rate would both be wrong, so the sensors stay unavailable until it is read; the law is retried every six hours and the card clears by itself.

  • recorder_missing_<entry> — Home Assistant runs without its recorder, which keeps the meters' history every cost is priced from. The cost sensors stay unknown rather than show a year priced at fees alone, the price sensors are not affected, and no backfill runs; the card clears once the recorder runs.

  • register_pair_incomplete_<entry> — one register of a day/night pair records nothing, or stopped while its twin carries on (a rename, an integration swap, a meter replacement). The pair is billed only on the days both registers report, so the running cost reads low until it is rewired (high on an injection pair, whose missing days lose only their feed-in); the card names the sensor. A register that started long after its twin, as one rewired after a replacement does, is named too: the pair bills only from its first day, so the card stays until the pair covers most of the year, or a totals sensor wired on that side bills it. The card is read over the same window as the bill. When the totals sensor bills that side in full the card still names the broken register, on a wording that says so and that the cost is unaffected. The same card names a consumption or injection meter that stopped while the other side carries on, and of a day/night pair only the register that stopped. The days a consumption meter misses are billed on neither side, rather than as a household that used nothing while its feed-in was credited, so the cost reads low; the days an injection meter misses are billed on their consumption without the feed-in, so it reads high. An injection meter that recorded nothing at all is named too, and so is a consumption meter that compiles no statistics while its state still reads, whatever the solar regime: the year is then billed on today's reading alone.

    The first four of those eight are not failures either: each clears when the supplier prints the missing row again, the kVA one as soon as the inverter capacity is filled in, the direct-debit one as soon as the question is answered, the Brussels one as soon as Brugel's sheet can be read, and the register one as soon as both halves report again.

be_electricity_prices.refresh service

Drops the cached supplier snapshot, the month cards the year-to-date is billed on, and today's and tomorrow's ENTSO-E prices for every loaded entry, or only the one entry_id names, then re-fetches them immediately. Handy after a tariff card update or to clear a transient fetch error without waiting for the next hourly tick.

Field Default Meaning
entry_id every entry Refresh only this config entry. An id that matches no loaded entry is refused with an error.
clear_history false Also discard the cached past hourly prices that current_year_cost replays, and re-fetch them. Off by default because it re-fetches every day since 1 January (or the contract start, if you bill from it) against a rate-limited endpoint. Only those days come back: the days before them, which projected_year_cost and the comparison pages credit a per-slot feed-in on, are not fetched again, so that credit is left out until a full year of day-ahead is held again.

Without clear_history the past day-ahead cache is left alone, which is worth knowing when a year-to-date figure looks wrong: an ordinary refresh will not correct a wrong hourly price in it. Little else repairs that cache either, since a cached day already holding at least 20 of its 24 hours is never re-fetched however wrong the values are (a restart only discards a price off the scale entirely, below -1 or above 5 EUR/kWh), so this flag is the only way to correct one short of deleting and re-adding the entry.

be_electricity_prices.cheapest_window / most_expensive_window services

Return the cheapest (or most expensive) contiguous N-hour window in the upcoming price table. Both services share the same fields:

Field Default Description
duration_hours required Window length in hours (1-48), rounded to the nearest whole hour, a half rounding up. On a 15-minute contract (Cociter / EBEM / Ecofix / Ecopower Dynamische Burgerstroom / energie.be / Energy Knights Agilior Online, Optima Online and their Green twins / EnergyVision / Engie / OCTA+ / Trevion, Bolt or Frank Energie with the quarter-hour box ticked, and the expert custom supplier with its Bill per quarter-hour box ticked) the window aligns to quarter-hour boundaries.
entry_id first loaded Optional config entry to target.
earliest_start now Don't consider windows starting before this time. The time is truncated down to its price slot (the hour, or the quarter on a 15-minute contract), so 14:30 still considers the 14:00 slot on an hourly contract.
latest_end end of the cached table Don't consider windows ending after this time.

Response shape:

start: "2026-04-30T03:00:00+02:00"
end:   "2026-04-30T06:00:00+02:00"
duration_hours: 3
resolution: "PT60M"
average_eur_per_kwh: 0.184372
hours:
  - hour: "2026-04-30T03:00:00+02:00"
    all_in: 0.18012
  - hour: "2026-04-30T04:00:00+02:00"
    all_in: 0.18391
  - hour: "2026-04-30T05:00:00+02:00"
    all_in: 0.18908

On the last day a monthly card covers, the table already holds tomorrow's hours (a dynamic contract's from ENTSO-E's curve, any other's forward-filled), and a window reaching past midnight is priced on the current card, although tomorrow_prices_available is off and the tomorrow_* sensors are empty. On a spot-priced contract a new positive factor or base on the energy leg moves every hour alike, so the window it picks holds; the prices it returns may not, once next month's card is out.

Example automation that starts EV charging at the cheapest 4 h block of the night. Its condition is off on the last day of a card that stops at month-end, so it skips that night; drop the condition to charge anyway on the current card's figures:

trigger:
  - platform: time
    at: "13:30:00"  # ENTSO-E next-day curve is published around 13:00 CET
condition:
  - condition: state
    entity_id: binary_sensor.<your_entry>_tomorrow_prices_available
    state: "on"
action:
  - service: be_electricity_prices.cheapest_window
    data:
      duration_hours: 4
      earliest_start: "{{ today_at('22:00') }}"
      latest_end: "{{ (today_at('06:00') + timedelta(days=1)) }}"
    response_variable: window
  - service: switch.turn_on
    target:
      entity_id: switch.ev_charger
    # Schedule the rest of the automation at window.start.

be_electricity_prices.backfill_statistics service

Populates the recorder's long-term statistics for this entry's price sensors (current_price, energy_component, network_component, taxes_component, plus injection_price for injection-regime users) and the current_year_cost running bill. The Energy dashboard and the Statistics graph card then show price + cost history that predates the entry's first live update tick. On an entry that recorded a supplier switch, each hour is priced on the contract that supplied it, and the running bill carries on across the switch.

The integration runs the backfill by itself on first install (or after a database reset) covering the year-to-date window (Jan 1 of the current local year, or the contract start date if the entry, or a contract kept by a recorded supplier switch, bills from there) through "now", and leaves it alone on later restarts; the service is for re-runs after fixing a tariff card or to redo a narrower window:

Field Default Description
entry_id first loaded Optional config entry to target.
start Start of the year-to-date window (Jan 1 00:00 local, or the contract start date if the entry, or a contract kept by a recorded supplier switch, bills from there) First hour to backfill. The price sensors are written from this hour. current_year_cost resets at that window start, so it is backfilled only for the end year, accumulated from that Jan 1 — a mid-year start still carries the correct year-to-date total, and a multi-year range backfills only the current year's running cost (avoiding a spurious negative jump at the year boundary).
end current hour First hour NOT to backfill (exclusive); the in-progress hour is left to the live coordinator. Set it on or before the start of the year-to-date window (1 January of the current year, or the contract start date if the entry, or a contract kept by a recorded supplier switch, bills from there) and only the price sensors are rebuilt: a cost series ending there would sit immediately before the current one, and the recorder ignores last_reset on imported statistics, so the join would show roughly minus one annual bill on the Energy dashboard. The response then carries a skipped note saying so.
clear false Destructive. Wipes each target statistic series in full, not just the requested range (the cost series only when the window starts exactly at the year-to-date start; otherwise it is overwritten, not wiped), while the re-import only repopulates [start, end) — for the price series, anything outside the window is gone. Use it to re-run the whole default window (the start of the year-to-date window → now). To redo a narrower window after fixing a tariff card, leave it off: the re-import upserts on (statistic_id, hour) and already overwrites exactly those hours. With clear on, a window that starts after the end year's year-to-date start (1 January, or the contract start date if the entry, or a contract kept by a recorded supplier switch, bills from there) is refused, unless it ends on or before the current year-to-date start, where the cost series is neither wiped nor re-imported.

Re-runs without clear are idempotent (rows are upserted by (statistic_id, hour)). For dynamic suppliers the service reuses the coordinator's ENTSO-E historical-spot cache, so a year-wide backfill on a fresh install can take tens of seconds while the spots land. The backfill lets Home Assistant carry on between each day of hours, so the rest of the system stays responsive while it runs. Response is a {rows_written, sensors, range} object you can inspect from Developer Tools → Services. When the cost series could not price every hour it also carries energy_hours_unpriced (hours whose energy had no day-ahead price, or no network row on that month's card) and injection_hours_uncredited (exported hours whose spot-indexed feed-in credit had no price), and the same is logged as a warning. Unlike the live sensor, the imported rows keep that gap until you run the backfill again once the prices are cached.

The backfill ends up with what the live sensor shows. The days of an earlier contract (after a recorded supplier switch) whose cards no archive kept are billed on today's card, as the live sensor bills them; the response lists them under stand_in and the log names them in a warning. A card read that failed just then (a timeout, or GitHub briefly down) is different: the live sensor bills those days on their own card again as soon as it can be read, so the backfill leaves them out of the price sensors rather than write a stand-in, and lists them under retry. That covers a past month the archive could not serve, every month of a contract whose signing-month card could not be read, an earlier contract whose supplier's site did not answer, and a past month whose card lacks your network operator while a nearer card of the same year, which may print it, could not be read. The automatic backfill runs again an hour later, then less often, up to once a day, until those days are written, and a restart does not lose it: the next start carries on. After a service call, call it again once those cards can be read. The running cost is written whole on every run, and the automatic run that fills those days in rewrites it from the start of the year, so a card or setting changed in between moves the whole total rather than one hour of it. A recorder that does not answer, a busy database at startup for instance, is never taken for an empty one: the automatic backfill waits and asks again rather than start, and the running cost is not written from meter or statistics reads that failed (the service result says so under skipped).

States history (the per-entity timeline shown in the History view) is append-only by design and is not affected; only the long-term statistics tables are written.

Diagnostics

Settings → Devices & services → Belgian Electricity Prices → three-dot menu → Download diagnostics dumps the active config (with the ENTSO-E API key and the entity ids of your meter and capacity peak sensors redacted), the snapshot metadata, and the full hourly breakdown for today + tomorrow. It also summarises the replayed day-ahead cache per delivery month (hour count, mean, min and max), the archived card labels used for past months, and the shared-fetch failure marker when the integration has been backing off. For each side it gives both the raw kWh of every wired meter sensor and what the bill actually reads: the sensors it is billed off, named by the setting that holds them (billed_from, which shows a totals sensor standing in for a day/night pair), the kWh the last bill priced off them (billed_ytd_kwh, without a month it left out) and whether that side reports once a day and was spread over the hours (read_once_a_day), plus silent_meter when one side stopped recording under the other. The year-to-date cost split into its capacity, prosumer and standing-charge legs is not in the dump: it lives on the current_year_cost sensor's own attributes. Attach it when reporting an issue.

When the year-to-date looks too low

A current_year_cost well under your real bill is almost always the kWh side rather than the tariff side, and the integration says so in the log rather than quietly billing what it was given. Five messages are worth searching for:

  • "negative change" — Home Assistant restarts the running total behind a sensor after an outage longer than purge_keep_days, and the restart lands as one large negative hour. Adding that up cancels real energy elsewhere in the year: a meter that moved 4687 kWh was billed for 942. Those hours are now ignored and the sensor is named; on a consumption sensor the feed-in of that hour or day is left out too, rather than credited against no consumption. The rest of the year corrects itself on the next refresh, since the year is recomputed from scratch each time, but the period around the restart still reads low until its statistics are rebuilt.

  • "returned no statistics" — one half of a day/night register pair is wired but produces nothing, so the pair cannot be billed and the running cost falls to the fixed fees, unless a totals sensor is wired on the same side, which is then billed instead. A sensor with device_class: energy but no state_class compiles no long-term statistics at all, and state_class: measurement compiles no running total, which is what the bill reads; both look perfectly normal in the UI. A lone consumption sensor of that kind, or a pair whose registers both are, bills the year on today's live reading alone, and the register Repairs card names it.

  • "has diverged" — both halves of the pair report, but not on the same days: one stopped (a rename, an integration swap, a meter replacement) or started late. Only the days both report are billed, feed-in included, and days_seen (or hours_seen) says how many, rather than the surviving band being billed alone as though the other used nothing. A totals sensor wired on the same side is billed instead, since it covers both bands on every day, but only when it reports at least as many days as the pair still bills and its kWh over the days both report are at least 90% of the pair's: one that records fewer days, nothing at all, or rows of zero because it is frozen at its last reading, would bill the year short. The same totals sensor also takes over from a pair that reports every day but bills under 90% of it, which is a register frozen at its last reading.

    Both warnings are logged once a day, over the same window as the year-to-date bill (1 January, or your contract start date, to today; after a recorded supplier switch, from the switch date), and say what that bill does: the days both registers report, or the totals sensor it reads instead.

  • "accumulated before the window" — the first hour or day of the window carried energy from before it, which happens when the run-up to 1 January (or to your contract start date) is missing from the recorder. That bucket is left out rather than billed, so the year no longer over-bills, but its own energy is lost with it.

  • "dropped from the year-to-date figure" — a past month's archived card has no row for your network operator and neither has any other card of the same calendar year, or (on a fixed or variable contract) was a dynamic or time-of-use card with no flat rate a day can be billed at. That month is left out of the bill whole, network and taxes included, and its days and kWh are left out of days_priced (or hours_priced) and consumption_ytd_kwh with it. A past card whose network table lists other operators but not yours is otherwise billed on its own prices with your operator's network figures from the nearest card of the same calendar year that prints them; a card with no network table read at all is not, and its month stays out. Never from another year's card: network tariffs change on 1 January, so it would bill the month on a network it was never charged. While a nearer card cannot be read yet, the month stays out until it can. The running month is part of this both ways: its card, when a supplier's reader keeps it short, takes the area from an earlier card of the year, so the month so far is billed in full while the price of the current hour still comes from the live card as published; and it can lend its area to an earlier month, which the first update after a restart then leaves out until the cards are fetched again a moment later.

If the consumption meter stops (renamed, replaced, or a sensor with no state_class) while the injection meter carries on, the days after it stopped are left out of both sides and the Repairs card above names it. So are the days before its first reading, when it was renamed or replaced during the year: their feed-in is not credited against no consumption. A wired consumption meter that has never recorded anything bills nothing at all. If the injection meter stops instead, or records nothing at all, the consumption is still billed and only the feed-in is left out, which makes the year read high rather than low; the card names that meter too. An injection meter that starts later, because the panels came later in the year, is expected, cuts nothing and is not named, and its feed-in is credited from the day its statistics start. Without a solar regime the injection meters are not read at all, so one that is broken or stopped changes nothing and raises no Repairs card: wiring them only for the Energy dashboard is fine.

If none of those appear, check the coverage attributes on the sensor: hours_seen against hours_elapsed on an hourly-billed contract (TOU, dynamic, monthly-indexed, Impact DSO mode or an exclusive-night meter), or days_seen against days_elapsed on a fixed or variable one, for what the meter recorded. hours_priced (or days_priced) below hours_seen (or days_seen) means part of what was recorded is not fully on the bill: an hour with no cached spot price is billed without its energy term, and a month dropped as above is not billed at all.

On the injection regime, a feed-in credit that follows the hour's spot price (Bolt fixed, Bolt variable billed per quarter-hour, Cociter Variable and trihoraire) cannot be credited for an hour the day-ahead cache holds no price for. Those hours are credited nothing rather than a guess, and injection_hours_uncredited appears and counts them, so the bill reads high by their feed-in. Past hours are only fetched with an ENTSO-E API key or the keyless setting, which is what these contracts need.

Dashboard cards

The current_price sensor already carries the whole price curve in its today and tomorrow attributes, so a price graph needs no second integration (EPEX Spot, Nordpool) alongside this one, and it plots your own all-in rate rather than the raw wholesale spot.

The built-in history and statistics cards cannot draw it: they only render the past, while half of this curve is in the future, and the today / tomorrow arrays are deliberately kept out of the recorder so they never bloat the database. Use the ApexCharts Card (HACS, frontend) and build the series from the live attributes with data_generator, which bypasses history entirely:

type: custom:apexcharts-card
header:
  show: true
  title: Electricity Prices
graph_span: 2d
span:
  start: day
now:
  show: true
  label: Now
update_interval: 1min
yaxis:
  - decimals: 1
series:
  - entity: sensor.YOUR_ENTRY_current_price
    name: All-in price
    type: column
    unit: c€/kWh
    float_precision: 2
    data_generator: |
      const rows = [...(entity.attributes.today || []),
                    ...(entity.attributes.tomorrow || [])];
      return rows.map(r => [new Date(r.start).getTime(), r.all_in * 100]);

Replace sensor.YOUR_ENTRY_current_price with your own entity id (Developer Tools → States, search current_price). The * 100 converts EUR/kWh to c€/kWh; drop it to plot EUR/kWh.

update_interval is what keeps the Now marker honest. ApexCharts only redraws when the sensor writes a new state, which here happens at each slot boundary — once an hour on an hourly contract, every 15 minutes on a quarter-hourly one — so without it the marker drifts up to a full slot behind the clock. It costs nothing because data_generator reads the attributes directly and never queries the database.

Three notes on the tomorrow half of the chart:

  • On a dynamic contract it stays empty until the day-ahead curve publishes, around 13:00 CET. The tomorrow_prices_available binary sensor on the same device says when it has arrived.
  • On a fixed or variable contract the curve is flat by design, so the chart is a row of equal bars, except on a bi-hourly or smart (dynamic) meter or a time-of-use contract, where the day/night step shows, and under the Walloon Tarif Impact distribution mode, where the PIC / MEDIUM / ECO bands show as a three-level step on any meter.
  • On the last day a monthly card covers it stays empty, on every contract still on sale, until the next month's card is out, exactly when tomorrow_prices_available is off: the rates it would show are not published yet.

Each row also carries the energy, network and taxes components, so stacking them shows where the money actually goes — something a spot price alone cannot tell you. Set stacked: true and give each component its own series:

type: custom:apexcharts-card
header:
  show: true
  title: Electricity Prices
graph_span: 2d
span:
  start: day
stacked: true
now:
  show: true
  label: Now
update_interval: 1min
series:
  - entity: sensor.YOUR_ENTRY_current_price
    name: Energy
    type: column
    unit: c€/kWh
    data_generator: |
      return [...(entity.attributes.today || []),
              ...(entity.attributes.tomorrow || [])]
        .map(r => [new Date(r.start).getTime(), r.energy * 100]);
  - entity: sensor.YOUR_ENTRY_current_price
    name: Network
    type: column
    unit: c€/kWh
    data_generator: |
      return [...(entity.attributes.today || []),
              ...(entity.attributes.tomorrow || [])]
        .map(r => [new Date(r.start).getTime(), r.network * 100]);
  - entity: sensor.YOUR_ENTRY_current_price
    name: Taxes
    type: column
    unit: c€/kWh
    data_generator: |
      return [...(entity.attributes.today || []),
              ...(entity.attributes.tomorrow || [])]
        .map(r => [new Date(r.start).getTime(), r.taxes * 100]);

On the injection regime the injection_price sensor exposes the same today / tomorrow shape, with an injection key instead of all_in, so the same card draws the injection curve after swapping the entity and the field:

type: custom:apexcharts-card
header:
  show: true
  title: Injection Price
graph_span: 2d
span:
  start: day
now:
  show: true
  label: Now
update_interval: 1min
yaxis:
  - decimals: 1
series:
  - entity: sensor.YOUR_ENTRY_injection_price
    name: Injection price
    type: column
    unit: c€/kWh
    float_precision: 2
    data_generator: |
      const rows = [...(entity.attributes.today || []),
                    ...(entity.attributes.tomorrow || [])];
      return rows.map(r => [new Date(r.start).getTime(), r.injection * 100]);

The bars can dip below zero at low spot, where you pay to inject. The curve moves during the day on every dynamic contract, both Cociter variable cards, every Bolt fixed card, a Bolt variable card billed per quarter-hour, Engie Empower Flextime, and Trevion Groene Energie Vast, or a Bolt variable card billed on its variable settlement in Flanders or Brussels, on a bi-hourly or dynamic meter; on a flat, fixed or monthly-indexed feed-in every bar shows the same credit, the one the sensor reads. The chart stays empty only on a card that prints no feed-in price.

Exclusive-night meter circuit

Belgian households with an electric water heater or night-storage heater often have a separate exclusive-night meter circuit billed at the supplier's published exclusive_night rate. Configure it as a second config entry:

  1. Add a new Belgian Electricity Prices entry alongside your primary one.
  2. On the meter step, pick Exclusive-night circuit (separate meter).
  3. On the energy meters step, point Total consumption (kWh) at the kWh sensor wired to the exclusive-night circuit.

Energy is billed at the supplier's exclusive_night rate; distribution uses the DSO's published exclusive-night rate when the supplier's card prints it (Aspiravi, Bolt, Cociter, DATS 24, EBEM, Ecofix, Ecopower, Elegant, Eneco, energie.be, Energy Knights, EnergyVision, Engie, Frank, Luminus, Mega, OCTA+, the social tariff, Trevion, and TotalEnergies in Wallonia and Brussels), falling back to the DSO's off-peak rate where it does not, and finally to the single day rate on a card that publishes neither. TotalEnergies' Flemish cards are that last case: such an entry raises an exclusive_night_rate_missing repair saying the night circuit is being billed at the day distribution rate, since no figure can be substituted for a column the supplier does not print. The supplier's own exclusive-night energy rate still applies; it is only the network leg that cannot be resolved. The primary entry keeps your day-circuit consumption on mono / bi / dynamic, and YTD works normally on both entries.

The capacity tariff is billed on the primary entry only. Fluvius bills a household with an exclusive-night meter one monthly peak: not the sum of the two meters' own peaks, but the highest sum of their quarter-hour peaks in the same quarter-hour. So the night entry asks for no peak sensor and bills no capacity_cost, and the primary entry's peak source stands for the whole household: a sensor adding both meters' quarter-hour demand gives the figure Fluvius bills, while the main meter's own Maximum demand current month leaves out whatever the night circuit drew in that quarter-hour. A household whose exclusive-night meter is still analog keeps its analog meters until 2028 (Fluvius defers the digital meter for it) and pays the capacity tariff as one flat amount, the minimum contribution: tick Analog meter on both entries, and the primary one bills that flat charge while the night entry bills the analog kWh rate and no capacity at all.

Development

Architecture and internals are documented for contributors under docs/: a module map and end-to-end data flow, the coordinator refresh lifecycle, the pricing model, the config and options flow, the ENTSO-E and backfill data sources, the provider framework, and one reference page per supplier that publishes a card. Start with docs/README.md.

pip install -r requirements-dev.txt
ruff check .
ruff format --check .
mypy --strict custom_components/be_electricity_prices
mypy custom_components/ tests/ scripts/
python scripts/doc_ref_check.py
pytest tests/ -n auto --dist loadfile
python scripts/live_check.py    # hits real supplier endpoints
python scripts/archive_cards.py --out tmp/archive   # stores today's cards there

scripts/gate.sh runs the lint, type, doc-reference and test checks above at once (not the live check or the archive walk, so it contacts no supplier), plus actionlint when tmp/actionlint exists, against a throwaway worktree of HEAD, so commit first. It refuses to start unless pip, reading requirements-dev.txt in a dry run that looks nothing up, finds nothing to install in .venv, and prints what pip found; a URL or editable line there would still be fetched. When GATE_REMOTE names a host that answers over ssh (it needs a venv at ~/be_gate/.venv), pytest runs there on the same snapshot while the rest runs locally: about 6 minutes with an eight-thread Mac, against 13 on a Raspberry Pi alone. Unset, everything runs locally. Pass a test file (scripts/gate.sh tests/test_ebem.py) for a quick pass.

Tests run against fixture PDFs and HTML snippets in tests/fixtures/ (real supplier cards from June 2025 on, and social tariff cards back to October 2022, one or more per card-publishing supplier — the expert custom supplier has no card — plus tiny HTML snippets under tests/fixtures/discover/ for catalog-discovery tests). Refresh a current-month fixture with the supplier's current PDF to re-run against new data; the dated archive fixtures are pinned to their month on purpose to guard the archive parsers, and must not be refreshed.

A daily GitHub Actions workflow (.github/workflows/live_check.yml) runs three phases against the live supplier endpoints, plus cross-checks that file their own threads, taking the text of any card the cards repository already holds from there so only a card that changed since the morning's archive walk is rendered again:

  • Extractor phase — every (contract, region) tuple is fetched and parsed; each fetch is tried up to three times on a transient network error, and the CI workflow runs the whole check up to seven times with escalating backoff. Only a check that fails in every one of those runs opens or updates a GitHub issue titled [live-check] supplier extractor broken …, so a slow runner timing out on a different supplier each time stays quiet, and a supplier that stays broken is commented on once a week rather than once a day.
  • Catalog phase — the discover() of every supplier that implements one (all but energie.be, the social tariff and the expert custom supplier) is run against its public listing page; any product visible at the supplier but missing from the registry opens a separate issue [live-check] new supplier products detected … so a parser regression and a catalogue addition stay in distinct threads. A discovery that fails, or sees no product at all, files its own issue, [live-check] supplier product discovery failed …, since it means that supplier's new products would go unseen. A registered product the listing no longer names files [live-check] a registered product left the supplier listing, which is what a withdrawn product looks like from there.
  • Freshness phase — for the eight supplier-families that pick a card from several advertised ones, the card actually resolved is compared against the newest one the supplier advertises. A superseded card still downloads and still parses, so without this a stale URL looks identical to a healthy run. Suppliers that construct a single URL per contract are excluded: there is no candidate set to choose wrongly from, so a bad resolution fails loudly on its own.
  • Cross-checks: a supplier whose fetch blows its latency or size budget files [live-check] supplier drift detected, and a card whose federal tax block, stated VAT rate, Flemish network ceiling or regulated network figure disagrees with the month's other cards files its own issue, as does a Fluvius tariff sheet the ceiling can no longer be read from, and a Fluvius or ORES page whose published day/night or Impact hours stop matching the ones the integration bills by. A TotalEnergies card whose Dutch edition reads differently from the French one, or stands in for a French card that does not read, files one too.

The card archive

Most suppliers keep their past cards somewhere the integration can fetch them from, which is what the year-to-date cost and the signing-cohort pricing walk (the suppliers listed under current_year_cost in Sensors). Some do not (TotalEnergies, Ecofix), and some archive only part of their range (Bolt's variable cards are named by version rather than by month, Frank skips a month now and then), so a past month on those was billed on today's card.

A second daily workflow (.github/workflows/archive_cards.yml, running scripts/archive_cards.py) closes that gap: it has run every morning since September 2026. It fetches every registered (supplier, contract, region) card exactly as the integration would, and commits what it parsed to be_price_cards, a repository shared with the gas and water integrations in which this integration owns the electricity/ directory, as electricity/cards/<supplier>/<contract>/<region>/<YYYY-MM>.json, with the text of every page or document that parse read under electricity/texts/<YYYY-MM>/, so a card can be re-read or checked by hand later. Every run also mirrors the last 12 months from the supplier archives into it, for the months it does not hold yet, which fills a newly added supplier's year with no one asking and keeps those months readable should a supplier drop its own archive. The archive keeps 36 months, as long as a fixed contract runs on its signing card, and a manual run can mirror up to that many. That only covers suppliers walked while they were still publishing: DATS 24 left the market before this archive existed, so none of its months are in here and they are read from its own server for as long as that lasts.

Finding a stored card by hand. Everything is addressed by the same three ids the integration uses, which are the directory names under electricity/cards/: the supplier (totalenergies, bolt, ...), the contract (totalenergies_electricite_fixe, bolt_fix, ...) and the region (flanders, wallonia, brussels). Browse the repository to see them.

  1. The parsed card is one JSON per month at electricity/cards/<supplier>/<contract>/<region>/<YYYY-MM>.json, for example totalenergies/totalenergies_electricite_fixe/wallonia/2026-09.json. It holds the energy, DSO, tax and injection figures exactly as the integration stores them, plus _seen_on (the day it was captured), _via (live for a card captured while it was current, archive for one mirrored from the supplier's archive, or replaced by the settled copy from that archive once a month-indexed card's month had closed; a row settled in place, at Luminus, TotalEnergies and Bolt, keeps the one it had) and _sources: every page or document the parse read, each with its text file under texts/ and, for a PDF, the digest of the file.
  2. The original PDF is easiest through coverage.md in that repository, which names one sheet per supplier under coverage/: a row per contract and region, a column per month. Each month cell carries two links: pdf downloads the card from the cards repository's releases (page opens the text of the page instead, for a card parsed from a page) and json opens the parsed card above; a month marked (mirror) was copied from the supplier's own archive. Each release's notes point to these sheets, so a file seen on the releases page can be named too: search that repository for the file's name. Behind them is pdfs.json, which maps a digest to electricity-<YYYY-MM>/<digest>.pdf in those releases; the digest in a JSON's _sources is the same key.
  3. The text the parser read is under texts/<YYYY-MM>/, named by the digest of the text itself and listed in the JSON's _sources, for checking a figure against the card without opening the PDF. A card is filed under the month its own label names, which is what a supplier publishing in arrears (Ecopower's definitive card) or ahead needs; a month is rewritten only when the parse changed, and months more than 36 before the running one are dropped.

The cards themselves are kept as well, as the real thing a parser can be re-run against later: every PDF the archive has not seen before is uploaded to an electricity-<YYYY-MM> release of that same repository. A release holds the cards for one month, whatever day each was captured or mirrored on, each file named by its SHA-256. A card that did not change is uploaded once, under the first month it was filed for, and a release is kept as long as a stored month still names one of its cards. Each stored card names its PDF by that digest under _sources, and electricity/pdfs.json says which release holds it. A month of cards runs to tens of MB and more, which is why they live in releases rather than in the tree. That digest also keeps the daily run cheap: a card whose bytes have not changed is served the text the archive already holds instead of being rendered again. And a parser fix reaches the stored months on its own: when the parser sources change, the next run replays every stored month from the texts it kept, with the clock set to the day the card was captured and no supplier contacted, and rewrites what came out differently; a parser that now needs the card read another way gets the kept PDF back.

The integration reads that archive first for any closed month, one small JSON per month straight from raw.githubusercontent.com against a PDF download and a parse from the supplier; the supplier's own archive answers for a month the project's does not hold, and the current card stands in when neither has it, with the federal levies of the month it stands in for. A card indexed on its own month at Aspiravi (Eco Plus Flex, energy and feed-in), Cociter (Variable and Trihoraire), Elegant (Flex and ComfortFlex, energy and feed-in, and the feed-in credit of Zeker & Vast), Eneco (Flex, and the feed-in credit of every card but Dynamic), EBEM (the variable cards), Engie (the EPEXDAM cards), Mega (the Flex and Impact range) or Trevion (the monthly contracts) prints last month's index while it runs, and the supplier settles the month on the card after it. Ecopower's Groene Burgerstroom card is published once its month has closed and prints that month's settled energy and feed-in indices, which only the month lookup applies, since the live card stands in for the running month. A month the archive caught live on such a card is therefore read from the supplier once it has closed, and billed on the archive's copy, re-asked daily, until that card is out; the daily run then replaces the archive's copy with the settled one. Luminus (MaxxFlex, SmartFlex, BasicFlex and the monthly feed-in, and ComfyFlex and ComfyFlex+ a quarter at a time on the index the next quarter's card names) is settled the same way but in place: its own archive drops the new-customer campaign a live card carries, so the card kept for the month stays whole and only its indexed rates move to the index the next card names. TotalEnergies keeps no archive of its own and settles the rows the daily run caught in place too, while the card after the month is still its live one. Bolt's variable cards are settled in place too, a quarter at a time: a closed quarter's months move to the indices the next quarter's card prints, one per meter register and Impact band. Ecofix's Flexy cards print the formula at an earlier month's index, one to three months back, and no later card names the month's own, so a closed month is billed on its delivery month's mean with a key or the keyless setting and on the printed figure without either. The request names the supplier, contract, region and month and nothing else, and the month lookup only asks for a month the archive can hold: a closed one, and for a supplier with no archive of its own not before May 2026, the earliest month filed for one (Ecofix's May cards, filed by hand before the daily captures began). A signing month older than the 36 kept, which the supplier's own archive does not hold either, bills each month on that month's own card as an entry with no signing month does, unless you typed the rate you signed. A signing card an installation has read is kept on disk until an update changes the stored card format, so this matters when the entry is new, its signing date changes, or such an update is installed.

The archive also stands in for the running month's card when that cannot be had. An entry whose card is current keeps pricing on it. One whose card is more than a week past its validity, or was not refreshed for a week, takes the archive's newest card when that is a later one. One that holds none (a new entry, or one whose stored card an update set aside and which is too old to replay) is priced on the newest card the archive holds, this month's first and back up to a year, so its sensors keep values, and the Repairs card about the failed fetch stays up until a fetch succeeds. The same run writes vat.json: for each month, the VAT rate the cards agree on, when at least three suppliers state one rate and none states another. The integration reads it once a day to price what a card leaves without a rate (see The VAT rate); a month the cards disagree on is left out, and the last month they agreed on stands. The Read past cards from the project's archive box on the meters step, on by default, switches both off per entry: the integration then never contacts GitHub, those months are priced on the current card, a rate a card does not state is the last one read while the box was on, or else 6% for a household and 21% on a professional contract, and a card published as page images (Ecofix's) has no prices, since the archive's reading of it is the only one. A row holds what the extractor of that day parsed, and the day after a parser change every row is re-parsed from the texts the archive kept, so a fix reaches past months within a day.

It is not a complete record. A stored month carries no PDF bytes of its own, only the digest of the release file that holds the card; a supplier that blocks the GitHub runners for a day (Mega has, the live check's timeouts show) just misses that day's capture; and a card no parser can read (Ecofix's page images) is read by an OCR engine built for those cards and stored as an ordinary row, flagged so an entry served one says where the figures came from. One that even the OCR refuses is kept as a PDF and named on the coverage sheet with no JSON beside it. The OCR reads a font only through the glyphs it has learnt, and no Ecofix card it learnt from sets a bold 4, 7, 8, 9, Q, X, Y or Z; a card published as page images still embeds the fonts of the text set over its picture, so each run looks at the cards it stored and files [archive-cards] a card embeds glyphs the OCR library has not learnt when one carries such a glyph, naming the card to learn it from. A card the OCR refuses outright files [archive-cards] the OCR could not read a card.

License

BSD 2-Clause. See LICENSE.

About

Home Assistant integration for Belgian all-in electricity prices: energy, network tariffs and levies parsed from the suppliers' own tariff cards, day-ahead spot from ENTSO-E, with year-to-date cost, contract comparison and solar injection credits.

Topics

Resources

Stars

63 stars

Watchers

4 watching

Forks

Releases

Packages

Contributors

Languages