Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERP outputs endpoint; set reopt_version in meta data programmatically #594

Merged
merged 32 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
49af53a
try to set reopt_version in APIMeta automatically
hdunham Jun 13, 2024
eb40045
simplify logic constructing dict
hdunham Jun 13, 2024
a69755b
convert reopt version to string
hdunham Jun 13, 2024
6a499e3
add reopt version number check to test
hdunham Jun 13, 2024
5112184
set reopt_version in ERPMeta automatically
hdunham Jun 14, 2024
4bc00de
check reopt_version in erp test
hdunham Jun 14, 2024
04182ce
comment out extra tests
hdunham Jun 14, 2024
9605654
test cant be empty
hdunham Jun 14, 2024
b355051
Revert "test cant be empty"
hdunham Jun 14, 2024
4ce4b83
Revert "comment out extra tests"
hdunham Jun 14, 2024
a5ceff1
add erp_outputs view
hdunham Jul 1, 2024
9e1dc87
add erp/inputs and erp/outputs urls
hdunham Jul 1, 2024
d09ee96
Update CHANGELOG.md
hdunham Jul 2, 2024
7c3a4ab
delete commented out reopt_version
hdunham Jul 10, 2024
c4a5f42
Merge branch 'develop' into reoptjl-version
hdunham Jul 10, 2024
c833848
Update CHANGELOG.md
hdunham Jul 10, 2024
6e36f16
update reopt_version check in tests to not be specific number
hdunham Jul 10, 2024
4535484
convert JsonResponse to Dict before keys
hdunham Jul 10, 2024
e618edc
test erp/inputs and erp/outputs endpoints
hdunham Jul 10, 2024
3944ca5
Merge branch 'develop' into erp-outputs-help
hdunham Jul 10, 2024
77ee9a0
Merge branch 'develop' into reoptjl-version
adfarth Jul 18, 2024
5c0fa4a
Merge branch 'develop' into erp-outputs-help
adfarth Jul 18, 2024
1efd3fb
Update views.py
hdunham Jul 18, 2024
c87efc5
Merge branch 'erp-outputs-help' of https://github.com/NREL/REopt_API …
hdunham Jul 22, 2024
9a5ec88
make error messages from erp help, inputs, and outputs endpoints alig…
hdunham Jul 22, 2024
c3cf08f
Merge pull request #593 from NREL/reoptjl-version
hdunham Jul 22, 2024
4ac54c4
Update CHANGELOG.md
hdunham Jul 22, 2024
24e16c7
Merge branch 'develop' into erp-outputs-help
hdunham Jul 22, 2024
9cf84ae
Merge pull request #589 from NREL/erp-outputs-help
hdunham Jul 22, 2024
1abc1d7
Update CHANGELOG.md
hdunham Jul 22, 2024
3710aff
Revert "Update CHANGELOG.md"
hdunham Jul 29, 2024
4ec5df9
Reapply "Update CHANGELOG.md"
hdunham Jul 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ Classify the change according to the following categories:
##### Removed
### Patches

## v3.9.3
### Minor Updates
#### Added
- `/erp/inputs` endpoint (calls `erp_help()`, same as `/erp/help`)
- `/erp/outputs` endpoint that GETs the ERP output field info (calls `erp_outputs()`)
#### Changed
- Set **reopt_version** in **APIMeta** and **ERPMeta** programatically based on actual REopt.jl package version in Julia environment instead of hardcoded so doesn't need to be updated by hand

## v3.9.2
#### Added
- Added attribute `thermal_efficiency` to the arguments of http endpoint `chp_defaults`
Expand Down
17 changes: 9 additions & 8 deletions julia_src/http.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ end
function reopt(req::HTTP.Request)
d = JSON.parse(String(req.body))
error_response = Dict()
settings = d["Settings"]
if !isempty(get(d, "api_key", ""))
ENV["NREL_DEVELOPER_API_KEY"] = pop!(d, "api_key")
else
ENV["NREL_DEVELOPER_API_KEY"] = test_nrel_developer_api_key
delete!(d, "api_key")
end
settings = d["Settings"]
solver_name = get(settings, "solver_name", "HiGHS")
if solver_name == "Xpress" && !(xpress_installed=="True")
solver_name = "HiGHS"
Expand Down Expand Up @@ -140,23 +140,22 @@ function reopt(req::HTTP.Request)

if isempty(error_response)
@info "REopt model solved with status $(results["status"])."
response = Dict(
"results" => results,
"reopt_version" => string(pkgversion(reoptjl))
)
if results["status"] == "error"
response = Dict(
"results" => results
)
if !isempty(inputs_with_defaults_set_in_julia)
response["inputs_with_defaults_set_in_julia"] = inputs_with_defaults_set_in_julia
end
return HTTP.Response(400, JSON.json(response))
else
response = Dict(
"results" => results,
"inputs_with_defaults_set_in_julia" => inputs_with_defaults_set_in_julia
)
response["inputs_with_defaults_set_in_julia"] = inputs_with_defaults_set_in_julia
return HTTP.Response(200, JSON.json(response))
end
else
@info "An error occured in the Julia code."
error_response["reopt_version"] = string(pkgversion(reoptjl))
return HTTP.Response(500, JSON.json(error_response))
end
end
Expand All @@ -169,9 +168,11 @@ function erp(req::HTTP.Request)
results = Dict()
try
results = reoptjl.backup_reliability(erp_inputs)
results["reopt_version"] = string(pkgversion(reoptjl))
catch e
@error "Something went wrong in the ERP Julia code!" exception=(e, catch_backtrace())
error_response["error"] = sprint(showerror, e)
error_response["reopt_version"] = string(pkgversion(reoptjl))
end
GC.gc()
if isempty(error_response)
Expand Down
1 change: 0 additions & 1 deletion reoptjl/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ def obj_create(self, bundle, **kwargs):
meta = {
"run_uuid": run_uuid,
"api_version": 3,
"reopt_version": "0.47.2",
"status": "Validating..."
}
bundle.data.update({"APIMeta": meta})
Expand Down
1 change: 1 addition & 0 deletions reoptjl/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class APIMeta(BaseModel, models.Model):
created = models.DateTimeField(auto_now_add=True)
reopt_version = models.TextField(
blank=True,
null=True,
default="",
help_text="Version number of the Julia package for REopt that is used to solve the problem."
)
Expand Down
2 changes: 2 additions & 0 deletions reoptjl/src/run_jump_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def run_jump_model(run_uuid):
if response.status_code == 500:
raise REoptFailedToStartError(task=name, message=response_json["error"], run_uuid=run_uuid, user_uuid=user_uuid)
results = response_json["results"]
reopt_version = response_json["reopt_version"]
if results["status"].strip().lower() != "error":
inputs_with_defaults_set_in_julia = response_json["inputs_with_defaults_set_in_julia"]
time_dict["pyjulia_run_reopt_seconds"] = time.time() - t_start
Expand Down Expand Up @@ -107,6 +108,7 @@ def run_jump_model(run_uuid):

profiler.profileEnd()
# TODO save profile times
APIMeta.objects.filter(run_uuid=run_uuid).update(reopt_version=reopt_version)
if status.strip().lower() != 'error':
update_inputs_in_database(inputs_with_defaults_set_in_julia, run_uuid)
process_results(results, run_uuid)
Expand Down
1 change: 1 addition & 0 deletions reoptjl/test/test_job_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def test_multiple_outages(self):
run_uuid = r.get('run_uuid')
resp = self.api_client.get(f'/v3/job/{run_uuid}/results')
r = json.loads(resp.content)
self.assertIn("reopt_version", r.keys())
results = r["outputs"]
self.assertEqual(np.array(results["Outages"]["unserved_load_series_kw"]).shape, (1,2,5))
self.assertEqual(np.array(results["Outages"]["generator_fuel_used_per_outage_gal"]).shape, (1,2))
Expand Down
4 changes: 2 additions & 2 deletions resilience_stats/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ def obj_create(self, bundle, **kwargs):

meta_dict = {
"run_uuid": erp_run_uuid,
"reopt_version": "0.45.0",
"status": "Validating..."
}

Expand Down Expand Up @@ -450,7 +449,8 @@ def process_erp_results(results: dict, run_uuid: str) -> None:
#TODO: get success or error status from julia
meta = ERPMeta.objects.get(run_uuid=run_uuid)
meta.status = 'Completed' #results.get("status")
meta.save(update_fields=['status'])
meta.reopt_version = results.pop("reopt_version")
meta.save(update_fields=['status','reopt_version'])
ERPOutputs.create(meta=meta, **results).save()


Expand Down
1 change: 1 addition & 0 deletions resilience_stats/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class ERPMeta(BaseModel, models.Model):
created = models.DateTimeField(auto_now_add=True)
reopt_version = models.TextField(
blank=True,
null=True,
default="",
help_text="Version number of the REopt Julia package that is used to calculate reliability."
)
Expand Down
22 changes: 20 additions & 2 deletions resilience_stats/tests/test_erp.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def setUp(self):
self.reopt_base_erp = '/v3/erp/'
self.reopt_base_erp_results = '/v3/erp/{}/results/'
self.reopt_base_erp_help = '/v3/erp/help/'
self.reopt_base_erp_inputs = '/v3/erp/inputs/'
self.reopt_base_erp_outputs = '/v3/erp/outputs/'
self.reopt_base_erp_chp_defaults = '/v3/erp/chp_defaults/?prime_mover={0}&is_chp={1}&size_kw={2}'
self.post_sim_gens_batt_pv_wind = os.path.join('resilience_stats', 'tests', 'ERP_sim_gens_batt_pv_wind_post.json')
self.post_sim_large_stor = os.path.join('resilience_stats', 'tests', 'ERP_sim_large_stor_post.json')
Expand All @@ -42,6 +44,12 @@ def get_results_sim(self, run_uuid):
def get_help(self):
return self.api_client.get(self.reopt_base_erp_help)

def get_inputs(self):
return self.api_client.get(self.reopt_base_erp_inputs)

def get_outputs(self):
return self.api_client.get(self.reopt_base_erp_outputs)

def get_chp_defaults(self, prime_mover, is_chp, size_kw):
return self.api_client.get(
self.reopt_base_erp_chp_defaults.format(prime_mover, is_chp, size_kw),
Expand All @@ -66,7 +74,9 @@ def test_erp_long_duration_battery(self):
r_sim = json.loads(resp.content)
erp_run_uuid = r_sim.get('run_uuid')
resp = self.get_results_sim(erp_run_uuid)
results_sim = json.loads(resp.content)["outputs"]
r = json.loads(resp.content)
self.assertIn("reopt_version", r.keys())
results_sim = r["outputs"]

expected_result = ([1]*79)+[0.999543,0.994178,0.9871,0.97774,0.965753,0.949429,0.926712,0.899543,0.863584,0.826712,0.785616,0.736416,0.683105,0.626256,0.571005,0.519064,0.47226,0.429909,0.391553,0.357306,0]
#TODO: resolve bug where unlimted fuel markov portion of results goes to zero 1 timestep early
Expand Down Expand Up @@ -182,14 +192,22 @@ def test_erp_with_no_opt(self):
resp = self.get_results_sim(erp_run_uuid)
self.assertHttpOK(resp)

def test_erp_help_view(self):
def test_erp_help_views(self):
"""
Tests hiting the erp/help url to get defaults and other info about inputs
"""

resp = self.get_help()
self.assertHttpOK(resp)
resp = json.loads(resp.content)

resp = self.get_inputs()
self.assertHttpOK(resp)
resp = json.loads(resp.content)

resp = self.get_outputs()
self.assertHttpOK(resp)
resp = json.loads(resp.content)

resp = self.get_chp_defaults("recip_engine", True, 10000)
self.assertHttpOK(resp)
Expand Down
2 changes: 2 additions & 0 deletions resilience_stats/urls_v3plus.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
urlpatterns = [
re_path(r'^erp/(?P<run_uuid>[0-9a-f-]+)/results/?$', views.erp_results),
re_path(r'^erp/help/?$', views.erp_help),
re_path(r'^erp/inputs/?$', views.erp_inputs),
re_path(r'^erp/outputs/?$', views.erp_outputs),
re_path(r'^erp/chp_defaults/?$', views.erp_chp_prime_gen_defaults),
]
57 changes: 46 additions & 11 deletions resilience_stats/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,23 +104,58 @@ def erp_results(request, run_uuid):
resp['status'] = 'Error'
return JsonResponse(resp, status=500)

def get_erp_inputs_info():
d = dict()
d["reopt_run_uuid"] = ERPMeta.info_dict(ERPMeta)["reopt_run_uuid"]
# do models need to be passed in as arg?
d[ERPOutageInputs.key] = ERPOutageInputs.info_dict(ERPOutageInputs)
d[ERPPVInputs.key] = ERPPVInputs.info_dict(ERPPVInputs)
d[ERPWindInputs.key] = ERPWindInputs.info_dict(ERPWindInputs)
d[ERPElectricStorageInputs.key] = ERPElectricStorageInputs.info_dict(ERPElectricStorageInputs)
d[ERPGeneratorInputs.key] = ERPGeneratorInputs.info_dict(ERPGeneratorInputs)
d[ERPPrimeGeneratorInputs.key] = ERPPrimeGeneratorInputs.info_dict(ERPPrimeGeneratorInputs)
#TODO: add wind once implemented
return JsonResponse(d)

def erp_help(request):
"""
Served at host/erp/help
:param request:
:return: JSON response with all erp inputs
"""
try:
d = dict()
d["reopt_run_uuid"] = ERPMeta.info_dict(ERPMeta)["reopt_run_uuid"]
# do models need to be passed in as arg?
d[ERPOutageInputs.key] = ERPOutageInputs.info_dict(ERPOutageInputs)
d[ERPPVInputs.key] = ERPPVInputs.info_dict(ERPPVInputs)
d[ERPWindInputs.key] = ERPWindInputs.info_dict(ERPWindInputs)
d[ERPElectricStorageInputs.key] = ERPElectricStorageInputs.info_dict(ERPElectricStorageInputs)
d[ERPGeneratorInputs.key] = ERPGeneratorInputs.info_dict(ERPGeneratorInputs)
d[ERPPrimeGeneratorInputs.key] = ERPPrimeGeneratorInputs.info_dict(ERPPrimeGeneratorInputs)
#TODO: add wind once implemented
return JsonResponse(d)
resp = get_erp_inputs_info()
return resp

except Exception as e:
return JsonResponse({"Error": "Unexpected error in ERP help endpoint: {}".format(e.args[0])}, status=500)

def erp_inputs(request):
"""
Served at host/erp/inputs
:param request:
:return: JSON response with all erp inputs
"""
try:
resp = get_erp_inputs_info()
return resp

except Exception as e:
return JsonResponse({"Error": "Unexpected error in ERP inputs endpoint: {}".format(e.args[0])}, status=500)

def erp_outputs(request):
"""
Served at host/erp/outputs
:return: JSON response with all erp outputs
"""

try:
d = ERPOutputs.info_dict(ERPOutputs)
return JsonResponse(d)

except Exception as e:
return JsonResponse({"Error": "Unexpected error in ERP outputs endpoint: {}".format(e.args[0])}, status=500)

def erp_chp_prime_gen_defaults(request):
prime_mover = str(request.GET.get('prime_mover'))
is_chp = bool(request.GET.get('is_chp'))
Expand Down