diff --git a/app/epa/settings.py b/app/epa/settings.py index 1379b19b..92e0a4f6 100644 --- a/app/epa/settings.py +++ b/app/epa/settings.py @@ -215,6 +215,10 @@ MVS_SA_POST_URL = f"{MVS_API_HOST}/sendjson/openplan/sensitivity-analysis" MVS_SA_GET_URL = f"{MVS_API_HOST}/check-sensitivity-analysis/" +EZP_API_HOST = env("EZP_API_HOST", default="") +EZP_POST_URL = f"{EZP_API_HOST}/sendjson/" +EZP_GET_URL = f"{EZP_API_HOST}/check/" + # Allow iframes to show in page X_FRAME_OPTIONS = "SAMEORIGIN" diff --git a/app/projects/requests.py b/app/projects/requests.py index 9bd26860..ba96c3ee 100644 --- a/app/projects/requests.py +++ b/app/projects/requests.py @@ -10,6 +10,8 @@ MVS_GET_URL, MVS_SA_POST_URL, MVS_SA_GET_URL, + EZP_POST_URL, + EZP_GET_URL, ) from dashboard.models import ( FancyResults, @@ -24,6 +26,83 @@ logger = logging.getLogger(__name__) +def ezp_simulation_request(data: str): + headers = {"content-type": "application/json"} + payload = data + + try: + response = requests.post( + EZP_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) + + +def ezp_simulation_check_status(token): + try: + response = requests.get(EZP_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_ezp_simulation_results(simulation): + if simulation.status == PENDING: + response = ezp_simulation_check_status(token=simulation.mvs_token) + try: + simulation.status = response["status"] + simulation.errors = ( + json.dumps(response["results"][ERROR]) + if simulation.status == ERROR + else None + ) + simulation.results = ( + response["results"] if simulation.status == DONE else None + ) + + # simulation.mvs_version = response["mvs_version"] + logger.info(f"The simulation {simulation.id} is finished") + except: + simulation.status = ERROR + simulation.results = None + + simulation.elapsed_seconds = (datetime.now() - simulation.start_date).seconds + + # Cancel simulation if it has been going on > 48h + max_simulation_seconds = 48 * 60 * 60 + if simulation.elapsed_seconds > max_simulation_seconds: + simulation.status = ERROR + simulation.results = None + + simulation.end_date = ( + datetime.now() if simulation.status in [ERROR, DONE] else None + ) + simulation.save() + + return simulation.status != PENDING + + def mvs_simulation_request(data: dict): headers = {"content-type": "application/json"} payload = json.dumps(data) diff --git a/app/projects/urls.py b/app/projects/urls.py index 8314f7d9..ad7edbbe 100644 --- a/app/projects/urls.py +++ b/app/projects/urls.py @@ -163,6 +163,16 @@ project_export_as_datapackage, name="project_export_as_datapackage", ), + path( + "scenario/export/datapackage/jsonified/", + scenario_export_as_jsonified_datapackage, + name="scenario_export_as_jsonified_datapackage", + ), + path( + "scenario/export/datapackage/jsonified//number/", + scenario_export_as_jsonified_datapackage, + name="scenario_export_as_jsonified_datapackage", + ), path("scenario/upload/", scenario_upload, name="scenario_upload"), # path('scenario/upload/', LoadScenarioFromFileView.as_view(), name='scenario_upload'), # Timeseries Model @@ -255,6 +265,11 @@ request_mvs_simulation, name="request_mvs_simulation", ), + path( + "topology/ezp_simulation/", + request_ezp_simulation, + name="request_ezp_simulation", + ), path( "topology/update_simulation_rating/", update_simulation_rating, diff --git a/app/projects/views.py b/app/projects/views.py index 5937e005..efa02d9e 100644 --- a/app/projects/views.py +++ b/app/projects/views.py @@ -27,11 +27,16 @@ from jsonview.decorators import json_view from django.db.models import Q + +from oemof.datapackage.datapackage import export_dp_to_json + from epa.settings import MVS_GET_URL, MVS_LP_FILE_URL, MVS_SA_GET_URL from .forms import * from .requests import ( mvs_simulation_request, fetch_mvs_simulation_results, + ezp_simulation_request, + fetch_ezp_simulation_results, mvs_sensitivity_analysis_request, fetch_mvs_sa_results, parse_mvs_results, @@ -1382,6 +1387,25 @@ def scenario_export_as_datapackage(request, scen_id, n_timestamps=None): return response +@login_required +@require_http_methods(["GET"]) +def scenario_export_as_jsonified_datapackage(request, scen_id, n_timestamps=None): + 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, number=n_timestamps) + + json_dp = json.loads(export_dp_to_json(scenario_folder)) + response = JsonResponse(json_dp, status=200, content_type="application/json") + + return response + + @login_required @require_http_methods(["GET"]) def project_export_as_datapackage(request, proj_id, n_timestamps=None): @@ -2025,6 +2049,71 @@ def request_mvs_simulation(request, scen_id=0): return answer +@login_required +@require_http_methods(["GET", "POST"]) +def request_ezp_simulation(request, scen_id=0): + if scen_id == 0: + answer = JsonResponse( + {"status": "error", "error": "No scenario id provided"}, + status=500, + content_type="application/json", + ) + # Load scenario + scenario = Scenario.objects.get(pk=scen_id) + json_dp = scenario_export_as_jsonified_datapackage(request, scen_id) + + # if request.method == "POST": + # output_lp_file = request.POST.get("output_lp_file", None) + # if output_lp_file == "on": + # data_clean["simulation_settings"]["output_lp_file"] = "true" + + # Make simulation request to eesyplan server + results = ezp_simulation_request(json_dp.text) + + 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: + # delete existing simulation + Simulation.objects.filter(scenario_id=scen_id).delete() + + # Create empty Simulation model object + simulation = Simulation(start_date=datetime.datetime.now(), scenario_id=scen_id) + + simulation.mvs_token = ( + results["id"] if results["id"] else None + ) # TODO change token + + if "status" in results.keys() and ( + results["status"] == DONE or results["status"] == ERROR + ): + simulation.status = results["status"] + simulation.results = results["results"] + simulation.end_date = datetime.datetime.now() + else: # PENDING + simulation.status = results["status"] + # create a task which will update simulation status + # create_or_delete_simulation_scheduler(mvs_token=simulation.mvs_token) + + simulation.elapsed_seconds = ( + datetime.datetime.now() - simulation.start_date + ).seconds + simulation.save() + + answer = HttpResponseRedirect( + reverse("scenario_review", args=[scenario.project.id, scen_id]) + ) + + return answer + + @json_view @login_required @require_http_methods(["POST"])