diff --git a/app/dashboard/helpers.py b/app/dashboard/helpers.py index 5ea7eeb3..c70a3b2c 100644 --- a/app/dashboard/helpers.py +++ b/app/dashboard/helpers.py @@ -25,6 +25,7 @@ EMPTY_SUBCAT = "none" KPI_PARAMETERS = {} +KPI_PARAMETERS_ASSETS = {} if os.path.exists(staticfiles_storage.path("MVS_kpis_list.csv")) is True: with open(staticfiles_storage.path("MVS_kpis_list.csv")) as csvfile: @@ -65,17 +66,24 @@ for i, row in enumerate(csvreader): if i == 0: hdr = [el.replace(" ", "_").replace(":", "").lower() for el in row] - print(hdr) label_idx = hdr.index("label") cat_idx = hdr.index("category") + scope_idx = hdr.index("scope") else: label = row[label_idx] category = row[cat_idx] + scope = row[scope_idx] + if category != "files": KPI_PARAMETERS[label] = { k: _(v) if k == "verbose" or k == "definition" else v for k, v in zip(hdr, row) } + if "asset" in scope: + KPI_PARAMETERS_ASSETS[label] = { + k: _(v) if k == "verbose" or k == "definition" else v + for k, v in zip(hdr, row) + } #### FUNCTIONS #### diff --git a/app/epa/settings.py b/app/epa/settings.py index 68b98868..a25a9ce0 100644 --- a/app/epa/settings.py +++ b/app/epa/settings.py @@ -184,6 +184,8 @@ MVS_POST_URL = f"{MVS_API_HOST}/sendjson/openplan" MVS_GET_URL = f"{MVS_API_HOST}/check/" MVS_LP_FILE_URL = f"{MVS_API_HOST}/get_lp_file/" +MVS_SA_POST_URL = f"{MVS_API_HOST}/sendjson/openplan/sensitivity-analysis" +MVS_SA_GET_URL = f"{MVS_API_HOST}/check-sensitivity-analysis/" # Allow iframes to show in page X_FRAME_OPTIONS = "SAMEORIGIN" diff --git a/app/projects/dtos.py b/app/projects/dtos.py index 40b7e5fb..b485fdf6 100644 --- a/app/projects/dtos.py +++ b/app/projects/dtos.py @@ -4,7 +4,16 @@ from numpy.core import long from datetime import date, datetime, time -from projects.models import * +from projects.models import ( + ConnectionLink, + Scenario, + Project, + EconomicData, + Asset, + Bus, + Constraint, + ValueType, +) class ProjectDataDto: diff --git a/app/projects/forms.py b/app/projects/forms.py index 2155fd0f..5b4bd665 100644 --- a/app/projects/forms.py +++ b/app/projects/forms.py @@ -23,6 +23,8 @@ from projects.models import * from projects.constants import MAP_EPA_MVS, RENEWABLE_ASSETS +from dashboard.helpers import KPI_PARAMETERS_ASSETS + from django.utils.translation import ugettext_lazy as _ from django.conf import settings as django_settings @@ -36,7 +38,11 @@ label_idx = hdr.index("label") else: label = row[label_idx] - PARAMETERS[label] = {k: v for k, v in zip(hdr, row)} + PARAMETERS[label] = {} + for k, v in zip(hdr, row): + if k == "sensitivity_analysis": + v = bool(int(v)) + PARAMETERS[label][k] = v def gettext_variables(some_string, lang="de"): @@ -71,7 +77,7 @@ def set_parameter_info(param_name, field, parameters=PARAMETERS): verbose = None default_value = None if param_name in PARAMETERS: - help_text = PARAMETERS[param_name][":Definition:"] + help_text = PARAMETERS[param_name][":Definition_Short:"] unit = PARAMETERS[param_name][":Unit:"] verbose = PARAMETERS[param_name]["verbose"] default_value = PARAMETERS[param_name][":Default:"] @@ -481,6 +487,54 @@ class Meta: exclude = ["scenario", "value"] +class SensitivityAnalysisForm(ModelForm): + output_parameters_names = forms.MultipleChoiceField( + choices=[ + (v, _(KPI_PARAMETERS_ASSETS[v]["verbose"])) for v in KPI_PARAMETERS_ASSETS + ] + ) + + class Meta: + model = SensitivityAnalysis + fields = [ + "variable_name", + "variable_min", + "variable_max", + "variable_step", + "variable_reference", + "output_parameters_names", + ] + + def __init__(self, *args, **kwargs): + scen_id = kwargs.pop("scen_id", None) + super().__init__(*args, **kwargs) + + forbidden_parameters_for_sa = ("name", "input_timeseries") + + if scen_id is not None: + scenario = Scenario.objects.get(id=scen_id) + asset_parameters = [] + for asset in scenario.asset_set.all(): + asset_parameters += [ + (f"{asset.name}.{p}", _(p) + f" ({asset.name})") + for p in asset.visible_fields + if p not in forbidden_parameters_for_sa + ] + self.fields["variable_name"] = forms.ChoiceField(choices=asset_parameters) + # self.fields["output_parameters_names"] = forms.MultipleChoiceField(choices = [(v, _(KPI_PARAMETERS_ASSETS[v]["verbose"])) for v in KPI_PARAMETERS_ASSETS]) + # TODO restrict possible parameters here + self.fields["output_parameters_names"].choices = [ + (v, _(KPI_PARAMETERS_ASSETS[v]["verbose"])) + for v in KPI_PARAMETERS_ASSETS + ] + + def clean_output_parameters_names(self): + """method which gets called upon form validation""" + data = self.cleaned_data["output_parameters_names"] + data_js = json.dumps(data) + return data_js + + class BusForm(OpenPlanModelForm): def __init__(self, *args, **kwargs): bus_type_name = kwargs.pop("asset_type", None) # always = bus diff --git a/app/projects/helpers.py b/app/projects/helpers.py new file mode 100644 index 00000000..a513267a --- /dev/null +++ b/app/projects/helpers.py @@ -0,0 +1,121 @@ +import json +from projects.dtos import convert_to_dto + + +# Helper method to clean dict data from empty values +def remove_empty_elements(d): + def empty(x): + return x is None or x == {} or x == [] + + if not isinstance(d, (dict, list)): + return d + elif isinstance(d, list): + return [v for v in (remove_empty_elements(v) for v in d) if not empty(v)] + else: + return { + k: v + for k, v in ((k, remove_empty_elements(v)) for k, v in d.items()) + if not empty(v) + } + + +# Helper to convert Scenario data to MVS importable json +def format_scenario_for_mvs(scenario_to_convert): + mvs_request_dto = convert_to_dto(scenario_to_convert) + dumped_data = json.loads( + json.dumps(mvs_request_dto.__dict__, default=lambda o: o.__dict__) + ) + + # format the constraints in MVS format directly, thus avoiding the need to maintain MVS-EPA + # parser in multi-vector-simulator package + constraint_dict = {} + for constraint in dumped_data["constraints"]: + constraint_dict[constraint["label"]] = constraint["value"] + dumped_data["constraints"] = constraint_dict + + # Remove None values + return remove_empty_elements(dumped_data) + + +def sensitivity_analysis_payload( + variable_parameter_name="", + variable_parameter_range="", + variable_parameter_ref_val="", + output_parameter_names=None, +): + """format the parameters required to request a sensitivity analysis in a specific JSON""" + if output_parameter_names is None: + output_parameter_names = [] + return { + "sensitivity_analysis_settings": { + "variable_parameter_name": variable_parameter_name, + "variable_parameter_range": variable_parameter_range, + "variable_parameter_ref_val": variable_parameter_ref_val, + "output_parameter_names": output_parameter_names, + } + } + + +SA_RESPONSE_SCHEMA = { + "type": "object", + "required": ["server_info", "mvs_version", "id", "status", "results"], + "properties": { + "server_info": {"type": "string"}, + "mvs_version": {"type": "string"}, + "id": {"type": "string"}, + "status": {"type": "string"}, + "results": { + "type": "object", + "required": ["reference_simulation_id", "sensitivity_analysis_steps"], + "properties": { + "reference_simulation_id": {"type": "string"}, + "sensitivity_analysis_steps": { + "type": "array", + "items": {"type": "object"}, + }, + }, + "additionalProperties": False, + }, + "ref_sim_id": {"type": "string"}, + "sensitivity_analysis_ids": {"type": "array", "items": {"type": "string"}}, + }, + "additionalProperties": False, +} + + +# Used to proof the json objects stored as text in the db +SA_OUPUT_NAMES_SCHEMA = {"type": "array", "items": {"type": "string"}} + + +def sa_output_values_schema_generator(output_names): + return { + "type": "object", + "required": output_names, + "properties": { + output_name: { + "type": "object", + "required": ["value", "path"], + "properties": { + "value": { + "oneOf": [ + {"type": "null"}, + { + "type": "array", + "items": { + "anyOf": [{"type": "number"}, {"type": "null"}] + }, + }, + ] + }, + "path": { + "oneOf": [ + {"type": "string"}, + {"type": "array", "items": {"type": "string"}}, + ] + }, + }, + } + for output_name in output_names + }, + "additionalProperties": False, + } diff --git a/app/projects/models/__init__.py b/app/projects/models/__init__.py new file mode 100644 index 00000000..97d33510 --- /dev/null +++ b/app/projects/models/__init__.py @@ -0,0 +1,2 @@ +from .base_models import * +from .simulation_models import Simulation, SensitivityAnalysis diff --git a/app/projects/models.py b/app/projects/models/base_models.py similarity index 89% rename from app/projects/models.py rename to app/projects/models/base_models.py index 3945e9b6..ff17a1ce 100644 --- a/app/projects/models.py +++ b/app/projects/models/base_models.py @@ -1,6 +1,5 @@ import uuid import json -from django.core.validators import MinValueValidator, MaxValueValidator from django.conf import settings from django.db import models from django.core.validators import MaxValueValidator, MinValueValidator @@ -8,7 +7,7 @@ from django.forms.models import model_to_dict from django.utils.translation import gettext_lazy as _ from django.core.exceptions import ValidationError -from .constants import ( +from projects.constants import ( ASSET_CATEGORY, ASSET_TYPE, COUNTRY, @@ -17,6 +16,7 @@ FLOW_DIRECTION, MVS_TYPE, SIMULATION_STATUS, + PENDING, TRUE_FALSE_CHOICES, BOOL_CHOICES, USER_RATING, @@ -174,6 +174,10 @@ def export(self): dm = model_to_dict(self, exclude=["id"]) return dm + @property + def visible_fields(self): + return self.asset_fields.replace("[", "").replace("]", "").split(",") + class TopologyNode(models.Model): name = models.CharField(max_length=60, null=False, blank=False) @@ -279,6 +283,28 @@ def save(self, *args, **kwargs): def fields(self): return [f.name for f in self._meta.fields + self._meta.many_to_many] + @property + def visible_fields(self): + return self.asset_type.visible_fields + + def has_parameter(self, param_name): + return param_name in self.visible_fields + + def parameter_path(self, param_name): + # TODO for storage + if self.has_parameter(param_name): + # TODO if (unit, value) formatting, add "value" at the end + if self.asset_type.asset_category == "energy_provider": + asset_category = "energy_providers" + else: + asset_category = self.asset_type.asset_category + if param_name == "optimize_cap": + param_name = "optimize_capacity" + answer = (asset_category, self.name, param_name) + else: + answer = None + return answer + @property def timestamps(self): return self.scenario.get_timestamps() @@ -393,15 +419,17 @@ class ScenarioFile(models.Model): file = models.FileField(upload_to="tempFiles/", null=True, blank=True) -class Simulation(models.Model): +class AbstractSimulation(models.Model): + start_date = models.DateTimeField(auto_now_add=True, null=False) end_date = models.DateTimeField(null=True) elapsed_seconds = models.FloatField(null=True) mvs_token = models.CharField(max_length=200, null=True) - status = models.CharField(max_length=20, choices=SIMULATION_STATUS, null=False) - scenario = models.OneToOneField(Scenario, on_delete=models.CASCADE, null=False) - user_rating = models.PositiveSmallIntegerField( - null=True, choices=USER_RATING, default=None + status = models.CharField( + max_length=20, choices=SIMULATION_STATUS, null=False, default=PENDING ) results = models.TextField(null=True, max_length=30e6) errors = models.TextField(null=True) + + class Meta: + abstract = True diff --git a/app/projects/models/simulation_models.py b/app/projects/models/simulation_models.py new file mode 100644 index 00000000..22e96468 --- /dev/null +++ b/app/projects/models/simulation_models.py @@ -0,0 +1,178 @@ +from .base_models import AbstractSimulation, Scenario + + +import json +import jsonschema +import numpy as np +import logging + +logger = logging.getLogger(__name__) +from django.db import models +from django.core.validators import MaxValueValidator, MinValueValidator + +from django.utils.translation import gettext_lazy as _ +from datetime import datetime + +from projects.constants import USER_RATING, DONE, PENDING, ERROR +from projects.helpers import ( + sensitivity_analysis_payload, + SA_OUPUT_NAMES_SCHEMA, + sa_output_values_schema_generator, + SA_RESPONSE_SCHEMA, + format_scenario_for_mvs, +) + +from dashboard.helpers import nested_dict_crawler + + +class Simulation(AbstractSimulation): + scenario = models.OneToOneField(Scenario, on_delete=models.CASCADE, null=False) + user_rating = models.PositiveSmallIntegerField( + null=True, choices=USER_RATING, default=None + ) + + +class SensitivityAnalysis(AbstractSimulation): + name = models.CharField(max_length=50) + # attribute linked to output_parameter_names + output_parameters_names = models.TextField() + # attributes linked to variable_parameter_name + variable_name = models.CharField(max_length=100) + # attributes to compute the variable_parameter_range + variable_min = models.FloatField() + variable_max = models.FloatField() + variable_step = models.FloatField(validators=[MinValueValidator(0.0)]) + # attributes linked to variable_parameter_ref_val + variable_reference = models.FloatField( + help_text=_( + "The value of the variable parameter for the reference scenario of the sensitivity analysis, if is it different than the value of the current scenario user need to decide whether to take current scenario as reference with its actual value for this parameter, duplicate the scenario and use the duplicata with chosen reference value or keep current scenario but change parameter value to chosen one" + ) + ) # label=_("Variable parameter of the reference scenario"), + scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE, null=True) + # attribute linked to the output values of the sensitivity analysis + output_parameters_values = models.TextField() + nested_dict_pathes = None + + # TODO look at jsonschema to create a custon TextField --> https://dev.to/saadullahaleem/adding-validation-support-for-json-in-django-models-5fbm + + def save(self, *args, **kwargs): + # if self.output_parameters_names is not None: + # self.output_parameters_names = json.dumps(self.output_parameters_names) + super().save(*args, **kwargs) + if self.scenario is not None: + self.nested_dict_pathes = nested_dict_crawler( + format_scenario_for_mvs(self.scenario) + ) + + def set_reference_scenario(self, scenario): + self.scenario = scenario + self.save() + + @property + def variable_range(self): + return np.arange( + self.variable_min, self.variable_max, self.variable_step + ).tolist() + + @property + def output_names(self): + try: + answer = json.loads(self.output_parameters_names) + try: + jsonschema.validate(answer, SA_OUPUT_NAMES_SCHEMA) + except jsonschema.exceptions.ValidationError: + answer = [] + except json.decoder.JSONDecodeError: + answer = [] + + return answer + + @property + def output_values(self): + try: + out_values = json.loads(self.output_parameters_values) + answer = {} + for in_value, sa_step in zip(self.variable_range, out_values): + try: + jsonschema.validate( + sa_step, sa_output_values_schema_generator(self.output_names) + ) + answer[in_value] = sa_step + except jsonschema.exceptions.ValidationError: + answer[in_value] = None + except json.decoder.JSONDecodeError: + answer = {} + return answer + + def parse_server_response(self, sa_results): + try: + # make sure the response is formatted as expected + jsonschema.validate(sa_results, SA_RESPONSE_SCHEMA) + self.status = sa_results["status"] + self.errors = ( + json.dumps(sa_results["results"][ERROR]) + if self.status == ERROR + else None + ) + if self.status == DONE: + sa_steps = sa_results["results"]["sensitivity_analysis_steps"] + sa_steps_processed = [] + # make sure that each step is formatted as expected + for step_idx, sa_step in enumerate(sa_steps): + try: + jsonschema.validate( + sa_step, + sa_output_values_schema_generator(self.output_names), + ) + sa_steps_processed.append(sa_step) + except jsonschema.exceptions.ValidationError as e: + logger.error( + f"Could not parse the results of the sensitivity analysis {self.id} for step {step_idx}" + ) + sa_steps_processed.append(None) + self.output_parameters_values = json.dumps(sa_steps_processed) + + except jsonschema.exceptions.ValidationError as e: + self.status = ERROR + self.output_parameters_values = "" + self.errors = str(e) + + self.elapsed_seconds = (datetime.now() - self.start_date).seconds + self.end_date = ( + datetime.now() if sa_results["status"] in [ERROR, DONE] else None + ) + self.save() + + @property + def payload(self): + return sensitivity_analysis_payload( + variable_parameter_name=self.variable_name_path, + variable_parameter_range=self.variable_range, + variable_parameter_ref_val=self.variable_reference, + output_parameter_names=self.output_names, + ) + + @property + def variable_name_path(self): + """Provided with a (nested) dict, find the path to the variable_name""" + if self.nested_dict_pathes is None: + variable_name_path = self.variable_name + else: + if "." in self.variable_name: + asset_name, variable_name = self.variable_name.split(".") + else: + variable_name = self.variable_name + asset_name = None + variable_name_path = self.nested_dict_pathes.get(variable_name, None) + if variable_name_path is None: + if asset_name is not None: + asset = self.scenario.asset_set.get(name=asset_name) + variable_name_path = asset.parameter_path(variable_name) + if variable_name_path is None: + logging.error( + f"The variable '{self.variable_name}' cannot be found in the scenario json structure" + ) + + if isinstance(variable_name_path, list): + variable_name_path = variable_name_path[0] + return variable_name_path diff --git a/app/projects/requests.py b/app/projects/requests.py index 7da0b2ef..1fe67164 100644 --- a/app/projects/requests.py +++ b/app/projects/requests.py @@ -3,7 +3,13 @@ import json # from requests.exceptions import HTTPError -from epa.settings import PROXY_CONFIG, MVS_POST_URL, MVS_GET_URL +from epa.settings import ( + PROXY_CONFIG, + MVS_POST_URL, + MVS_GET_URL, + MVS_SA_POST_URL, + MVS_SA_GET_URL, +) from dashboard.models import AssetsResults, KPICostsMatrixResults, KPIScalarResults from projects.constants import DONE, PENDING, ERROR import logging @@ -53,7 +59,24 @@ def mvs_simulation_check_status(token): return json.loads(response.text) -def fetch_mvs_simulation_status(simulation): +def mvs_sa_check_status(token): + try: + response = requests.get( + MVS_SA_GET_URL + token, proxies=PROXY_CONFIG, verify=False + ) + response.raise_for_status() + except requests.HTTPError as http_err: + logger.error(f"HTTP error occurred: {http_err}") + return None + except Exception as err: + logger.error(f"Other error occurred: {err}") + return None + else: + logger.info("Success!") + return json.loads(response.text) + + +def fetch_mvs_simulation_results(simulation): if simulation.status == PENDING: response = mvs_simulation_check_status(token=simulation.mvs_token) try: @@ -79,6 +102,20 @@ def fetch_mvs_simulation_status(simulation): ) simulation.save() + return simulation.status != PENDING + + +def fetch_mvs_sa_results(simulation): + if simulation.status == PENDING: + response = mvs_sa_check_status(token=simulation.mvs_token) + + simulation.parse_server_response(response) + + if simulation.status == DONE: + logger.info(f"The simulation {simulation.id} is finished") + + return simulation.status != PENDING + def get_mvs_simulation_results(simulation): # TODO do not repeat if the simulation is not on the server anymore, or if the results are already loaded @@ -99,7 +136,7 @@ def get_mvs_simulation_results(simulation): simulation.save() else: - fetch_mvs_simulation_status(simulation) + fetch_mvs_simulation_results(simulation) def parse_mvs_results(simulation, response_results): @@ -149,3 +186,30 @@ def parse_mvs_results(simulation, response_results): assets_list=json.dumps(data_subdict), simulation=simulation ) return response_results + + +def mvs_sensitivity_analysis_request(data: dict): + + headers = {"content-type": "application/json"} + payload = json.dumps(data) + + try: + response = requests.post( + MVS_SA_POST_URL, + data=payload, + headers=headers, + proxies=PROXY_CONFIG, + verify=False, + ) + + # If the response was successful, no Exception will be raised + response.raise_for_status() + except requests.HTTPError as http_err: + logger.error(f"HTTP error occurred: {http_err}") + return None + except Exception as err: + logger.error(f"Other error occurred: {err}") + return None + else: + logger.info("The simulation was sent successfully to MVS API.") + return json.loads(response.text) diff --git a/app/projects/scenario_topology_helpers.py b/app/projects/scenario_topology_helpers.py index 9fd1238c..f8e509fe 100644 --- a/app/projects/scenario_topology_helpers.py +++ b/app/projects/scenario_topology_helpers.py @@ -14,7 +14,6 @@ from projects.forms import AssetCreateForm, BusForm, StorageForm # region sent db nodes to js -from projects.dtos import convert_to_dto from crispy_forms.templatetags import crispy_forms_filters from django.http import JsonResponse import logging @@ -502,38 +501,3 @@ def create_ESS_objects(all_ess_assets_node_list, scen_id): if asset.name == "capacity": # check if there is a connection link to a bus pass - - -# Helper method to clean dict data from empty values -def remove_empty_elements(d): - def empty(x): - return x is None or x == {} or x == [] - - if not isinstance(d, (dict, list)): - return d - elif isinstance(d, list): - return [v for v in (remove_empty_elements(v) for v in d) if not empty(v)] - else: - return { - k: v - for k, v in ((k, remove_empty_elements(v)) for k, v in d.items()) - if not empty(v) - } - - -# Helper to convert Scenario data to MVS importable json -def get_topology_json(scenario_to_convert): - mvs_request_dto = convert_to_dto(scenario_to_convert) - dumped_data = json.loads( - json.dumps(mvs_request_dto.__dict__, default=lambda o: o.__dict__) - ) - - # format the constraints in MVS format directly, thus avoiding the need to maintain MVS-EPA - # parser in multi-vector-simulator package - constraint_dict = {} - for constraint in dumped_data["constraints"]: - constraint_dict[constraint["label"]] = constraint["value"] - dumped_data["constraints"] = constraint_dict - - # Remove None values - return remove_empty_elements(dumped_data) diff --git a/app/projects/urls.py b/app/projects/urls.py index 37b5b35b..433a86c5 100644 --- a/app/projects/urls.py +++ b/app/projects/urls.py @@ -123,20 +123,36 @@ name="update_simulation_rating", ), # path('topology/simulation_status/', check_simulation_status, name='check_simulation_status'), - re_path( - r"^topology/simulation_status/(?P\d+)?$", - check_simulation_status, - name="check_simulation_status_regex", + path( + "simulation/fetch-results/", + fetch_simulation_results, + name="fetch_simulation_results", + ), + # Sensitivity analysis + path( + "scenario//sensitivity-analysis/create", + sensitivity_analysis_create, + name="sensitivity_analysis_create", + ), + path( + "scenario//sensitivity-analysis/", + sensitivity_analysis_create, + name="sensitivity_analysis_review", + ), + path( + "scenario//sensitivity-analysis/run", + sensitivity_analysis_create, + name="sensitivity_analysis_run", ), path( - "topology/simulation_status/", - check_simulation_status, - name="check_simulation_status", + "scenario//sensitivity-analysis/error", + sensitivity_analysis_create, + name="sensitivity_analysis_error", ), path( - "project//scenario/", - update_simulation_results, - name="update_simulation_results", + "sensitivity-analysis/fetch-results/", + fetch_sensitivity_analysis_results, + name="fetch_sensitivity_analysis_results", ), # User Feedback path("user_feedback", user_feedback, name="user_feedback"), diff --git a/app/projects/views.py b/app/projects/views.py index badf5348..e1911074 100644 --- a/app/projects/views.py +++ b/app/projects/views.py @@ -16,12 +16,13 @@ from datetime import datetime from users.models import CustomUser from django.db.models import Q -from epa.settings import MVS_GET_URL, MVS_LP_FILE_URL +from epa.settings import MVS_GET_URL, MVS_LP_FILE_URL, MVS_SA_GET_URL from .forms import * from .requests import ( mvs_simulation_request, - mvs_simulation_check_status, - get_mvs_simulation_results, + fetch_mvs_simulation_results, + mvs_sensitivity_analysis_request, + fetch_mvs_sa_results, ) from .models import * from .scenario_topology_helpers import ( @@ -33,9 +34,9 @@ update_deleted_objects_from_database, duplicate_scenario_objects, duplicate_scenario_connections, - get_topology_json, load_scenario_from_dict, ) +from projects.helpers import format_scenario_for_mvs from .constants import DONE, ERROR, MODIFIED from .services import ( create_or_delete_simulation_scheduler, @@ -328,7 +329,6 @@ def project_duplicate(request, proj_id): # duplicate the project project.pk = None - print(project.economic_data.pk) economic_data = project.economic_data economic_data.pk = None economic_data.save() @@ -491,6 +491,7 @@ def scenario_create_parameters(request, proj_id, scen_id=None, step_id=1, max_st "form": form, "proj_id": proj_id, "proj_name": project.name, + "scenario": scenario, "scen_id": scen_id, "step_id": step_id, "step_list": STEP_LIST, @@ -748,6 +749,7 @@ def scenario_review(request, proj_id, scen_id, step_id=4, max_step=5): simulation = qs.first() context.update( { + "sim_id": simulation.id, "simulation_status": simulation.status, "secondsElapsed": simulation.elapsed_seconds, "rating": simulation.user_rating, @@ -892,6 +894,107 @@ def scenario_delete(request, scen_id): # endregion Scenario +@login_required +@require_http_methods(["GET", "POST"]) +def sensitivity_analysis_create(request, scen_id, sa_id=None, step_id=5): + excuses_design_under_development(request) + scenario = get_object_or_404(Scenario, id=scen_id) + if scenario.project.user != request.user: + raise PermissionDenied + + if request.method == "GET": + if sa_id is not None: + sa_item = get_object_or_404(SensitivityAnalysis, id=sa_id) + sa_form = SensitivityAnalysisForm(scen_id=scen_id, instance=sa_item) + sa_status = sa_item.status + else: + sa_item = None + sa_status = None + sa_form = SensitivityAnalysisForm(scen_id=scen_id) + + answer = render( + request, + "scenario/sensitivity_analysis.html", + { + "proj_id": scenario.project.id, + "proj_name": scenario.project.name, + "scenario": scenario, + "scen_id": scen_id, + "step_id": step_id, + "step_list": STEP_LIST + [_("Sensitivity analysis")], + "max_step": 5, + "MVS_SA_GET_URL": MVS_SA_GET_URL, + "sa_form": sa_form, + "sa_status": sa_status, + "sa_id": sa_id, + }, + ) + + if request.method == "POST": + qs = request.POST + sa_form = SensitivityAnalysisForm(qs) + + if sa_form.is_valid(): + sa_item = sa_form.save(commit=False) + # TODO if the reference value is not the same as in the current scenario, duplicate the scenario and bind the duplicate to sa_item + # TODO check if the scenario is already bound to a SA + sa_item.set_reference_scenario(scenario) + try: + data_clean = format_scenario_for_mvs(scenario) + except Exception as e: + error_msg = f"Scenario Serialization ERROR! User: {scenario.project.user.username}. Scenario Id: {scenario.id}. Thrown Exception: {e}." + logger.error(error_msg) + messages.error(request, error_msg) + answer = JsonResponse( + {"error": f"Scenario Serialization ERROR! Thrown Exception: {e}."}, + status=500, + content_type="application/json", + ) + + sa_item.save() + + # Add the information about the sensitivity analysis to the json + data_clean.update(sa_item.payload) + # Make simulation request to MVS + results = mvs_sensitivity_analysis_request(data_clean) + + if results is None: + error_msg = "Could not communicate with the simulation server." + logger.error(error_msg) + messages.error(request, error_msg) + # TODO redirect to prefilled feedback form / bug form + answer = JsonResponse( + {"status": "error", "error": error_msg}, + status=407, + content_type="application/json", + ) + else: + sa_item.mvs_token = results["id"] if results["id"] else None + + if "status" in results.keys() and ( + results["status"] == DONE or results["status"] == ERROR + ): + sa_item.status = results["status"] + sa_item.results = results["results"] + # Simulation.objects.filter(scenario_id=scen_id).delete() + # TODO the reference scenario should have its simulation replaced by this one if successful, this can be done via the mvs_token of the simulation + + sa_item.end_date = datetime.now() + else: # PENDING + sa_item.status = results["status"] + # create a task which will update simulation status + # TODO check it does the right thing with sensitivity analysis + # create_or_delete_simulation_scheduler(mvs_token=sa_item.mvs_token) + + sa_item.elapsed_seconds = (datetime.now() - sa_item.start_date).seconds + sa_item.save() + answer = HttpResponseRedirect( + reverse("sensitivity_analysis_review", args=[scen_id, sa_item.id]) + ) + + return answer + + # region Asset @@ -1042,8 +1145,7 @@ def view_mvs_data_input(request, scen_id=0): raise PermissionDenied try: - data_clean = get_topology_json(scenario) - print(data_clean) + data_clean = format_scenario_for_mvs(scenario) except Exception as e: logger.error( @@ -1072,7 +1174,7 @@ def request_mvs_simulation(request, scen_id=0): # Load scenario scenario = Scenario.objects.get(pk=scen_id) try: - data_clean = get_topology_json(scenario) + data_clean = format_scenario_for_mvs(scenario) # err = 1/0 except Exception as e: error_msg = f"Scenario Serialization ERROR! User: {scenario.project.user.username}. Scenario Id: {scenario.id}. Thrown Exception: {e}." @@ -1155,27 +1257,28 @@ def update_simulation_rating(request): @json_view @login_required -@require_http_methods(["GET", "POST"]) -def check_simulation_status(request, scen_id): - scenario = get_object_or_404(Scenario, pk=scen_id) - if scenario.simulation: - return JsonResponse( - mvs_simulation_check_status(scenario.simulation.mvs_token), - status=200, - content_type="application/json", - ) +@require_http_methods(["GET"]) +def fetch_simulation_results(request, sim_id): + simulation = get_object_or_404(Simulation, id=sim_id) + are_result_ready = fetch_mvs_simulation_results(simulation) + return JsonResponse( + dict(areResultReady=are_result_ready), + status=200, + content_type="application/json", + ) +@json_view @login_required @require_http_methods(["GET"]) -def update_simulation_results(request, proj_id, scen_id): - scenario = get_object_or_404(Scenario, pk=scen_id) - - simulation = scenario.simulation - - get_mvs_simulation_results(simulation) - - return HttpResponseRedirect(reverse("scenario_review", args=[proj_id, scen_id])) +def fetch_sensitivity_analysis_results(request, sa_id): + sa_item = get_object_or_404(SensitivityAnalysis, id=sa_id) + are_result_ready = fetch_mvs_sa_results(sa_item) + return JsonResponse( + dict(areResultReady=are_result_ready), + status=200, + content_type="application/json", + ) # endregion MVS JSON Related diff --git a/app/static/MVS_parameters_list.csv b/app/static/MVS_parameters_list.csv index ccec70d8..9f8a2c28 100644 --- a/app/static/MVS_parameters_list.csv +++ b/app/static/MVS_parameters_list.csv @@ -1,54 +1,54 @@ -verbose,:Default:,:Definition_Short:,:Definition_Long:,:Example:,:Restrictions:,:Type:,:Unit:,label,ref,category,see_also,,,, -None,5,The number of years the asset has already been in operation,,10,Natural number,numeric,Year,age_installed,age_ins-label,conversion,production,,,, -None,1,The rate at which the storage can be charged or discharged relative to the nominal capacity of the storage, A C-rate of 1 implies that the battery can be fully charged or discharged completely in a single timestep. A C-rate of 0.5 implies that the battery needs at least 2 timesteps to be fully charged or discharged.,"*storage capacity*: NaN, *input power*: 1, *output power*: 1","Real number between 0 and 1. Only the columns 'input power' and 'output power' require a value, in column 'storage capacity' c_rate should be set to NaN.",numeric,Factor,c-rate,crate-label,storage_csv,,,,, -None,None,The name of the country where the project is being deployed,,Norway,None,str,None,country,country-label,project_data,,,,, -None,None,The currency of the country where the project is implemented,,EUR,None,str,None,currency,currency-label,economic_data,,,,, -None,0,Fixed project costs,This could be planning and development costs which do not depend on the (optimized) capacities of the assets.,10000,Positive real number,numeric,€,development_costs,developmentcosts-label,conversion,storage_csv,production,fixcost,, -Discount factor,0.05,"The factor which accounts for the depreciation in the value of money in the future, compared to the current value of the same money. The common method is to calculate the weighted average cost of capital (WACC) and use it as the discount rate.",,0.06,Between 0 and 1,numeric,Factor,discount_factor,discountfactor-label,economic_data,,,,, -None,0.10,Costs associated with a flow through/from the asset,e.g. in EUR/kWh,0.64,"In 'storage_xx.csv' only the columns 'input power' and 'output power' require a value, in column 'storage capacity' dispatch_price should be set to NaN.",numeric,€/kWh,dispatch_price,dispatchprice-label,conversion,production,storage_csv,fixcost,, -None,False,"Stands for Demand Side Management. Currently, not implemented.",,False,Acceptable values are either True or False.,boolean,None,dsm,dsm-label,hidden,,,,, -None,0.8,Ratio of energy output/energy input,"The battery efficiency is the ratio of the energy taken out from the battery, to the energy put into the battery. It means that it is not possible to retrieve as much energy as provided to the battery due to the discharge losses. The efficiency of the 'input power' and 'ouput power' columns should be set equal to the charge and discharge efficiencies respectively, while the 'storage capacity' efficiency should be equal to the storage's efficiency/ability to hold charge over time (`= 1 - self-discharge/decay`), which is usually in the range of 0.95 to 1 for electrical storages.",0.95,Between 0 and 1,numeric,Factor,efficiency,efficiency-label,conversion,storage_csv,,,, -None,0.20,Emissions per unit dispatch of an asset,,14.4,Positive real number,numeric,kgCO2eq/asset unit,emission_factor,emissionfactor-label,providers,production,,,, -None,0.30,Price of the energy carrier sourced from the utility grid.,,0.1,None,numeric,€/energy carrier unit,energy_price,energyprice-label,providers,,,,, -None,Electricity,"Energy vector/commodity. Convention: For an energy conversion asset define energyVector of the output. For a sink define based on inflow. For a source define based on output flow. For a storage, define based on stored energy carrier.",,Electricity,"One of “Electricity”, “Gas”, “Bio-Gas”, “Diesel”, “Heat”, “H2”",str,None,energyVector,energyvector-label,busses,consumption,production,storage,providers,conversion -None,365,The number of days for which the simulation is to be run,,365,Natural number,numeric,Day,evaluated_period,evaluatedperiod-label,simulation_settings,,,,, -None,0.10,Price received for feeding electricity into the grid,,0.7,Real number between 0 and 1,numeric,€/kWh,feedin_tariff,feedintariff-label,providers,,,,, -None,None,Name of a csv file containing the input generation or demand timeseries.,,demand_harbor.csv,This file must be placed in a folder named “time_series” inside your input folder.,str,None,file_name,filename-label,consumption,production,storage,,, -None,0,Thermal losses of storage independent of state of charge and independent of nominal storage capacity between two consecutive timesteps.,,0.0003,Between 0 and 1,numeric,factor,fixed_thermal_losses_absolute,fixed_thermal_losses_absolute-label,storage_csv,,,,, -None,0,Thermal losses of storage independent of state of charge between two consecutive timesteps relative to nominal storage capacity.,,0.0016,Between 0 and 1,numeric,factor,fixed_thermal_losses_relative,fixed_thermal_losses_relative-label,storage_csv,,,,, -None,None,The label of the bus/component from which the energyVector is arriving into the asset.,,Electricity,None,str,None,inflow_direction,inflowdirection-label,consumption,conversion,providers,storage,, -None,0,The already existing installed capacity in-place,"If the project lasts longer than its remaining lifetime, its replacement costs will be taken into account.",50,Each component in the “energy production” category must have a value.,numeric,kWp,installedCap,installedcap-label,conversion,production,storage_csv,,, -None,None,Name of the asset for display purposes,,pv_plant_01,"Input the names in a computer friendly format, preferably with underscores instead of spaces, and avoiding special characters",str,None,label,labl-label,fixcost,,,,, -Location's latitude,None,Latitude coordinate of the project's geographical location,,45.641603,Should follow geographical convention,numeric,None,latitude,latitude-label,project_data,,,,, -None,20,Number of operational years of the asset until it has to be replaced,,30,Natural number,numeric,Year,lifetime,lifetime-label,conversion,production,storage_csv,fixcost,, -Location's longitude,None,Longitude coordinate of the project's geographical location,,10.95787,Should follow geographical convention,numeric,None,longitude,longitude-label,project_data,,,,, -None,None,The maximum amount of total emissions which are allowed in the optimized energy system,,100000,Acceptable values are either a positive real number or None,numeric,kgCO2eq/a,maximum_emissions,maxemissions-label,constraints,,,,, -None,None,The maximum total capacity of an asset that can be installed at the project site,"This includes the installed and the also the maximum additional capacity possible. An example would be that a roof can only carry 50 kWp PV (maximumCap), whereas the installed capacity is already 10 kWp. The optimization would only be allowed to add 40 kWp PV at maximum.",1050,Acceptable values are either a positive real number or None,numeric,kWp,maximumCap,maxcap-label,production,,,,, -None,None,The minimum degree of autonomy that needs to be met by the optimization,,0.3,Between 0 and 1,numeric,factor,minimal_degree_of_autonomy,minda-label,constraints,,,,, -None,None,The minimum share of energy supplied by renewable generation that needs to be met by the optimization,Insert the value 0 to deactivate this constraint.,0.7,Between 0 and 1,numeric,factor,minimal_renewable_factor,minrenshare-label,constraints,,,,, -None,False,Specifies whether optimization needs to result into a net zero energy system (True) or not (False),,True,Acceptable values are either True or False.,boolean,None,net_zero_energy,nzeconstraint-label,constraints,,,,, -None,False,Choose if capacity optimization should be performed for this asset.,,True,Permissible values are either True or False,boolean,None,optimizeCap,optimizecap-label,conversion,production,providers,storage,, -None,None,The label of bus/component towards which the energyVector is leaving from the asset.,,PV plant (mono),None,str,None,outflow_direction,outflowdirec-label,consumption,conversion,providers,storage,, -None,False,"Enable the generation of a file with the linear equation system describing the simulation, ie., with the objective function and all the constraints. This lp file enables the user look at the underlying equations of the optimization.",,False,Acceptable values are either True or False,boolean,None,output_lp_file,outputlpfile-label,simulation_settings,,,,, -None,None,Price to be paid additionally for energy consumption based on the peak demand of a given period,,60,None,numeric,€/kW,peak_demand_pricing,peakdemand-label,providers,peakdemandperiod-label,,,, -None,1,Number of reference periods in one year for the peak demand pricing,,2,"Only one of the following are acceptable values: 1 (yearly), 2, 3 ,4, 6, 12 (monthly)",numeric,"times per year (1,2,3,4,6,12)",peak_demand_pricing_period,peakdemandperiod-label,providers,peakdemand-label,,,, -Project duration,20,The number of years the project is intended to be operational,The project duration also sets the installation time of the assets used in the simulation. After the project ends these assets are 'sold' and the refund is charged against the initial investment costs.,30,Natural number,numeric,Years,project_duration,projectduration-label,economic_data,,,,, -None,None,Assign a project ID as per your preference.,,1,Cannot be the same as an already existing project,str,None,project_id,projectid-label,project_data,,,,, -None,None,Assign a project name as per your preference.,,Borg Havn,None,str,None,project_name,projectname-label,project_data,,,,, -None,0.44,The share of renewables in the generation mix of the energy supplied by the DSO utility,,0.1,Real number between 0 and 1,numeric,Factor,renewable_share,renshare-label,providers,,,,, -None,None,Choose if this asset should be tagged as renewable.,,True,Acceptable values are either True or False,boolean,None,renewableAsset,renewableasset-label,production,,,,, -None,None,Brief description of the scenario being simulated,,This scenario simulates a sector-coupled energy system,None,str,None,scenario_description,scenariodescription-label,project_data,,,,, -None,None,Assign a scenario id as per your preference.,,1,Cannot be the same as an already existing scenario within the project,str,None,scenario_id,scenarioid-label,project_data,,,,, -None,None,Assign a scenario name as per your preference.,,Warehouse 14,None,str,None,scenario_name,scenarioname-label,project_data,,,,, -None,None,The level of charge (as a factor of the actual capacity) in the storage in the zeroth time-step.,,":code:`storage capacity`: None, :code:`input power`: NaN, :code:`output power`: NaN","Acceptable values are either None or the factor. Only the column :code:`storage capacity` requires a value, in column :code:`input power` and :code:`output power` :code:`soc_initial` should be set to NaN. The :code:`soc_initial` has to be within the [0,1] interval.",numeric,None or factor,soc_initial,socin-label,storage_csv,,,,, -None,1,"The maximum permissible level of charge in the battery (generally, it is when the battery is filled to its nominal capacity), represented by the value 1.0. Users can also specify a certain value as a factor of the actual capacity.",,":code:`storage capacity`: 1, :code:`input power`: NaN, :code:`output power`: NaN","Only the column :code:`storage capacity` requires a value, in column :code:`input power` and :code:`output power` :code:`soc_max` should be set to NaN. The :code:`soc_max` has to be in the [0,1] interval.",numeric,Factor,soc_max,socmax-label,storage_csv,,,,, -None,0,The minimum permissible level of charge in the battery as a factor of the nominal capacity of the battery.,,":code:`storage capacity`:0.2, :code:`input power`: NaN, :code:`output power`: NaN","Only the column :code:`storage capacity` requires a value, in column :code:`input power` and :code:`output power` :code:`soc_min` should be set to NaN. The soc_min has to be in the [0,1] interval.",numeric,Factor,soc_min,socmin-label,storage_csv,,,,, -None,4000,"Actual CAPEX of an asset, i.e., specific investment costs",,4000,None,numeric,€/unit,specific_costs,specificcosts-label,conversion,production,storage_csv,fixcost,, -None,120,"Actual OPEX of an asset, i.e., specific operational and maintenance costs.",,120,None,numeric,€/unit/year,specific_costs_om,specificomcosts-label,conversion,production,storage_csv,fixcost,, -None,None,The date and time on which the simulation starts at the first step.,,2018-01-01 00:00:00,Acceptable format is YYYY-MM-DD HH:MM:SS,str,None,start_date,startdate-label,simulation_settings,,,,, -None,None,Name of a csv file containing the properties of a storage component,,storage_01.csv,Follows the convention of 'storage_xx.csv' where 'xx' is a number. This file must be placed in a folder named “csv_elements” inside your input folder.,str,None,storage_filename,storagefilename-label,storage,,,,, -Tax factor,0.07,Tax factor.,,0,Between 0 and 1,numeric,Factor,tax,tax-label,economic_data,,,,, -None,60,Length of the time-steps.,,60,Can only be 60 at the moment,numeric,Minutes,timestep,timestep-label,simulation_settings,,,,, -None,None,The type of the component.,,demand,*demand*,str,None,type_asset,typeasset-label,hidden,,,,, -None,None,"Input the type of OEMOF component. For example, a PV plant would be a source, a solar inverter would be a transformer, etc. The `type_oemof` will later on be determined through the EPA.",,sink,*sink* or *source* or one of the other component classes of OEMOF.,str,None,type_oemof,typeoemof-label,consumption,conversion,production,providers,storage, -None,None,Unit associated with the capacity of the component,,"Storage could have units like kW or kWh, transformer station could have kVA, and so on.",Appropriate scientific unit,str,NA,unit,unit-label,consumption,conversion,production,providers,storage_csv, +verbose,:Default:,:Definition_Short:,:Definition_Long:,:Example:,:Restrictions:,:Type:,:Unit:,sensitivity_analysis,label,ref,category,see_also +None,5,The number of years the asset has already been in operation,,10,Natural number,numeric,Year,1,age_installed,age_ins-label,conversion:production, +None,1,The rate at which the storage can be charged or discharged relative to the nominal capacity of the storage, A C-rate of 1 implies that the battery can be fully charged or discharged completely in a single timestep. A C-rate of 0.5 implies that the battery needs at least 2 timesteps to be fully charged or discharged.,"*storage capacity*: NaN, *input power*: 1, *output power*: 1","Real number between 0 and 1. Only the columns 'input power' and 'output power' require a value, in column 'storage capacity' c_rate should be set to NaN.",numeric,Factor,1,c-rate,crate-label,storage_csv, +None,None,The name of the country where the project is being deployed,,Norway,None,str,None,0,country,country-label,project_data, +None,None,The currency of the country where the project is implemented,,EUR,None,str,None,0,currency,currency-label,economic_data, +None,0,Fixed project costs,This could be planning and development costs which do not depend on the (optimized) capacities of the assets.,10000,Positive real number,numeric,€,1,development_costs,developmentcosts-label,conversion;storage_csv;production;fixcost, +Discount factor,0.05,"The factor which accounts for the depreciation in the value of money in the future, compared to the current value of the same money. The common method is to calculate the weighted average cost of capital (WACC) and use it as the discount rate.",,0.06,Between 0 and 1,numeric,Factor,0,discount_factor,discountfactor-label,economic_data, +None,0.1,Costs associated with a flow through/from the asset,e.g. in EUR/kWh,0.64,"In 'storage_xx.csv' only the columns 'input power' and 'output power' require a value, in column 'storage capacity' dispatch_price should be set to NaN.",numeric,€/kWh,1,dispatch_price,dispatchprice-label,conversion;storage_csv;production;fixcost, +None,False,"Stands for Demand Side Management. Currently, not implemented.",,False,Acceptable values are either True or False.,boolean,None,0,dsm,dsm-label,hidden, +None,0.8,Ratio of energy output/energy input,"The battery efficiency is the ratio of the energy taken out from the battery, to the energy put into the battery. It means that it is not possible to retrieve as much energy as provided to the battery due to the discharge losses. The efficiency of the 'input power' and 'ouput power' columns should be set equal to the charge and discharge efficiencies respectively, while the 'storage capacity' efficiency should be equal to the storage's efficiency/ability to hold charge over time (`= 1 - self-discharge/decay`), which is usually in the range of 0.95 to 1 for electrical storages.",0.95,Between 0 and 1,numeric,Factor,1,efficiency,efficiency-label,conversion;storage_csv, +None,0.2,Emissions per unit dispatch of an asset,,14.4,Positive real number,numeric,kgCO2eq/asset unit,1,emission_factor,emissionfactor-label,providers;production, +None,0.3,Price of the energy carrier sourced from the utility grid.,,0.1,None,numeric,€/energy carrier unit,1,energy_price,energyprice-label,providers, +None,Electricity,"Energy vector/commodity. Convention: For an energy conversion asset define energyVector of the output. For a sink define based on inflow. For a source define based on output flow. For a storage, define based on stored energy carrier.",,Electricity,"One of “Electricity”, “Gas”, “Bio-Gas”, “Diesel”, “Heat”, “H2”",str,None,0,energyVector,energyvector-label,busses;consumption;production;storage;providers;conversion, +None,365,The number of days for which the simulation is to be run,,365,Natural number,numeric,Day,1,evaluated_period,evaluatedperiod-label,simulation_settings, +None,0.1,Price received for feeding electricity into the grid,,0.7,Real number between 0 and 1,numeric,€/kWh,1,feedin_tariff,feedintariff-label,providers, +None,None,Name of a csv file containing the input generation or demand timeseries.,,demand_harbor.csv,This file must be placed in a folder named “time_series” inside your input folder.,str,None,0,file_name,filename-label,consumption;production;storage_csv, +None,0,Thermal losses of storage independent of state of charge and independent of nominal storage capacity between two consecutive timesteps.,,0.0003,Between 0 and 1,numeric,factor,1,fixed_thermal_losses_absolute,fixed_thermal_losses_absolute-label,storage_csv, +None,0,Thermal losses of storage independent of state of charge between two consecutive timesteps relative to nominal storage capacity.,,0.0016,Between 0 and 1,numeric,factor,1,fixed_thermal_losses_relative,fixed_thermal_losses_relative-label,storage_csv, +None,None,The label of the bus/component from which the energyVector is arriving into the asset.,,Electricity,None,str,None,0,inflow_direction,inflowdirection-label,consumption;conversion;providers;storage, +None,0,The already existing installed capacity in-place,"If the project lasts longer than its remaining lifetime, its replacement costs will be taken into account.",50,Each component in the “energy production” category must have a value.,numeric,kWp,1,installedCap,installedcap-label,conversion;production;storage_csv, +None,None,Name of the asset for display purposes,,pv_plant_01,"Input the names in a computer friendly format, preferably with underscores instead of spaces, and avoiding special characters",str,None,0,label,labl-label,fixcost, +Location's latitude,None,Latitude coordinate of the project's geographical location,,45.641603,Should follow geographical convention,numeric,None,0,latitude,latitude-label,project_data, +None,20,Number of operational years of the asset until it has to be replaced,,30,Natural number,numeric,Year,1,lifetime,lifetime-label,conversion;storage_csv;production;fixcost, +Location's longitude,None,Longitude coordinate of the project's geographical location,,10.95787,Should follow geographical convention,numeric,None,0,longitude,longitude-label,project_data, +None,None,The maximum amount of total emissions which are allowed in the optimized energy system,,100000,Acceptable values are either a positive real number or None,numeric,kgCO2eq/a,1,maximum_emissions,maxemissions-label,constraints, +None,None,The maximum total capacity of an asset that can be installed at the project site,"This includes the installed and the also the maximum additional capacity possible. An example would be that a roof can only carry 50 kWp PV (maximumCap), whereas the installed capacity is already 10 kWp. The optimization would only be allowed to add 40 kWp PV at maximum.",1050,Acceptable values are either a positive real number or None,numeric,kWp,1,maximumCap,maxcap-label,production, +None,None,The minimum degree of autonomy that needs to be met by the optimization,,0.3,Between 0 and 1,numeric,factor,1,minimal_degree_of_autonomy,minda-label,constraints, +None,None,The minimum share of energy supplied by renewable generation that needs to be met by the optimization,Insert the value 0 to deactivate this constraint.,0.7,Between 0 and 1,numeric,factor,1,minimal_renewable_factor,minrenshare-label,constraints, +None,False,Specifies whether optimization needs to result into a net zero energy system (True) or not (False),,True,Acceptable values are either True or False.,boolean,None,0,net_zero_energy,nzeconstraint-label,constraints, +None,False,Choose if capacity optimization should be performed for this asset.,,True,Permissible values are either True or False,boolean,None,0,optimizeCap,optimizecap-label,conversion;storage;providers;production, +None,None,The label of bus/component towards which the energyVector is leaving from the asset.,,PV plant (mono),None,str,None,0,outflow_direction,outflowdirec-label,consumption;conversion;storage;providers, +None,False,"Enable the generation of a file with the linear equation system describing the simulation, ie., with the objective function and all the constraints. This lp file enables the user look at the underlying equations of the optimization.",,False,Acceptable values are either True or False,boolean,None,0,output_lp_file,outputlpfile-label,simulation_settings, +None,None,Price to be paid additionally for energy consumption based on the peak demand of a given period,,60,None,numeric,€/kW,1,peak_demand_pricing,peakdemand-label,providers,peakdemandperiod-label +None,1,Number of reference periods in one year for the peak demand pricing,,2,"Only one of the following are acceptable values: 1 (yearly), 2, 3 ,4, 6, 12 (monthly)",numeric,"times per year (1,2,3,4,6,12)",1,peak_demand_pricing_period,peakdemandperiod-label,providers,peakdemand-label +Project duration,20,The number of years the project is intended to be operational,The project duration also sets the installation time of the assets used in the simulation. After the project ends these assets are 'sold' and the refund is charged against the initial investment costs.,30,Natural number,numeric,Years,0,project_duration,projectduration-label,economic_data, +None,None,Assign a project ID as per your preference.,,1,Cannot be the same as an already existing project,str,None,0,project_id,projectid-label,project_data, +None,None,Assign a project name as per your preference.,,Borg Havn,None,str,None,0,project_name,projectname-label,project_data, +None,0.44,The share of renewables in the generation mix of the energy supplied by the DSO utility,,0.1,Real number between 0 and 1,numeric,Factor,1,renewable_share,renshare-label,providers, +None,None,Choose if this asset should be tagged as renewable.,,True,Acceptable values are either True or False,boolean,None,0,renewableAsset,renewableasset-label,production, +None,None,Brief description of the scenario being simulated,,This scenario simulates a sector-coupled energy system,None,str,None,0,scenario_description,scenariodescription-label,project_data, +None,None,Assign a scenario id as per your preference.,,1,Cannot be the same as an already existing scenario within the project,str,None,0,scenario_id,scenarioid-label,project_data, +None,None,Assign a scenario name as per your preference.,,Warehouse 14,None,str,None,0,scenario_name,scenarioname-label,project_data, +None,None,The level of charge (as a factor of the actual capacity) in the storage in the zeroth time-step.,,":code:`storage capacity`: None, :code:`input power`: NaN, :code:`output power`: NaN","Acceptable values are either None or the factor. Only the column :code:`storage capacity` requires a value, in column :code:`input power` and :code:`output power` :code:`soc_initial` should be set to NaN. The :code:`soc_initial` has to be within the [0,1] interval.",numeric,None or factor,1,soc_initial,socin-label,storage_csv, +None,1,"The maximum permissible level of charge in the battery (generally, it is when the battery is filled to its nominal capacity), represented by the value 1.0. Users can also specify a certain value as a factor of the actual capacity.",,":code:`storage capacity`: 1, :code:`input power`: NaN, :code:`output power`: NaN","Only the column :code:`storage capacity` requires a value, in column :code:`input power` and :code:`output power` :code:`soc_max` should be set to NaN. The :code:`soc_max` has to be in the [0,1] interval.",numeric,Factor,1,soc_max,socmax-label,storage_csv, +None,0,The minimum permissible level of charge in the battery as a factor of the nominal capacity of the battery.,,":code:`storage capacity`:0.2, :code:`input power`: NaN, :code:`output power`: NaN","Only the column :code:`storage capacity` requires a value, in column :code:`input power` and :code:`output power` :code:`soc_min` should be set to NaN. The soc_min has to be in the [0,1] interval.",numeric,Factor,1,soc_min,socmin-label,storage_csv, +None,4000,"Actual CAPEX of an asset, i.e., specific investment costs",,4000,None,numeric,€/unit,1,specific_costs,specificcosts-label,conversion;storage_csv;production;fixcost, +None,120,"Actual OPEX of an asset, i.e., specific operational and maintenance costs.",,120,None,numeric,€/unit/year,1,specific_costs_om,specificomcosts-label,conversion;storage_csv;production;fixcost, +None,None,The date and time on which the simulation starts at the first step.,,2018-01-01 00:00:00,Acceptable format is YYYY-MM-DD HH:MM:SS,str,None,0,start_date,startdate-label,simulation_settings, +None,None,Name of a csv file containing the properties of a storage component,,storage_01.csv,Follows the convention of 'storage_xx.csv' where 'xx' is a number. This file must be placed in a folder named “csv_elements” inside your input folder.,str,None,0,storage_filename,storagefilename-label,storage, +Tax factor,0.07,Tax factor.,,0,Between 0 and 1,numeric,Factor,0,tax,tax-label,economic_data, +None,60,Length of the time-steps.,,60,Can only be 60 at the moment,numeric,Minutes,1,timestep,timestep-label,simulation_settings, +None,None,The type of the component.,,demand,*demand*,str,None,0,type_asset,typeasset-label,hidden, +None,None,"Input the type of OEMOF component. For example, a PV plant would be a source, a solar inverter would be a transformer, etc. The `type_oemof` will later on be determined through the EPA.",,sink,*sink* or *source* or one of the other component classes of OEMOF.,str,None,0,type_oemof,typeoemof-label,conversion;storage;production;fixcost;consumption;providers, +None,None,Unit associated with the capacity of the component,,"Storage could have units like kW or kWh, transformer station could have kVA, and so on.",Appropriate scientific unit,str,NA,0,unit,unit-label,conversion;storage_csv;production;fixcost;consumption;providers, diff --git a/app/static/js/simulation_requests.js b/app/static/js/simulation_requests.js new file mode 100644 index 00000000..a8406c74 --- /dev/null +++ b/app/static/js/simulation_requests.js @@ -0,0 +1,20 @@ + +const myInterval = setInterval(check_if_simulation_is_done, 3000); + +function check_if_simulation_is_done(url=checkSimulationUrl){ + + $.ajax({ + type: "GET", + url: url, + success: function (resp) { + console.log(resp); + if(resp.areResultReady == true){ + clearInterval(myInterval); + location.reload(); + } + }, + error: function (XMLHttpRequest, textStatus, errorThrown) { + clearInterval(myInterval); + } + }); +}; diff --git a/app/templates/scenario/scenario_step4.html b/app/templates/scenario/scenario_step4.html index 0779aea1..6acef653 100644 --- a/app/templates/scenario/scenario_step4.html +++ b/app/templates/scenario/scenario_step4.html @@ -39,7 +39,6 @@
{% if simulation_status == "DONE" %} - {% translate "Check results dashboard" %} {% elif simulation_status == "ERROR" %}

{{ simulation_error_msg }}

@@ -47,16 +46,16 @@ {% translate "Link to send bug report automatically" %} {% elif simulation_status == "PENDING" %} -
- {% csrf_token %} - -
+ The simulation {{ mvs_token }} has been started, its status is {{ simulation_status }} {% endif %}
+ {% else %}
{% translate "You can now click on 'Run simulation' button" %}
{% endif %} - @@ -69,9 +68,13 @@ {% block end_body_scripts %} +{% if simulation_status == "PENDING" %} + +{% endif %} {% endblock end_body_scripts %} diff --git a/app/templates/scenario/sensitivity_analysis.html b/app/templates/scenario/sensitivity_analysis.html new file mode 100644 index 00000000..bc6e57dc --- /dev/null +++ b/app/templates/scenario/sensitivity_analysis.html @@ -0,0 +1,79 @@ +{% extends 'scenario/scenario_progression.html' %} +{% load static %} +{% load crispy_forms_tags %} +{% load custom_filters %} +{% load i18n %} + +{% block head_block %} + +{% endblock head_block %} + + +{% block progression_content %} + + + +
+ +
+
+ + {% if sa_status %} +
+ + {% if sa_status != "MODIFIED" %} + The simulation {{ sa_item.mvs_token }} has been started, its status is {{ sa_status }} + {% else %} + The parameters of the scenario linked to the simulation {{ mvs_token }} have been changed, you can rerun the simulation to update the results, please note that existing results will be erased only if the simulation does not have errors + {% endif %} +
+ + + {% else %} +
{% translate "There is no simulation_status" %}
+
+
+ {% csrf_token %} + {{ sa_form|crispy }} + +
+
+ {% endif %} + +
+
+ + +
+ + + +{% endblock progression_content %} + + +{% block end_body_scripts %} + + +{% if sa_status == "PENDING" %} + + +{% endif %} + +{% endblock end_body_scripts %} + +{% block footer %} + +{% endblock footer %} \ No newline at end of file