From d153911d854e32ebe211be429e6bee488a8b6915 Mon Sep 17 00:00:00 2001 From: "pierre-francois.duc" Date: Thu, 4 Jul 2024 12:55:17 +0200 Subject: [PATCH 1/3] Update the name of storage children assets upon save --- app/projects/scenario_topology_helpers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/projects/scenario_topology_helpers.py b/app/projects/scenario_topology_helpers.py index 5c2f091a5..31268c772 100644 --- a/app/projects/scenario_topology_helpers.py +++ b/app/projects/scenario_topology_helpers.py @@ -169,9 +169,13 @@ def handle_storage_unit_form_post( new_name = form.cleaned_data.pop("name", None) if new_name is not None: ess_asset.name = new_name + ess_capacity_asset.name = f"{ess_asset.name} capacity" + ess_charging_power_asset.name = f"{ess_asset.name} input power" + ess_discharging_power_asset.name = f"{ess_asset.name} output power" ess_asset.save() else: # Create the ESS Parent Asset + ess_asset = Asset.objects.create( name=form.cleaned_data.pop("name"), asset_type=get_object_or_404( From 20de0f9d6d053f069893728511b6e50b2ff0d868 Mon Sep 17 00:00:00 2001 From: "pierre-francois.duc" Date: Thu, 4 Jul 2024 14:37:40 +0200 Subject: [PATCH 2/3] Fix bus result view --- app/dashboard/views.py | 205 +++++++++++++++++++++-------------------- 1 file changed, 103 insertions(+), 102 deletions(-) diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 4f65e8404..0c6061601 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -705,7 +705,7 @@ def view_asset_parameters(request, scen_id, asset_type_name, asset_uuid): form = BusForm( asset_type=asset_type_name, instance=existing_bus, view_only=True ) - + existing_asset = None context = {"form": form} elif asset_type_name in ["bess", "h2ess", "gess", "hess"]: template = "asset/storage_asset_create_form.html" @@ -765,125 +765,126 @@ def view_asset_parameters(request, scen_id, asset_type_name, asset_uuid): } ) - # fetch optimized capacity and flow if they exist - qs = FancyResults.objects.filter(simulation=scenario.simulation) + if existing_asset is not None: + # fetch optimized capacity and flow if they exist + qs = FancyResults.objects.filter(simulation=scenario.simulation) - if qs.exists(): - qs_fine = qs.exclude(asset__contains="@").filter( - asset__contains=existing_asset.name - ) - negative_direction = "out" - if existing_asset.is_storage is True and optimized_cap is True: - for cap in qs_fine.values_list("optimized_capacity", flat=True): - context.update( - {"optimized_add_cap": {"value": round(cap, 2), "unit": "kWh"}} - ) + if qs.exists(): + qs_fine = qs.exclude(asset__contains="@").filter( + asset__contains=existing_asset.name + ) + negative_direction = "out" + if existing_asset.is_storage is True and optimized_cap is True: + for cap in qs_fine.values_list("optimized_capacity", flat=True): + context.update( + {"optimized_add_cap": {"value": round(cap, 2), "unit": "kWh"}} + ) - elif existing_asset.is_provider is True: - negative_direction = "in" - else: - qs_fine = qs_fine.filter(asset=existing_asset.name) + elif existing_asset.is_provider is True: + negative_direction = "in" + else: + qs_fine = qs_fine.filter(asset=existing_asset.name) - traces = [] - total_flows = [] - timestamps = scenario.get_timestamps(json_format=True) + traces = [] + total_flows = [] + timestamps = scenario.get_timestamps(json_format=True) - if len(qs_fine) == 1: - asset_results = qs_fine.get() - total_flows.append( - { - "value": round(asset_results.total_flow, 2), - "unit": "kWh", - "label": "", - } - ) - if existing_asset.optimize_cap is True: - context.update( + if len(qs_fine) == 1: + asset_results = qs_fine.get() + total_flows.append( { - "optimized_add_cap": { - "value": round(asset_results.optimized_capacity, 2), - "unit": "kW", + "value": round(asset_results.total_flow, 2), + "unit": "kWh", + "label": "", + } + ) + if existing_asset.optimize_cap is True: + context.update( + { + "optimized_add_cap": { + "value": round(asset_results.optimized_capacity, 2), + "unit": "kW", + } } + ) + traces.append( + { + "value": json.loads(asset_results.flow_data), + "name": existing_asset.name, + "unit": "kW", } ) - traces.append( - { - "value": json.loads(asset_results.flow_data), - "name": existing_asset.name, - "unit": "kW", - } - ) - else: + else: - qs_fine = qs_fine.annotate( - name=Case( - When( - Q(asset_type__contains="chp") & Q(direction="in"), - then=Concat("asset", Value(" out ("), "bus", Value(")")), + qs_fine = qs_fine.annotate( + name=Case( + When( + Q(asset_type__contains="chp") & Q(direction="in"), + then=Concat("asset", Value(" out ("), "bus", Value(")")), + ), + When( + Q(asset_type__contains="chp") & Q(direction="out"), + then=Concat("asset", Value(" in")), + ), + When( + Q(asset_type__contains="ess") & Q(direction="in"), + then=Concat("asset", Value(" " + _("Discharge"))), + ), + When( + Q(asset_type__contains="ess") & Q(direction="out"), + then=Concat("asset", Value(" " + _("Charge"))), + ), + When( + Q(oemof_type="transformer") & Q(direction="out"), + then=Concat("asset", Value(" in")), + ), + When( + Q(oemof_type="transformer") & Q(direction="in"), + then=Concat("asset", Value(" out")), + ), + default=F("asset"), ), - When( - Q(asset_type__contains="chp") & Q(direction="out"), - then=Concat("asset", Value(" in")), + unit=Case( + When(Q(asset_type__contains="ess"), then=Value("kWh")), + default=Value("kW"), ), - When( - Q(asset_type__contains="ess") & Q(direction="in"), - then=Concat("asset", Value(" " + _("Discharge"))), - ), - When( - Q(asset_type__contains="ess") & Q(direction="out"), - then=Concat("asset", Value(" " + _("Charge"))), - ), - When( - Q(oemof_type="transformer") & Q(direction="out"), - then=Concat("asset", Value(" in")), - ), - When( - Q(oemof_type="transformer") & Q(direction="in"), - then=Concat("asset", Value(" out")), - ), - default=F("asset"), - ), - unit=Case( - When(Q(asset_type__contains="ess"), then=Value("kWh")), - default=Value("kW"), - ), - value=F("flow_data"), - ) - - for y_vals in qs_fine.order_by("direction").values( - "name", "value", "unit", "direction", "total_flow" - ): - # make consumption values negative other wise inflow of asset is negative - if y_vals["direction"] == negative_direction: - y_vals["value"] = ( - -1 * np.array(json.loads(y_vals["value"])) - ).tolist() - else: - y_vals["value"] = json.loads(y_vals["value"]) + value=F("flow_data"), + ) - traces.append(y_vals) + for y_vals in qs_fine.order_by("direction").values( + "name", "value", "unit", "direction", "total_flow" + ): + # make consumption values negative other wise inflow of asset is negative + if y_vals["direction"] == negative_direction: + y_vals["value"] = ( + -1 * np.array(json.loads(y_vals["value"])) + ).tolist() + else: + y_vals["value"] = json.loads(y_vals["value"]) - total_flows.append( - { - "value": round(y_vals["total_flow"], 2), - "unit": y_vals["unit"], - "label": y_vals["name"], - } - ) + traces.append(y_vals) - if existing_asset.is_provider is True: - # add the possibility to see the cap limit on the feedin on the result graph - feedin_cap = existing_asset.feedin_cap - if feedin_cap is not None: - traces.append( + total_flows.append( { - "value": [feedin_cap for t in timestamps], - "name": parameters_helper.get_doc_verbose("feedin_cap"), - "unit": parameters_helper.get_doc_unit("feedin_cap"), - "options": {"visible": "legendonly"}, + "value": round(y_vals["total_flow"], 2), + "unit": y_vals["unit"], + "label": y_vals["name"], } ) + if existing_asset.is_provider is True: + # add the possibility to see the cap limit on the feedin on the result graph + feedin_cap = existing_asset.feedin_cap + if feedin_cap is not None: + traces.append( + { + "value": [feedin_cap for t in timestamps], + "name": parameters_helper.get_doc_verbose("feedin_cap"), + "unit": parameters_helper.get_doc_unit("feedin_cap"), + "options": {"visible": "legendonly"}, + } + ) + context.update( { "form": form, From 1b426ac5224c50b8e7732736b33e972a0b035d0f Mon Sep 17 00:00:00 2001 From: "pierre-francois.duc" Date: Mon, 22 Apr 2024 12:50:04 +0200 Subject: [PATCH 3/3] Allow plotting the sankey diagram for single timesteps --- app/dashboard/models.py | 38 ++++++++++++++----- app/dashboard/urls.py | 5 +++ app/dashboard/views.py | 11 ++++-- app/templates/report/single_scenario.html | 10 ++++- .../scenario/scenario_results_page.html | 10 ++++- 5 files changed, 58 insertions(+), 16 deletions(-) diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 9ac7471d8..cf0c579df 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -1368,7 +1368,8 @@ def graph_costs( return simulations_results -def graph_sankey(simulation, energy_vector): +def graph_sankey(simulation, energy_vector, timestep=None): + ts = timestep if isinstance(energy_vector, list) is False: energy_vector = [energy_vector] if energy_vector is not None: @@ -1395,9 +1396,14 @@ def graph_sankey(simulation, energy_vector): labels.append(bus_label) colors.append("blue") - asset_to_bus_names = qs.filter(bus=bus.name, direction="in").values_list( - "asset", "total_flow" - ) + if ts is None: + asset_to_bus_names = qs.filter( + bus=bus.name, direction="in" + ).values_list("asset", "total_flow") + else: + asset_to_bus_names = qs.filter( + bus=bus.name, direction="in" + ).values_list("asset", "flow_data") for component_label, val in asset_to_bus_names: # draw link from the component to the bus @@ -1408,17 +1414,25 @@ def graph_sankey(simulation, energy_vector): sources.append(labels.index(component_label)) targets.append(labels.index(bus_label)) + if ts is not None: + val = json.loads(val) + val = val[ts] + if component_label in chp_in_flow: chp_in_flow[component_label]["value"] += val if val == 0: - val = 1e-6 - + val = 1e-9 values.append(val) + if ts is None: + bus_to_asset_names = qs.filter( + bus=bus.name, direction="out" + ).values_list("asset", "total_flow") + else: + bus_to_asset_names = qs.filter( + bus=bus.name, direction="out" + ).values_list("asset", "flow_data") - bus_to_asset_names = qs.filter(bus=bus.name, direction="out").values_list( - "asset", "total_flow" - ) # TODO potentially rename feedin period and consumption period for component_label, val in bus_to_asset_names: # draw link from the bus to the component @@ -1432,8 +1446,12 @@ def graph_sankey(simulation, energy_vector): if component_label in chp_in_flow: chp_in_flow[component_label]["bus"] = bus_label + if ts is not None: + val = json.loads(val) + val = val[ts] + if val == 0: - val = 1e-6 + val = 1e-9 values.append(val) # TODO display the installed capacity, max capacity and optimized_add_capacity on the nodes if applicable diff --git a/app/dashboard/urls.py b/app/dashboard/urls.py index 0976c132e..44d4e8de0 100644 --- a/app/dashboard/urls.py +++ b/app/dashboard/urls.py @@ -93,6 +93,11 @@ scenario_visualize_sankey, name="scenario_visualize_sankey", ), + re_path( + r"^scenario/results/request_sankey/(?P\d+)?(/(?P\d+))?$", + scenario_visualize_sankey, + name="scenario_visualize_sankey", + ), re_path( r"^project/(?P\d+)/scenario/results/request-capacities/(?P\d+)?$", scenario_visualize_capacities, diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 0c6061601..6e1a2d3ce 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -221,11 +221,13 @@ def scenario_visualize_results(request, proj_id=None, scen_id=None): topology_data_list = load_scenario_topology_from_db(scen_id) + timestamps = scenario.get_timestamps() answer = render( request, "report/single_scenario.html", { "scen_id": scen_id, + "timestamps": timestamps, "proj_id": proj_id, "project_list": user_projects, "scenario_list": user_scenarios, @@ -1117,18 +1119,21 @@ def scenario_visualize_costs(request, proj_id, scen_id=None): # TODO: Sector coupling must be refined (including transformer flows) -def scenario_visualize_sankey(request, scen_id): +def scenario_visualize_sankey(request, scen_id, ts=None): scenario = get_object_or_404(Scenario, pk=scen_id) if (scenario.project.user != request.user) and ( scenario.project.viewers.filter(user__email=request.user.email).exists() is False ): raise PermissionDenied - + if ts is not None: + ts = int(ts) results_json = report_item_render_to_json( report_item_id="sankey", data=REPORT_GRAPHS[GRAPH_SANKEY]( - simulation=scenario.simulation, energy_vector=scenario.energy_vectors + simulation=scenario.simulation, + energy_vector=scenario.energy_vectors, + timestep=ts, ), title="Sankey", report_item_type=GRAPH_SANKEY, diff --git a/app/templates/report/single_scenario.html b/app/templates/report/single_scenario.html index fa84c0e4c..d8c21e221 100644 --- a/app/templates/report/single_scenario.html +++ b/app/templates/report/single_scenario.html @@ -110,6 +110,12 @@
{% include "report/graph_template.html" with id="sankey" title="Sankey diagram" %} + {% include "report/graph_template.html" with id="capacities" title="Installed and optimized capacities" %} {% include "report/graph_template.html" with id="stacked_timeseries" title="Stacked timeseries by sector" %} {% include "report/graph_template.html" with id="all_timeseries" title="All timeseries" %} @@ -128,6 +134,8 @@ {% block results_end_body_scripts %} {% endif %} -{% endblock results_end_body_scripts %} \ No newline at end of file +{% endblock results_end_body_scripts %} diff --git a/app/templates/scenario/scenario_results_page.html b/app/templates/scenario/scenario_results_page.html index 4b61a5770..2d201705e 100644 --- a/app/templates/scenario/scenario_results_page.html +++ b/app/templates/scenario/scenario_results_page.html @@ -348,9 +348,15 @@

{% translate "Simulation results" %}

}; -function scenario_visualize_sankey(scen_id){ +function scenario_visualize_sankey(scen_id, ts=null){ + var urlParams = scen_id; + if( ts === null || ts === ""){ + } + else{ + urlParams = scen_id + "/" + ts; + } $.ajax({ - url: "{% url 'scenario_visualize_sankey' %}" + scen_id, + url: "{% url 'scenario_visualize_sankey' %}" + urlParams, type: "GET", success: async (parameters) => { await graph_type_mapping[parameters.type](parameters.id, parameters);