pq / app.py
Daniel Varga
uploadable data files doing something, flaky yet
d58b6ea
# port of
# https://colab.research.google.com/drive/1PJgcJ4ly7x5GuZy344eJeYSODo8trbM4#scrollTo=39F2u-4hvwLU
import numpy as np
import pandas as pd
import gradio as gr
from types import SimpleNamespace
import datetime
# from simulation import *
from data_processing import *
from visualization import *
from supplier import Supplier, precalculate_supplier
from architecture import simulator, add_dummy_predictions
from decider import Decider, RandomDecider
from bess import BatteryParameters, BatteryModel
#@title ### Downloading the data
# !wget "https://static.renyi.hu/ai-shared/daniel/pq/PL_44527.19-21.csv.gz"
# !wget "https://static.renyi.hu/ai-shared/daniel/pq/pq_terheles_2021_adatok.tsv"
OLD_DATASET = False
MINI = False
if MINI:
met_default_filename = 'PL_44527.2101.csv.gz'
cons_default_filename = 'pq_terheles_202101_adatok.tsv'
else:
met_default_filename = 'PL_44527.19-21.csv.gz'
cons_default_filename = 'pq_terheles_2021_adatok.tsv'
met_2021_data, cons_2021_data = read_datasets(met_default_filename, cons_default_filename, old_dataset=OLD_DATASET)
# TODO some gui to upload consumption data.
# and maybe solar data? but we don't have a solar data -> production model built in. just scale and switch years from a dropdown?
def recalculate(ui):
np.random.seed(1)
supplier = create_supplier(ui)
solar_parameters = SolarParameters(solar_cell_num=ui.solar_cell_num, panel_power_at_NOCT=ui.solar_cell_nominal_capacity)
met_data = met_2021_data
cons_data = cons_2021_data
changed_datasource = False
if ui.meteorology_csv is not None:
met_filename = ui.meteorology_csv.name
changed_datasource = True
else:
met_filename = met_default_filename
if ui.consumption_csv is not None:
print("Hm, we should do something with this CSV.", ui.consumption_csv.name)
cons_filename = ui.consumption_csv.name
changed_datasource = True
else:
cons_filename = cons_default_filename
if changed_datasource:
met_data, cons_data = read_datasets(met_filename, cons_filename, old_dataset=OLD_DATASET)
else:
met_data, cons_data = met_2021_data, cons_2021_data
add_production_field(met_data, solar_parameters)
if OLD_DATASET:
# i've obsoleted this, but it's not a 100% replacement,
# it treats daylight savings changes differently.
# new version drops repeated hour and interpolates skipped hour.
all_data = interpolate_and_join(met_data, cons_data)
else:
all_data = join_consumption_meteorology(
met_data,
cons_data,
target_freq="5min",
)
time_interval_min = all_data.index.freq.n
time_interval_h = time_interval_min / 60
battery_parameters = BatteryParameters(
capacity_Ah = ui.bess_capacity_Ah,
voltage_V = ui.bess_voltage_V,
charge_kW = ui.bess_charge_kW,
discharge_kW = ui.bess_discharge_kW
)
battery_model = BatteryModel(battery_parameters, time_interval_h=time_interval_h)
# for faster testing:
DATASET_TRUNCATED_SIZE = None
if DATASET_TRUNCATED_SIZE is not None:
print("Truncating dataset to", DATASET_TRUNCATED_SIZE, "datapoints, that is", DATASET_TRUNCATED_SIZE * time_interval_h / 24, "days")
all_data = all_data.iloc[:DATASET_TRUNCATED_SIZE]
if ui.fixed_consumption_kW is not None:
all_data['Consumption'] = ui.fixed_consumption_kW
all_data_with_predictions = all_data.copy()
add_dummy_predictions(all_data_with_predictions)
precalculated_supplier = precalculate_supplier(supplier, all_data.index)
# we delete the supplier to avoid accidentally calling it instead of precalculated_supplier
supplier = None
# param_1 is prob of choosing PASSIVE
# param_2 is prob of choosing NETWORK_CHARGE
# so this corresponds to constant DISCHARGE mode, where the priorities are:
# 1. try solar 2. try bess 3. try network
decider = RandomDecider(np.array([0.0, 0.0]), precalculated_supplier)
all_data_with_predictions['Consumption_fees'] = precalculated_supplier.consumption_fees # [HUF / kWh]
results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
print(f"{total_network_fee=}")
return results
# that's very error-prone, sorry. must be kept in sync with the gradio button click call,
# the caller ui_refresh and recalculate().
def list_to_namespace(
solar_cell_num, solar_cell_nominal_capacity,
bess_capacity_Ah, bess_voltage_V, bess_charge_kW, bess_discharge_kW,
fixed_consumption_kW,
base_price_HUFpkWh, peak_price_HUFpkWh,
consumption_csv, meteorology_csv
):
return SimpleNamespace(
solar_cell_num=solar_cell_num,
solar_cell_nominal_capacity=solar_cell_nominal_capacity,
bess_capacity_Ah=bess_capacity_Ah,
bess_voltage_V=bess_voltage_V,
bess_charge_kW=bess_charge_kW,
bess_discharge_kW=bess_discharge_kW,
# not passed to recalculate(ui):
# fixed_consumption_checkbox=fixed_consumption_checkbox,
fixed_consumption_kW=fixed_consumption_kW,
base_price_HUFpkWh=base_price_HUFpkWh,
peak_price_HUFpkWh=peak_price_HUFpkWh,
# not passed to recalculate(ui):
# week_a=week_a, week_b=week_b,
consumption_csv=consumption_csv, meteorology_csv=meteorology_csv
)
def create_supplier(ui):
supplier = Supplier(price=ui.base_price_HUFpkWh)
supplier.set_price_for_daily_interval_on_workdays(start=6, end=18, price=ui.peak_price_HUFpkWh)
return supplier
def ui_refresh(
solar_cell_num, solar_cell_nominal_capacity,
bess_capacity_Ah, bess_voltage_V, bess_charge_kW, bess_discharge_kW,
fixed_consumption_checkbox, fixed_consumption_kW,
base_price_HUFpkWh, peak_price_HUFpkWh,
week_a, week_b,
consumption_csv, meteorology_csv):
if not fixed_consumption_checkbox:
fixed_consumption_kW = None # None means use dataframe.
ui = list_to_namespace(
solar_cell_num, solar_cell_nominal_capacity,
bess_capacity_Ah, bess_voltage_V, bess_charge_kW, bess_discharge_kW,
fixed_consumption_kW,
base_price_HUFpkWh, peak_price_HUFpkWh,
consumption_csv, meteorology_csv
)
results = recalculate(ui)
# TODO this is a bit too vibey
start_a = (week_a if isinstance(week_a, datetime.datetime) else
datetime.datetime.fromisoformat(week_a))
end_a = start_a + datetime.timedelta(days=6)
start_b = (week_b if isinstance(week_b, datetime.datetime) else
datetime.datetime.fromisoformat(week_b))
end_b = start_b + datetime.timedelta(days=6)
date_range_a = (start_a.strftime("%Y-%m-%d"), end_a.strftime("%Y-%m-%d"))
date_range_b = (start_b.strftime("%Y-%m-%d"), end_b.strftime("%Y-%m-%d"))
fig1 = plotly_visualize_simulation(results, date_range=date_range_a)
fig2 = plotly_visualize_simulation(results, date_range=date_range_b)
# (12, 3), the 3 indexed with (network, solar, bess):
consumptions_in_mwh = monthly_analysis(results)
fig_monthly = plotly_visualize_monthly(consumptions_in_mwh)
network, solar, bess = consumptions_in_mwh.sum(axis=0)
html = "<table>\n"
for column, column_name in zip((network, solar, bess), ("Network", "Solar directly", "Solar via BESS")):
html += f"<tr><td>Yearly consumption served by {column_name}:&nbsp;&nbsp;&nbsp;</td><td>{column:0.2f} MWh</td></tr>\n"
supplier = create_supplier(ui)
fee = supplier.fee(results["consumption_from_network"])
html += f"<tr><td>Network energy usage charge:</td><td>{fee/1e6:.3f} million HUF</td></tr>\n"
html += "</table>"
return (html, fig_monthly, fig1, fig2)
with gr.Blocks() as ui:
with gr.Row():
# LEFT: Input controls
with gr.Column(scale=1): # narrower column
solar_cell_num = gr.Slider(0, 3000, 1140, label="Number of installed solar cells")
solar_cell_nominal_capacity = gr.Slider(0, 1000, 280, label="Solar cell nominal capacity at NOCT [W]")
bess_capacity_Ah = gr.Slider(0, 2000, 330, label="BESS nominal capacity [Ah]")
fixed_consumption_checkbox = gr.Checkbox(value=False, label="Use fixed consumption")
fixed_consumption_kW = gr.Slider(0, 100, 10, label="Amount of fixed consumption [kW]")
with gr.Accordion("Extra BESS settings", open=False):
bess_voltage_V = gr.Number(value=600, label="BESS voltage [V]")
bess_charge_kW = gr.Number(value=50, label="BESS charge [kW]")
bess_discharge_kW = gr.Number(value=60, label="BESS discharge [kW]")
with gr.Accordion("Pricing settings", open=False):
peak_price_HUFpkWh = gr.Number(value=60, label="Peak energy price [HUF/kWh]")
base_price_HUFpkWh = gr.Number(value=60, label="Base energy price [HUF/kWh]")
with gr.Accordion("Custom consumption/meteorology data", open=False):
consumption_csv = gr.File(label="Upload consumption CSV", file_types=[".csv", ".gz", ".tsv"])
meteorology_csv = gr.File(label="Upload meteorology CSV", file_types=[".csv", ".gz", ".tsv"])
run_btn = gr.Button("Run Simulation")
week_a_default = "2021-02-01" ; week_b_default = "2021-08-02"
# week_b_default = datetime.date(2021, 2, 1) ; week_b_default = datetime.date(2021, 8, 2)
# RIGHT: Output display
with gr.Column(scale=2): # wider column
html_out = gr.HTML()
plot1 = gr.Plot(label="Monthly energy use")
# date selector + Plot 2
week_a = gr.DateTime(
value=week_a_default, include_time=False, label="Start date for Week A", type="string"
)
plot2 = gr.Plot(label="Week A")
# date selector + Plot 3
week_b = gr.DateTime(
value=week_b_default, include_time=False, label="Start date for Week B", type="string"
)
plot3 = gr.Plot(label="Week B")
inputs = [
solar_cell_num, solar_cell_nominal_capacity,
bess_capacity_Ah, bess_voltage_V, bess_charge_kW, bess_discharge_kW,
fixed_consumption_checkbox, fixed_consumption_kW,
base_price_HUFpkWh, peak_price_HUFpkWh,
week_a, week_b,
consumption_csv, meteorology_csv
]
run_btn.click(
ui_refresh,
inputs=inputs,
# [solar_cell_num_slider, bess_slider, fixed_consumption_slider, base_price, peak_price],
outputs=[html_out, plot1, plot2, plot3],
)
ui.launch()