This solution relies on data from the Dutch Meteorological Institute (KNMI) and as such is only relevant for people living in the Netherlands.
There a several different implementations on this forum. See this post for some of them. I believe this one is slightly different because it enables you to predict your energy usage for heating for a year, starting at october 1st.
Requirements: NodeRED, two HA input number helpers and a sensor that reports the energy usage of your heating system.
Get data from KNMI to calculated projected gas (or kWh) usage based on degree days
The KNMI data is published in yearly cycles starting at October 1st and ending at September 30th.
Each day a new value value for the number of degrees days since October 1st is published and another value for the estimated ammount of degree days to occur in the remainder of the year.
The values can be used to compute your estimated gas usage in a year.
How does it work
The flow fetches the two values each night using an anonymous token and publishes them using NodeRED sensors.
To be able to make the computation the ammount of energy usage for heating since the start of the year must be known.
This flow establishes this with a HA helper that holds the meter reading at October 1st. The flow snapshots the meter reading at October 1st but it can also be set from HA when the year is already underway.
Furthermore, another HA helper is used to store the estimated gas usage per month for heating water for other purposes (e.g. shower). (I’m using a central heating boiler that is responsible for heating and hot water, depending on your situation you may not need this helper)
Calculations
Each night, when the new values for past and future degree days are known, the past gas usage is computed:
gu = current gas meter reading - estimated gas usage for other purposes(corrected for period) - meter reading at October 1st
The gas usage is then divided by the number of degreedays since the start of the year. This is the gas usage per degreeday:
gu_dd = gu / past degreedays
After this the projected gas usage can be computed:
projected gas usage = gu + (gu_dd * future degreedays) + estimated gas usage for other purposes(for period of a year)
Thoughts
When a new cycle starts gu_dd is calculated from scratch and may cause inaccurate or fluctuating projections.
It could help to calculate a weighted average of the last value of gu_dd from the previous cycle and the new values of gu_dd.
References
https://www.knmi.nl/over-het-knmi/nieuws/graaddagen-in-gasjaar-2021
NodeRED flow
Be sure to add the helpers and replace the entity names in the Nodes named:
- Calculate projected gas usage
- Gasmeter value
- Gasmeter at season start
[{"id":"7c03c1cf114ffe29","type":"tab","label":"Graaddagen","disabled":false,"info":"","env":[]},{"id":"cc23c99bb2f54109","type":"group","z":"7c03c1cf114ffe29","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["d2d3cd8df7296beb","43de2e89d36cf4ee","dbf0f43688cf3cae","e6b546e4071f63c6","e7c1e518a6fcdf7e","ae212f637e0cf411","966615adb49fca3f","929daec153e162b1","dc37b8fe5fbd978b","8881f349e7114d03","0b6f6067ab213f11","69eb640535e9651c","563eee2fad1d85c6","9c1bb91b9a47a047","aa075441ca5a3358"],"x":14,"y":59,"w":1092,"h":462},{"id":"c4bae9a392b96b31","type":"group","z":"7c03c1cf114ffe29","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["ab3f79b116629885","99b9a1cf3b67ba7a","d2df3856c2230890","022232ad3e2d5bff","cc896b45b9505d8b"],"x":14,"y":559,"w":772,"h":182},{"id":"55fc1fb32de827af","type":"group","z":"7c03c1cf114ffe29","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["5a1456788037514b","7533462b24160bc2","327d15f746a81d95","441f248c63b9246b","bee50ccdd123c3e0","45ed212f506b0c5a"],"x":14,"y":759,"w":1072,"h":142},{"id":"d2d3cd8df7296beb","type":"http request","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"","method":"GET","ret":"obj","paytoqs":"query","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":550,"y":200,"wires":[["e6b546e4071f63c6"]]},{"id":"43de2e89d36cf4ee","type":"inject","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"each night","props":[],"repeat":"","crontab":"50 23 * * *","once":false,"onceDelay":0.1,"topic":"","x":130,"y":200,"wires":[["dbf0f43688cf3cae"]]},{"id":"dbf0f43688cf3cae","type":"function","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Get most recent filename","func":"msg.url = \"https://api.dataplatform.knmi.nl/open-data/v1/datasets/graaddagen/versions/1.0/files\"\n// This is the anonymous token. Expires 30 June 2025\n// Get the new one at https://developer.dataplatform.knmi.nl/open-data-api#token\nmsg.headers = {'Authorization': 'eyJvcmciOiI1ZTU1NGUxOTI3NGE5NjAwMDEyYTNlYjEiLCJpZCI6ImE1OGI5NGZmMDY5NDRhZDNhZjFkMDBmNDBmNTQyNjBkIiwiaCI6Im11cm11cjEyOCJ9'}\nmsg.payload = {\n 'maxKeys': 1,\n 'sorting': 'desc',\n}\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":200,"wires":[["d2d3cd8df7296beb"]]},{"id":"e6b546e4071f63c6","type":"change","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Get filename","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.files[0].filename","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":200,"wires":[["aa075441ca5a3358"]]},{"id":"e7c1e518a6fcdf7e","type":"function","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Get temp URL","func":"node.status({fill:\"green\",shape:\"dot\",text: msg.payload});\nmsg.url = \"https://api.dataplatform.knmi.nl/open-data/v1/datasets/graaddagen/versions/1.0/files/\" + msg.payload + \"/url\"\nmsg.headers = {'Authorization': 'eyJvcmciOiI1ZTU1NGUxOTI3NGE5NjAwMDEyYTNlYjEiLCJpZCI6ImE1OGI5NGZmMDY5NDRhZDNhZjFkMDBmNDBmNTQyNjBkIiwiaCI6Im11cm11cjEyOCJ9'}\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":260,"wires":[["ae212f637e0cf411"]]},{"id":"ae212f637e0cf411","type":"http request","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":550,"y":260,"wires":[["966615adb49fca3f"]]},{"id":"966615adb49fca3f","type":"change","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Set temp url","rules":[{"t":"set","p":"url","pt":"msg","to":"payload.temporaryDownloadUrl","tot":"jsonata"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":260,"wires":[["929daec153e162b1"]]},{"id":"929daec153e162b1","type":"http request","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":550,"y":320,"wires":[["dc37b8fe5fbd978b"]]},{"id":"dc37b8fe5fbd978b","type":"function","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Get past and future degree days","func":"const array = msg.payload.split(':')\nmsg.past = array[1].trim().split(\" \")[0]\nmsg.future = array[2].trim()\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":380,"wires":[["0b6f6067ab213f11","69eb640535e9651c"]]},{"id":"8881f349e7114d03","type":"inject","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"File content","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Het aantal gerealiseerde graaddagen van 1-10-2024 tot en met 21-11-2024 : 300.6 Het aantal verwachte graaddagen van 22-11-2024 tot en met 30-09-2025 : 1933.8 ","payloadType":"str","x":130,"y":480,"wires":[["dc37b8fe5fbd978b"]]},{"id":"0b6f6067ab213f11","type":"ha-sensor","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Past","entityConfig":"52120e3bc9f7d776","version":0,"state":"past","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":710,"y":380,"wires":[[]]},{"id":"69eb640535e9651c","type":"ha-sensor","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Future","entityConfig":"5356b60eb2117d6b","version":0,"state":"future","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":710,"y":440,"wires":[[]]},{"id":"5a1456788037514b","type":"ha-get-entities","z":"7c03c1cf114ffe29","g":"55fc1fb32de827af","name":"Gasmeter value","server":"e6dcda3f.fd1418","version":1,"rules":[{"condition":"state_object","property":"entity_id","logic":"is","value":"sensor.gas_consumption","valueType":"str"}],"outputType":"split","outputEmptyResults":false,"outputLocationType":"msg","outputLocation":"payload","outputResultsCount":1,"x":520,"y":860,"wires":[["327d15f746a81d95"]]},{"id":"7533462b24160bc2","type":"inject","z":"7c03c1cf114ffe29","g":"55fc1fb32de827af","name":"each night","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"01 00 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":860,"wires":[["441f248c63b9246b"]]},{"id":"327d15f746a81d95","type":"change","z":"7c03c1cf114ffe29","g":"55fc1fb32de827af","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.state","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":860,"wires":[["bee50ccdd123c3e0"]]},{"id":"441f248c63b9246b","type":"function","z":"7c03c1cf114ffe29","g":"55fc1fb32de827af","name":"Only at October 1st","func":"// Target month and day\nconst targetMonth = 10; // October\nconst targetDay = 1; // 1st\n\nconst now = new Date();\n\n// Check if the current date matches the target month and day\nif (now.getMonth() + 1 === targetMonth && now.getDate() === targetDay) {\n node.status({fill:\"green\",shape:\"dot\",text: \"ok\"});\n return { msg }; // Pass message to the next node\n}\nnode.status({fill:\"red\",shape:\"dot\",text: \"not yet\"});\nreturn null; // Do nothing if it's not the right time\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":860,"wires":[["5a1456788037514b"]]},{"id":"bee50ccdd123c3e0","type":"api-call-service","z":"7c03c1cf114ffe29","g":"55fc1fb32de827af","name":"Gasmeter at season start","server":"e6dcda3f.fd1418","version":7,"debugenabled":false,"action":"input_number.set_value","floorId":[],"areaId":[],"deviceId":[],"entityId":["input_number.gasmeterstand_begin_seizoen"],"labelId":[],"data":"{\"value\": payload}","dataType":"jsonata","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","blockInputOverrides":false,"domain":"input_number","service":"set_value","x":950,"y":860,"wires":[[]]},{"id":"ab3f79b116629885","type":"inject","z":"7c03c1cf114ffe29","g":"c4bae9a392b96b31","name":"each night","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"55 23 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":660,"wires":[["99b9a1cf3b67ba7a"]]},{"id":"99b9a1cf3b67ba7a","type":"function","z":"7c03c1cf114ffe29","g":"c4bae9a392b96b31","name":"Calculate projected gas usage","func":"\n// Set some helpers\nconst haCtx = global.get(\"homeassistant\");\nconst configCtx = haCtx.homeAssistant;\nconst one_day = 1000 * 60 * 60 * 24;\nconst startMonth = 9 // October\nconst startDay = 1\n\n// Calculate start year\nconst presentDate = new Date();\nlet startYear = presentDate.getFullYear()\nif (presentDate.getMonth() < startMonth) {\n startYear = startYear - 1\n}\n\n// Calculate days passed since start of season\nconst startSeason = new Date(startYear, startMonth, 1);\nconst daysPassed = Math.round((presentDate.getTime() - startSeason.getTime()) / one_day);\n\n// Get the states of relevant sensors\nconst gasValueAtSeasonStart = configCtx.states[\"input_number.gasmeterstand_begin_seizoen\"].state;\nconst gasValueNow = configCtx.states[\"sensor.gas_consumption\"].state;\nconst warmWaterUsagePerMonth = configCtx.states[\"input_number.gasverbruik_per_maand_voor_warm_water\"].state;\nconst past = configCtx.states[\"sensor.graaddagen_sinds_1_oktober\"].state;\nconst future = configCtx.states[\"sensor.graaddagen_tot_30_september\"].state;\n\n// Do calculations\nconst gasUsedForHeating = gasValueNow - (warmWaterUsagePerMonth * 12 / 365 * daysPassed) - gasValueAtSeasonStart\n\nconst gasPerDegreeDay = gasUsedForHeating / past\n\nconst gasForHeatingPrognosis = gasPerDegreeDay * future\n\nconst gasPrognosis = gasUsedForHeating + gasForHeatingPrognosis + (warmWaterUsagePerMonth * 12)\n\n// Send results\nmsg.perDegreeDay = gasPerDegreeDay\nmsg.payload = gasPrognosis\nreturn msg\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":660,"wires":[["d2df3856c2230890","022232ad3e2d5bff"]]},{"id":"d2df3856c2230890","type":"ha-sensor","z":"7c03c1cf114ffe29","g":"c4bae9a392b96b31","name":"Gas Prognosis","entityConfig":"7c2f66d2702f42ae","version":0,"state":"payload","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":680,"y":640,"wires":[[]]},{"id":"022232ad3e2d5bff","type":"ha-sensor","z":"7c03c1cf114ffe29","g":"c4bae9a392b96b31","name":"Gas per Degreeday","entityConfig":"57917f757d3f27e3","version":0,"state":"perDegreeDay","stateType":"msg","attributes":[],"inputOverride":"allow","outputProperties":[],"x":670,"y":700,"wires":[[]]},{"id":"563eee2fad1d85c6","type":"comment","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Acquire degree days from KNMI","info":"","x":170,"y":100,"wires":[]},{"id":"cc896b45b9505d8b","type":"comment","z":"7c03c1cf114ffe29","g":"c4bae9a392b96b31","name":"Calculate usage and projection","info":"","x":170,"y":600,"wires":[]},{"id":"45ed212f506b0c5a","type":"comment","z":"7c03c1cf114ffe29","g":"55fc1fb32de827af","name":"Snapshot gas meter at season start","info":"","x":180,"y":800,"wires":[]},{"id":"782c92878d03f345","type":"comment","z":"7c03c1cf114ffe29","name":"README","info":"# Get data from KNMI to calculated projected gas (or kWh) usage based on degree days\nThe KNMI data is published in yearly cycles starting at October 1st and ending at September 30th.\nEach day a new value value for the number of degrees days since October 1st is published and another value for the estimated ammount of degree days to occur in the remainder of the year.\n\nThe values can be used to compute your estimated gas usage in a year. \n\n## How does it work\nTo be able to make this computation the ammount of gas usage for heating since the start of the yearly must be known. \n\nThis flow establishes this with a HA helper that holds the meter reading at October 1st. The flow snapshots the meter reading at October 1st but it can also be set from HA when the year is already underway.\nFurthermore, another HA helper is used to store the estimated gas usage _per month_ for heating water for other purposes (e.g. shower).\n\nEach night, when the new values for past and future degree days are known, the past gas usage is computed:\n> **gu** = current gas meter reading - estimated gas usage for other purposes(corrected for period) - meter reading at October 1st\n\nThe gas usage is then divided by the number of degreedays since the start of the year. This is the gas usage per degreeday:\n> **gu_dd** = **gu** / past degreedays\n\nAfter this the projected gas usage can be computed:\n> projected gas usage = **gu** + (**gu_dd** * future degreedays) + estimated gas usage for other purposes(for period of a year)\n\n## Thoughts\nWhen a new cycle starts gu_dd is calculated from scratch and may cause inaccurate or fluctuating projections.\nIt could help to calculate a weighted average of the last value of gu_dd from the previous cycle and the new values of gu_dd.\n \n## References\nhttps://dataplatform.knmi.nl/dataset/graaddagen-1-0\nhttps://www.knmi.nl/over-het-knmi/nieuws/graaddagen-in-gasjaar-2021\n\n\n","x":80,"y":40,"wires":[],"icon":"node-red/alert.svg"},{"id":"9c1bb91b9a47a047","type":"inject","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Response","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"isTruncated\":true,\"resultCount\":1,\"files\":[{\"filename\":\"graaddagen_20241001.dat\",\"size\":167,\"created\":\"2024-11-25T07:05:41+00:00\",\"lastModified\":\"2024-11-25T07:05:41+00:00\"}],\"maxResults\":1,\"startAfterFilename\":\"\",\"nextPageToken\":\"eyJmaWxlX25hbWUiOiAiZ3JhYWRkYWdlbl8yMDI0MTEyNC5kYXQiLCAiaWQiOiAiZ3JhYWRkYWdlbl8xLjBfZ3JhYWRkYWdlbl8yMDI0MTEyNC5kYXQifQ==\"}","payloadType":"json","x":540,"y":140,"wires":[["e6b546e4071f63c6"]]},{"id":"aa075441ca5a3358","type":"function","z":"7c03c1cf114ffe29","g":"cc23c99bb2f54109","name":"Disregard first report in cycle","func":"// graaddagen_20241001.dat\n\n// Het aantal gerealiseerde graaddagen van 1-10-2023 tot en met 30-9-2024 : 1877.1\n// Het aantal verwachte graaddagen van 1-10-2024 tot en met 30-09-2025 : 2265.7\n\n// This report can't be used in this flow, because all degree days are summed in effect\n\nif ( msg.payload.match(/1001\\.dat/)) { // October 1st\n return null\n}\nreturn msg\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":960,"y":200,"wires":[["e7c1e518a6fcdf7e"]]},{"id":"52120e3bc9f7d776","type":"ha-entity-config","server":"e6dcda3f.fd1418","deviceConfig":"0c32a8baac30d089","name":"Graaddagen sinds 1 oktober","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Graaddagen sinds 1 oktober"},{"property":"icon","value":"mdi:sun-snowflake-variant"},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":""},{"property":"unit_of_measurement","value":"DD"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"5356b60eb2117d6b","type":"ha-entity-config","server":"e6dcda3f.fd1418","deviceConfig":"8d8239946af4df71","name":"Graaddagen tot 30 september","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Graaddagen tot 30 september"},{"property":"icon","value":"mdi:sun-snowflake-variant"},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":""},{"property":"unit_of_measurement","value":"DD"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"e6dcda3f.fd1418","type":"server","name":"Home Assistant","addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"","connectionDelay":true,"cacheJson":false,"heartbeat":false,"heartbeatInterval":"","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":":","statusYear":"hidden","statusMonth":"long","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m:s","enableGlobalContextStore":true},{"id":"7c2f66d2702f42ae","type":"ha-entity-config","server":"e6dcda3f.fd1418","deviceConfig":"8640a35f96c0b49c","name":"Gas Prognosis","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Gas Prognosis"},{"property":"icon","value":"mdi:gas-burner"},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":"gas"},{"property":"unit_of_measurement","value":"m³"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"57917f757d3f27e3","type":"ha-entity-config","server":"e6dcda3f.fd1418","deviceConfig":"0b6b963112a8f58f","name":"Gas per Degreeday","version":"6","entityType":"sensor","haConfig":[{"property":"name","value":"Gas per Degreeday"},{"property":"icon","value":"mdi:gas-burner"},{"property":"entity_picture","value":""},{"property":"entity_category","value":""},{"property":"device_class","value":"gas"},{"property":"unit_of_measurement","value":"m³"},{"property":"state_class","value":"measurement"}],"resend":false,"debugEnabled":false},{"id":"0c32a8baac30d089","type":"ha-device-config","name":"Graaddagen sinds 1 oktober","hwVersion":"","manufacturer":"KNMI","model":"","swVersion":""},{"id":"8d8239946af4df71","type":"ha-device-config","name":"Graaddagen tot 30 september","hwVersion":"","manufacturer":"KNMI","model":"","swVersion":""},{"id":"8640a35f96c0b49c","type":"ha-device-config","name":"Gas Prognosis","hwVersion":"","manufacturer":"Hjalmgunnarson","model":"","swVersion":""},{"id":"0b6b963112a8f58f","type":"ha-device-config","name":"Gas per Degreeday","hwVersion":"","manufacturer":"Hjalmgunnarson","model":"","swVersion":""}]