diff --git a/app/compose/production/app_postgres/Dockerfile b/app/compose/production/app_postgres/Dockerfile index 0f96f7409..ae0a1dd99 100644 --- a/app/compose/production/app_postgres/Dockerfile +++ b/app/compose/production/app_postgres/Dockerfile @@ -10,16 +10,16 @@ RUN mkdir ${CONFIG_ROOT} COPY app/requirements/base.txt ${CONFIG_ROOT}/base.txt COPY app/requirements/production.txt ${CONFIG_ROOT}/production.txt +# Install gettext utilities for translations +RUN apt-get update && \ + apt-get install -y --no-install-recommends gettext-base gettext git && \ + rm -rf /var/lib/apt/lists/* RUN pip install --upgrade pip \ && pip install --no-cache-dir -r ${CONFIG_ROOT}/production.txt WORKDIR ${APP_ROOT} -# Install gettext utilities for translations -RUN apt-get update && \ - apt-get install -y --no-install-recommends gettext-base gettext && \ - rm -rf /var/lib/apt/lists/* ADD app/ ${APP_ROOT} diff --git a/app/projects/forms.py b/app/projects/forms.py index c9ce93dc6..10c129bb7 100644 --- a/app/projects/forms.py +++ b/app/projects/forms.py @@ -834,7 +834,7 @@ def __init__(self, *args, **kwargs): """ for field in self.fields: if field == "renewable_asset" and self.asset_type_name in RENEWABLE_ASSETS: - self.fields[field].initial = True + self.initial[field] = True self.fields[field].widget.attrs.update({f"df-{field}": ""}) if field == "input_timeseries": self.fields[field].required = self.is_input_timeseries_empty() @@ -1130,15 +1130,15 @@ def __init__(self, *args, **kwargs): asset_type_name = kwargs.pop("asset_type", None) super(StorageForm, self).__init__(*args, asset_type="capacity", **kwargs) self.fields["dispatchable"].widget = forms.HiddenInput() - self.fields["dispatchable"].initial = True + self.initial["dispatchable"] = True if asset_type_name != "hess": self.fields["fixed_thermal_losses_relative"].widget = forms.HiddenInput() - self.fields["fixed_thermal_losses_relative"].initial = 0 + self.initial["fixed_thermal_losses_relative"] = 0 self.fields["fixed_thermal_losses_absolute"].widget = forms.HiddenInput() - self.fields["fixed_thermal_losses_absolute"].initial = 0 + self.initial["fixed_thermal_losses_absolute"] = 0 self.fields["thermal_loss_rate"].widget = forms.HiddenInput() - self.fields["thermal_loss_rate"].initial = 0 + self.initial["thermal_loss_rate"] = 0 else: field_name = "fixed_thermal_losses_relative" help_text = self.fields[field_name].help_text diff --git a/app/projects/management/commands/datapackage.py b/app/projects/management/commands/datapackage.py index 0910e2bba..b9ab85495 100644 --- a/app/projects/management/commands/datapackage.py +++ b/app/projects/management/commands/datapackage.py @@ -1,17 +1,9 @@ from django.core.management.base import BaseCommand, CommandError -from projects.models import ( - Asset, - AssetType, - TopologyNode, - Scenario, - Timeseries, - ConnectionLink, - Bus, -) -import pandas as pd +from projects.models import Scenario from pathlib import Path -import numpy as np import shutil +from oemof.datapackage.datapackage import building +import datapackage as dp class Command(BaseCommand): @@ -19,94 +11,37 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument("scen_id", nargs="+", type=int) - - parser.add_argument( - "--overwrite", action="store_true", help="Overwrite the datapackage" - ) + parser.add_argument("-o", "--outfile", type=str, nargs="?", const="") def handle(self, *args, **options): - overwrite = options["overwrite"] for scen_id in options["scen_id"]: try: scenario = Scenario.objects.get(pk=scen_id) except Scenario.DoesNotExist: raise CommandError('Scenario "%s" does not exist' % scen_id) - - destination_path = Path(__file__).resolve().parents[4] - - # Create a folder with a datapackage structure - scenario_folder = destination_path / f"scenario_{scen_id}" - create_folder = True - - if scenario_folder.exists(): - if not overwrite: - create_folder = False - else: - shutil.rmtree(scenario_folder) - - elements_folder = scenario_folder / "data" / "elements" - sequences_folder = scenario_folder / "data" / "sequences" - - if create_folder: - # create subfolders - (scenario_folder / "scripts").mkdir(parents=True) - elements_folder.mkdir(parents=True) - sequences_folder.mkdir(parents=True) - - # List all components of the scenario (except the busses) - qs_assets = Asset.objects.filter(scenario=scenario) - # List all distinct components' assettypes (or facade name) - facade_names = qs_assets.distinct().values_list( - "asset_type__asset_type", flat=True + destination_path = options["outfile"] + if destination_path == "": + destination_path = Path(__file__).resolve().parents[4] + else: + destination_path = Path(destination_path) + + scenario_folder = destination_path / f"scenario_{scenario.name}".replace( + " ", "_" ) + if scenario_folder.exists(): + shutil.rmtree(scenario_folder) - bus_resource_records = [] - profile_resource_records = {} - for facade_name in facade_names: - resource_records = [] - for i, asset in enumerate( - qs_assets.filter(asset_type__asset_type=facade_name) - ): - resource_rec, bus_resource_rec, profile_resource_rec = ( - asset.to_datapackage() - ) - resource_records.append(resource_rec) - # those constitute the busses and sequences used by this asset - bus_resource_records.extend(bus_resource_rec) - profile_resource_records.update(profile_resource_rec) - - if resource_records: - out_path = elements_folder / f"{facade_name}.csv" - Path(out_path).parent.mkdir(parents=True, exist_ok=True) - df = pd.DataFrame(resource_records) - df.to_csv(out_path, index=False) + dp_json = scenario_folder / "datapackage.json" - # Save all unique busses to a elements resource - if bus_resource_records: - out_path = elements_folder / f"bus.csv" - Path(out_path).parent.mkdir(parents=True, exist_ok=True) - df = pd.DataFrame(bus_resource_records) - df.drop_duplicates("name").to_csv(out_path, index=False) + if dp_json.exists(): + print("Only inferring metadata") + p = dp.Package(str(dp_json)) + building.infer_package_foreign_keys(p, fk_targets=["project"]) + p.descriptor["resources"].sort(key=lambda x: (x["path"], x["name"])) + p.commit() + p.save(dp_json) - # Save all profiles to a sequences resource - if profile_resource_records: - out_path = sequences_folder / f"profiles.csv" - Path(out_path).parent.mkdir(parents=True, exist_ok=True) - # add timestamps to the profiles - profile_resource_records["timeindex"] = scenario.get_timestamps() - try: - df = pd.DataFrame(profile_resource_records) - except ValueError as e: - # If not all profiles have the same length we pad the shorter profiles with np.nan - max_len = max(len(v) for v in profile_resource_records.values()) - profile_resource_records = { - k: v + [np.nan] * (max_len - len(v)) - for k, v in profile_resource_records.items() - } - df = pd.DataFrame(profile_resource_records) - print( - f"Some profiles have more timesteps that other profiles in scenario {scenario.name}({scen_id}) --> the shorter profiles will be expanded with NaN values" - ) - # TODO check if there are column duplicates - df.set_index("timeindex").to_csv(out_path, index=True) + else: + print("Creating datapackage.json") + scenario.to_datapackage(destination_path) diff --git a/app/projects/models/base_models.py b/app/projects/models/base_models.py index 91c553405..3a4c626e5 100644 --- a/app/projects/models/base_models.py +++ b/app/projects/models/base_models.py @@ -1,7 +1,13 @@ import datetime import json +import logging import uuid from datetime import timedelta +import pandas as pd +from pathlib import Path +import numpy as np +from oemof.datapackage.datapackage import building + import oemof.thermal.compression_heatpumps_and_chillers as cmpr_hp_chiller from django.conf import settings @@ -106,6 +112,17 @@ def export(self, bind_scenario_data=True): dm["scenario_set_data"] = scenario_data return dm + def to_datapackage(self): + """""" + dp = model_to_dict(self.economic_data, exclude=["id", "currency"]) + dp["name"] = self.name + dp["type"] = "project" + dp["discount_factor"] = dp.pop("discount") + dp["lifetime"] = dp.pop("duration") + dp["shortage_cost"] = 999 + dp["excess_cost"] = 99 + return dp + def add_viewer_if_not_exist(self, email=None, share_rights=""): user = None success = False @@ -297,6 +314,94 @@ def export(self, bind_project_data=False): dm["busses"] = busses return dm + def to_datapackage(self, destination_path): + + # Create a folder with a datapackage structure + scenario_folder = destination_path / f"scenario_{self.name}".replace(" ", "_") + + data_folder = scenario_folder / "data" + elements_folder = data_folder / "elements" + sequences_folder = data_folder / "sequences" + + # create subfolders + (scenario_folder / "scripts").mkdir(parents=True) + elements_folder.mkdir(parents=True) + sequences_folder.mkdir(parents=True) + + # Save the project specifics + proj = self.project + out_path = data_folder / f"project.csv" + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + df = pd.DataFrame([proj.to_datapackage()]) + df.drop_duplicates("name").to_csv(out_path, index=False) + + # List all components of the scenario (except the busses) + qs_assets = Asset.objects.filter(scenario=self) + # List all distinct components' assettypes (or facade name) which are not children + facade_names = ( + qs_assets.filter(parent_asset__isnull=True) + .distinct() + .values_list("asset_type__asset_type", flat=True) + ) + + bus_resource_records = [] + profile_resource_records = {} + for facade_name in facade_names: + resource_records = [] + for i, asset in enumerate( + qs_assets.filter(asset_type__asset_type=facade_name) + ): + resource_rec, bus_resource_rec, profile_resource_rec = ( + asset.to_datapackage() + ) + resource_records.append(resource_rec) + # those constitute the busses and sequences used by this asset + bus_resource_records.extend(bus_resource_rec) + profile_resource_records.update(profile_resource_rec) + + if resource_records: + out_path = elements_folder / f"{facade_name}.csv" + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + df = pd.DataFrame(resource_records) + df.to_csv(out_path, index=False) + + # Save all unique busses to a elements resource + if bus_resource_records: + out_path = elements_folder / f"bus.csv" + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + df = pd.DataFrame(bus_resource_records) + df.drop_duplicates("name").to_csv(out_path, index=False) + + # Save all profiles to a sequences resource + if profile_resource_records: + out_path = sequences_folder / f"profiles.csv" + Path(out_path).parent.mkdir(parents=True, exist_ok=True) + # add timestamps to the profiles + profile_resource_records["timeindex"] = self.get_timestamps() + try: + df = pd.DataFrame(profile_resource_records) + except ValueError as e: + # If not all profiles have the same length we pad the shorter profiles with np.nan + max_len = max(len(v) for v in profile_resource_records.values()) + profile_resource_records = { + k: v + [np.nan] * (max_len - len(v)) + for k, v in profile_resource_records.items() + } + df = pd.DataFrame(profile_resource_records) + logging.warning( + 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 + df.set_index("timeindex").to_csv(out_path, index=True) + + # creating datapackage.json metadata file at the root of the datapackage + building.infer_metadata_from_data( + package_name=f"scenario_{self.name}".replace(" ", "_"), + path=scenario_folder, + fk_targets=["project"], + ) + return scenario_folder + def get_default_timeseries(): return list([]) @@ -662,24 +767,94 @@ def input_timeseries_values(self): return answer def to_datapackage(self): + """Return the asset's attributes in a datapackage form""" dp = {"type": self.asset_type.asset_type} + if ( + "demand" not in self.asset_type.asset_type + and "dso" not in self.asset_type.asset_type + ): + dp["project_data"] = self.scenario.project.name # to collect the timeseries used by the asset profile_resource_rec = {} - for field in self.asset_type.visible_fields: - value = getattr(self, field) - # if the field is a candidate for a scalar/list - if isinstance(value, str) and field != "name": - value = json.loads(value) - if isinstance(value, list): - col = f"{self.name}__{field}" - profile_resource_rec[col] = value + + # Storage assets are the only one to have children (namely `capacity`, `charge` and `discharge` + qs_children = Asset.objects.filter(parent_asset__id=self.id) + if qs_children.exists(): + # Only keep the values from the capacity children asset of the storage + capacity = qs_children.get(asset_type__asset_type="capacity") + for attribute in [ + "capex_fix", + "capex_var", + "opex_fix", + "opex_var", + "lifetime", + "crate", + "efficiency", + "soc_max", + "soc_min", + "maximum_capacity", + "optimize_cap", + "installed_capacity", + "age_installed", + "thermal_loss_rate", # only for hess + "fixed_thermal_losses_relative", # only for hess + "fixed_thermal_losses_absolute", # only for hess + ]: + setattr(self, attribute, getattr(capacity, attribute)) + + if self.asset_type.asset_type != "hess": + attributes = [ + f + for f in AssetType.objects.get(asset_type="capacity").visible_fields + if f + not in ( + "thermal_loss_rate", + "fixed_thermal_losses_relative", + "fixed_thermal_losses_absolute", + ) + ] + else: + attributes = AssetType.objects.get(asset_type="capacity").visible_fields + else: + attributes = self.asset_type.visible_fields + + for field in attributes: + if ( + field != "dispatchable" + ): # TODO remove this when `dispatchable` not a visible field anymore + value = getattr(self, field) + # if the field is a candidate for a scalar/list + if isinstance(value, str) and field != "name": + value = json.loads(value) + if isinstance(value, list): + col = f"{self.name}__{field}" + profile_resource_rec[col] = value + value = col + elif isinstance(value, Timeseries): + col = value.name + profile_resource_rec[col] = value.values value = col - elif isinstance(value, Timeseries): - col = value.name - profile_resource_rec[col] = value.values - value = col - dp[field] = value + if self.asset_type.asset_type == "chp_fixed_ratio": + if field == "efficiency": + field = "conversion_factor_to_electricity" + elif field == "efficiency_multiple": + field = "conversion_factor_to_heat" + elif self.asset_type.asset_type == "chp": + if field == "thermal_loss_rate": + field = "beta" + elif field == "efficiency": + field = "conversion_factor_to_electricity" + elif field == "efficiency_multiple": + field = "conversion_factor_to_heat" + elif self.asset_type.asset_type == "heat_pump": + if field == "efficiency": + field = "cop" + + elif self.asset_type.asset_type == "electrolyzer": + if field == "efficiency_multiple": + field = "efficiency_heat" + dp[field] = value # to collect the bus(ses) used by the asset bus_resource_rec = [] @@ -829,7 +1004,8 @@ class Bus(TopologyNode): def to_datapackage(self): dm = model_to_dict(self, fields=["type", "name"]) - dm["facade"] = "bus" + dm["carrier"] = dm["type"] + dm["type"] = "CarrierBus" dm["balanced"] = "True" dm["excess"] = "False" dm["excess_costs"] = "0.0" diff --git a/app/projects/urls.py b/app/projects/urls.py index 16fb3b13b..d21bd956a 100644 --- a/app/projects/urls.py +++ b/app/projects/urls.py @@ -143,6 +143,11 @@ name="scenario_duplicate", ), path("scenario/export/", scenario_export, name="scenario_export"), + path( + "scenario/export/datapackage/", + scenario_export_as_datapackage, + name="scenario_export_as_datapackage", + ), path("scenario/upload/", scenario_upload, name="scenario_upload"), # path('scenario/upload/', LoadScenarioFromFileView.as_view(), name='scenario_upload'), # Timeseries Model diff --git a/app/projects/views.py b/app/projects/views.py index a97fb8d91..23f90a104 100644 --- a/app/projects/views.py +++ b/app/projects/views.py @@ -1,4 +1,9 @@ # from bootstrap_modal_forms.generic import BSModalCreateView +import tempfile +from pathlib import Path +import zipfile + + from django.contrib.auth.decorators import login_required import datetime from django.http import ( @@ -1360,6 +1365,38 @@ def scenario_export(request, proj_id): return response +@login_required +@require_http_methods(["GET"]) +def scenario_export_as_datapackage(request, scen_id): + + scenario = get_object_or_404(Scenario, id=int(scen_id)) + + if scenario.project.user != request.user: + raise PermissionDenied + + 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) + + 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()) + + zip_buffer.seek(0) + response = HttpResponse(zip_buffer.getvalue(), content_type="application/zip") + response["Content-Disposition"] = ( + f'attachment; filename="datapackage_scenario_{scen_id}.zip"' + ) + + return response + + @login_required @require_http_methods(["POST"]) def scenario_delete(request, scen_id): diff --git a/app/requirements/base.txt b/app/requirements/base.txt index 01a1acc43..3b78bb46a 100644 --- a/app/requirements/base.txt +++ b/app/requirements/base.txt @@ -2,6 +2,7 @@ Django==4.2.4 django-bootstrap-modal-forms==2.0.0 django-compressor==4.1 django-crispy-forms==1.9.2 +django-environ django-extensions==3.0.9 django-jsonview==2.0.0 django-q==1.3.4 @@ -12,12 +13,11 @@ httpx==0.23.0 jsonschema==4.4.0 libsass==0.21.0 numpy>=1.22.4 -oemof-solph==0.4.4 -oemof-thermal==0.0.5 +oemof-datapackage +oemof-thermal==0.0.8 openpyxl==3.0.10 plotly==5.6.0 psycopg2-binary==2.9.3 requests==2.24.0 -XlsxWriter==1.3.9 -django-environ whitenoise==6.9.0 +XlsxWriter==1.3.9 diff --git a/app/static/assettypes_list.csv b/app/static/assettypes_list.csv index 05d6c700c..71fc4c15e 100644 --- a/app/static/assettypes_list.csv +++ b/app/static/assettypes_list.csv @@ -1,33 +1,33 @@ asset_type,asset_category,energy_vector,mvs_type,asset_fields,unit,ports -dso,energy_provider,Electricity,source,"[name,energy_price,feedin_tariff,peak_demand_pricing,peak_demand_pricing_period,renewable_share,feedin_cap]",,"{'output_1': ['to_bus','Electricity']}" -gas_dso,energy_provider,Gas,source,"[name,energy_price,feedin_tariff,peak_demand_pricing,peak_demand_pricing_period,renewable_share,feedin_cap]",,"{'output_1': ['to_bus','Gas']}" -h2_dso,energy_provider,H2,source,"[name,energy_price,feedin_tariff,peak_demand_pricing,peak_demand_pricing_period,renewable_share,feedin_cap]",,"{'output_1': ['to_bus','H2']}" -heat_dso,energy_provider,Heat,source,"[name,energy_price,feedin_tariff,peak_demand_pricing,peak_demand_pricing_period,renewable_share,feedin_cap]",,"{'output_1': ['to_bus','Heat']}" -demand,energy_consumption,Electricity,sink,"[name,input_timeseries]",kWh,"{'input_1':['from_bus','Electricity']}" -gas_demand,energy_consumption,Gas,sink,"[name,input_timeseries]",,"{'input_1':['from_bus','Gas']}" -h2_demand,energy_consumption,H2,sink,"[name,input_timeseries]",,"{'input_1':['from_bus','H2']}" -heat_demand,energy_consumption,Heat,sink,"[name,input_timeseries]",,"{'input_1':['from_bus','Heat']}" -transformer_station_in,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency]",kVA,"{'input_1':['from_bus','Electricity'],'output_1': ['to_bus','Electricity']}" -transformer_station_out,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency]",kVA,"{'input_1':['from_bus','Electricity'],'output_1': ['to_bus','Electricity']}" -storage_charge_controller_in,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,efficiency]",kVA,"{'input_1':['from_bus','Electricity'],'output_1': ['to_bus','Electricity']}" -storage_charge_controller_out,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,efficiency]",kVA,"{'input_1':['from_bus','Electricity'],'output_1': ['to_bus','Electricity']}" -solar_inverter,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,efficiency]",kVA,"{'input_1':['from_bus','Electricity'],'output_1': ['to_bus','Electricity']}" -diesel_generator,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency]",kVA,"{'input_1':['from_bus','Gas'],'output_1': ['to_bus','Electricity']}" -fuel_cell,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,efficiency]",kVA,"{'input_1':['from_bus','H2'],'output_1': ['to_bus','Electricity']}" -gas_boiler,energy_conversion,Gas,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency]",kW,"{'input_1':['from_bus','Gas'],'output_1': ['to_bus','Heat']}" -electrolyzer,energy_conversion,H2,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency,efficiency_multiple]",kVA,"{'input_1':['el_bus','Electricity'],'output_1': ['heat_bus','Heat'], 'output_2': ['h2_bus','H2']}" -heat_pump,energy_conversion,Heat,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_var,opex_fix,lifetime,optimize_cap,maximum_capacity,efficiency]",kVA,"{'input_1':['electricity_bus','Electricity'], 'input_2': ['heat_bus','Heat'],'output_1': ['to_bus','Heat']}" -pv_plant,energy_production,Electricity,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",kWp,"{'output_1': ['to_bus','Electricity']}" -wind_plant,energy_production,Electricity,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",kW,"{'output_1': ['to_bus','Electricity']}" -biogas_plant,energy_production,Gas,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",,"{'output_1': ['to_bus','Gas']}" -geothermal_conversion,energy_production,Electricity,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",kWh,"{'output_1': ['to_bus','Heat']}" -solar_thermal_plant,energy_production,Heat,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",m³,"{'output_1': ['to_bus','Heat']}" +dso,energy_provider,Electricity,source,"[name,energy_price,feedin_tariff,peak_demand_pricing,peak_demand_pricing_period,renewable_share,feedin_cap]",,"{'output_1': ['bus_electricity','Electricity']}" +gas_dso,energy_provider,Gas,source,"[name,energy_price,feedin_tariff,peak_demand_pricing,peak_demand_pricing_period,renewable_share,feedin_cap]",,"{'output_1': ['bus_fuel','Gas']}" +h2_dso,energy_provider,H2,source,"[name,energy_price,feedin_tariff,peak_demand_pricing,peak_demand_pricing_period,renewable_share,feedin_cap]",,"{'output_1': ['bus_h2','H2']}" +heat_dso,energy_provider,Heat,source,"[name,energy_price,feedin_tariff,peak_demand_pricing,peak_demand_pricing_period,renewable_share,feedin_cap]",,"{'output_1': ['bus_heat','Heat']}" +demand,energy_consumption,Electricity,sink,"[name,input_timeseries]",kWh,"{'input_1':['bus_in_electricity','Electricity']}" +gas_demand,energy_consumption,Gas,sink,"[name,input_timeseries]",,"{'input_1':['bus_in_fuel','Gas']}" +h2_demand,energy_consumption,H2,sink,"[name,input_timeseries]",,"{'input_1':['bus_in_h2','H2']}" +heat_demand,energy_consumption,Heat,sink,"[name,input_timeseries]",,"{'input_1':['bus_in_heat','Heat']}" +transformer_station_in,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency]",kVA,"{'input_1':['bus_in_electricity','Electricity'],'output_1': ['bus_out_electricity','Electricity']}" +transformer_station_out,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency]",kVA,"{'input_1':['bus_in_electricity','Electricity'],'output_1': ['bus_out_electricity','Electricity']}" +storage_charge_controller_in,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,efficiency]",kVA,"{'input_1':['bus_in_electricity','Electricity'],'output_1': ['bus_out_electricity','Electricity']}" +storage_charge_controller_out,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,efficiency]",kVA,"{'input_1':['bus_in_electricity','Electricity'],'output_1': ['bus_out_electricity','Electricity']}" +solar_inverter,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,efficiency]",kVA,"{'input_1':['bus_in_electricity','Electricity'],'output_1': ['bus_out_electricity','Electricity']}" +diesel_generator,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency]",kVA,"{'input_1':['bus_in_fuel','Gas'],'output_1': ['bus_out_electricity','Electricity']}" +fuel_cell,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,efficiency]",kVA,"{'input_1':['bus_in_h2','H2'],'output_1': ['bus_out_electricity','Electricity']}" +gas_boiler,energy_conversion,Gas,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency]",kW,"{'input_1':['bus_in_fuel','Gas'],'output_1': ['bus_out_heat','Heat']}" +electrolyzer,energy_conversion,H2,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency,efficiency_multiple]",kVA,"{'input_1':['bus_in_electricity','Electricity'],'output_1': ['bus_out_heat','Heat'], 'output_2': ['bus_out_h2','H2']}" +heat_pump,energy_conversion,Heat,transformer,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_var,opex_fix,lifetime,optimize_cap,maximum_capacity,efficiency]",kVA,"{'input_1':['bus_in_electricity','Electricity'], 'input_2': ['bus_in_heat','Heat'],'output_1': ['bus_out_heat','Heat']}" +pv_plant,energy_production,Electricity,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",kWp,"{'output_1': ['bus_out_electricity','Electricity']}" +wind_plant,energy_production,Electricity,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",kW,"{'output_1': ['bus_out_electricity','Electricity']}" +biogas_plant,energy_production,Gas,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",,"{'output_1': ['bus_out_fuel','Gas']}" +geothermal_conversion,energy_production,Electricity,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",kWh,"{'output_1': ['bus_out_heat','Heat']}" +solar_thermal_plant,energy_production,Heat,source,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,renewable_asset,input_timeseries]",m³,"{'output_1': ['bus_out_heat','Heat']}" charging_power,energy_storage,Electricity,storage,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,crate,efficiency,dispatchable]",kW,"{'input_1':['charge','Electricity'],'output_1': ['discharge','Electricity']}" discharging_power,energy_storage,Electricity,storage,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,crate,efficiency,dispatchable]",kW,"{'input_1':['charge','Electricity'],'output_1': ['discharge','Electricity']}" capacity,energy_storage,Electricity,storage,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_fix,opex_var,lifetime,optimize_cap,maximum_capacity,efficiency,soc_max,soc_min,crate,dispatchable,thermal_loss_rate,fixed_thermal_losses_relative,fixed_thermal_losses_absolute]",kWh,"{'input_1':['charge','Electricity'],'output_1': ['discharge','Electricity']}" -bess,energy_storage,Electricity,storage,[name],kW (el),"{'input_1':['charge','Electricity'],'output_1': ['discharge','Electricity']}" -gess,energy_storage,Gas,storage,[name],l,"{'input_1':['charge','Gas'],'output_1': ['discharge','Gas']}" -h2ess,energy_storage,H2,storage,[name],kgH2,"{'input_1':['charge','H2'],'output_1': ['discharge','H2']}" -hess,energy_storage,Heat,storage,[name],kW (therm),"{'input_1':['charge','Heat'],'output_1': ['discharge','Heat']}" -chp,energy_conversion,Electricity,extractionTurbineCHP,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_var,opex_fix,lifetime,optimize_cap,maximum_capacity,efficiency_multiple,efficiency,thermal_loss_rate]",kW,"{'input_1':['fuel','Gas'],'output_1': ['heat_bus','Heat'], 'output_2': ['electricity_bus','Electricity']}" -chp_fixed_ratio,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_var,opex_fix,lifetime,optimize_cap,maximum_capacity,efficiency_multiple,efficiency]",kW,"{'input_1':['fuel','Gas'],'output_1': ['heat_bus','Heat'], 'output_2': ['electricity_bus','Electricity']}" +bess,energy_storage,Electricity,storage,[name],kW (el),"{'input_1':['bus_in_electricity','Electricity'],'output_1': ['bus_out_electricity','Electricity']}" +gess,energy_storage,Gas,storage,[name],l,"{'input_1':['bus_in_fuel','Gas'],'output_1': ['bus_out_fuel','Gas']}" +h2ess,energy_storage,H2,storage,[name],kgH2,"{'input_1':['bus_in_h2','H2'],'output_1': ['bus_out_h2','H2']}" +hess,energy_storage,Heat,storage,[name],kW (therm),"{'input_1':['bus_in_heat','Heat'],'output_1': ['bus_out_heat','Heat']}" +chp,energy_conversion,Electricity,extractionTurbineCHP,"[name,age_installed,installed_capacity,capex_fix,capex_var,opex_var,opex_fix,lifetime,optimize_cap,maximum_capacity,efficiency_multiple,efficiency,thermal_loss_rate]",kW,"{'input_1':['bus_in_fuel','Gas'],'output_1': ['bus_out_heat','Heat'], 'output_2': ['bus_out_electricity','Electricity']}" +chp_fixed_ratio,energy_conversion,Electricity,transformer,"[name,age_installed,installed_capacity,capex_var,opex_fix,lifetime,optimize_cap,maximum_capacity,efficiency_multiple,efficiency]",kW,"{'input_1':['bus_in_fuel','Gas'],'output_1': ['bus_out_heat','Heat'], 'output_2': ['bus_out_electricity','Electricity']}" diff --git a/app/templates/scenario/scenario_progression.html b/app/templates/scenario/scenario_progression.html index 0da181fd8..5978043fd 100644 --- a/app/templates/scenario/scenario_progression.html +++ b/app/templates/scenario/scenario_progression.html @@ -36,10 +36,16 @@

+ + {% if scen_id %} + + {% endif %} + +