Refactoring
This commit is contained in:
37
app/models/battery.rb
Normal file
37
app/models/battery.rb
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
class Battery
|
||||||
|
|
||||||
|
attr_accessor :battery_kwh, :battery_max_kwh
|
||||||
|
|
||||||
|
def initialize(battery_capacity=10.0)
|
||||||
|
@battery_kwh = 0.0
|
||||||
|
@battery_max_kwh = battery_capacity
|
||||||
|
end
|
||||||
|
|
||||||
|
def charge(kwh)
|
||||||
|
return 0.0 if kwh.nil?
|
||||||
|
if battery_kwh + kwh <= battery_max_kwh
|
||||||
|
@battery_kwh += kwh.to_f
|
||||||
|
#p "Battery is now at %s kwh" % battery_kwh
|
||||||
|
return kwh
|
||||||
|
else
|
||||||
|
old_kwh = battery_kwh
|
||||||
|
battery_kwh = battery_max_kwh
|
||||||
|
#p "Battery is now at %s kwh" % battery_kwh
|
||||||
|
return (battery_max_kwh-old_kwh)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def discharge(kwh)
|
||||||
|
return 0.0 if kwh.nil?
|
||||||
|
if battery_kwh > kwh.to_f
|
||||||
|
@battery_kwh -= kwh.to_f
|
||||||
|
#p "Battery is now at %s kwh" % battery_kwh
|
||||||
|
return kwh
|
||||||
|
else
|
||||||
|
old_kwh = battery_kwh
|
||||||
|
battery_kwh = 0.0
|
||||||
|
#p "Battery is now at %s kwh" % battery_kwh
|
||||||
|
return old_kwh
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
253
app/models/cost.rb
Normal file
253
app/models/cost.rb
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
require 'open-uri'
|
||||||
|
EASY_ENERGY_TARIFFS = {}
|
||||||
|
|
||||||
|
class Cost
|
||||||
|
|
||||||
|
|
||||||
|
attr_reader :battery, :max_charge_kwh, :entsoe
|
||||||
|
|
||||||
|
def initialize(battery_capacity=10.0, max_charge=5.0)
|
||||||
|
@entsoe = Entsoe.new
|
||||||
|
@max_charge_kwh = max_charge
|
||||||
|
@battery = Battery.new(battery_capacity)
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_cost(cost)
|
||||||
|
cost ? "EUR %0.03f" % cost : "EUR ?"
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_tax(formatted_hour,usage_kwh,usage_kwh_cost,return_kwh, return_kwh_cost)
|
||||||
|
return nil if (usage_kwh.nil? || usage_kwh_cost.nil? || return_kwh.nil? || return_kwh_cost.nil?)
|
||||||
|
|
||||||
|
year = Date.parse(formatted_hour).year
|
||||||
|
case year
|
||||||
|
when 2020
|
||||||
|
usage_kwh * (usage_kwh_cost + 0.11822 + 0.03303) - return_kwh * (return_kwh_cost + 0.11822 + 0.03303)
|
||||||
|
when 2021
|
||||||
|
# see https://www.vastelastenbond.nl/blog/overzicht-energiebelasting-en-ode-2021-2022-en-je-energierekening-2021/
|
||||||
|
usage_kwh * (usage_kwh_cost + 0.11408 + 0.03630) - return_kwh * (return_kwh_cost + 0.11408 + 0.03630)
|
||||||
|
when 2022
|
||||||
|
usage_kwh * (usage_kwh_cost + 0.04452 + 0.03691) - return_kwh * (return_kwh_cost + 0.04452 + 0.03691)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Easy Energy API (proprietary) - better to use entsoe
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
# returns a hash with keys formatted "yyyy-mm-dd-hr" and values [usage, return]
|
||||||
|
# e.g. { 2021-12-16-06"=>[0.36058, 0.298] }
|
||||||
|
def easy_energy_tariffs(date)
|
||||||
|
p "Fetching EasyEnergy tariffs for %s" % date.strftime("%F-%H")
|
||||||
|
|
||||||
|
# calculate offset (date is in UTC, and we want to have tariffs in Amsterdam zone)
|
||||||
|
zone = 'Amsterdam'
|
||||||
|
offset = DateTime.now.in_time_zone(zone).utc_offset
|
||||||
|
date = date.beginning_of_day.advance(seconds: offset)
|
||||||
|
|
||||||
|
url = "https://mijn.easyenergy.com/nl/api/tariff/getapxtariffs?startTimestamp=%s&endTimestamp=%s&grouping=" % [date.strftime("%F %T"),date.advance(:hours => 24).strftime("%F %T")]
|
||||||
|
#p urlvandaag
|
||||||
|
json = JSON.load(URI::open(url))
|
||||||
|
# advancing with 1 hrs (to offset for something?)
|
||||||
|
json.map{|t| [DateTime.parse(t["Timestamp"]).strftime("%F-%H"), [t["TariffUsage"], t["TariffReturn"]]]}.to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
def easy_energy_rate(formatted_hour)
|
||||||
|
unless EASY_ENERGY_TARIFFS.key?(formatted_hour)
|
||||||
|
EASY_ENERGY_TARIFFS.merge!(easy_energy_tariffs(Date.parse(formatted_hour)))
|
||||||
|
end
|
||||||
|
EASY_ENERGY_TARIFFS[formatted_hour]
|
||||||
|
end
|
||||||
|
|
||||||
|
def easy_energy_cost(formatted_hour, usage_kwh, return_kwh)
|
||||||
|
return nil if (usage_kwh.nil? || return_kwh.nil?)
|
||||||
|
|
||||||
|
usage_kwh_cost, return_kwh_cost = easy_energy_rate(formatted_hour)
|
||||||
|
add_tax(formatted_hour, usage_kwh, usage_kwh_cost, return_kwh, return_kwh_cost)
|
||||||
|
end
|
||||||
|
|
||||||
|
def easy_energy_high_low(date)
|
||||||
|
hour_start = date.beginning_of_day
|
||||||
|
day_end = hour_start.advance(days: 1)
|
||||||
|
max_rate = 0.0
|
||||||
|
min_rate = 100.0
|
||||||
|
min_hour = nil
|
||||||
|
max_hour = nil
|
||||||
|
while(hour_start < day_end) do
|
||||||
|
easy_usage_rate, easy_return_rate = easy_energy_rate(hour_start.strftime("%F-%H"))
|
||||||
|
unless easy_usage_rate.nil?
|
||||||
|
# determine max
|
||||||
|
if easy_usage_rate > max_rate
|
||||||
|
max_rate = easy_usage_rate
|
||||||
|
max_hour = hour_start.strftime("%F-%H")
|
||||||
|
end
|
||||||
|
# determine min
|
||||||
|
if easy_usage_rate < min_rate
|
||||||
|
min_rate = easy_usage_rate
|
||||||
|
min_hour = hour_start.strftime("%F-%H")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
hour_start = hour_start.advance(:hours => 1)
|
||||||
|
end
|
||||||
|
return min_hour,max_hour
|
||||||
|
end
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Oxxio rates and cost
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
def oxxio_rate(formatted_hour, high_tariff)
|
||||||
|
year = Date.parse(formatted_hour).year
|
||||||
|
case year
|
||||||
|
when 2020
|
||||||
|
high_tariff ? 0.07865 : 0.06215
|
||||||
|
when 2021
|
||||||
|
high_tariff ? 0.06782 : 0.05259
|
||||||
|
when 2022
|
||||||
|
high_tariff ? 0.23665 : 0.19408
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def oxxio_energy_cost(formatted_hour, normaal_kwh, dal_kwh, year_shift=0)
|
||||||
|
return nil if (normaal_kwh.nil? || dal_kwh.nil?)
|
||||||
|
year = Date.parse(formatted_hour).year+year_shift
|
||||||
|
case year
|
||||||
|
when 2020
|
||||||
|
normaal_kwh * (0.07865 + 0.11822 + 0.03303) + dal_kwh * (0.06215 + 0.11822 + 0.03303)
|
||||||
|
when 2021
|
||||||
|
normaal_kwh * (0.06782 + 0.11408 + 0.03630) + dal_kwh * (0.05259 + 0.11408 + 0.03630)
|
||||||
|
when 2022
|
||||||
|
normaal_kwh * (0.23665 + 0.04452 + 0.03691) + dal_kwh * (0.19408 + 0.04452 + 0.03691)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Entsoe
|
||||||
|
#
|
||||||
|
|
||||||
|
def entsoe_energy_cost(formatted_hour, usage_kwh, return_kwh)
|
||||||
|
return nil if (usage_kwh.nil? || return_kwh.nil?)
|
||||||
|
|
||||||
|
usage_kwh_cost = return_kwh_cost = entsoe.price_at(formatted_hour)
|
||||||
|
add_tax(formatted_hour, usage_kwh, usage_kwh_cost, return_kwh, return_kwh_cost)
|
||||||
|
end
|
||||||
|
|
||||||
|
######################################################
|
||||||
|
# Calculate the per_hour usage and costs
|
||||||
|
######################################################
|
||||||
|
|
||||||
|
def hours(date, year_shift=0)
|
||||||
|
hour_start = date.beginning_of_day
|
||||||
|
day_end = hour_start.advance(days: 1)
|
||||||
|
result = []
|
||||||
|
lowest_hour, highest_hour,high_hours = entsoe.high_low_hours(date)
|
||||||
|
|
||||||
|
while(hour_start < day_end) do
|
||||||
|
hour_end = hour_start.end_of_hour
|
||||||
|
formatted_hour = hour_start.strftime("%F-%H")
|
||||||
|
|
||||||
|
#p "Fetching meter readings between %s and %s" % [hour_start,hour_end]
|
||||||
|
hour_readings = Reading.where("created_at > :begin AND created_at < :end", {:begin => hour_start, :end => hour_end})
|
||||||
|
hour_diff = hour_readings.last ? hour_readings.last.diff(hour_readings.first) : UNKNOWN_READING
|
||||||
|
# calculate cost of this hour
|
||||||
|
usage_kwh = hour_diff[:total_kwh_consumed_high] + hour_diff[:total_kwh_consumed_low] rescue nil
|
||||||
|
return_kwh = hour_diff[:total_kwh_produced_high] + hour_diff[:total_kwh_produced_low] rescue nil
|
||||||
|
entsoe_cost = entsoe_energy_cost(formatted_hour, usage_kwh, return_kwh) # without battery use
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make battery work
|
||||||
|
#
|
||||||
|
if !usage_kwh.nil?
|
||||||
|
# charge battery with return_kwh
|
||||||
|
return_kwh -= battery.charge(return_kwh)
|
||||||
|
|
||||||
|
if low_hour.eql?(formatted_hour)
|
||||||
|
# charge battery during lowest hour
|
||||||
|
usage_kwh += battery.charge(max_charge_kwh)
|
||||||
|
else
|
||||||
|
# if during expensive hours || more than <max_charge_kwh> kwh then discharge_battery
|
||||||
|
if (battery.battery_kwh > max_charge_kwh) || high_hours.include?(formatted_hour)
|
||||||
|
usage_kwh -= battery.discharge(usage_kwh)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
entsoe_cost_with_battery = entsoe_energy_cost(formatted_hour, usage_kwh, return_kwh) # with battery use
|
||||||
|
|
||||||
|
#
|
||||||
|
# end of battery work
|
||||||
|
#
|
||||||
|
|
||||||
|
normal_usage_kwh = hour_diff[:total_kwh_consumed_low] - hour_diff[:total_kwh_produced_low] rescue nil
|
||||||
|
low_usage_kwh = hour_diff[:total_kwh_consumed_high] - hour_diff[:total_kwh_produced_high] rescue nil
|
||||||
|
oxxio_cost = oxxio_energy_cost(formatted_hour,normal_usage_kwh, low_usage_kwh, year_shift)
|
||||||
|
oxxio_rate = oxxio_rate(formatted_hour, (hour_readings.first.high_tarif rescue true))
|
||||||
|
|
||||||
|
one_hour = [formatted_hour,
|
||||||
|
hour_diff[:total_kwh_consumed_high],
|
||||||
|
hour_diff[:total_kwh_consumed_low],
|
||||||
|
hour_diff[:total_kwh_produced_high],
|
||||||
|
hour_diff[:total_kwh_produced_low],
|
||||||
|
usage_kwh,
|
||||||
|
return_kwh,
|
||||||
|
battery.battery_kwh,
|
||||||
|
entsoe.price_at(formatted_hour), # entsoe rate
|
||||||
|
entsoe_cost_with_battery,
|
||||||
|
entsoe_cost,
|
||||||
|
oxxio_rate,
|
||||||
|
oxxio_cost]
|
||||||
|
|
||||||
|
p "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" % (one_hour[0..7] + one_hour[8..12].map{|c| format_cost(c)})
|
||||||
|
result << one_hour
|
||||||
|
|
||||||
|
hour_start = hour_start.advance(:hours => 1)
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def summarize_hours(hours)
|
||||||
|
usage_kwh = hours.map{|e| (e[1] && e[2]) ? (e[1] + e[2]) : nil}.compact.sum
|
||||||
|
return_kwh = hours.map{|e| (e[3] && e[4]) ? (e[3] + e[4]) : nil}.compact.sum
|
||||||
|
usage_kwh_with_battery = hours.map{|e| e[5]}.compact.sum
|
||||||
|
return_kwh_with_battery = hours.map{|e| e[6]}.compact.sum
|
||||||
|
entsoe_cost_with_battery = hours.map{|e| e[9]}.compact.sum
|
||||||
|
entsoe_cost = hours.map{|e| e[10]}.compact.sum
|
||||||
|
oxxio_cost = hours.map{|e| e[12]}.compact.sum
|
||||||
|
|
||||||
|
return usage_kwh, return_kwh, usage_kwh_with_battery, return_kwh_with_battery, format_cost(entsoe_cost_with_battery), format_cost(entsoe_cost), format_cost(oxxio_cost)
|
||||||
|
end
|
||||||
|
|
||||||
|
def hours_in_month(date, year_shift=0)
|
||||||
|
day = date.beginning_of_month
|
||||||
|
last_day = date.end_of_month
|
||||||
|
all_hours = []
|
||||||
|
while (day <= last_day)
|
||||||
|
all_hours += hours(day, year_shift)
|
||||||
|
day = day.advance(:days => 1)
|
||||||
|
end
|
||||||
|
all_hours
|
||||||
|
end
|
||||||
|
|
||||||
|
# heel2021 = hours_in(Date.parse("2021-01-01"), Date.parse("2021-12-31"), 1)
|
||||||
|
|
||||||
|
def hours_in(from,to, year_shift=0)
|
||||||
|
day = from.to_date
|
||||||
|
last_day = to.to_date
|
||||||
|
all_hours = []
|
||||||
|
while (day <= last_day)
|
||||||
|
all_hours += hours(day, year_shift)
|
||||||
|
day = day.advance(:days => 1)
|
||||||
|
end
|
||||||
|
all_hours
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_csv(filename,hours)
|
||||||
|
CSV.open(filename, "wb") do |csv|
|
||||||
|
csv << ["hour", "kwh_consumed_low", "kwh_consumed_high", "kwh_produced_low", "kwh_produced_high", "easy_usage_rate", "easy_return_rate", "easy_cost", "oxxio_rate", "oxxio_cost"]
|
||||||
|
hours.each do |row|
|
||||||
|
csv << row
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
@@ -10,26 +10,79 @@ class Entsoe
|
|||||||
|
|
||||||
URL = 'https://transparency.entsoe.eu'
|
URL = 'https://transparency.entsoe.eu'
|
||||||
|
|
||||||
def initialize(api_key)
|
attr_reader :storage_cost
|
||||||
|
|
||||||
|
def initialize(api_key = "c2287e07-0c26-4950-b430-22b7f75a8f2e")
|
||||||
@api_key = api_key
|
@api_key = api_key
|
||||||
|
@kwh_prices = {}
|
||||||
|
@storage_cost = 0.05 # how much does it cost to store 1 kwh in battery
|
||||||
end
|
end
|
||||||
|
|
||||||
def day_ahead_prices(date)
|
def price_at(formatted_hour)
|
||||||
|
unless @kwh_prices.key?(formatted_hour)
|
||||||
|
@kwh_prices.merge!(query_day_ahead_prices(Date.parse(formatted_hour)))
|
||||||
|
end
|
||||||
|
@kwh_prices[formatted_hour]
|
||||||
|
end
|
||||||
|
|
||||||
|
def prices_at(date)
|
||||||
|
hour_start = date.beginning_of_day
|
||||||
|
day_end = hour_start.advance(days: 1)
|
||||||
|
result = {}
|
||||||
|
while(hour_start < day_end) do
|
||||||
|
formatted_hour = hour_start.strftime("%F-%H")
|
||||||
|
result.merge!({ formatted_hour => price_at(formatted_hour)})
|
||||||
|
hour_start = hour_start.advance(:hours => 1)
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def high_low_hours(date)
|
||||||
|
sorted_prices = prices_at(date).to_a.sort_by(&:last) # sort according to price
|
||||||
|
lowest_hour = sorted_prices.first[0]
|
||||||
|
highest_hour = sorted_prices.last[0]
|
||||||
|
# calculate hours where rate > charge_rate + storage_cost (typically 0.05)
|
||||||
|
charge_rate = sorted_prices.first[1] # assume we charge at lowest hour
|
||||||
|
high_hours = sorted_prices.select{|p| p[1] > charge_rate + storage_cost}.to_h.keys
|
||||||
|
|
||||||
|
return lowest_hour,highest_hour,high_hours
|
||||||
|
end
|
||||||
|
|
||||||
|
def query_day_ahead_prices(date)
|
||||||
start_date = date.beginning_of_day
|
start_date = date.beginning_of_day
|
||||||
end_date = date.end_of_day
|
end_date = date.end_of_day
|
||||||
|
|
||||||
# A44 - Document type => Price Document
|
# A44 - Document type => Price Document
|
||||||
# A01 - Process type => Day Ahead
|
|
||||||
# NL = '10YNL----------L'
|
# NL = '10YNL----------L'
|
||||||
|
|
||||||
domain = '10YNL----------L'
|
domain = '10YNL----------L'
|
||||||
url = URL + "/api?securityToken=%s&documentType=A44&processType=A01&in_Domain=%s&out_Domain=%s&periodStart=%s&periodEnd=%s" % [@api_key, domain, domain, start_date.iso8601, end_date.iso8601]
|
url = URL + "/api?securityToken=%s&documentType=A44&in_Domain=%s&out_Domain=%s&periodStart=%s&periodEnd=%s" % [@api_key, domain, domain, start_date.strftime("%Y%m%d%H%M"), end_date.strftime("%Y%m%d%H00")]
|
||||||
p url
|
#p url
|
||||||
|
|
||||||
|
base_request(date, url)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def base_request(url)
|
# get position and amount from XML snippet
|
||||||
|
# <Point>
|
||||||
|
# <position>1</position>
|
||||||
|
# <price.amount>196.23</price.amount>
|
||||||
|
# </Point>
|
||||||
|
#
|
||||||
|
# convert price to EUR per kwh, including VAT (21%)
|
||||||
|
#
|
||||||
|
def parse_point(xml)
|
||||||
|
return xml.children[1].text.to_i, ((xml.children[3].text.to_f/1000)*1.21).round(5)
|
||||||
|
end
|
||||||
|
|
||||||
|
def base_request(date, url)
|
||||||
|
formatted_date = date.strftime("%F")
|
||||||
|
|
||||||
doc = Nokogiri::XML(URI.open(url))
|
doc = Nokogiri::XML(URI.open(url))
|
||||||
|
prices = doc.xpath('.//xmlns:Point').map{|p| parse_point(p)}
|
||||||
|
|
||||||
|
#returns a hash with keys formatted "yyyy-mm-dd-hr" and values price (per kwh)
|
||||||
|
prices.map{|p| ["%s-%02d" % [formatted_date,(p[0]-1)], p[1]]}.to_h
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
require 'open-uri'
|
|
||||||
|
|
||||||
UNKNOWN_READING = { :total_kwh_consumed_high => nil, :total_kwh_consumed_low => nil, :total_kwh_produced_high => nil, :total_kwh_produced_low => nil, :total_m3_gas_consumed => nil}
|
UNKNOWN_READING = { :total_kwh_consumed_high => nil, :total_kwh_consumed_low => nil, :total_kwh_produced_high => nil, :total_kwh_produced_low => nil, :total_m3_gas_consumed => nil}
|
||||||
EASY_ENERGY_TARIFFS = {}
|
|
||||||
BATTERY_MAX_KWH = 10.0
|
|
||||||
MAX_CHARGE_KWH = 5.0
|
|
||||||
|
|
||||||
class Reading < ActiveRecord::Base
|
class Reading < ActiveRecord::Base
|
||||||
|
|
||||||
@@battery_kwh = 0.0
|
|
||||||
|
|
||||||
def eql_reading?(reading)
|
def eql_reading?(reading)
|
||||||
self.total_kwh_consumed_high == reading.total_kwh_consumed_high &&
|
self.total_kwh_consumed_high == reading.total_kwh_consumed_high &&
|
||||||
self.total_kwh_consumed_low == reading.total_kwh_consumed_low &&
|
self.total_kwh_consumed_low == reading.total_kwh_consumed_low &&
|
||||||
@@ -73,246 +68,14 @@ class Reading < ActiveRecord::Base
|
|||||||
Reading.where("created_at > :begin AND created_at < :end", { :begin => date.to_date.beginning_of_day, :end => date.to_date.end_of_day})
|
Reading.where("created_at > :begin AND created_at < :end", { :begin => date.to_date.beginning_of_day, :end => date.to_date.end_of_day})
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns a hash with keys formatted "yyyy-mm-dd-hr" and values [usage, return]
|
def max_charge_kwh=(kwh)
|
||||||
# e.g. { 2021-12-16-06"=>[0.36058, 0.298] }
|
@@max_charge_kwh = kwh
|
||||||
def easy_energy_tariffs(date)
|
|
||||||
p "Fetching EasyEnergy tariffs for %s" % date.strftime("%F-%H")
|
|
||||||
|
|
||||||
# calculate offset (date is in UTC, and we want to have tariffs in Amsterdam zone)
|
|
||||||
zone = 'Amsterdam'
|
|
||||||
offset = DateTime.now.in_time_zone(zone).utc_offset
|
|
||||||
date = date.beginning_of_day.advance(seconds: offset)
|
|
||||||
|
|
||||||
url = "https://mijn.easyenergy.com/nl/api/tariff/getapxtariffs?startTimestamp=%s&endTimestamp=%s&grouping=" % [date.strftime("%F %T"),date.advance(:hours => 24).strftime("%F %T")]
|
|
||||||
#p urlvandaag
|
|
||||||
json = JSON.load(URI::open(url))
|
|
||||||
# advancing with 1 hrs (to offset for something?)
|
|
||||||
json.map{|t| [DateTime.parse(t["Timestamp"]).strftime("%F-%H"), [t["TariffUsage"], t["TariffReturn"]]]}.to_h
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_tax(formatted_hour,usage_kwh,usage_kwh_cost,return_kwh, return_kwh_cost)
|
def max_charge_kwh
|
||||||
return nil if (usage_kwh.nil? || usage_kwh_cost.nil? || return_kwh.nil? || return_kwh_cost.nil?)
|
@@max_charge_kwh
|
||||||
|
|
||||||
year = Date.parse(formatted_hour).year
|
|
||||||
case year
|
|
||||||
when 2020
|
|
||||||
usage_kwh * (usage_kwh_cost + 0.11822 + 0.03303) - return_kwh * (return_kwh_cost + 0.11822 + 0.03303)
|
|
||||||
when 2021
|
|
||||||
# see https://www.vastelastenbond.nl/blog/overzicht-energiebelasting-en-ode-2021-2022-en-je-energierekening-2021/
|
|
||||||
usage_kwh * (usage_kwh_cost + 0.11408 + 0.03630) - return_kwh * (return_kwh_cost + 0.11408 + 0.03630)
|
|
||||||
when 2022
|
|
||||||
usage_kwh * (usage_kwh_cost + 0.04452 + 0.03691) - return_kwh * (return_kwh_cost + 0.04452 + 0.03691)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def easy_energy_high_low(date)
|
|
||||||
hour_start = date.beginning_of_day
|
|
||||||
day_end = hour_start.advance(days: 1)
|
|
||||||
max_rate = 0.0
|
|
||||||
min_rate = 100.0
|
|
||||||
min_hour = nil
|
|
||||||
max_hour = nil
|
|
||||||
while(hour_start < day_end) do
|
|
||||||
easy_usage_rate, easy_return_rate = easy_energy_rate(hour_start.strftime("%F-%H"))
|
|
||||||
unless easy_usage_rate.nil?
|
|
||||||
# determine max
|
|
||||||
if easy_usage_rate > max_rate
|
|
||||||
max_rate = easy_usage_rate
|
|
||||||
max_hour = hour_start.strftime("%F-%H")
|
|
||||||
end
|
|
||||||
# determine min
|
|
||||||
if easy_usage_rate < min_rate
|
|
||||||
min_rate = easy_usage_rate
|
|
||||||
min_hour = hour_start.strftime("%F-%H")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
hour_start = hour_start.advance(:hours => 1)
|
|
||||||
end
|
|
||||||
return min_hour,max_hour
|
|
||||||
end
|
|
||||||
|
|
||||||
def easy_energy_rate(formatted_hour)
|
|
||||||
unless EASY_ENERGY_TARIFFS.key?(formatted_hour)
|
|
||||||
EASY_ENERGY_TARIFFS.merge!(easy_energy_tariffs(Date.parse(formatted_hour)))
|
|
||||||
end
|
|
||||||
EASY_ENERGY_TARIFFS[formatted_hour]
|
|
||||||
end
|
|
||||||
|
|
||||||
def easy_energy_cost(formatted_hour, usage_kwh, return_kwh)
|
|
||||||
return nil if (usage_kwh.nil? || return_kwh.nil?)
|
|
||||||
|
|
||||||
usage_kwh_cost, return_kwh_cost = easy_energy_rate(formatted_hour)
|
|
||||||
add_tax(formatted_hour, usage_kwh, usage_kwh_cost, return_kwh, return_kwh_cost)
|
|
||||||
end
|
|
||||||
|
|
||||||
def oxxio_rate(formatted_hour, high_tariff)
|
|
||||||
year = Date.parse(formatted_hour).year
|
|
||||||
case year
|
|
||||||
when 2020
|
|
||||||
high_tariff ? 0.07865 : 0.06215
|
|
||||||
when 2021
|
|
||||||
high_tariff ? 0.06782 : 0.05259
|
|
||||||
when 2022
|
|
||||||
high_tariff ? 0.23665 : 0.19408
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def oxxio_energy_cost(formatted_hour, normaal_kwh, dal_kwh, year_shift=0)
|
|
||||||
return nil if (normaal_kwh.nil? || dal_kwh.nil?)
|
|
||||||
year = Date.parse(formatted_hour).year+year_shift
|
|
||||||
case year
|
|
||||||
when 2020
|
|
||||||
normaal_kwh * (0.07865 + 0.11822 + 0.03303) + dal_kwh * (0.06215 + 0.11822 + 0.03303)
|
|
||||||
when 2021
|
|
||||||
normaal_kwh * (0.06782 + 0.11408 + 0.03630) + dal_kwh * (0.05259 + 0.11408 + 0.03630)
|
|
||||||
when 2022
|
|
||||||
normaal_kwh * (0.23665 + 0.04452 + 0.03691) + dal_kwh * (0.19408 + 0.04452 + 0.03691)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_cost(cost)
|
|
||||||
cost ? "EUR %0.03f" % cost : "EUR ?"
|
|
||||||
end
|
|
||||||
|
|
||||||
def charge_battery(kwh)
|
|
||||||
return 0.0 if kwh.nil?
|
|
||||||
if @@battery_kwh + kwh <= BATTERY_MAX_KWH
|
|
||||||
@@battery_kwh += kwh.to_f
|
|
||||||
p "Battery is now at %s kwh" % @@battery_kwh
|
|
||||||
return kwh
|
|
||||||
else
|
|
||||||
old_kwh = @@battery_kwh
|
|
||||||
@@battery_kwh = BATTERY_MAX_KWH
|
|
||||||
p "Battery is now at %s kwh" % @@battery_kwh
|
|
||||||
return (BATTERY_MAX_KWH-old_kwh)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def discharge_battery(kwh)
|
|
||||||
return 0.0 if kwh.nil?
|
|
||||||
if @@battery_kwh > kwh.to_f
|
|
||||||
@@battery_kwh -= kwh.to_f
|
|
||||||
p "Battery is now at %s kwh" % @@battery_kwh
|
|
||||||
return kwh
|
|
||||||
else
|
|
||||||
old_kwh = @@battery_kwh
|
|
||||||
@@battery_kwh = 0.0
|
|
||||||
p "Battery is now at %s kwh" % @@battery_kwh
|
|
||||||
return old_kwh
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hours(date, year_shift=0)
|
|
||||||
hour_start = date.beginning_of_day
|
|
||||||
day_end = hour_start.advance(days: 1)
|
|
||||||
result = []
|
|
||||||
low_hour, high_hour = easy_energy_high_low(date)
|
|
||||||
low_usage_rate, low_return_rate = easy_energy_rate(low_hour)
|
|
||||||
|
|
||||||
while(hour_start < day_end) do
|
|
||||||
hour_end = hour_start.end_of_hour
|
|
||||||
formatted_hour = hour_start.strftime("%F-%H")
|
|
||||||
|
|
||||||
#p "Fetching meter readings between %s and %s" % [hour_start,hour_end]
|
|
||||||
hour_readings = Reading.where("created_at > :begin AND created_at < :end", {:begin => hour_start, :end => hour_end})
|
|
||||||
hour_diff = hour_readings.last ? hour_readings.last.diff(hour_readings.first) : UNKNOWN_READING
|
|
||||||
# calculate cost of this hour
|
|
||||||
usage_kwh = hour_diff[:total_kwh_consumed_high] + hour_diff[:total_kwh_consumed_low] rescue nil
|
|
||||||
return_kwh = hour_diff[:total_kwh_produced_high] + hour_diff[:total_kwh_produced_low] rescue nil
|
|
||||||
easy_cost = easy_energy_cost(formatted_hour, usage_kwh, return_kwh) # without battery use
|
|
||||||
|
|
||||||
if !usage_kwh.nil?
|
|
||||||
# charge battery with return_kwh
|
|
||||||
return_kwh -= charge_battery(return_kwh)
|
|
||||||
|
|
||||||
if low_hour.eql?(formatted_hour)
|
|
||||||
# charge battery during lowest hour
|
|
||||||
usage_kwh += charge_battery(MAX_CHARGE_KWH)
|
|
||||||
else
|
|
||||||
easy_usage_rate, easy_return_rate = easy_energy_rate(formatted_hour)
|
|
||||||
# if rate > charge_rate + 0.05 || more than 5.0 kwh then discharge_battery
|
|
||||||
if @@battery_kwh > 5.0 || (easy_usage_rate.to_f > (low_usage_rate.to_f + 0.05))
|
|
||||||
usage_kwh -= discharge_battery(usage_kwh)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
easy_cost_with_battery = easy_energy_cost(formatted_hour, usage_kwh, return_kwh) # with battery use
|
|
||||||
|
|
||||||
normal_usage_kwh = hour_diff[:total_kwh_consumed_low] - hour_diff[:total_kwh_produced_low] rescue nil
|
|
||||||
low_usage_kwh = hour_diff[:total_kwh_consumed_high] - hour_diff[:total_kwh_produced_high] rescue nil
|
|
||||||
oxxio_cost = oxxio_energy_cost(formatted_hour,normal_usage_kwh, low_usage_kwh, year_shift)
|
|
||||||
oxxio_rate = oxxio_rate(formatted_hour, (hour_readings.first.high_tarif rescue true))
|
|
||||||
|
|
||||||
one_hour = [formatted_hour,
|
|
||||||
hour_diff[:total_kwh_consumed_high],
|
|
||||||
hour_diff[:total_kwh_consumed_low],
|
|
||||||
hour_diff[:total_kwh_produced_high],
|
|
||||||
hour_diff[:total_kwh_produced_low],
|
|
||||||
usage_kwh,
|
|
||||||
return_kwh,
|
|
||||||
@@battery_kwh,
|
|
||||||
easy_usage_rate,
|
|
||||||
easy_return_rate,
|
|
||||||
easy_cost_with_battery,
|
|
||||||
easy_cost,
|
|
||||||
oxxio_rate,
|
|
||||||
oxxio_cost]
|
|
||||||
|
|
||||||
p "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s" % (one_hour[0..7] + one_hour[8..13].map{|c| format_cost(c)})
|
|
||||||
result << one_hour
|
|
||||||
|
|
||||||
hour_start = hour_start.advance(:hours => 1)
|
|
||||||
end
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
def summarize_hours(hours)
|
|
||||||
usage_kwh = hours.map{|e| (e[1] && e[2]) ? (e[1] + e[2]) : nil}.compact.sum
|
|
||||||
return_kwh = hours.map{|e| (e[3] && e[4]) ? (e[3] + e[4]) : nil}.compact.sum
|
|
||||||
usage_kwh_with_battery = hours.map{|e| e[5]}.compact.sum
|
|
||||||
return_kwh_with_battery = hours.map{|e| e[6]}.compact.sum
|
|
||||||
easy_cost_with_battery = hours.map{|e| e[10]}.compact.sum
|
|
||||||
easy_cost = hours.map{|e| e[11]}.compact.sum
|
|
||||||
oxxio_cost = hours.map{|e| e[13]}.compact.sum
|
|
||||||
|
|
||||||
return usage_kwh, return_kwh, usage_kwh_with_battery, return_kwh_with_battery, format_cost(easy_cost_with_battery), format_cost(easy_cost), format_cost(oxxio_cost)
|
|
||||||
end
|
|
||||||
|
|
||||||
def hours_in_month(date, year_shift=0)
|
|
||||||
day = date.beginning_of_month
|
|
||||||
last_day = date.end_of_month
|
|
||||||
all_hours = []
|
|
||||||
while (day <= last_day)
|
|
||||||
all_hours += hours(day, year_shift)
|
|
||||||
day = day.advance(:days => 1)
|
|
||||||
end
|
|
||||||
all_hours
|
|
||||||
end
|
|
||||||
|
|
||||||
# heel2021 = hours_in(Date.parse("2021-01-01"), Date.parse("2021-12-31"), 1)
|
|
||||||
|
|
||||||
def hours_in(from,to, year_shift=0)
|
|
||||||
day = from.to_date
|
|
||||||
last_day = to.to_date
|
|
||||||
all_hours = []
|
|
||||||
while (day <= last_day)
|
|
||||||
all_hours += hours(day, year_shift)
|
|
||||||
day = day.advance(:days => 1)
|
|
||||||
end
|
|
||||||
all_hours
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_csv(filename,hours)
|
|
||||||
CSV.open(filename, "wb") do |csv|
|
|
||||||
csv << ["hour", "kwh_consumed_low", "kwh_consumed_high", "kwh_produced_low", "kwh_produced_high", "easy_usage_rate", "easy_return_rate", "easy_cost", "oxxio_rate", "oxxio_cost"]
|
|
||||||
hours.each do |row|
|
|
||||||
csv << row
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
####
|
|
||||||
|
|
||||||
def diff_on(date)
|
def diff_on(date)
|
||||||
readings_on = day(date)
|
readings_on = day(date)
|
||||||
|
|||||||
Reference in New Issue
Block a user