Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion app/projects/models/base_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,19 @@ def export(self, bind_project_data=False):
dm["busses"] = busses
return dm

def to_datapackage(self, destination_path):
def to_datapackage(self, destination_path, number=None):
"""

Parameters
----------
destination_path: Path
Path where the datapackage should be saved
number: int
Number of timesteps which should be considered for the exported datapackage
Returns
-------
A Path to the scenario datapackage
"""
# Create a folder with a datapackage structure
scenario_folder = destination_path / f"scenario_{self.name}".replace(" ", "_")

Expand Down Expand Up @@ -392,6 +404,10 @@ def to_datapackage(self, destination_path):
f"Some profiles have more timesteps that other profiles in scenario {self.name}({self.id}) --> the shorter profiles will be expanded with NaN values"
)
# TODO check if there are column duplicates

# restrict the size of the profiles
if number is not None:
df = df.iloc[:number]
df.set_index("timeindex").to_csv(out_path, index=True)

# creating datapackage.json metadata file at the root of the datapackage
Expand Down Expand Up @@ -859,6 +875,12 @@ def to_datapackage(self):
# to collect the bus(ses) used by the asset
bus_resource_rec = []

qs_unmapped_ports = self.connectionlink_set.filter(
asset_connection_port="no_mapping"
)
for connection in qs_unmapped_ports:
connection.assign_port_if_missing()

if hasattr(self.asset_type, "connection_ports"):
# port mapping contains the information to what bus is expected to be connected to which port
port_mapping = self.asset_type.connection_ports
Expand Down Expand Up @@ -1042,6 +1064,48 @@ def __str__(self):
asset_connection_port = "input_1"
return f"{self.bus.name}.{self.bus_connection_port} → {self.asset.name}.{asset_connection_port} (scenario {self.scenario.name})"

def assign_port_if_missing(self):
asset_connection_port = self.asset_connection_port
flow_direction = self.flow_direction
if asset_connection_port == "no_mapping":
logging.warning(
"A connection had no mapping to asset port, probably old scenario, assigning a mapping ..."
)

energy_vector = self.bus.type
asset_type = self.asset.asset_type
if flow_direction == "A2B":
asset_connection_port = "output_1"
if asset_type.n_outputs > 1:
qs = asset_type.ports.filter(
energy_vector=energy_vector, direction="output"
)
if qs.exists():
asset_connection_port = qs.first().port_key
else:
logging.warning(
f"No output port with energy carrier for {energy_vector} found within the port mapping of the component {db_connection.asset.name}"
)

elif flow_direction == "B2A":
asset_connection_port = "input_1"
if asset_type.n_inputs > 1:
qs = asset_type.ports.filter(
energy_vector=energy_vector, direction="input"
)
if qs.exists():
asset_connection_port = qs.first().port_key
else:
logging.warning(
f"No input port with energy carrier for {energy_vector} found within the port mapping of the component {db_connection.asset.name}"
)
self.asset_connection_port = asset_connection_port
self.save(update_fields=["asset_connection_port"])
logging.warning(
f"... the asset {self.asset.name} port to connect to the bus {self.bus.name} was set to {asset_connection_port}"
)
return asset_connection_port


class Constraint(models.Model):
scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE, null=False)
Expand Down
38 changes: 2 additions & 36 deletions app/projects/scenario_topology_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,43 +438,9 @@ def db_connection_links_to_list(scen_id):
all_db_connection_links = ConnectionLink.objects.filter(scenario_id=scen_id)
connections_list = list()
for db_connection in all_db_connection_links:
asset_connection_port = db_connection.asset_connection_port
flow_direction = db_connection.flow_direction
if asset_connection_port == "no_mapping":
logging.warning(
"A connection had no mapping to asset port, probably old scenario, assigning a mapping ..."
)

energy_vector = db_connection.bus.type
asset_type = db_connection.asset.asset_type
if flow_direction == "A2B":
asset_connection_port = "output_1"
if asset_type.n_outputs > 1:
qs = asset_type.ports.filter(
energy_vector=energy_vector, direction="output"
)
if qs.exists():
asset_connection_port = qs.first().port_key
else:
logging.warning(
f"No output port with energy carrier for {energy_vector} found within the port mapping of the component {db_connection.asset.name}"
)
asset_connection_port = db_connection.assign_port_if_missing()

elif flow_direction == "B2A":
asset_connection_port = "input_1"
if asset_type.n_inputs > 1:
qs = asset_type.ports.filter(
energy_vector=energy_vector, direction="input"
)
if qs.exists():
asset_connection_port = qs.first().port_key
else:
logging.warning(
f"No input port with energy carrier for {energy_vector} found within the port mapping of the component {db_connection.asset.name}"
)
logging.warning(
f"... the asset {db_connection.asset.name} port to connect to the bus {db_connection.bus.name} was set to {asset_connection_port}"
)
flow_direction = db_connection.flow_direction
db_connection_dict = {
"bus_id": db_connection.bus_id,
"asset_id": db_connection.asset.unique_id,
Expand Down
15 changes: 15 additions & 0 deletions app/projects/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,21 @@
scenario_export_as_datapackage,
name="scenario_export_as_datapackage",
),
path(
"scenario/export/datapackage/<int:scen_id>/number/<int:n_timestamps>",
scenario_export_as_datapackage,
name="scenario_export_as_datapackage",
),
path(
"project/export/datapackage/<int:proj_id>",
project_export_as_datapackage,
name="project_export_as_datapackage",
),
path(
"project/export/datapackage/<int:proj_id>/number/<int:n_timestamps>",
project_export_as_datapackage,
name="project_export_as_datapackage",
),
path("scenario/upload/<int:proj_id>", scenario_upload, name="scenario_upload"),
# path('scenario/upload/<int:proj_id>', LoadScenarioFromFileView.as_view(), name='scenario_upload'),
# Timeseries Model
Expand Down
38 changes: 36 additions & 2 deletions app/projects/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1351,7 +1351,7 @@ def scenario_export(request, proj_id):

@login_required
@require_http_methods(["GET"])
def scenario_export_as_datapackage(request, scen_id):
def scenario_export_as_datapackage(request, scen_id, n_timestamps=None):
scenario = get_object_or_404(Scenario, id=int(scen_id))

if scenario.project.user != request.user:
Expand All @@ -1360,7 +1360,7 @@ def scenario_export_as_datapackage(request, scen_id):
with tempfile.TemporaryDirectory() as temp_dir:
destination_path = Path(temp_dir)
# write the content of the scenario into a temp directory
scenario_folder = scenario.to_datapackage(destination_path)
scenario_folder = scenario.to_datapackage(destination_path, number=n_timestamps)

# Place the temp directory into a zip folder
zip_buffer = io.BytesIO()
Expand All @@ -1382,6 +1382,40 @@ def scenario_export_as_datapackage(request, scen_id):
return response


@login_required
@require_http_methods(["GET"])
def project_export_as_datapackage(request, proj_id, n_timestamps=None):
project = get_object_or_404(Project, id=int(proj_id))

if project.user != request.user:
raise PermissionDenied

with tempfile.TemporaryDirectory() as temp_dir:
destination_path = Path(temp_dir)

for scenario in project.scenario_set.all():
scenario.to_datapackage(destination_path, number=n_timestamps)

# Place the temp directory into a zip folder
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w") as zip_file:
for file_path in destination_path.rglob("*"): # Recursively walk all files
if file_path.is_file():
# Relative path inside ZIP
arcname = file_path.relative_to(destination_path)
with open(file_path, "rb") as f:
zip_file.writestr(str(arcname), f.read())

# Let the user download the zip file
zip_buffer.seek(0)
response = HttpResponse(zip_buffer.getvalue(), content_type="application/zip")
response["Content-Disposition"] = (
f'attachment; filename="datapackage_project_{proj_id}.zip"'
)

return response


@login_required
@require_http_methods(["POST"])
def scenario_delete(request, scen_id):
Expand Down
Loading