Skip to content

Commit 69bdbfc

Browse files
authored
Merge pull request #222 from open-plan-tool/change/results-graphs
Add diverse new graph types
2 parents d134061 + 07a37e7 commit 69bdbfc

File tree

24 files changed

+580
-377
lines changed

24 files changed

+580
-377
lines changed

app/dashboard/forms.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ def __init__(self, *args, **kwargs):
142142
super().__init__(*args, **kwargs)
143143

144144

145+
class LoadDurationGraphForm(forms.Form):
146+
energy_vector = forms.ChoiceField(
147+
label=_("Energy vector"), choices=ENERGY_VECTOR, initial=ENERGY_VECTOR[1][0]
148+
)
149+
150+
def __init__(self, *args, **kwargs):
151+
scen_ids = kwargs.pop("scenario_ids", None)
152+
super().__init__(*args, **kwargs)
153+
154+
145155
class SensitivityAnalysisGraphForm(ModelForm):
146156
def __init__(self, *args, **kwargs):
147157
proj_id = kwargs.pop("proj_id", None)
@@ -173,6 +183,8 @@ def graph_parameters_form_factory(report_type, *args, **kwargs):
173183
answer = StackedTimeseriesGraphForm(*args, **kwargs)
174184
if report_type == GRAPH_CAPACITIES:
175185
answer = StackedCapacitiesGraphForm(*args, **kwargs)
186+
if report_type == GRAPH_LOAD_DURATION:
187+
answer = LoadDurationGraphForm(*args, **kwargs)
176188
# GRAPH_BAR,
177189
# GRAPH_PIE,
178190
# GRAPH_LOAD_DURATION,

app/dashboard/helpers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,12 @@ def decode_sa_graph_id(report_id):
528528
},
529529
GRAPH_BAR: {},
530530
GRAPH_PIE: {},
531-
GRAPH_LOAD_DURATION: {},
531+
GRAPH_LOAD_DURATION: {
532+
"type": "object",
533+
"required": ["energy_vector"],
534+
"properties": {"energy_vector": {"type": "string"}},
535+
"additionalProperties": False,
536+
},
532537
GRAPH_SANKEY: {
533538
"type": "object",
534539
"required": ["energy_vector"],
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 3.2.15 on 2022-11-14 00:21
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("projects", "0011_capping_non_required_crate_greater_than_one"),
11+
("dashboard", "0002_initial"),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name="FlowResults",
17+
fields=[
18+
(
19+
"id",
20+
models.AutoField(
21+
auto_created=True,
22+
primary_key=True,
23+
serialize=False,
24+
verbose_name="ID",
25+
),
26+
),
27+
("flow_data", models.TextField()),
28+
(
29+
"simulation",
30+
models.ForeignKey(
31+
on_delete=django.db.models.deletion.CASCADE,
32+
to="projects.simulation",
33+
),
34+
),
35+
],
36+
)
37+
]

app/dashboard/models.py

Lines changed: 238 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import numpy as np
77
import plotly.graph_objects as go
8+
import pandas as pd
89

910

1011
from django.utils.translation import ugettext_lazy as _
@@ -151,6 +152,195 @@ class KPICostsMatrixResults(models.Model):
151152
simulation = models.ForeignKey(Simulation, on_delete=models.CASCADE)
152153

153154

155+
class OemofBusResults(pd.DataFrame): # real results
156+
def __init__(self, results):
157+
158+
js = json.loads(results)
159+
mindex = pd.MultiIndex.from_tuples(
160+
js["columns"],
161+
names=[
162+
"bus",
163+
"energy_vector",
164+
"direction",
165+
"asset",
166+
"asset_type",
167+
"oemof_type",
168+
],
169+
)
170+
df = pd.DataFrame(data=js["data"], columns=mindex)
171+
172+
ts_df = df.iloc[:-1]
173+
ts_index = pd.to_datetime(js["index"][:-1], unit="ms")
174+
investments = df.iloc[-1]
175+
ts_df.index = ts_index
176+
177+
super().__init__(
178+
data=ts_df.T.to_dict(orient="split")["data"],
179+
index=mindex,
180+
columns=ts_df.index,
181+
)
182+
183+
self["investments"] = investments
184+
self.sort_index(inplace=True)
185+
186+
def to_json(self, **kwargs):
187+
kwargs["orient"] = "split"
188+
return self.T.to_json(**kwargs)
189+
190+
def bus_flows(self):
191+
return self.loc[:, self.columns != "investments"]
192+
193+
def asset_optimized_capacities(self):
194+
return self.loc[:, "investments"]
195+
196+
def asset_optimized_capacity(self, asset_name):
197+
optimized_capacity = self.loc[
198+
self.index.get_level_values("asset") == asset_name, "investments"
199+
].dropna()
200+
if len(optimized_capacity) == 1:
201+
optimized_capacity = optimized_capacity[0]
202+
return optimized_capacity
203+
204+
205+
class FlowResults(models.Model):
206+
flow_data = models.TextField() # to store the assets list
207+
simulation = models.ForeignKey(Simulation, on_delete=models.CASCADE)
208+
__df_flows = None
209+
__df_capacities = None
210+
211+
@property
212+
def df_flows(self):
213+
if self.__df_flows is None:
214+
self.__df_flows = OemofBusResults(self.flow_data).bus_flows()
215+
216+
return self.__df_flows
217+
218+
def asset_optimized_capacity(self, asset_name):
219+
return OemofBusResults(self.flow_data).asset_optimized_capacity(asset_name)
220+
221+
@property
222+
def busses(self):
223+
"""returns a mapping of the bus to their energy_vectors"""
224+
return {
225+
k: v
226+
for k, v in zip(
227+
self.df_flows.index.get_level_values("bus"),
228+
self.df_flows.index.get_level_values("energy_vector"),
229+
)
230+
}
231+
232+
def single_bus_flows(self, bus_name):
233+
df_bus = self.df_flows.loc[bus_name]
234+
energy_vector = df_bus.index.get_level_values("energy_vector").unique()[0]
235+
df_bus.index = df_bus.index.droplevel(
236+
["asset_type", "energy_vector", "oemof_type"]
237+
)
238+
df = pd.concat(
239+
[
240+
df_bus.loc[df_bus.index.get_level_values("direction") == "in"].T,
241+
df_bus.loc[df_bus.index.get_level_values("direction") == "out"].T * -1,
242+
],
243+
axis=1,
244+
)
245+
df.name = bus_name
246+
247+
df.energy_vector = energy_vector
248+
249+
return df
250+
251+
def single_bus_flows_figure(self, bus_name):
252+
df = self.single_bus_flows(bus_name)
253+
fig = go.Figure(
254+
data=[
255+
go.Scatter(
256+
x=df.index.tolist(),
257+
y=df.loc[:, col].values.tolist(),
258+
name=col[1],
259+
stackgroup=col[0],
260+
)
261+
for col in df.columns
262+
],
263+
layout=dict(
264+
title=f"{bus_name} ({df.energy_vector})", hovermode="x unified"
265+
),
266+
)
267+
268+
return fig.to_dict()
269+
270+
# def all_bus_flows_figure(self, exclude=None):
271+
# if exclude is None:
272+
# exclude = []
273+
# df = self.single_bus_flows(bus_name)
274+
# fig = go.Figure(
275+
# data=[
276+
# go.Scatter(
277+
# x=df.index, y=df.loc[:, col].values, name=col[1], stackgroup=col[0]
278+
# )
279+
# for col in df.columns
280+
# ],
281+
# layout=dict(
282+
# title=f"{bus_name} ({df.energy_vector})", hovermode="x unified"
283+
# ),
284+
# )
285+
#
286+
# return fig.to_dict()
287+
288+
def load_duration_figure(self, energy_vector):
289+
df_consumption = (
290+
self.df_flows.loc[
291+
(self.df_flows.index.get_level_values("direction") == "out")
292+
& (
293+
self.df_flows.index.get_level_values("energy_vector")
294+
== energy_vector
295+
)
296+
]
297+
.groupby(level="asset_type")
298+
.sum()
299+
.T
300+
)
301+
302+
# df_consumption["excess"] *= 0
303+
df_consumption = df_consumption.sum(axis=1)
304+
df_production = (
305+
self.df_flows.loc[
306+
(self.df_flows.index.get_level_values("direction") == "in")
307+
& (
308+
self.df_flows.index.get_level_values("energy_vector")
309+
== energy_vector
310+
)
311+
]
312+
.groupby(level="asset_type")
313+
.sum()
314+
.T
315+
)
316+
percentage = np.linspace(0, 100, df_production.index.size)
317+
fig = go.Figure(
318+
data=[
319+
go.Scatter(
320+
x=percentage.tolist(),
321+
y=df_production.loc[:, col]
322+
.sort_values(ascending=False)
323+
.values.tolist(),
324+
name=col,
325+
stackgroup="production",
326+
)
327+
for col in df_production.columns
328+
]
329+
+ [
330+
go.Scatter(
331+
x=percentage.tolist(),
332+
y=df_consumption.sort_values(ascending=False).values.tolist(),
333+
name="demand",
334+
)
335+
],
336+
layout=dict(
337+
title=f"Load duration curve for {energy_vector}", hovermode="x unified"
338+
),
339+
)
340+
341+
return fig.to_dict()
342+
343+
154344
class AssetsResults(models.Model):
155345
assets_list = models.TextField() # to store the assets list
156346
simulation = models.ForeignKey(Simulation, on_delete=models.CASCADE)
@@ -466,6 +656,8 @@ def graph_capacities(simulations, y_variables):
466656

467657
assets_results_obj = AssetsResults.objects.get(simulation=simulation)
468658

659+
# qs = FlowResults.objects.filter(simulation=simulation)
660+
469661
results_dict = json.loads(simulation.results)
470662

471663
kpi_scalar_matrix = results_dict["kpi"]["scalar_matrix"]
@@ -484,25 +676,45 @@ def graph_capacities(simulations, y_variables):
484676
else _("Opt. Cap.") + f"{simulation.scenario.name} (kW)",
485677
}
486678
for y_var in y_variables:
487-
679+
do_not_add = False
488680
if "@" not in y_var:
489681
asset = assets_results_obj.single_asset_results(asset_name=y_var)
490-
x_values.append(y_var)
682+
491683
if asset is not None:
492684
installed_cap = asset["installed_capacity"]["value"]
685+
print(asset["asset_type"])
686+
if "dso" in asset["asset_type"] or "demand" in asset["asset_type"]:
687+
do_not_add = True
493688
else:
494689
installed_cap = 0
495-
installed_capacity_dict["capacity"].append(installed_cap)
690+
if do_not_add is False:
691+
x_values.append(y_var)
692+
installed_capacity_dict["capacity"].append(installed_cap)
693+
# TODO have all graphs made via this way
694+
# if qs.exists():
695+
# flow_results = qs.get()
696+
#
697+
# optimized_cap = flow_results.asset_optimized_capacity(y_var)
698+
#
699+
# if isinstance(optimized_cap, pd.Series):
700+
# if optimized_cap.empty is False:
701+
# optimized_cap = optimized_cap[
702+
# optimized_cap.index.get_level_values("direction")
703+
# == "out"
704+
# ].values[0]
705+
# else:
706+
# optimized_cap = None
707+
# else:
496708
if y_var in kpi_scalar_matrix:
497-
optimized_capacity_dict["capacity"].append(
498-
kpi_scalar_matrix[y_var]["optimized_add_cap"]
499-
)
709+
optimized_cap = kpi_scalar_matrix[y_var]["optimized_add_cap"]
500710
else:
501711
optimized_cap = 0
502712
if asset is not None:
503713
if "optimized_add_cap" in asset:
504714
optimized_cap = asset["optimized_add_cap"]["value"]
715+
if optimized_cap is not None and do_not_add is False:
505716
optimized_capacity_dict["capacity"].append(optimized_cap)
717+
506718
y_values.append(installed_capacity_dict)
507719
y_values.append(optimized_capacity_dict)
508720

@@ -800,6 +1012,26 @@ def fetch_parameters_values(self):
8001012
simulation=self.simulations.get(), energy_vector=energy_vector
8011013
)
8021014

1015+
if self.report_type == GRAPH_LOAD_DURATION:
1016+
energy_vector = parameters.get("energy_vector", None)
1017+
1018+
simulation = self.simulations.get()
1019+
# if isinstance(energy_vector, list) is False:
1020+
# energy_vector = [energy_vector]
1021+
if energy_vector is not None:
1022+
1023+
sim = simulation
1024+
qs = FlowResults.objects.filter(simulation=sim)
1025+
if qs.exists():
1026+
flow_results = qs.get()
1027+
fig_dict = flow_results.load_duration_figure(energy_vector)
1028+
else:
1029+
fig_dict = {
1030+
"layout": {"title": "There is an error with this graph."}
1031+
}
1032+
1033+
return fig_dict
1034+
8031035

8041036
def get_project_reportitems(project):
8051037
"""Given a project, return the ReportItem instances linked to that project"""

app/dashboard/urls.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,6 @@
4848
ajax_get_sensitivity_analysis_parameters,
4949
name="ajax_get_sensitivity_analysis_parameters",
5050
),
51-
path(
52-
"scenario/results/request/<int:scen_id>",
53-
scenario_request_results,
54-
name="scenario_request_results",
55-
),
5651
path(
5752
"scenario/results/available/<int:scen_id>",
5853
scenario_available_results,

0 commit comments

Comments
 (0)