├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── REopt API Scripts ├── archived_v2_scripts │ ├── electric_rates │ │ └── PGE_E20.json │ ├── future_cost_notebook.ipynb │ ├── inputs │ │ ├── Scenario_POST1.json │ │ ├── Scenario_POST2.json │ │ ├── Scenario_POST3.json │ │ ├── Scenario_POST4.json │ │ ├── Scenario_POST5.json │ │ ├── all_api_inputs.csv │ │ └── scenarios.csv │ ├── load_profiles │ │ └── site3_8760_loads_kw.csv │ ├── multi_scenario_example.ipynb │ ├── outputs │ │ ├── my_results.json │ │ ├── results.json │ │ ├── results_summary.csv │ │ ├── results_template.csv │ │ ├── site3-No-PV.json │ │ └── site3-With-PV.json │ ├── single_scenario_example.ipynb │ └── src │ │ ├── __init__.py │ │ ├── create_input_template_with_values_from_help_endpoint.py │ │ ├── logger.py │ │ ├── multi_site_inputs_parser.py │ │ ├── parse_api_responses_to_csv.py │ │ ├── parse_api_responses_to_excel.py │ │ ├── post_and_poll.py │ │ ├── results_poller.py │ │ └── template.xlsx ├── electric_rates │ └── PGE_E20.json ├── google_colab_simple_examples.ipynb ├── inputs │ └── post_1.json ├── load_profiles │ └── site3_8760_loads_kw.csv ├── outputs │ ├── response_1.json │ └── results_file.json ├── single_scenario_example.ipynb └── src │ ├── __init__.py │ ├── create_input_template_with_values_from_help_endpoint.py │ ├── logger.py │ ├── multi_site_inputs_parser.py │ ├── parse_api_responses_to_csv.py │ ├── parse_api_responses_to_excel.py │ ├── post_and_poll.py │ ├── results_poller.py │ └── template.xlsx ├── REopt Julia Package Scripts ├── Manifest.toml ├── Project.toml ├── results │ ├── pv_retail.json │ └── wind_battery_hospital.json ├── scenarios │ ├── pv_retail.json │ └── wind_battery_hospital.json └── simple_examples.jl ├── docker-compose.yml ├── requirements.txt ├── v2_v3_inputs_map.csv └── v2_v3_outputs_map.csv /.gitignore: -------------------------------------------------------------------------------- 1 | .** 2 | !.gitignore 3 | env/ 4 | *.log 5 | venv/ 6 | ENV/ 7 | **.pyc 8 | 9 | # Custom files for analysis 10 | inputs/* 11 | outputs/* 12 | load_profiles/* 13 | electric_rates/* 14 | 15 | 16 | # Notebooks, except example/templates 17 | # *.ipynb 18 | !single_scenario_example.ipynb 19 | !multi_scenario_example.ipynb 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyter/datascience-notebook:latest 2 | 3 | RUN conda install requests openpyxl -y 4 | 5 | ENV JUPYTER_ENABLE_LAB=yes -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, National Renewable Energy Laboratory 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REopt Analysis Scripts for the REopt API and Julia Package 2 | 3 | [REopt](https://reopt.nrel.gov/) is a techno-economic decision support model 4 | from NREL which is used for optimizing energy systems for buildings, campuses, 5 | communities, and microgrids. REopt can be accessed in many ways: 6 | - Free, easy to use web tool: https://reopt.nrel.gov/ 7 | - By calling the REopt application programming interface (API) (examples in this repository) 8 | - By using the registered REopt Julia Package (examples in this repository) 9 | 10 | This repository includes useful guidance and example scripts for using REopt's API and Julia package. Please see the [wiki](https://github.com/NREL/REopt-Analysis-Scripts/wiki) for additional information and setup steps. 11 | 12 | [Open a cloud-hosted Jupyter Notebook to interface with the API using Google Colab](https://colab.research.google.com/github/NREL/REopt-Analysis-Scripts/blob/master/REopt%20API%20Scripts/google_colab_simple_examples.ipynb) 13 | 14 | If you instead wish to develop and use these codebases locally, see instructions [here](https://github.com/NREL/REopt_API/blob/master/README.md). 15 | 16 | **Note:** As of May 2024, the REopt API has decommissioned (end-of-life) v1 and v2 of the API, and the /stable URL is equivalent to v3 which is the only public version of the API. V3 of the API calls the REopt.jl Julia package internally. 17 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/electric_rates/PGE_E20.json: -------------------------------------------------------------------------------- 1 | {"energyweekdayschedule":[[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1]],"energyweekendschedule":[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],"energyratestructure":[[{"rate":0,"unit":"kWh"}],[{"rate":0.09716,"unit":"kWh"}],[{"rate":0.1133,"unit":"kWh"}],[{"rate":0.08981,"unit":"kWh"}],[{"rate":0.1196,"unit":"kWh"}],[{"rate":0.16299,"unit":"kWh"}]],"demandratestructure":[[{"rate":0}],[{"rate":0.0}],[{"rate":0.06}],[{"rate":5.88}],[{"rate":21.19}]],"demandweekdayschedule":[[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1]],"demandweekendschedule":[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],"flatdemandstructure":[[{"rate":0}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}]],"flatdemandmonths":[1,2,3,4,5,6,7,8,9,10,11,12],"demandratchetpercentage":[0,0,0,0,0,50.0,50.0,50.0,50.0,0,0,0],"fixedchargefirstmeter":45.08771,"fixedchargeunits":"$/day"} -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/future_cost_notebook.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Run a REopt API evaluation" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Initialization" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "import pandas as pd\n", 24 | "import numpy as np\n", 25 | "import json\n", 26 | "import requests\n", 27 | "import copy\n", 28 | "import os\n", 29 | "from src.post_and_poll import get_api_results\n", 30 | "API_KEY = 'uiMp56xYMT983T2OikI0RrEKAyAAEDpZHzuC8nTC' # REPLACE WITH YOUR API KEY" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# following is not necessary but silences warnings:\n", 40 | "# InsecureRequestWarning: Unverified HTTPS request is being made to host 'developer.nrel.gov'. Adding certificate verification is strongly advised.\n", 41 | "import urllib3\n", 42 | "urllib3.disable_warnings()" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "\"\"\"\n", 52 | "Here are some convenience definitions for using the Multi-scenario capabilities\n", 53 | "\"\"\"\n", 54 | "##############################################################################################################\n", 55 | "inputs_path = os.path.join(\".\", 'inputs')\n", 56 | "outputs_path = os.path.join(\".\", 'outputs')\n", 57 | "loads_path = os.path.join(\".\", 'load_profiles')\n", 58 | "rates_path = os.path.join(\".\", 'electric_rates')\n", 59 | "##############################################################################################################" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "## Load a previously saved API response .json file instead of running REopt" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 4, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "response_json = 'results'\n", 76 | "with open(os.path.join(outputs_path, response_json + '.json'), 'rb') as handle:\n", 77 | " api_response = json.load(handle)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "## Scenario Inputs (POST), if wanting to do a new API call" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 4, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "post_1 = {\"Scenario\": {\n", 94 | " \"Site\": {\n", 95 | " \"longitude\": -118.116,\n", 96 | " \"latitude\": 34.579,\n", 97 | " \"roof_squarefeet\": 5000.0,\n", 98 | " \"land_acres\": 1.0,\n", 99 | " \"PV\": {\n", 100 | " \"macrs_bonus_pct\": 0.4,\n", 101 | " \"installed_cost_us_dollars_per_kw\": 2000.0,\n", 102 | " \"tilt\": 34.579,\n", 103 | " \"degradation_pct\": 0.005,\n", 104 | " \"macrs_option_years\": 5,\n", 105 | " \"federal_itc_pct\": 0.3,\n", 106 | " \"module_type\": 0,\n", 107 | " \"array_type\": 1,\n", 108 | " \"om_cost_us_dollars_per_kw\": 16.0,\n", 109 | " \"macrs_itc_reduction\": 0.5,\n", 110 | " \"azimuth\": 180.0,\n", 111 | " \"federal_rebate_us_dollars_per_kw\": 350.0,\n", 112 | " \"dc_ac_ratio\": 1.1\n", 113 | " },\n", 114 | " \"LoadProfile\": {\n", 115 | " \"doe_reference_name\": \"RetailStore\",\n", 116 | " \"annual_kwh\": 10000000.0,\n", 117 | " \"city\": \"LosAngeles\"\n", 118 | " },\n", 119 | " \"Storage\": {\n", 120 | " \"total_rebate_per_kw\": 100.0,\n", 121 | " \"macrs_option_years\": 5,\n", 122 | " \"can_grid_charge\": True,\n", 123 | " \"macrs_bonus_pct\": 0.4,\n", 124 | " \"macrs_itc_reduction\": 0.5,\n", 125 | " \"total_itc_pct\": 0,\n", 126 | " \"installed_cost_us_dollars_per_kw\": 1000.0,\n", 127 | " \"installed_cost_us_dollars_per_kwh\": 500.0,\n", 128 | " \"replace_cost_us_dollars_per_kw\": 460.0,\n", 129 | " \"replace_cost_us_dollars_per_kwh\": 230.0\n", 130 | " },\n", 131 | " \"ElectricTariff\": {\n", 132 | " \"urdb_label\": \"5ed6c1a15457a3367add15ae\"\n", 133 | " },\n", 134 | " \"Financial\": {\n", 135 | " \"escalation_pct\": 0.026,\n", 136 | " \"offtaker_discount_pct\": 0.081,\n", 137 | " \"owner_discount_pct\": 0.081,\n", 138 | " \"analysis_years\": 20,\n", 139 | " \"offtaker_tax_pct\": 0.4,\n", 140 | " \"owner_tax_pct\": 0.4,\n", 141 | " \"om_cost_escalation_pct\": 0.025\n", 142 | " }\n", 143 | "}}}" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "### Load in a custom electric rate generated from https://reopt.nrel.gov/tool/custom_tariffs" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 6, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "load_electric_rate = \"PGE_E20\"\n", 160 | "with open(os.path.join(rates_path, load_electric_rate + \".json\"), 'r') as fp:\n", 161 | " rate_1 = json.load(fp)\n", 162 | "post_1[\"Scenario\"][\"Site\"][\"ElectricTariff\"][\"urdb_response\"] = rate_1" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## Save the POST to a .json file for sharing or future loading in" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 7, 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "# Convert python dictionary post into json and save to a .json file\n", 179 | "post_save = post_1\n", 180 | "post_name = \"post_1\"\n", 181 | "with open(os.path.join(inputs_path, post_name + \".json\"), 'w') as fp:\n", 182 | " json.dump(post_save, fp)" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "## Or load in a saved .json file for the inputs/POST" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 8, 195 | "metadata": { 196 | "scrolled": true 197 | }, 198 | "outputs": [], 199 | "source": [ 200 | "# Load a json into a python dictionary\n", 201 | "load_post = \"post_1\"\n", 202 | "with open(os.path.join(inputs_path, load_post + \".json\"), 'r') as fp:\n", 203 | " post_1 = json.load(fp)" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "## POST and poll (periodic GET request) the API to GET a new result, if not loading in a previous response. This may take a while!" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "### Note, the `api_url` in the `get_api_results` function below calls the **production server** hosted API (master/main branch/version, publicly accessible)\n", 218 | "\n", 219 | "#### For calling a locally-hosted (localhost) API, see:\n", 220 | "- https://github.com/NREL/REopt_Lite_API/wiki/localhost-URLs-for-calling-locally-hosted-API\n", 221 | "\n", 222 | "#### For calling an API hosted on an NREL-internal server (only NREL users can access this), see:\n", 223 | "- https://github.nrel.gov/REopt/API_scripts/wiki/API-URLs-for-NREL-internal-servers" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "`get_api_results` POST's your inputs to the API `job` endpoint, which provides a `run_uuid` if the input is valid, and then polls the `results` endpoint using the `run_uuid` until the results come back with a status other than `Optimizing...`.\n", 231 | "\n", 232 | "`get_api_results` also saves the results (full API response, including inputs) to the `results_file`.\n", 233 | "\n", 234 | "A log file is also created in the current working directory." 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": 5, 240 | "metadata": {}, 241 | "outputs": [ 242 | { 243 | "name": "stderr", 244 | "output_type": "stream", 245 | "text": [ 246 | "main INFO Response OK from https://developer.nrel.gov/api/reopt/stable/job/?api_key=uiMp56xYMT983T2OikI0RrEKAyAAEDpZHzuC8nTC.\n", 247 | "main INFO Polling https://developer.nrel.gov/api/reopt/stable/job/52bd746a-36f8-4314-8e44-930b898e9325/results/?api_key=uiMp56xYMT983T2OikI0RrEKAyAAEDpZHzuC8nTC for results with interval of 5s...\n", 248 | "main INFO Saved results to ./outputs/results_file.json\n" 249 | ] 250 | } 251 | ], 252 | "source": [ 253 | "outputs_file_name = \"results_file\"\n", 254 | "root_url = \"https://developer.nrel.gov/api/reopt/stable\"\n", 255 | "# root_url = \"http://host.docker.internal:8000/stable\"\n", 256 | "api_response = get_api_results(post=post_1, \n", 257 | " API_KEY=API_KEY, \n", 258 | " api_url=root_url, \n", 259 | " results_file=os.path.join(outputs_path, outputs_file_name + \".json\"), \n", 260 | " run_id=None)" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 6, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "root_url = \"https://developer.nrel.gov/api/reopt/dev/futurecosts\" + \"/?api_key=\" + API_KEY\n", 270 | "# root_url = \"http://host.docker.internal:8000/dev/futurecosts\" + \"/?api_key=\" + API_KEY\n", 271 | "resp = requests.post(url=root_url, json={\"run_uuid\": api_response[\"outputs\"][\"Scenario\"][\"run_uuid\"]}, verify=False)\n", 272 | "futurecosts_response = json.loads(resp.text)" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": 7, 278 | "metadata": {}, 279 | "outputs": [ 280 | { 281 | "data": { 282 | "text/plain": [ 283 | "{'status': 'Future Costs Job created for scenario 52bd746a-36f8-4314-8e44-930b898e9325'}" 284 | ] 285 | }, 286 | "execution_count": 7, 287 | "metadata": {}, 288 | "output_type": "execute_result" 289 | } 290 | ], 291 | "source": [ 292 | "futurecosts_response" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": 9, 298 | "metadata": {}, 299 | "outputs": [ 300 | { 301 | "data": { 302 | "text/plain": [ 303 | "" 304 | ] 305 | }, 306 | "execution_count": 9, 307 | "metadata": {}, 308 | "output_type": "execute_result" 309 | } 310 | ], 311 | "source": [ 312 | "resp" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": 8, 318 | "metadata": {}, 319 | "outputs": [ 320 | { 321 | "ename": "JSONDecodeError", 322 | "evalue": "Expecting value: line 1 column 1 (char 0)", 323 | "output_type": "error", 324 | "traceback": [ 325 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 326 | "\u001b[0;31mJSONDecodeError\u001b[0m Traceback (most recent call last)", 327 | "Input \u001b[0;32mIn [8]\u001b[0m, in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 3\u001b[0m get_url \u001b[38;5;241m=\u001b[39m root_url \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m/\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m+\u001b[39m api_response[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124moutputs\u001b[39m\u001b[38;5;124m\"\u001b[39m][\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mScenario\u001b[39m\u001b[38;5;124m\"\u001b[39m][\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrun_uuid\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m/results/?api_key=\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m+\u001b[39m API_KEY\n\u001b[1;32m 4\u001b[0m resp \u001b[38;5;241m=\u001b[39m requests\u001b[38;5;241m.\u001b[39mget(url\u001b[38;5;241m=\u001b[39mget_url, verify\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[0;32m----> 5\u001b[0m futurecosts_get_response \u001b[38;5;241m=\u001b[39m \u001b[43mjson\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mloads\u001b[49m\u001b[43m(\u001b[49m\u001b[43mresp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mtext\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 6\u001b[0m futurecosts_get_response\n", 328 | "File \u001b[0;32m/opt/conda/lib/python3.10/json/__init__.py:346\u001b[0m, in \u001b[0;36mloads\u001b[0;34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001b[0m\n\u001b[1;32m 341\u001b[0m s \u001b[38;5;241m=\u001b[39m s\u001b[38;5;241m.\u001b[39mdecode(detect_encoding(s), \u001b[38;5;124m'\u001b[39m\u001b[38;5;124msurrogatepass\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 343\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m (\u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[1;32m 344\u001b[0m parse_int \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m parse_float \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[1;32m 345\u001b[0m parse_constant \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_pairs_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m kw):\n\u001b[0;32m--> 346\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_default_decoder\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdecode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 347\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m 348\u001b[0m \u001b[38;5;28mcls\u001b[39m \u001b[38;5;241m=\u001b[39m JSONDecoder\n", 329 | "File \u001b[0;32m/opt/conda/lib/python3.10/json/decoder.py:337\u001b[0m, in \u001b[0;36mJSONDecoder.decode\u001b[0;34m(self, s, _w)\u001b[0m\n\u001b[1;32m 332\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mdecode\u001b[39m(\u001b[38;5;28mself\u001b[39m, s, _w\u001b[38;5;241m=\u001b[39mWHITESPACE\u001b[38;5;241m.\u001b[39mmatch):\n\u001b[1;32m 333\u001b[0m \u001b[38;5;124;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001b[39;00m\n\u001b[1;32m 334\u001b[0m \u001b[38;5;124;03m containing a JSON document).\u001b[39;00m\n\u001b[1;32m 335\u001b[0m \n\u001b[1;32m 336\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 337\u001b[0m obj, end \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mraw_decode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m_w\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mend\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 338\u001b[0m end \u001b[38;5;241m=\u001b[39m _w(s, end)\u001b[38;5;241m.\u001b[39mend()\n\u001b[1;32m 339\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m end \u001b[38;5;241m!=\u001b[39m \u001b[38;5;28mlen\u001b[39m(s):\n", 330 | "File \u001b[0;32m/opt/conda/lib/python3.10/json/decoder.py:355\u001b[0m, in \u001b[0;36mJSONDecoder.raw_decode\u001b[0;34m(self, s, idx)\u001b[0m\n\u001b[1;32m 353\u001b[0m obj, end \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mscan_once(s, idx)\n\u001b[1;32m 354\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[0;32m--> 355\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m JSONDecodeError(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mExpecting value\u001b[39m\u001b[38;5;124m\"\u001b[39m, s, err\u001b[38;5;241m.\u001b[39mvalue) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;28mNone\u001b[39m\n\u001b[1;32m 356\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m obj, end\n", 331 | "\u001b[0;31mJSONDecodeError\u001b[0m: Expecting value: line 1 column 1 (char 0)" 332 | ] 333 | } 334 | ], 335 | "source": [ 336 | "root_url = \"https://developer.nrel.gov/api/reopt/dev/futurecosts\"\n", 337 | "# root_url = \"http://host.docker.internal:8000/dev/futurecosts\"\n", 338 | "get_url = root_url + \"/\" + api_response[\"outputs\"][\"Scenario\"][\"run_uuid\"] + \"/results/?api_key=\" + API_KEY\n", 339 | "resp = requests.get(url=get_url, verify=False)\n", 340 | "futurecosts_get_response = json.loads(resp.text)\n", 341 | "futurecosts_get_response" 342 | ] 343 | }, 344 | { 345 | "cell_type": "markdown", 346 | "metadata": {}, 347 | "source": [ 348 | "### If you get disconnected from the polling function but you think it ran, copy the run_uuid from the log above to manually GET the results:" 349 | ] 350 | }, 351 | { 352 | "cell_type": "code", 353 | "execution_count": 10, 354 | "metadata": {}, 355 | "outputs": [], 356 | "source": [ 357 | "run_uuid = api_response[\"outputs\"][\"Scenario\"][\"run_uuid\"]\n", 358 | "results_url = root_url + '/job/' + run_uuid + '/results/?api_key=' + API_KEY\n", 359 | "resp = requests.get(url=results_url, verify=False)\n", 360 | "api_response = json.loads(resp.text)" 361 | ] 362 | }, 363 | { 364 | "cell_type": "markdown", 365 | "metadata": {}, 366 | "source": [ 367 | "## Get summary of results" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": 103, 373 | "metadata": {}, 374 | "outputs": [ 375 | { 376 | "name": "stdout", 377 | "output_type": "stream", 378 | "text": [ 379 | "NPV ($) = 356671.0\n", 380 | "Capital Cost, Net ($) = 184788.83\n", 381 | "PV size_kw = 216.6667\n", 382 | "Storage size_kw = 55.10861988839906\n", 383 | "Storage size_kwh = 77.46981408480224\n" 384 | ] 385 | } 386 | ], 387 | "source": [ 388 | "print(\"NPV ($) = \", api_response[\"outputs\"][\"Scenario\"][\"Site\"][\"Financial\"][\"npv_us_dollars\"])\n", 389 | "print(\"Capital Cost, Net ($) = \", api_response[\"outputs\"][\"Scenario\"][\"Site\"][\"Financial\"][\"net_capital_costs\"])\n", 390 | "tech_list = [\"PV\", \"Wind\", \"Storage\", \"CHP\", \"Generator\", \"HotTES\", \"ColdTES\", \"AbsorptionChiller\", \"GHP\", \"NewBoiler\", \"SteamTurbine\"]\n", 391 | "for tech in tech_list:\n", 392 | " if tech in post_1[\"Scenario\"][\"Site\"].keys():\n", 393 | " if tech == \"GHP\":\n", 394 | " print(\"GHX Number of Boreholes = \", api_response[\"outputs\"][\"Scenario\"][\"Site\"][tech][\"ghpghx_chosen_outputs\"].get(\"number_of_boreholes\"))\n", 395 | " print(\"GHP Heat Pump Capacity (ton) = \", api_response[\"outputs\"][\"Scenario\"][\"Site\"][tech][\"ghpghx_chosen_outputs\"].get(\"peak_combined_heatpump_thermal_ton\"))\n", 396 | " # PV and Storage are considered if the POST does not explicitly make max_[size] == 0\n", 397 | " for size_name_value in [(key, val) for key, val in api_response[\"outputs\"][\"Scenario\"][\"Site\"][tech].items() if \"size\" in key]:\n", 398 | " print(tech + \" \" + size_name_value[0], \" = \", size_name_value[1])\n", 399 | " elif tech in [\"PV\", \"Storage\"]:\n", 400 | " for size_name_value in [(key, val) for key, val in api_response[\"outputs\"][\"Scenario\"][\"Site\"][tech].items() if \"size\" in key]:\n", 401 | " print(tech + \" \" + size_name_value[0], \" = \", size_name_value[1]) " 402 | ] 403 | }, 404 | { 405 | "cell_type": "markdown", 406 | "metadata": {}, 407 | "source": [ 408 | "### Here are some results keys examples:" 409 | ] 410 | }, 411 | { 412 | "cell_type": "code", 413 | "execution_count": 12, 414 | "metadata": {}, 415 | "outputs": [ 416 | { 417 | "data": { 418 | "text/plain": [ 419 | "dict_keys(['outputs', 'inputs', 'messages'])" 420 | ] 421 | }, 422 | "execution_count": 13, 423 | "metadata": {}, 424 | "output_type": "execute_result" 425 | } 426 | ], 427 | "source": [ 428 | "api_response.keys()" 429 | ] 430 | }, 431 | { 432 | "cell_type": "code", 433 | "execution_count": 14, 434 | "metadata": {}, 435 | "outputs": [ 436 | { 437 | "data": { 438 | "text/plain": [ 439 | "'optimal'" 440 | ] 441 | }, 442 | "execution_count": 15, 443 | "metadata": {}, 444 | "output_type": "execute_result" 445 | } 446 | ], 447 | "source": [ 448 | "api_response[\"outputs\"][\"Scenario\"][\"status\"]" 449 | ] 450 | }, 451 | { 452 | "cell_type": "code", 453 | "execution_count": 16, 454 | "metadata": {}, 455 | "outputs": [ 456 | { 457 | "name": "stdout", 458 | "output_type": "stream", 459 | "text": [ 460 | "year_one_emissions_lb_C02\n", 461 | "year_one_emissions_bau_lb_C02\n", 462 | "outdoor_air_temp_degF\n", 463 | "renewable_electricity_energy_pct\n", 464 | "Financial\n", 465 | "LoadProfile\n", 466 | "LoadProfileBoilerFuel\n", 467 | "LoadProfileChillerThermal\n", 468 | "ElectricTariff\n", 469 | "FuelTariff\n", 470 | "Storage\n", 471 | "Generator\n", 472 | "Wind\n", 473 | "CHP\n", 474 | "Boiler\n", 475 | "ElectricChiller\n", 476 | "AbsorptionChiller\n", 477 | "HotTES\n", 478 | "ColdTES\n", 479 | "NewBoiler\n", 480 | "SteamTurbine\n", 481 | "GHP\n", 482 | "PV\n" 483 | ] 484 | } 485 | ], 486 | "source": [ 487 | "for k in api_response[\"outputs\"][\"Scenario\"][\"Site\"].keys():\n", 488 | " print(k)" 489 | ] 490 | }, 491 | { 492 | "cell_type": "markdown", 493 | "metadata": {}, 494 | "source": [ 495 | "## Save API response into a JSON file for later use" 496 | ] 497 | }, 498 | { 499 | "cell_type": "code", 500 | "execution_count": 17, 501 | "metadata": {}, 502 | "outputs": [], 503 | "source": [ 504 | "response_save = api_response\n", 505 | "file_name_to_save = \"response_1\"\n", 506 | "with open(os.path.join(outputs_path, file_name_to_save + \".json\"), 'w') as fp:\n", 507 | " json.dump(response_save, fp)" 508 | ] 509 | } 510 | ], 511 | "metadata": { 512 | "@webio": { 513 | "lastCommId": null, 514 | "lastKernelId": null 515 | }, 516 | "kernelspec": { 517 | "display_name": "Python 3 (ipykernel)", 518 | "language": "python", 519 | "name": "python3" 520 | }, 521 | "language_info": { 522 | "codemirror_mode": { 523 | "name": "ipython", 524 | "version": 3 525 | }, 526 | "file_extension": ".py", 527 | "mimetype": "text/x-python", 528 | "name": "python", 529 | "nbconvert_exporter": "python", 530 | "pygments_lexer": "ipython3", 531 | "version": "3.10.5" 532 | }, 533 | "latex_envs": { 534 | "LaTeX_envs_menu_present": true, 535 | "autoclose": false, 536 | "autocomplete": true, 537 | "bibliofile": "biblio.bib", 538 | "cite_by": "apalike", 539 | "current_citInitial": 1, 540 | "eqLabelWithNumbers": true, 541 | "eqNumInitial": 1, 542 | "hotkeys": { 543 | "equation": "Ctrl-E", 544 | "itemize": "Ctrl-I" 545 | }, 546 | "labels_anchors": false, 547 | "latex_user_defs": false, 548 | "report_style_numbering": false, 549 | "user_envs_cfg": false 550 | } 551 | }, 552 | "nbformat": 4, 553 | "nbformat_minor": 4 554 | } 555 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/inputs/Scenario_POST1.json: -------------------------------------------------------------------------------- 1 | {"Scenario": { 2 | "time_steps_per_hour": 1, 3 | "description": "existing PV with custom utility rates analysis", 4 | "Site": { 5 | "latitude": 35.2468, 6 | "longitude": -91.7337, 7 | 8 | "LoadProfile": { 9 | "doe_reference_name": "MidriseApartment", 10 | "annual_kwh": 259525, 11 | "year": 2017 12 | }, 13 | 14 | "ElectricTariff": { 15 | "urdb_rate_name": "custom", 16 | "blended_monthly_rates_us_dollars_per_kwh": [0.15, 0.2, 0.21, 0.23, 0.27, 0.19, 0.22, 0.17, 0.24, 0.26, 0.18,0.2], 17 | "blended_monthly_demand_charges_us_dollars_per_kw": [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] 18 | }, 19 | 20 | "PV": { 21 | "max_kw": "800", 22 | "existing_kw" : "200" 23 | 24 | }, 25 | 26 | "Storage": { 27 | "max_kw": 500, 28 | "max_kwh": 900 29 | }, 30 | 31 | "Wind": { 32 | "max_kw": 0, 33 | "max_kwh": 0 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/inputs/Scenario_POST2.json: -------------------------------------------------------------------------------- 1 | {"Scenario": { 2 | "time_steps_per_hour": 1, 3 | "description": "Wind with high utilty rates analysis", 4 | "Site": { 5 | "latitude": 39.7392, 6 | "longitude": -104.99, 7 | 8 | "LoadProfile": { 9 | "doe_reference_name": "LargeOffice", 10 | "annual_kwh": 500000, 11 | "year": 2017 12 | }, 13 | 14 | "ElectricTariff": { 15 | "urdb_rate_name": "custom", 16 | "blended_monthly_rates_us_dollars_per_kwh": [0.65, 0.52, 0.51, 0.43, 0.77, 0.69, 0.42, 0.47, 0.44, 0.36, 0.28,0.22], 17 | "blended_monthly_demand_charges_us_dollars_per_kw": [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] 18 | }, 19 | 20 | "PV": { 21 | "max_kw": "0", 22 | "existing_kw" : "0" 23 | 24 | }, 25 | 26 | "Storage": { 27 | "max_kw": 500, 28 | "max_kwh": 900 29 | }, 30 | 31 | "Wind": { 32 | "max_kw": 150 33 | 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/inputs/Scenario_POST5.json: -------------------------------------------------------------------------------- 1 | {"Scenario": { 2 | "time_steps_per_hour": 1, 3 | "Site": { 4 | "latitude": 35.2468, 5 | "longitude": -91.7337, 6 | 7 | "LoadProfile": { 8 | "doe_reference_name": "MidriseApartment", 9 | "annual_kwh": 259525, 10 | "year": 2017 11 | }, 12 | 13 | "ElectricTariff": { 14 | "urdb_rate_name": "custom", 15 | "blended_monthly_rates_us_dollars_per_kwh": [0.15, 0.2, 0.21, 0.23, 0.27, 0.19, 0.22, 0.17, 0.24, 0.26, 0.18,0.2], 16 | "blended_monthly_demand_charges_us_dollars_per_kw": [0.08, 0.11, 0, 0, 0.15, 0.14, 0.09, 0.06, 0, 0, 0.05, 0] 17 | }, 18 | 19 | "PV": { 20 | "max_kw": "200", 21 | "existing_kw" : "0" 22 | 23 | }, 24 | 25 | "Storage": { 26 | "max_kw": 0, 27 | "max_kwh": 0 28 | }, 29 | 30 | "Wind": { 31 | "max_kw": 0, 32 | "max_kwh": 0 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/inputs/all_api_inputs.csv: -------------------------------------------------------------------------------- 1 | site_number,AbsorptionChiller|chiller_cop,AbsorptionChiller|chiller_elec_cop,AbsorptionChiller|installed_cost_us_dollars_per_ton,AbsorptionChiller|macrs_bonus_pct,AbsorptionChiller|macrs_option_years,AbsorptionChiller|max_ton,AbsorptionChiller|min_ton,AbsorptionChiller|om_cost_us_dollars_per_ton,Boiler|boiler_efficiency,Boiler|emissions_factor_lb_CO2_per_mmbtu,Boiler|existing_boiler_production_type_steam_or_hw,Boiler|installed_cost_us_dollars_per_mmbtu_per_hr,Boiler|max_mmbtu_per_hr,Boiler|max_thermal_factor_on_peak_load,Boiler|min_mmbtu_per_hr,CHP|can_curtail,CHP|can_export_beyond_site_load,CHP|can_net_meter,CHP|can_wholesale,CHP|chp_unavailability_periods,CHP|cooling_thermal_factor,CHP|derate_slope_pct_per_degF,CHP|derate_start_temp_degF,CHP|elec_effic_full_load,CHP|elec_effic_half_load,CHP|emissions_factor_lb_CO2_per_mmbtu,CHP|federal_itc_pct,CHP|federal_rebate_us_dollars_per_kw,CHP|installed_cost_us_dollars_per_kw,CHP|macrs_bonus_pct,CHP|macrs_itc_reduction,CHP|macrs_option_years,CHP|max_derate_factor,CHP|max_kw,CHP|min_allowable_kw,CHP|min_kw,CHP|min_turn_down_pct,CHP|om_cost_us_dollars_per_hr_per_kw_rated,CHP|om_cost_us_dollars_per_kw,CHP|om_cost_us_dollars_per_kwh,CHP|pbi_max_us_dollars,CHP|pbi_system_max_kw,CHP|pbi_us_dollars_per_kwh,CHP|pbi_years,CHP|prime_mover,CHP|size_class,CHP|state_ibi_max_us_dollars,CHP|state_ibi_pct,CHP|state_rebate_max_us_dollars,CHP|state_rebate_us_dollars_per_kw,CHP|tech_size_for_cost_curve,CHP|thermal_effic_full_load,CHP|thermal_effic_half_load,CHP|use_default_derate,CHP|utility_ibi_max_us_dollars,CHP|utility_ibi_pct,CHP|utility_rebate_max_us_dollars,CHP|utility_rebate_us_dollars_per_kw,ColdTES|installed_cost_us_dollars_per_gal,ColdTES|internal_efficiency_pct,ColdTES|macrs_bonus_pct,ColdTES|macrs_option_years,ColdTES|max_gal,ColdTES|min_gal,ColdTES|om_cost_us_dollars_per_gal,ColdTES|soc_init_pct,ColdTES|soc_min_pct,ColdTES|thermal_decay_rate_fraction,ElectricChiller|installed_cost_us_dollars_per_kw,ElectricChiller|max_kw,ElectricChiller|max_thermal_factor_on_peak_load,ElectricChiller|min_kw,ElectricTariff|add_blended_rates_to_urdb_rate,ElectricTariff|add_tou_energy_rates_to_urdb_rate,ElectricTariff|blended_annual_demand_charges_us_dollars_per_kw,ElectricTariff|blended_annual_rates_us_dollars_per_kwh,ElectricTariff|blended_monthly_demand_charges_us_dollars_per_kw,ElectricTariff|blended_monthly_rates_us_dollars_per_kwh,ElectricTariff|chp_does_not_reduce_demand_charges,ElectricTariff|chp_standby_rate_us_dollars_per_kw_per_month,ElectricTariff|coincident_peak_load_active_timesteps,ElectricTariff|coincident_peak_load_charge_us_dollars_per_kw,ElectricTariff|emissions_factor_series_lb_CO2_per_kwh,ElectricTariff|interconnection_limit_kw,ElectricTariff|net_metering_limit_kw,ElectricTariff|tou_energy_rates_us_dollars_per_kwh,ElectricTariff|urdb_label,ElectricTariff|urdb_rate_name,ElectricTariff|urdb_response,ElectricTariff|urdb_utility_name,ElectricTariff|wholesale_rate_above_site_load_us_dollars_per_kwh,ElectricTariff|wholesale_rate_us_dollars_per_kwh,Financial|analysis_years,Financial|boiler_fuel_escalation_pct,Financial|chp_fuel_escalation_pct,Financial|escalation_pct,Financial|microgrid_upgrade_cost_pct,Financial|offtaker_discount_pct,Financial|offtaker_tax_pct,Financial|om_cost_escalation_pct,Financial|owner_discount_pct,Financial|owner_tax_pct,Financial|third_party_ownership,Financial|value_of_lost_load_us_dollars_per_kwh,FuelTariff|boiler_fuel_blended_annual_rates_us_dollars_per_mmbtu,FuelTariff|chp_fuel_blended_annual_rates_us_dollars_per_mmbtu,FuelTariff|chp_fuel_type,FuelTariff|existing_boiler_fuel_type,Generator|can_curtail,Generator|can_export_beyond_site_load,Generator|can_net_meter,Generator|can_wholesale,Generator|diesel_fuel_cost_us_dollars_per_gallon,Generator|emissions_factor_lb_CO2_per_gal,Generator|existing_kw,Generator|federal_itc_pct,Generator|federal_rebate_us_dollars_per_kw,Generator|fuel_avail_gal,Generator|fuel_intercept_gal_per_hr,Generator|fuel_slope_gal_per_kwh,Generator|generator_only_runs_during_grid_outage,Generator|generator_sells_energy_back_to_grid,Generator|installed_cost_us_dollars_per_kw,Generator|macrs_bonus_pct,Generator|macrs_itc_reduction,Generator|macrs_option_years,Generator|max_kw,Generator|min_kw,Generator|min_turn_down_pct,Generator|om_cost_us_dollars_per_kw,Generator|om_cost_us_dollars_per_kwh,Generator|pbi_max_us_dollars,Generator|pbi_system_max_kw,Generator|pbi_us_dollars_per_kwh,Generator|pbi_years,Generator|state_ibi_max_us_dollars,Generator|state_ibi_pct,Generator|state_rebate_max_us_dollars,Generator|state_rebate_us_dollars_per_kw,Generator|utility_ibi_max_us_dollars,Generator|utility_ibi_pct,Generator|utility_rebate_max_us_dollars,Generator|utility_rebate_us_dollars_per_kw,HotTES|installed_cost_us_dollars_per_gal,HotTES|internal_efficiency_pct,HotTES|macrs_bonus_pct,HotTES|macrs_option_years,HotTES|max_gal,HotTES|min_gal,HotTES|om_cost_us_dollars_per_gal,HotTES|soc_init_pct,HotTES|soc_min_pct,HotTES|thermal_decay_rate_fraction,LoadProfile|annual_kwh,LoadProfile|critical_load_pct,LoadProfile|critical_loads_kw,LoadProfile|critical_loads_kw_is_net,LoadProfile|doe_reference_name,LoadProfile|loads_kw,LoadProfile|loads_kw_is_net,LoadProfile|monthly_totals_kwh,LoadProfile|outage_end_hour,LoadProfile|outage_end_time_step,LoadProfile|outage_is_major_event,LoadProfile|outage_start_hour,LoadProfile|outage_start_time_step,LoadProfile|percent_share,LoadProfile|year,LoadProfileBoilerFuel|annual_mmbtu,LoadProfileBoilerFuel|doe_reference_name,LoadProfileBoilerFuel|loads_mmbtu_per_hour,LoadProfileBoilerFuel|monthly_mmbtu,LoadProfileBoilerFuel|percent_share,LoadProfileChillerThermal|annual_fraction,LoadProfileChillerThermal|annual_tonhour,LoadProfileChillerThermal|chiller_cop,LoadProfileChillerThermal|doe_reference_name,LoadProfileChillerThermal|loads_fraction,LoadProfileChillerThermal|loads_ton,LoadProfileChillerThermal|monthly_fraction,LoadProfileChillerThermal|monthly_tonhour,LoadProfileChillerThermal|percent_share,PV|array_type,PV|azimuth,PV|can_curtail,PV|can_export_beyond_site_load,PV|can_net_meter,PV|can_wholesale,PV|dc_ac_ratio,PV|degradation_pct,PV|existing_kw,PV|federal_itc_pct,PV|federal_rebate_us_dollars_per_kw,PV|gcr,PV|installed_cost_us_dollars_per_kw,PV|inv_eff,PV|location,PV|losses,PV|macrs_bonus_pct,PV|macrs_itc_reduction,PV|macrs_option_years,PV|max_kw,PV|min_kw,PV|module_type,PV|om_cost_us_dollars_per_kw,PV|pbi_max_us_dollars,PV|pbi_system_max_kw,PV|pbi_us_dollars_per_kwh,PV|pbi_years,PV|prod_factor_series_kw,PV|pv_name,PV|radius,PV|state_ibi_max_us_dollars,PV|state_ibi_pct,PV|state_rebate_max_us_dollars,PV|state_rebate_us_dollars_per_kw,PV|tilt,PV|utility_ibi_max_us_dollars,PV|utility_ibi_pct,PV|utility_rebate_max_us_dollars,PV|utility_rebate_us_dollars_per_kw,Storage|battery_replacement_year,Storage|canGridCharge,Storage|installed_cost_us_dollars_per_kw,Storage|installed_cost_us_dollars_per_kwh,Storage|internal_efficiency_pct,Storage|inverter_efficiency_pct,Storage|inverter_replacement_year,Storage|macrs_bonus_pct,Storage|macrs_itc_reduction,Storage|macrs_option_years,Storage|max_kw,Storage|max_kwh,Storage|min_kw,Storage|min_kwh,Storage|rectifier_efficiency_pct,Storage|replace_cost_us_dollars_per_kw,Storage|replace_cost_us_dollars_per_kwh,Storage|soc_init_pct,Storage|soc_min_pct,Storage|total_itc_pct,Storage|total_rebate_us_dollars_per_kw,Storage|total_rebate_us_dollars_per_kwh,Wind|can_curtail,Wind|can_export_beyond_site_load,Wind|can_net_meter,Wind|can_wholesale,Wind|federal_itc_pct,Wind|federal_rebate_us_dollars_per_kw,Wind|installed_cost_us_dollars_per_kw,Wind|macrs_bonus_pct,Wind|macrs_itc_reduction,Wind|macrs_option_years,Wind|max_kw,Wind|min_kw,Wind|om_cost_us_dollars_per_kw,Wind|pbi_max_us_dollars,Wind|pbi_system_max_kw,Wind|pbi_us_dollars_per_kwh,Wind|pbi_years,Wind|pressure_atmospheres,Wind|prod_factor_series_kw,Wind|size_class,Wind|state_ibi_max_us_dollars,Wind|state_ibi_pct,Wind|state_rebate_max_us_dollars,Wind|state_rebate_us_dollars_per_kw,Wind|temperature_celsius,Wind|utility_ibi_max_us_dollars,Wind|utility_ibi_pct,Wind|utility_rebate_max_us_dollars,Wind|utility_rebate_us_dollars_per_kw,Wind|wind_direction_degrees,Wind|wind_meters_per_sec,Site|address,Site|elevation_ft,Site|land_acres,Site|latitude,Site|longitude,Site|roof_squarefeet,dd_soc_incentive,description,ptimality_tolerance_bau,ptimality_tolerance_decomp_subproblem,ptimality_tolerance_techs,time_steps_per_hour,timeout_decomp_subproblem_seconds,timeout_seconds,use_decomposition_model,user_uuid,webtool_uuid 2 | 1,,14.1,,0.0,0,0.0,0.0,,,,,0.0,,1.25,0.0,False,False,False,False,,,,,,,,0.1,0.0,,1.0,0.5,5,,,,,,,,,10000000000.0,1000000000.0,0.0,1.0,,,10000000000.0,0.0,10000000000.0,0.0,,,,True,10000000000.0,0.0,10000000000.0,0.0,1.5,0.999999,0.0,0,0.0,0.0,0.0,0.5,0.1,0.0004,0.0,,1.25,0.0,False,False,,,,,False,0.0,,,,100000000.0,0.0,,5a3821035457a32645d2dd80,,,,0.0,0.0,25,0.034,0.034,0.023,0.3,0.083,0.26,0.025,0.083,0.26,False,100.0,0.0,0.0,natural_gas,natural_gas,False,False,False,False,3.0,,0.0,0.0,0.0,660.0,0.0,0.076,True,False,500.0,1.0,0.0,0,1000000000.0,0.0,0.0,10.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.5,0.999999,0.0,0,0.0,0.0,0.0,0.5,0.1,0.0004,1000000,0.5,,False,LargeOffice,,True,,,,True,,,100.0,2020,,,,,,,,,,,,,,,1,180.0,True,True,True,True,1.2,0.005,0.0,0.26,0.0,0.4,1600.0,0.96,both,0.14,1.0,0.5,5,1000000000.0,0.0,0,16.0,1000000000.0,1000000000.0,0.0,1.0,,,0.0,10000000000.0,0.0,10000000000.0,0.0,,10000000000.0,0.0,10000000000.0,0.0,10.0,True,840.0,420.0,0.975,0.96,10.0,1.0,0.5,7.0,1000000.0,1000000.0,0.0,0.0,0.96,410.0,200.0,0.5,0.2,0.0,0.0,0,True,True,True,True,0.26,0.0,3013.0,0.0,0.5,5,0.0,0.0,40.0,1000000000.0,1000000000.0,0.0,1,,,,10000000000.0,0.0,10000000000.0,0.0,,10000000000.0,0.0,10000000000.0,0.0,,,,0.0,,34,-118,,True,test site,0.001,0.02,0.001,1,120,420,False,, 3 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/inputs/scenarios.csv: -------------------------------------------------------------------------------- 1 | site_number,description,latitude,longitude,PV|max_kw,urdb_label,load_file,Wind|max_kw 2 | 1,site3-No-PV,34.1404,-118.2764,0,5a3821035457a32645d2dd80,site3_8760_loads_kw.csv,0 3 | 2,site3-With-PV,34.1404,-118.2764,,5a3821035457a32645d2dd80,site3_8760_loads_kw.csv,0 -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/multi_scenario_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "blocked-ozone", 6 | "metadata": {}, 7 | "source": [ 8 | "# Multi-scenario\n", 9 | "Multiple scenarios can be defined in a CSV file with rows for each scenrario and columns for the inputs. `all_api_inputs.csv` provides a template for all of the possible header values (input keys).\n", 10 | "\n", 11 | "Let's take a look at the example `scenarios.csv`:" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "id": "understood-equality", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "import json # needed to load in our scenario\n", 22 | "import os # to handle file paths across operating systems\n", 23 | "import pandas as pd # only used to show the csv file\n", 24 | "from src.post_and_poll import get_api_results\n", 25 | "API_KEY = 'DEMO_KEY' # REPLACE WITH YOUR API KEY" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 2, 31 | "id": "robust-appreciation", 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "# following is not necessary but silences warnings:\n", 36 | "# InsecureRequestWarning: Unverified HTTPS request is being made to host 'developer.nrel.gov'. Adding certificate verification is strongly advised.\n", 37 | "import urllib3\n", 38 | "urllib3.disable_warnings()" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 3, 44 | "id": "vertical-heritage", 45 | "metadata": {}, 46 | "outputs": [ 47 | { 48 | "data": { 49 | "text/html": [ 50 | "
\n", 51 | "\n", 64 | "\n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | "
site_numberdescriptionlatitudelongitudePV|max_kwurdb_labelload_fileWind|max_kw
01site3-No-PV34.1404-118.27640.05a3821035457a32645d2dd80site3_8760_loads_kw.csv0
12site3-With-PV34.1404-118.2764NaN5a3821035457a32645d2dd80site3_8760_loads_kw.csv0
\n", 103 | "
" 104 | ], 105 | "text/plain": [ 106 | " site_number description latitude longitude PV|max_kw \\\n", 107 | "0 1 site3-No-PV 34.1404 -118.2764 0.0 \n", 108 | "1 2 site3-With-PV 34.1404 -118.2764 NaN \n", 109 | "\n", 110 | " urdb_label load_file Wind|max_kw \n", 111 | "0 5a3821035457a32645d2dd80 site3_8760_loads_kw.csv 0 \n", 112 | "1 5a3821035457a32645d2dd80 site3_8760_loads_kw.csv 0 " 113 | ] 114 | }, 115 | "execution_count": 4, 116 | "metadata": {}, 117 | "output_type": "execute_result" 118 | } 119 | ], 120 | "source": [ 121 | "df = pd.read_csv(os.path.join(\"inputs\", \"scenarios.csv\"))\n", 122 | "\n", 123 | "df.head()" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "id": "disciplinary-alfred", 129 | "metadata": {}, 130 | "source": [ 131 | "We have two Scenarios:\n", 132 | "1. One with no PV nor Wind evaluated (by setting their `max_kw`s to zero)\n", 133 | "2. One with PV and no Wind evaluated\n", 134 | "\n", 135 | "Both Scenarios have the same location, the same electricity tariff (set via the `urdb_label`), and use the same custom load profile - which is passed in via another csv file." 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": 5, 141 | "id": "democratic-breed", 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [ 145 | "\"\"\"\n", 146 | "Here are some convenience definitions for using the Multi-scenario capabilities\n", 147 | "\"\"\"\n", 148 | "##############################################################################################################\n", 149 | "inputs_path = os.path.join(\".\", 'inputs')\n", 150 | "outputs_path = os.path.join(\".\", 'outputs')\n", 151 | "output_template = os.path.join(outputs_path, 'results_template.csv')\n", 152 | "output_file = os.path.join(outputs_path, 'results_summary.csv')\n", 153 | "output_file_spreadsheet_template = os.path.join('src', 'template.xlsx')\n", 154 | "output_file_spreadsheet = os.path.join(outputs_path, 'results_summary.xlsx')\n", 155 | "##############################################################################################################" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "id": "suspended-windsor", 161 | "metadata": {}, 162 | "source": [ 163 | "Let's start by converting the `scenarios.csv` into a list of POST's that we can send to the API:" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 6, 169 | "id": "empirical-composer", 170 | "metadata": {}, 171 | "outputs": [], 172 | "source": [ 173 | "from src.multi_site_inputs_parser import multi_site_csv_parser\n", 174 | "\n", 175 | "path_to_inputs = os.path.join(inputs_path, 'scenarios.csv')\n", 176 | "\n", 177 | "list_of_posts = multi_site_csv_parser(\n", 178 | " path_to_inputs, \n", 179 | " api_url='https://developer.nrel.gov/api/reopt/stable', \n", 180 | " API_KEY=API_KEY\n", 181 | ")" 182 | ] 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "id": "identified-first", 187 | "metadata": {}, 188 | "source": [ 189 | "Now we can collect all of the results using the `get_api_results` function from the Single Scenario example:" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 7, 195 | "id": "flexible-sellers", 196 | "metadata": {}, 197 | "outputs": [ 198 | { 199 | "name": "stderr", 200 | "output_type": "stream", 201 | "text": [ 202 | "main INFO Response OK from https://developer.nrel.gov/api/reopt/stable/job/?api_key=DEMO_KEY.\n", 203 | "main INFO Polling https://developer.nrel.gov/api/reopt/stable/job/7272e113-ccb6-4faa-9ed1-49336dec7570/results/?api_key=DEMO_KEY for results with interval of 5s...\n", 204 | "main INFO Saved results to ./outputs/site3-No-PV.json\n", 205 | "main INFO Response OK from https://developer.nrel.gov/api/reopt/stable/job/?api_key=DEMO_KEY.\n", 206 | "main INFO Polling https://developer.nrel.gov/api/reopt/stable/job/0de8db16-bb21-49b3-935f-cc25841cc771/results/?api_key=DEMO_KEY for results with interval of 5s...\n", 207 | "main INFO Saved results to ./outputs/site3-With-PV.json\n" 208 | ] 209 | } 210 | ], 211 | "source": [ 212 | "responses = []\n", 213 | "\n", 214 | "for post in list_of_posts:\n", 215 | " responses.append(\n", 216 | " get_api_results(\n", 217 | " post, \n", 218 | " results_file=os.path.join(outputs_path, post['Scenario']['description'] + '.json'),\n", 219 | " api_url=\"https://developer.nrel.gov/api/reopt/stable\", \n", 220 | " API_KEY=API_KEY\n", 221 | " )\n", 222 | " )" 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "id": "moved-midnight", 228 | "metadata": {}, 229 | "source": [ 230 | "Note that we used the `Scenario.description` to define the results file name. The `Scenario.description` was defined in the `scenarios.csv`." 231 | ] 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "id": "realistic-removal", 236 | "metadata": {}, 237 | "source": [ 238 | "### Summarizing multiple scenario results\n", 239 | "\n", 240 | "There are two options for making a summary of multiple scenarios' resutls:\n", 241 | "1. Write to a csv using a template with column headers for desired summary keys (scalar values only)\n", 242 | "2. Write all inputs, outputs, and dispatch to an Excel spreadsheet\n" 243 | ] 244 | }, 245 | { 246 | "cell_type": "markdown", 247 | "id": "italian-history", 248 | "metadata": {}, 249 | "source": [ 250 | "#### Option 1: Use a template CSV to collect certain results" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 8, 256 | "id": "saved-relaxation", 257 | "metadata": {}, 258 | "outputs": [], 259 | "source": [ 260 | "from src.parse_api_responses_to_csv import parse_responses_to_csv_with_template\n", 261 | "\n", 262 | "parse_responses_to_csv_with_template(\n", 263 | " csv_template=output_template, \n", 264 | " responses=responses, \n", 265 | " output_csv=output_file, \n", 266 | " input_csv=path_to_inputs,\n", 267 | " n_custom_columns=2\n", 268 | ")" 269 | ] 270 | }, 271 | { 272 | "cell_type": "markdown", 273 | "id": "mobile-space", 274 | "metadata": {}, 275 | "source": [ 276 | "#### Option 2: Write all results out to an Excel file" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 9, 282 | "id": "nervous-grass", 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "name": "stderr", 287 | "output_type": "stream", 288 | "text": [ 289 | "/Users/nlaws/.virtualenv/py36/lib/python3.6/site-packages/openpyxl/workbook/child.py:99: UserWarning: Title is more than 31 characters. Some applications may not be able to read the file\n", 290 | " warnings.warn(\"Title is more than 31 characters. Some applications may not be able to read the file\")\n" 291 | ] 292 | } 293 | ], 294 | "source": [ 295 | "from src.parse_api_responses_to_excel import parse_api_responses_to_excel\n", 296 | "\n", 297 | "parse_api_responses_to_excel(responses, template=output_file_spreadsheet_template, spreadsheet=output_file_spreadsheet)" 298 | ] 299 | } 300 | ], 301 | "metadata": { 302 | "kernelspec": { 303 | "display_name": "py36", 304 | "language": "python", 305 | "name": "py36" 306 | }, 307 | "language_info": { 308 | "codemirror_mode": { 309 | "name": "ipython", 310 | "version": 3 311 | }, 312 | "file_extension": ".py", 313 | "mimetype": "text/x-python", 314 | "name": "python", 315 | "nbconvert_exporter": "python", 316 | "pygments_lexer": "ipython3", 317 | "version": "3.6.8" 318 | } 319 | }, 320 | "nbformat": 4, 321 | "nbformat_minor": 5 322 | } 323 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/outputs/results_summary.csv: -------------------------------------------------------------------------------- 1 | site_number,description,npv_us_dollars,PV|size_kw,Storage|size_kw,Storage|size_kwh,year_one_bill_us_dollars,year_one_bill_bau_us_dollars,net_capital_costs_plus_om_us_dollars 2 | 1,site3-No-PV,771.0,0.0,2.719999999999999,3.586785258421318,69456.79,69901.62,3492.0 3 | 2,site3-With-PV,85660.0,128.6722,8.364710638101215,19.043681833160523,45578.94,69901.62,147404.0 4 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/outputs/results_template.csv: -------------------------------------------------------------------------------- 1 | npv_us_dollars,PV|size_kw,Storage|size_kw,Storage|size_kwh,year_one_bill_us_dollars,year_one_bill_bau_us_dollars,net_capital_costs_plus_om_us_dollars -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/single_scenario_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Run a REopt API evaluation" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Initialization" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "import pandas as pd\n", 24 | "import numpy as np\n", 25 | "import json\n", 26 | "import requests\n", 27 | "import copy\n", 28 | "import os\n", 29 | "from src.post_and_poll import get_api_results\n", 30 | "API_KEY = 'DEMO_KEY' # REPLACE WITH YOUR API KEY" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# following is not necessary but silences warnings:\n", 40 | "# InsecureRequestWarning: Unverified HTTPS request is being made to host 'developer.nrel.gov'. Adding certificate verification is strongly advised.\n", 41 | "import urllib3\n", 42 | "urllib3.disable_warnings()" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "\"\"\"\n", 52 | "Here are some convenience definitions for using the Multi-scenario capabilities\n", 53 | "\"\"\"\n", 54 | "##############################################################################################################\n", 55 | "inputs_path = os.path.join(\".\", 'inputs')\n", 56 | "outputs_path = os.path.join(\".\", 'outputs')\n", 57 | "loads_path = os.path.join(\".\", 'load_profiles')\n", 58 | "rates_path = os.path.join(\".\", 'electric_rates')\n", 59 | "##############################################################################################################" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "## Load a previously saved API response .json file instead of running REopt" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 4, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "response_json = 'results'\n", 76 | "with open(os.path.join(outputs_path, response_json + '.json'), 'rb') as handle:\n", 77 | " api_response = json.load(handle)" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": {}, 83 | "source": [ 84 | "## Scenario Inputs (POST), if wanting to do a new API call" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 5, 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "post_1 = {\"Scenario\": {\n", 94 | " \"Site\": {\n", 95 | " \"longitude\": -118.116,\n", 96 | " \"latitude\": 34.579,\n", 97 | " \"roof_squarefeet\": 5000.0,\n", 98 | " \"land_acres\": 1.0,\n", 99 | " \"PV\": {\n", 100 | " \"macrs_bonus_pct\": 0.4,\n", 101 | " \"installed_cost_us_dollars_per_kw\": 2000.0,\n", 102 | " \"tilt\": 34.579,\n", 103 | " \"degradation_pct\": 0.005,\n", 104 | " \"macrs_option_years\": 5,\n", 105 | " \"federal_itc_pct\": 0.3,\n", 106 | " \"module_type\": 0,\n", 107 | " \"array_type\": 1,\n", 108 | " \"om_cost_us_dollars_per_kw\": 16.0,\n", 109 | " \"macrs_itc_reduction\": 0.5,\n", 110 | " \"azimuth\": 180.0,\n", 111 | " \"federal_rebate_us_dollars_per_kw\": 350.0,\n", 112 | " \"dc_ac_ratio\": 1.1\n", 113 | " },\n", 114 | " \"LoadProfile\": {\n", 115 | " \"doe_reference_name\": \"RetailStore\",\n", 116 | " \"annual_kwh\": 10000000.0,\n", 117 | " \"city\": \"LosAngeles\"\n", 118 | " },\n", 119 | " \"Storage\": {\n", 120 | " \"total_rebate_per_kw\": 100.0,\n", 121 | " \"macrs_option_years\": 5,\n", 122 | " \"can_grid_charge\": True,\n", 123 | " \"macrs_bonus_pct\": 0.4,\n", 124 | " \"macrs_itc_reduction\": 0.5,\n", 125 | " \"total_itc_pct\": 0,\n", 126 | " \"installed_cost_us_dollars_per_kw\": 1000.0,\n", 127 | " \"installed_cost_us_dollars_per_kwh\": 500.0,\n", 128 | " \"replace_cost_us_dollars_per_kw\": 460.0,\n", 129 | " \"replace_cost_us_dollars_per_kwh\": 230.0\n", 130 | " },\n", 131 | " \"ElectricTariff\": {\n", 132 | " \"urdb_label\": \"5ed6c1a15457a3367add15ae\"\n", 133 | " },\n", 134 | " \"Financial\": {\n", 135 | " \"escalation_pct\": 0.026,\n", 136 | " \"offtaker_discount_pct\": 0.081,\n", 137 | " \"owner_discount_pct\": 0.081,\n", 138 | " \"analysis_years\": 20,\n", 139 | " \"offtaker_tax_pct\": 0.4,\n", 140 | " \"owner_tax_pct\": 0.4,\n", 141 | " \"om_cost_escalation_pct\": 0.025\n", 142 | " }\n", 143 | "}}}" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "### Load in a custom electric rate generated from https://reopt.nrel.gov/tool/custom_tariffs" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 6, 156 | "metadata": {}, 157 | "outputs": [], 158 | "source": [ 159 | "load_electric_rate = \"PGE_E20\"\n", 160 | "with open(os.path.join(rates_path, load_electric_rate + \".json\"), 'r') as fp:\n", 161 | " rate_1 = json.load(fp)\n", 162 | "post_1[\"Scenario\"][\"Site\"][\"ElectricTariff\"][\"urdb_response\"] = rate_1" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## Save the POST to a .json file for sharing or future loading in" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 7, 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "# Convert python dictionary post into json and save to a .json file\n", 179 | "post_save = post_1\n", 180 | "post_name = \"post_1\"\n", 181 | "with open(os.path.join(inputs_path, post_name + \".json\"), 'w') as fp:\n", 182 | " json.dump(post_save, fp)" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "## Or load in a saved .json file for the inputs/POST" 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "execution_count": 8, 195 | "metadata": { 196 | "scrolled": true 197 | }, 198 | "outputs": [], 199 | "source": [ 200 | "# Load a json into a python dictionary\n", 201 | "load_post = \"post_1\"\n", 202 | "with open(os.path.join(inputs_path, load_post + \".json\"), 'r') as fp:\n", 203 | " post_1 = json.load(fp)" 204 | ] 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "metadata": {}, 209 | "source": [ 210 | "## POST and poll (periodic GET request) the API to GET a new result, if not loading in a previous response. This may take a while!" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "### Note, the `api_url` in the `get_api_results` function below calls the **production server** hosted API (master/main branch/version, publicly accessible)\n", 218 | "\n", 219 | "#### For calling a locally-hosted (localhost) API, see:\n", 220 | "- https://github.com/NREL/REopt_Lite_API/wiki/localhost-URLs-for-calling-locally-hosted-API\n", 221 | "\n", 222 | "#### For calling an API hosted on an NREL-internal server (only NREL users can access this), see:\n", 223 | "- https://github.nrel.gov/REopt/API_scripts/wiki/API-URLs-for-NREL-internal-servers" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": {}, 229 | "source": [ 230 | "`get_api_results` POST's your inputs to the API `job` endpoint, which provides a `run_uuid` if the input is valid, and then polls the `results` endpoint using the `run_uuid` until the results come back with a status other than `Optimizing...`.\n", 231 | "\n", 232 | "`get_api_results` also saves the results (full API response, including inputs) to the `results_file`.\n", 233 | "\n", 234 | "A log file is also created in the current working directory." 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": 9, 240 | "metadata": {}, 241 | "outputs": [ 242 | { 243 | "name": "stderr", 244 | "output_type": "stream", 245 | "text": [ 246 | "main INFO Response OK from https://developer.nrel.gov/api/reopt/stable/job/?api_key=DEMO_KEY.\n", 247 | "main INFO Polling https://developer.nrel.gov/api/reopt/stable/job/61f4822c-e213-4751-bf08-3f9faae184fb/results/?api_key=DEMO_KEY for results with interval of 5s...\n", 248 | "main INFO Saved results to ./outputs/results_file.json\n" 249 | ] 250 | } 251 | ], 252 | "source": [ 253 | "outputs_file_name = \"results_file\"\n", 254 | "root_url = \"https://developer.nrel.gov/api/reopt/stable\"\n", 255 | "api_response = get_api_results(post=post_1, \n", 256 | " API_KEY=API_KEY, \n", 257 | " api_url=root_url, \n", 258 | " results_file=os.path.join(outputs_path, outputs_file_name + \".json\"), \n", 259 | " run_id=None)" 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | "### If you get disconnected from the polling function but you think it ran, copy the run_uuid from the log above to manually GET the results:" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 10, 272 | "metadata": {}, 273 | "outputs": [], 274 | "source": [ 275 | "run_uuid = api_response[\"outputs\"][\"Scenario\"][\"run_uuid\"]\n", 276 | "results_url = root_url + '/job/' + run_uuid + '/results/?api_key=' + API_KEY\n", 277 | "resp = requests.get(url=results_url, verify=False)\n", 278 | "api_response = json.loads(resp.text)" 279 | ] 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "metadata": {}, 284 | "source": [ 285 | "## Get summary of results" 286 | ] 287 | }, 288 | { 289 | "cell_type": "code", 290 | "execution_count": 11, 291 | "metadata": {}, 292 | "outputs": [ 293 | { 294 | "name": "stdout", 295 | "output_type": "stream", 296 | "text": [ 297 | "NPV ($) = 550779.0\n", 298 | "Capital Cost, Net ($) = 294265.01\n", 299 | "PV size_kw = 216.6667\n", 300 | "Storage size_kw = 96.4996266256644\n", 301 | "Storage size_kwh = 273.818171101346\n" 302 | ] 303 | } 304 | ], 305 | "source": [ 306 | "print(\"NPV ($) = \", api_response[\"outputs\"][\"Scenario\"][\"Site\"][\"Financial\"][\"npv_us_dollars\"])\n", 307 | "print(\"Capital Cost, Net ($) = \", api_response[\"outputs\"][\"Scenario\"][\"Site\"][\"Financial\"][\"net_capital_costs\"])\n", 308 | "tech_list = [\"PV\", \"Wind\", \"Storage\", \"CHP\", \"Generator\", \"HotTES\", \"ColdTES\", \"AbsorptionChiller\", \"GHP\", \"NewBoiler\", \"SteamTurbine\"]\n", 309 | "for tech in tech_list:\n", 310 | " if tech in post_1[\"Scenario\"][\"Site\"].keys():\n", 311 | " if tech == \"GHP\":\n", 312 | " print(\"GHX Number of Boreholes = \", api_response[\"outputs\"][\"Scenario\"][\"Site\"][tech][\"ghpghx_chosen_outputs\"].get(\"number_of_boreholes\"))\n", 313 | " print(\"GHP Heat Pump Capacity (ton) = \", api_response[\"outputs\"][\"Scenario\"][\"Site\"][tech][\"ghpghx_chosen_outputs\"].get(\"peak_combined_heatpump_thermal_ton\"))\n", 314 | " # PV and Storage are considered if the POST does not explicitly make max_[size] == 0\n", 315 | " for size_name_value in [(key, val) for key, val in api_response[\"outputs\"][\"Scenario\"][\"Site\"][tech].items() if \"size\" in key]:\n", 316 | " print(tech + \" \" + size_name_value[0], \" = \", size_name_value[1])\n", 317 | " elif tech in [\"PV\", \"Storage\"]:\n", 318 | " for size_name_value in [(key, val) for key, val in api_response[\"outputs\"][\"Scenario\"][\"Site\"][tech].items() if \"size\" in key]:\n", 319 | " print(tech + \" \" + size_name_value[0], \" = \", size_name_value[1]) " 320 | ] 321 | }, 322 | { 323 | "cell_type": "markdown", 324 | "metadata": {}, 325 | "source": [ 326 | "### Here are some results keys examples:" 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": 12, 332 | "metadata": {}, 333 | "outputs": [ 334 | { 335 | "data": { 336 | "text/plain": [ 337 | "dict_keys(['outputs', 'inputs', 'messages'])" 338 | ] 339 | }, 340 | "execution_count": 13, 341 | "metadata": {}, 342 | "output_type": "execute_result" 343 | } 344 | ], 345 | "source": [ 346 | "api_response.keys()" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "execution_count": 14, 352 | "metadata": {}, 353 | "outputs": [ 354 | { 355 | "data": { 356 | "text/plain": [ 357 | "'optimal'" 358 | ] 359 | }, 360 | "execution_count": 15, 361 | "metadata": {}, 362 | "output_type": "execute_result" 363 | } 364 | ], 365 | "source": [ 366 | "api_response[\"outputs\"][\"Scenario\"][\"status\"]" 367 | ] 368 | }, 369 | { 370 | "cell_type": "code", 371 | "execution_count": 16, 372 | "metadata": {}, 373 | "outputs": [ 374 | { 375 | "name": "stdout", 376 | "output_type": "stream", 377 | "text": [ 378 | "year_one_emissions_lb_C02\n", 379 | "year_one_emissions_bau_lb_C02\n", 380 | "outdoor_air_temp_degF\n", 381 | "renewable_electricity_energy_pct\n", 382 | "Financial\n", 383 | "LoadProfile\n", 384 | "LoadProfileBoilerFuel\n", 385 | "LoadProfileChillerThermal\n", 386 | "ElectricTariff\n", 387 | "FuelTariff\n", 388 | "Storage\n", 389 | "Generator\n", 390 | "Wind\n", 391 | "CHP\n", 392 | "Boiler\n", 393 | "ElectricChiller\n", 394 | "AbsorptionChiller\n", 395 | "HotTES\n", 396 | "ColdTES\n", 397 | "NewBoiler\n", 398 | "SteamTurbine\n", 399 | "GHP\n", 400 | "PV\n" 401 | ] 402 | } 403 | ], 404 | "source": [ 405 | "for k in api_response[\"outputs\"][\"Scenario\"][\"Site\"].keys():\n", 406 | " print(k)" 407 | ] 408 | }, 409 | { 410 | "cell_type": "markdown", 411 | "metadata": {}, 412 | "source": [ 413 | "## Save API response into a JSON file for later use" 414 | ] 415 | }, 416 | { 417 | "cell_type": "code", 418 | "execution_count": 17, 419 | "metadata": {}, 420 | "outputs": [], 421 | "source": [ 422 | "response_save = api_response\n", 423 | "file_name_to_save = \"response_1\"\n", 424 | "with open(os.path.join(outputs_path, file_name_to_save + \".json\"), 'w') as fp:\n", 425 | " json.dump(response_save, fp)" 426 | ] 427 | } 428 | ], 429 | "metadata": { 430 | "@webio": { 431 | "lastCommId": null, 432 | "lastKernelId": null 433 | }, 434 | "kernelspec": { 435 | "display_name": "Python 3", 436 | "language": "python", 437 | "name": "python3" 438 | }, 439 | "language_info": { 440 | "codemirror_mode": { 441 | "name": "ipython", 442 | "version": 3 443 | }, 444 | "file_extension": ".py", 445 | "mimetype": "text/x-python", 446 | "name": "python", 447 | "nbconvert_exporter": "python", 448 | "pygments_lexer": "ipython3", 449 | "version": "3.8.8" 450 | }, 451 | "latex_envs": { 452 | "LaTeX_envs_menu_present": true, 453 | "autoclose": false, 454 | "autocomplete": true, 455 | "bibliofile": "biblio.bib", 456 | "cite_by": "apalike", 457 | "current_citInitial": 1, 458 | "eqLabelWithNumbers": true, 459 | "eqNumInitial": 1, 460 | "hotkeys": { 461 | "equation": "Ctrl-E", 462 | "itemize": "Ctrl-I" 463 | }, 464 | "labels_anchors": false, 465 | "latex_user_defs": false, 466 | "report_style_numbering": false, 467 | "user_envs_cfg": false 468 | } 469 | }, 470 | "nbformat": 4, 471 | "nbformat_minor": 4 472 | } 473 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/REopt-Analysis-Scripts/50dc58078a021ff69f46dfb12ce613e35e1f972b/REopt API Scripts/archived_v2_scripts/src/__init__.py -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/create_input_template_with_values_from_help_endpoint.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import pandas as pd 4 | from src.logger import log 5 | from collections import OrderedDict 6 | API_KEY = 'DEMO_KEY' 7 | api_url = 'https://developer.nrel.gov/api/reopt/stable' 8 | 9 | 10 | # these keys are excluded from the input template because their values are arrays that can't be in the DataFrame/CSV 11 | array_inputs_to_exclude = [ 12 | "FuelTariff|boiler_fuel_blended_monthly_rates_us_dollars_per_mmbtu", 13 | "FuelTariff|chp_fuel_blended_monthly_rates_us_dollars_per_mmbtu", 14 | "Site|outdoor_air_temp_degF", 15 | ] 16 | # TODO figure out a work-around for including these values in all_api_inputs.csv that is compatible with multi_site 17 | # analysis 18 | 19 | 20 | def set_default(nested_dict, flat_dict, k, piped_key): 21 | """ 22 | Try to set a default value from nested_input_definitions. If there is no default value, set to None 23 | :param d: dictionary 24 | :param k: key 25 | :return: None 26 | """ 27 | if piped_key.startswith("Scenario"): 28 | piped_key = piped_key.lstrip("Scenario|") 29 | try: 30 | if k not in ['tilt']: 31 | flat_dict[piped_key] = nested_dict[k]['default'] 32 | else: # tilt default is "Site latitude" and is set to such in the api 33 | flat_dict[piped_key] = None 34 | 35 | except KeyError as e: 36 | if 'default' in e.args: 37 | log.debug("No default value exists for {}. Set value to None".format(k)) 38 | flat_dict[piped_key] = None 39 | else: 40 | raise e 41 | 42 | 43 | def flatten_nested_dict(nested_dict, flat_dict=None, obj=None): 44 | """ 45 | recursive function for converting a nested dictionary into a flat one using the piped key convention from 46 | multi_site_inputs_parser.py 47 | :param nested_dict: 48 | :param flat_dict: 49 | :param obj: 50 | :return: 51 | """ 52 | if flat_dict is None: # for initial call to flatten_nested_dict 53 | flat_dict = OrderedDict() 54 | 55 | for k, v in list(sorted(nested_dict.items())): 56 | 57 | piped_key = None 58 | if isinstance(obj, str): 59 | piped_key = obj + '|' + k 60 | 61 | if k[0].islower() and isinstance(v, dict): 62 | 63 | if piped_key is not None: 64 | set_default(nested_dict, flat_dict, k, piped_key) 65 | 66 | if k in nested_dict.keys(): # k could be deleted in set_default if there is no default value 67 | if isinstance(nested_dict[k], dict): 68 | if any([isinstance(i, dict) for i in nested_dict[k].values()]): # nested dict with definitions 69 | flatten_nested_dict(nested_dict[k], flat_dict, obj=str(k)) # dig deeper into nested_dict 70 | return flat_dict 71 | 72 | 73 | def create_input_template_with_values_from_help_endpoint(api_url, API_KEY): 74 | input_definitions = json.loads(requests.get(api_url + '/help?API_KEY=' + API_KEY).content) 75 | flat_dict = flatten_nested_dict(input_definitions) 76 | # fill in enough values to run an example 77 | flat_dict["site_number"] = 1 78 | flat_dict["description"] = "test site" 79 | flat_dict["Site|latitude"] = 34 80 | flat_dict["Site|longitude"] = -118 81 | flat_dict["ElectricTariff|urdb_label"] = "5a3821035457a32645d2dd80" 82 | flat_dict["LoadProfile|doe_reference_name"] = "LargeOffice" 83 | flat_dict["LoadProfile|annual_kwh"] = 1000000 84 | flat_dict.move_to_end("site_number", last=False) 85 | for k in array_inputs_to_exclude: 86 | flat_dict.pop(k, None) 87 | df = pd.DataFrame(flat_dict, index=[1]) 88 | df.to_csv("all_api_inputs.csv", index=False) 89 | 90 | 91 | if __name__ == "__main__": 92 | create_input_template_with_values_from_help_endpoint(api_url, API_KEY) 93 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/logger.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom logging set up, with handlers for writing to .log file and console. 3 | 4 | The _handler.setLevel determines the logging level to write to file or console. 5 | Logging levels are: 6 | 7 | Level Numeric value 8 | CRITICAL 50 9 | ERROR 40 10 | WARNING 30 11 | INFO 20 12 | DEBUG 10 13 | NOTSET 0 14 | """ 15 | import logging 16 | 17 | log = logging.getLogger('main') 18 | log.setLevel(logging.DEBUG) 19 | 20 | file_handler = logging.FileHandler(filename='main.log', mode='w') 21 | file_formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s') 22 | file_handler.setFormatter(file_formatter) 23 | file_handler.setLevel(logging.INFO) 24 | 25 | console_handler = logging.StreamHandler() 26 | console_formatter = logging.Formatter('%(name)-12s %(levelname)-8s %(message)s') 27 | console_handler.setFormatter(console_formatter) 28 | console_handler.setLevel(logging.INFO) 29 | 30 | log.addHandler(file_handler) 31 | log.addHandler(console_handler) 32 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/multi_site_inputs_parser.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import os 4 | import requests 5 | import json 6 | import copy 7 | from src.logger import log 8 | 9 | 10 | def set_default(d, k): 11 | """ 12 | Try to set a default value from nested_input_definitions. If there is no default value, delete the key (k) 13 | :param d: dictionary 14 | :param k: key 15 | :return: None 16 | """ 17 | try: 18 | if k not in ['tilt']: 19 | d[k] = d[k]['default'] 20 | else: # tilt default is "Site latitude" and is set to such in the api 21 | del d[k] 22 | 23 | except KeyError as e: 24 | if 'default' in e.args: 25 | log.debug("No default value exists for {}.".format(k)) 26 | del d[k] 27 | else: 28 | raise e 29 | 30 | 31 | def make_nested_dict(flat_dict, nested_dict, obj=None): 32 | """ 33 | Use flat_dict, with key:value pairs from csv, and create a dict for posting to reopt api 34 | :param flat_dict: key:value pairs for one site 35 | :param nested_dict: nested_input_definitions from help endpoint 36 | :param obj: upper case key in nested_dict, represents reopt api class and used for '|' parameters from input csv 37 | :return: 38 | """ 39 | 40 | for k, v in list(nested_dict.items()): 41 | 42 | piped_key = None 43 | if isinstance(obj, str): 44 | piped_key = obj + '|' + k 45 | 46 | if k[0].islower() and isinstance(v, dict): # then this key represents an input value 47 | 48 | if k in flat_dict.keys(): 49 | 50 | if pd.isnull(flat_dict[k]): # empty cell in input csv file 51 | set_default(nested_dict, k) 52 | else: # use value from input csv 53 | input_val = flat_dict[k] 54 | # work-around for pd.df.to_dict() converting to numpy types, yargh. 55 | if type(input_val) is np.int64: 56 | input_val = int(input_val) 57 | if isinstance(input_val, np.bool_): 58 | input_val = bool(input_val) 59 | nested_dict[k] = input_val 60 | 61 | elif piped_key in flat_dict.keys() and piped_key is not None: # eg. PV|max_kw NOTE: NO SPACES IN piped_key 62 | 63 | if pd.isnull(flat_dict[piped_key]): # empty cell in input csv file 64 | set_default(nested_dict, k) 65 | else: # use value from input csv 66 | input_val = flat_dict[piped_key] 67 | # work-around for pd.df.to_dict() converting to numpy types, yargh. 68 | if type(input_val) is np.int64: 69 | input_val = int(input_val) 70 | if isinstance(input_val, np.bool_): 71 | input_val = bool(input_val) 72 | nested_dict[k] = input_val 73 | 74 | else: # no column in input csv 75 | set_default(nested_dict, k) 76 | 77 | if k in nested_dict.keys(): # k could be deleted in set_default if there is no default value 78 | if isinstance(nested_dict[k], dict): 79 | if any([isinstance(i, dict) for i in nested_dict[k].values()]): # nested dict with definitions 80 | make_nested_dict(flat_dict, nested_dict[k], obj=str(k)) # dig deeper into nested_dict 81 | 82 | return nested_dict 83 | 84 | 85 | def add_load_profile_inputs(flat_dict, nested_dict, path_to_load_files="../inputs/load_profiles"): 86 | """ 87 | If flat_dict has a "load_file" key (i.e. same column in csv file), 88 | then a custom load profile is added to the inputs (which is used by API even if other optional inputs are filled 89 | in, such as doe_reference_name and annual_kwh) 90 | :param flat_dict: inputs from site(s) data csv file 91 | :param nested_dict: nested_dict that has already passed through make_nested_dict (filled in single value inputs) 92 | :return: None 93 | """ 94 | if "load_file" in flat_dict.keys(): 95 | if not pd.isnull(flat_dict["load_file"]): # case for some sites having custom load profiles 96 | fp = os.path.join(path_to_load_files, flat_dict["load_file"]) 97 | load_profile = pd.read_csv(fp, header=None, squeeze=True).tolist() 98 | load_profile = [float(v) for v in load_profile] # numpy floats are not JSON serializable 99 | assert len(load_profile) in [8760, 17520, 35040] 100 | nested_dict['Scenario']['Site']['LoadProfile']['loads_kw'] = load_profile 101 | else: 102 | log.info("Using built-in profile for Site number {}.".format(flat_dict['site_number'])) 103 | 104 | 105 | def multi_site_csv_parser(path_to_csv, api_url, API_KEY, n_sites=None): 106 | """ 107 | Script to read a multi-sites input csv file and parse it into dictionaries for passing to the API. 108 | :param path_to_csv: path to csv file containing rows for each site and column headers. 109 | :param n_sites: default=None. If integer value is passed then only that many sites will be processed. 110 | :return: list of dictionaries, with length equal to n_sites, which can be posted to API. Additional keys may be 111 | added for creating output summary csv. 112 | TODO: [ ] pass start row and end row for running scenarios. 113 | """ 114 | 115 | df = pd.read_csv(path_to_csv) 116 | 117 | if isinstance(n_sites, int): 118 | df = df.iloc[:n_sites] 119 | 120 | input_definitions = json.loads(requests.get(api_url + '/help?API_KEY=' + API_KEY).content) 121 | if "error" in input_definitions.keys(): 122 | raise BlockingIOError(input_definitions["error"]) 123 | 124 | posts = [] 125 | for i in range(len(df)): 126 | nested_dict = copy.deepcopy(input_definitions) 127 | 128 | site_inputs = df.iloc[i].to_dict() 129 | posts.append(make_nested_dict(site_inputs, nested_dict)) 130 | 131 | path_to_load_files = "./load_profiles" 132 | add_load_profile_inputs(site_inputs, posts[-1], path_to_load_files=path_to_load_files) 133 | 134 | path_to_rate_files = "./electric_rates" 135 | if "urdb_json_file" in df.columns: 136 | if pd.isnull(df.loc[i,"urdb_json_file"]) == False: 137 | rate_i = json.load(open(os.path.join(path_to_rate_files, df["urdb_json_file"][i]), "r")) 138 | posts[i]["Scenario"]["Site"]["ElectricTariff"]["urdb_response"] = rate_i 139 | else: 140 | pass 141 | else: 142 | pass 143 | 144 | posts[i]['Scenario']['Site']['Wind'] = {'max_kw': 0} # hack for Wind not in help endpoint 145 | 146 | return posts 147 | 148 | 149 | test = False 150 | if test: 151 | list_of_posts = multi_site_csv_parser( 152 | './test_inputs/test_scenarios.csv', 153 | api_url='https://developer.nrel.gov/api/reopt/stable', 154 | API_KEY="DEMO_KEY" 155 | ) 156 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/parse_api_responses_to_csv.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import pandas as pd 3 | from collections import OrderedDict 4 | 5 | 6 | def get_nested_output(key, resp, obj=None): 7 | 8 | if '|' in key: 9 | obj = key[:key.index('|')] 10 | val = key[key.index('|') + 1:] 11 | 12 | return resp['outputs']['Scenario']['Site'][obj][val] 13 | 14 | else: 15 | site = {k: v for k, v in resp['outputs']['Scenario']['Site'].items() if k[0].isupper()} 16 | # Site now has lower case keys that must be removed 17 | for d in site.values(): 18 | 19 | if key in d.keys(): 20 | return d[key] 21 | 22 | return None 23 | 24 | 25 | def parse_responses_to_csv_with_template(csv_template, responses, output_csv, input_csv=None, n_custom_columns=0): 26 | """ 27 | 28 | :param csv_template: path to csv file with headers for output fields, and rows for input scenarios 29 | :param responses: list of dicts, each dict is API response for input scenario (in same order is input csv) 30 | :param output_csv: str, path to output csv that parser writes summary to. 31 | :param input_csv: str, path to input csv to copy custom columns from 32 | :param n_custom_columns: number of custom columns to copy from input scenarios csv to output_csv 33 | (columns that are not modified by script) 34 | :return: None (writes results to csv). 35 | """ 36 | 37 | results = OrderedDict() 38 | 39 | # open template and initialize results dict with custom cols filled in and black output columns 40 | with open(csv_template, 'r') as f: 41 | reader = csv.reader(f) 42 | outputs = next(reader) # names of parameters to pull out of responses and put in output_csv 43 | 44 | if input_csv is not None: 45 | # copy and paste custom columns' values 46 | 47 | with open(input_csv, 'r') as f: 48 | reader = csv.reader(f) 49 | hdr = next(reader) 50 | for i in range(n_custom_columns): 51 | results[hdr[i]] = [] 52 | 53 | for row in reader: 54 | for i in range(n_custom_columns): 55 | results[hdr[i]].append(row[i]) 56 | 57 | for output in outputs: 58 | results[output] = [] 59 | 60 | for i, resp in enumerate(responses): 61 | 62 | for output in outputs: 63 | results[output].append(get_nested_output(output, resp)) 64 | 65 | df = pd.DataFrame.from_dict(results) 66 | df.index = df.iloc[:, 0] 67 | df.drop(df.columns[0], axis=1, inplace=True) 68 | df.to_csv(output_csv) 69 | 70 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/parse_api_responses_to_excel.py: -------------------------------------------------------------------------------- 1 | import openpyxl as xl 2 | 3 | 4 | def dict_to_worksheet(d, wb, row_idx, description, name): 5 | """ 6 | Write dictionary of scalar results to worksheet. Creates the worksheet using `name` as the name if it does not 7 | exist. 8 | :param d: dictionary from API JSON response 9 | :param wb: openpyxl excel workbook 10 | :param row_idx: equivalent to scenario index; the row in the worksheet to write results 11 | :param description: scenario description; used as the left-most entry in the row 12 | :param name: worksheet name 13 | :return: nested dictionary with time-series values for each API "object"; 14 | eg. {'PV': 15 | {'year_one_power_production_series_kw : [ 0.0, ..., 0.0] } 16 | } 17 | """ 18 | time_series_dict = dict() # for collecting lists 19 | if row_idx == 0: 20 | ws = wb.create_sheet(name) 21 | else: 22 | ws = wb[name] 23 | 24 | keys = [k for k in d.keys() if k.islower()] 25 | ws.cell(row=row_idx + 2, column=1, value=description) 26 | 27 | col_idx = 0 28 | for hdr in sorted(keys): 29 | if isinstance(d[hdr], list): 30 | time_series_dict[hdr] = d[hdr] 31 | continue 32 | if isinstance(d[hdr], dict) or 'series' in hdr.lower(): 33 | continue 34 | if row_idx == 0: # write header 35 | ws.cell(row=row_idx + 1, column=col_idx + 2, value=hdr) 36 | val = d[hdr] 37 | ws.cell(row=row_idx + 2, column=col_idx + 2, value=val) 38 | col_idx += 1 39 | 40 | return time_series_dict 41 | 42 | 43 | def time_series_to_worksheet(d, wb, description): 44 | """ 45 | Writes out all time series values from `d` to a worksheet in `wb` with the name: description + '_time_series' 46 | :param d: nested dictionary of time series (from dict_to_worksheet) 47 | :param wb: openpyxl excel workbook 48 | :param description: scenario description; used to name worksheet: description + '_time_series' 49 | :return: None 50 | """ 51 | ws = wb.create_sheet(description + '_time_series') 52 | col_idx = 0 53 | for K in [Key for Key in d.keys() if len(d[Key].keys()) > 0]: 54 | for k in [key for key in d[K].keys() if len(d[K][key]) > 0]: 55 | ws.cell(row=1, column=col_idx + 1, value=K + '|' + k) 56 | for j, val in enumerate(d[K][k]): 57 | ws.cell(row=2 + j, column=col_idx + 1, value=val) 58 | col_idx += 1 59 | 60 | 61 | def parse_api_responses_to_excel(responses, template="template.xlsx", spreadsheet='results_summary.xlsx'): 62 | """ 63 | Writes all inputs, outputs, and dispatches to separate worksheets in an Excel workbook. 64 | REopt object inputs and outputs are on separate sheets (eg. 'PV_inputs' and 'PV_outputs') with each row 65 | corresponding to a scenario. 66 | Dispatch time-series for each scenario are written to one worksheet per scenario. 67 | :param responses: list of dictionaries - one dict = one api response 68 | :param template: str, path/to/template.xlsx which is the template file for the spreadsheet 69 | :param spreadsheet: str, path/to/excel_spreadsheet.xlsx to write results summary 70 | :return: None 71 | """ 72 | wb = xl.load_workbook(template) 73 | ws_sites = wb.active 74 | 75 | for row_idx, resp in enumerate(responses): 76 | time_series_dict = dict() 77 | inputs_dict = resp['inputs']['Scenario'] 78 | outputs_dict = resp['outputs']['Scenario'] 79 | description = inputs_dict['description'] 80 | ws_sites.cell(row=row_idx + 2, column=1, value=description) 81 | scenario_inputs = [k for k in inputs_dict.keys() if k.islower()] 82 | site_inputs = [k for k in inputs_dict['Site'].keys() if k.islower()] 83 | 84 | for col_idx, hdr in enumerate(sorted(scenario_inputs) + sorted(site_inputs)): 85 | if hdr == 'description': 86 | continue 87 | if row_idx == 0: # write header 88 | ws_sites.cell(row=row_idx + 1, column=col_idx + 1, value=hdr) 89 | try: 90 | val = inputs_dict[hdr] 91 | except KeyError: 92 | val = inputs_dict['Site'][hdr] 93 | ws_sites.cell(row=row_idx + 2, column=col_idx + 1, value=val) 94 | 95 | err_msg = resp['messages'].get('error') 96 | if row_idx == 0: # write header 97 | ws_sites.cell(row=row_idx + 1, column=col_idx + 2, value='error_message') 98 | ws_sites.cell(row=row_idx + 2, column=col_idx + 2, value=err_msg) 99 | 100 | for Key in [K for K in inputs_dict['Site'].keys() if K[0].isupper()]: 101 | time_series_dict[Key] = dict_to_worksheet(inputs_dict['Site'][Key], wb, row_idx, description, Key + '_inputs') 102 | 103 | for Key in [K for K in outputs_dict['Site'].keys() if K[0].isupper()]: 104 | time_series_dict[Key] = dict_to_worksheet(outputs_dict['Site'][Key], wb, row_idx, description, Key + '_outputs') 105 | 106 | time_series_to_worksheet(time_series_dict, wb, description) 107 | wb.save(spreadsheet) 108 | 109 | # TODO address "UserWarning: Title is more than 31 characters. Some applications may not be able to read the file" -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/post_and_poll.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from src.logger import log 4 | from src.results_poller import poller, poller_v3 5 | 6 | 7 | def get_api_results(post, API_KEY, api_url, results_file='results.json', run_id=None): 8 | """ 9 | Function for posting job and polling results end-point 10 | :param post: 11 | :param results_file: 12 | :param API_KEY: 13 | :param api_url: 14 | :return: results dictionary / API response 15 | """ 16 | 17 | if run_id is None: 18 | run_id = get_run_uuid(post, API_KEY=API_KEY, api_url=api_url) 19 | 20 | if run_id is not None: 21 | 22 | results_url = api_url + '/job//results/?api_key=' + API_KEY 23 | if "dev/" in results_url: 24 | results = poller_v3(url=results_url.replace('', run_id)) 25 | else: 26 | results = poller(url=results_url.replace('', run_id)) 27 | 28 | with open(results_file, 'w') as fp: 29 | json.dump(obj=results, fp=fp) 30 | 31 | log.info("Saved results to {}".format(results_file)) 32 | else: 33 | results = None 34 | log.error("Unable to get results: no run_uuid from POST.") 35 | 36 | return results 37 | 38 | 39 | def get_run_uuid(post, API_KEY, api_url): 40 | """ 41 | Function for posting job 42 | :param post: 43 | :param API_KEY: 44 | :param api_url: 45 | :return: job run_uuid 46 | """ 47 | post_url = api_url + '/job/?api_key=' + API_KEY 48 | resp = requests.post(post_url, json=post) 49 | run_id = None 50 | if not resp.ok: 51 | log.error("Status code {}. {}".format(resp.status_code, resp.content)) 52 | else: 53 | log.info("Response OK from {}.".format(post_url)) 54 | 55 | run_id_dict = json.loads(resp.text) 56 | 57 | try: 58 | run_id = run_id_dict['run_uuid'] 59 | except KeyError: 60 | msg = "Response from {} did not contain run_uuid.".format(post_url) 61 | log.error(msg) 62 | 63 | return run_id 64 | 65 | 66 | if __name__ == '__main__': 67 | """ 68 | In case just need to re-save json response 69 | """ 70 | run_uuid = "my_run_id" 71 | file_name = "my_results.json" 72 | api_url = 'https://developer.nrel.gov/api/reopt/stable' 73 | API_KEY = "DEMO_KEY" 74 | 75 | results = get_api_results(post={}, API_KEY=API_KEY, run_id=run_uuid, results_file=file_name, api_url=api_url) 76 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/results_poller.py: -------------------------------------------------------------------------------- 1 | """ 2 | function for polling reopt api results url 3 | """ 4 | import requests 5 | import json 6 | import time 7 | from src.logger import log 8 | 9 | 10 | def poller(url, poll_interval=5): 11 | """ 12 | Function for polling the REopt API results URL until status is not "Optimizing..." 13 | :param url: results url to poll 14 | :param poll_interval: seconds 15 | :return: dictionary response (once status is not "Optimizing...") 16 | """ 17 | 18 | key_error_count = 0 19 | key_error_threshold = 3 20 | status = "Optimizing..." 21 | log.info("Polling {} for results with interval of {}s...".format(url, poll_interval)) 22 | while True: 23 | 24 | resp = requests.get(url=url, verify=False) 25 | resp_dict = json.loads(resp.content) 26 | 27 | try: 28 | status = resp_dict['outputs']['Scenario']['status'] 29 | except KeyError: 30 | key_error_count += 1 31 | log.info('KeyError count: {}'.format(key_error_count)) 32 | if key_error_count > key_error_threshold: 33 | log.info('Breaking polling loop due to KeyError count threshold of {} exceeded.' 34 | .format(key_error_threshold)) 35 | break 36 | 37 | if status != "Optimizing...": 38 | break 39 | else: 40 | time.sleep(poll_interval) 41 | 42 | return resp_dict 43 | 44 | 45 | def poller_v3(url, poll_interval=5): 46 | """ 47 | Function for polling the REopt API results URL until status is not "Optimizing..." 48 | :param url: results url to poll 49 | :param poll_interval: seconds 50 | :return: dictionary response (once status is not "Optimizing...") 51 | """ 52 | 53 | key_error_count = 0 54 | key_error_threshold = 3 55 | status = "Optimizing..." 56 | log.info("Polling {} for results with interval of {}s...".format(url, poll_interval)) 57 | while True: 58 | 59 | resp = requests.get(url=url, verify=False) 60 | resp_dict = json.loads(resp.content) 61 | 62 | try: 63 | status = resp_dict['status'] 64 | except KeyError: 65 | key_error_count += 1 66 | log.info('KeyError count: {}'.format(key_error_count)) 67 | if key_error_count > key_error_threshold: 68 | log.info('Breaking polling loop due to KeyError count threshold of {} exceeded.' 69 | .format(key_error_threshold)) 70 | break 71 | 72 | if status != "Optimizing...": 73 | break 74 | else: 75 | time.sleep(poll_interval) 76 | 77 | return resp_dict 78 | 79 | -------------------------------------------------------------------------------- /REopt API Scripts/archived_v2_scripts/src/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/REopt-Analysis-Scripts/50dc58078a021ff69f46dfb12ce613e35e1f972b/REopt API Scripts/archived_v2_scripts/src/template.xlsx -------------------------------------------------------------------------------- /REopt API Scripts/electric_rates/PGE_E20.json: -------------------------------------------------------------------------------- 1 | {"energyweekdayschedule":[[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,4,4,4,3,3,3],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1]],"energyweekendschedule":[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],"energyratestructure":[[{"rate":0,"unit":"kWh"}],[{"rate":0.09716,"unit":"kWh"}],[{"rate":0.1133,"unit":"kWh"}],[{"rate":0.08981,"unit":"kWh"}],[{"rate":0.1196,"unit":"kWh"}],[{"rate":0.16299,"unit":"kWh"}]],"demandratestructure":[[{"rate":0}],[{"rate":0.0}],[{"rate":0.06}],[{"rate":5.88}],[{"rate":21.19}]],"demandweekdayschedule":[[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,3,3,3,3,4,4,4,4,4,4,3,3,3,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1],[1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1]],"demandweekendschedule":[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]],"flatdemandstructure":[[{"rate":0}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}],[{"rate":21.3}]],"flatdemandmonths":[1,2,3,4,5,6,7,8,9,10,11,12],"demandratchetpercentage":[0,0,0,0,0,50.0,50.0,50.0,50.0,0,0,0],"fixedchargefirstmeter":45.08771,"fixedchargeunits":"$/day"} -------------------------------------------------------------------------------- /REopt API Scripts/google_colab_simple_examples.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "dFDOz76wrdAN" 7 | }, 8 | "source": [ 9 | "# Running a REopt API evaluation" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": { 15 | "id": "nim-ucBnMoCW" 16 | }, 17 | "source": [ 18 | "Steps: \n", 19 | "1. Open this Jupyter Notebook directly in Google Colab [here](https://colab.research.google.com/github/NREL/REopt-Analysis-Scripts/blob/master/REopt%20API%20Scripts/google_colab_simple_examples.ipynb)\n", 20 | "2. Run each cell to see examples of interfacing with the REopt API (modify cells if desired) " 21 | ] 22 | }, 23 | { 24 | "cell_type": "markdown", 25 | "metadata": { 26 | "id": "rd_rseJCrdAO" 27 | }, 28 | "source": [ 29 | "## Initialization" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 1, 35 | "metadata": { 36 | "colab": { 37 | "base_uri": "https://localhost:8080/" 38 | }, 39 | "id": "3PJcqvC1se5t", 40 | "outputId": "b62f59ad-b035-446d-e83e-7bf4a57b5228" 41 | }, 42 | "outputs": [ 43 | { 44 | "name": "stdout", 45 | "output_type": "stream", 46 | "text": [ 47 | "Cloning into 'cloned-repo'...\n", 48 | "warning: --local is ignored\n", 49 | "remote: Enumerating objects: 1160, done.\u001b[K\n", 50 | "remote: Counting objects: 100% (396/396), done.\u001b[K\n", 51 | "remote: Compressing objects: 100% (204/204), done.\u001b[K\n", 52 | "remote: Total 1160 (delta 264), reused 250 (delta 192), pack-reused 764 (from 1)\u001b[K\n", 53 | "Receiving objects: 100% (1160/1160), 19.89 MiB | 5.59 MiB/s, done.\n", 54 | "Resolving deltas: 100% (717/717), done.\n", 55 | "/content/cloned-repo\n", 56 | "/content/cloned-repo/REopt API Scripts\n", 57 | "archived_v2_scripts inputs\t outputs\t\t\t src\n", 58 | "electric_rates\t load_profiles single_scenario_example.ipynb\n" 59 | ] 60 | } 61 | ], 62 | "source": [ 63 | "# Clone the REopt-Analysis-Scripts GitHub Repository to this Google Colab Notebook\n", 64 | "!git clone -l -s https://github.com/NREL/REopt-Analysis-Scripts.git cloned-repo\n", 65 | "%cd cloned-repo\n", 66 | "\n", 67 | "# Change directory to \"REopt API Scripts\" folder\n", 68 | "%cd \"REopt API Scripts\"\n", 69 | "\n", 70 | "# Print contents\n", 71 | "!ls" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 2, 77 | "metadata": { 78 | "id": "HrXkdl__rdAO" 79 | }, 80 | "outputs": [], 81 | "source": [ 82 | "import pandas as pd\n", 83 | "import numpy as np\n", 84 | "import json\n", 85 | "import requests\n", 86 | "import copy\n", 87 | "import os\n", 88 | "from src.post_and_poll import get_api_results\n", 89 | "import matplotlib.pyplot as plt\n", 90 | "API_KEY = 'DEMO_KEY' # REPLACE WITH YOUR API KEY" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 3, 96 | "metadata": { 97 | "id": "t_CbFMdLrdAP" 98 | }, 99 | "outputs": [], 100 | "source": [ 101 | "# following is not necessary but silences warnings:\n", 102 | "# InsecureRequestWarning: Unverified HTTPS request is being made to host 'developer.nrel.gov'. Adding certificate verification is strongly advised.\n", 103 | "import urllib3\n", 104 | "urllib3.disable_warnings()" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": 4, 110 | "metadata": { 111 | "id": "AKoSD1qNrdAP" 112 | }, 113 | "outputs": [], 114 | "source": [ 115 | "\"\"\"\n", 116 | "File paths\n", 117 | "\"\"\"\n", 118 | "##############################################################################################################\n", 119 | "inputs_path = os.path.join(\".\", 'inputs')\n", 120 | "outputs_path = os.path.join(\".\", 'outputs')\n", 121 | "loads_path = os.path.join(\".\", 'load_profiles')\n", 122 | "rates_path = os.path.join(\".\", 'electric_rates')\n", 123 | "##############################################################################################################" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "metadata": { 129 | "id": "5fJZDAFbrdAQ" 130 | }, 131 | "source": [ 132 | "## Scenario Inputs (POST), if wanting to do a new API call\n", 133 | "\n", 134 | "- View all possible inputs, default values, and explanations, here: https://nrel.github.io/REopt.jl/dev/reopt/inputs/\n", 135 | "- Defaults will be used for any input not supplied\n" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "metadata": { 142 | "id": "2zReIBGNrdAQ" 143 | }, 144 | "outputs": [], 145 | "source": [ 146 | "post = {\n", 147 | " \"Site\": {\n", 148 | " # Location of site\n", 149 | " \"latitude\": 50.44534458236023,\n", 150 | " \"longitude\": 30.529943967917802,\n", 151 | " # Area available\n", 152 | " \"land_acres\": 1,\n", 153 | " \"roof_squarefeet\": 5000\n", 154 | " },\n", 155 | " \"PV\": {\n", 156 | " # Modify PV cost (fully-installed cost)\n", 157 | " \"installed_cost_per_kw\": 800.0\n", 158 | " },\n", 159 | " # Supply a blank dictionary to evaluate the technology without chaning any defaults\n", 160 | " \"ElectricStorage\" : {},\n", 161 | " \"ElectricLoad\": {\n", 162 | " # Define building type and annual load - to use modeled loads from DOE Commercial Reference Buildings\n", 163 | " \"doe_reference_name\": \"RetailStore\",\n", 164 | " \"annual_kwh\": 100000.0\n", 165 | " },\n", 166 | " \"ElectricTariff\": {\n", 167 | " # Average energy and demand charges, applied monthly\n", 168 | " \"blended_annual_energy_rate\": 0.20,\n", 169 | " \"blended_annual_demand_rate\": 5\n", 170 | " },\n", 171 | " \"ElectricUtility\" : {\n", 172 | " # Specify grid emissions factor\n", 173 | " \"emissions_factor_series_lb_CO2_per_kwh\": 1.04\n", 174 | " },\n", 175 | " \"Financial\": {\n", 176 | " # Define financial parameters\n", 177 | " \"elec_cost_escalation_rate_fraction\": 0.05,\n", 178 | " \"offtaker_discount_rate_fraction\": 0.13,\n", 179 | " \"analysis_years\": 20,\n", 180 | " \"offtaker_tax_rate_fraction\": 0.18,\n", 181 | " \"om_cost_escalation_rate_fraction\": 0.025\n", 182 | " }\n", 183 | "}" 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": { 189 | "id": "U3ZkbzJLLCqO" 190 | }, 191 | "source": [ 192 | "## POST and poll (periodic GET request) the API to GET the REopt results. This can take a while!" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": { 198 | "id": "_qPv0tdMLP7l" 199 | }, 200 | "source": [ 201 | "`get_api_results` POST's your inputs to the API `job` endpoint, which provides a `run_uuid` if the input is valid, and then polls the `results` endpoint using the `run_uuid` until the results come back with a status other than `Optimizing...`.\n", 202 | "\n", 203 | "`get_api_results` also saves the results (full API response, including inputs) to the `results_file`." 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "metadata": { 210 | "colab": { 211 | "base_uri": "https://localhost:8080/" 212 | }, 213 | "id": "X_IHh0C7K7UE", 214 | "outputId": "b8c49d02-ba64-4c47-84bc-7c5cfff42d0c" 215 | }, 216 | "outputs": [], 217 | "source": [ 218 | "outputs_file_name = \"my_results_file\"\n", 219 | "root_url = \"https://developer.nrel.gov/api/reopt/stable\" # /stable == /v3\n", 220 | "\n", 221 | "api_response = get_api_results(post=post,\n", 222 | " API_KEY=API_KEY,\n", 223 | " api_url=root_url,\n", 224 | " results_file=os.path.join(outputs_path, outputs_file_name + \".json\"),\n", 225 | " run_id=None)" 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": { 231 | "id": "zdHErwBxLci0" 232 | }, 233 | "source": [ 234 | "## Explore API Response\n", 235 | "- View all results fields here: https://nrel.github.io/REopt.jl/dev/reopt/outputs/" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": null, 241 | "metadata": { 242 | "colab": { 243 | "base_uri": "https://localhost:8080/" 244 | }, 245 | "id": "jTHsRPoULbv-", 246 | "outputId": "4ff45bad-2b98-46a3-b7ce-ddb603522396" 247 | }, 248 | "outputs": [], 249 | "source": [ 250 | "# Keys within full response JSON\n", 251 | "api_response.keys()" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": null, 257 | "metadata": { 258 | "colab": { 259 | "base_uri": "https://localhost:8080/" 260 | }, 261 | "id": "wCEzB--FLqKF", 262 | "outputId": "5f9b704e-44f8-4a51-8f2a-bf8c909a625c" 263 | }, 264 | "outputs": [], 265 | "source": [ 266 | "# Full inputs dictionary\n", 267 | "api_response[\"inputs\"];\n", 268 | "\n", 269 | "# View just \"Financial\" inputs\n", 270 | "api_response[\"inputs\"][\"Financial\"]" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": null, 276 | "metadata": { 277 | "colab": { 278 | "base_uri": "https://localhost:8080/" 279 | }, 280 | "id": "dVgtq8YKLqXP", 281 | "outputId": "05c50f2a-a18c-42fd-96e6-659fe5b59f56" 282 | }, 283 | "outputs": [], 284 | "source": [ 285 | "# Output keys\n", 286 | "print(\"Output Keys: \", list(api_response[\"outputs\"].keys()))" 287 | ] 288 | }, 289 | { 290 | "cell_type": "code", 291 | "execution_count": null, 292 | "metadata": { 293 | "colab": { 294 | "base_uri": "https://localhost:8080/" 295 | }, 296 | "id": "l5TOnWxpGir-", 297 | "outputId": "0ecbcfb4-2c61-4579-d0cb-100fb1c6c8ed" 298 | }, 299 | "outputs": [], 300 | "source": [ 301 | "# Example: Calculate year one bill savings\n", 302 | "year1_bill_optimized = api_response[\"outputs\"][\"ElectricTariff\"][\"year_one_bill_before_tax\"] - api_response[\"outputs\"][\"ElectricTariff\"][\"year_one_export_benefit_before_tax\"]\n", 303 | "year1_bill_bau = api_response[\"outputs\"][\"ElectricTariff\"][\"year_one_bill_before_tax_bau\"] - api_response[\"outputs\"][\"ElectricTariff\"][\"year_one_export_benefit_before_tax_bau\"]\n", 304 | "print(\"Year 1 Bill Savings ($): \", round(year1_bill_bau - year1_bill_optimized))" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": null, 310 | "metadata": { 311 | "colab": { 312 | "base_uri": "https://localhost:8080/" 313 | }, 314 | "id": "nm1qZs0EL1Fc", 315 | "outputId": "85a7693a-ced3-42fe-a08e-8e842968c080" 316 | }, 317 | "outputs": [], 318 | "source": [ 319 | "# Good idea to view info messages and warnings.\n", 320 | "api_response[\"messages\"]" 321 | ] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": null, 326 | "metadata": { 327 | "colab": { 328 | "base_uri": "https://localhost:8080/" 329 | }, 330 | "id": "oOfUxmZLL_5G", 331 | "outputId": "1ecf1ee3-7709-4be4-e99f-e7fe7b276307" 332 | }, 333 | "outputs": [], 334 | "source": [ 335 | "# Summary of results\n", 336 | "print(\"NPV ($) = \", api_response[\"outputs\"][\"Financial\"][\"npv\"])\n", 337 | "print(\"Capital Cost, Net ($) = \", api_response[\"outputs\"][\"Financial\"][\"lifecycle_capital_costs\"])\n", 338 | "tech_list = [\"PV\", \"Wind\", \"ElectricStorage\", \"CHP\", \"Generator\", \"HotThermalStorage\", \"ColdThermalStorage\", \"AbsorptionChiller\", \"GHP\", \"Boiler\", \"SteamTurbine\"]\n", 339 | "for tech in tech_list:\n", 340 | " if tech in post.keys():\n", 341 | " if tech == \"GHP\":\n", 342 | " print(\"GHX Number of Boreholes = \", api_response[\"outputs\"][tech][\"ghpghx_chosen_outputs\"].get(\"number_of_boreholes\"))\n", 343 | " print(\"GHP Heat Pump Capacity (ton) = \", api_response[\"outputs\"][tech][\"ghpghx_chosen_outputs\"].get(\"peak_combined_heatpump_thermal_ton\"))\n", 344 | " for size_name_value in [(key, val) for key, val in api_response[\"outputs\"][tech].items() if \"size\" in key]:\n", 345 | " print(tech + \" \" + size_name_value[0], \" = \", size_name_value[1])" 346 | ] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": null, 351 | "metadata": { 352 | "colab": { 353 | "base_uri": "https://localhost:8080/", 354 | "height": 472 355 | }, 356 | "id": "jHMNyjhYh00R", 357 | "outputId": "8a8e279c-e0b5-4047-bcba-047d4e5a480b" 358 | }, 359 | "outputs": [], 360 | "source": [ 361 | "# Choose some timeseries results to visualize\n", 362 | "loads_kw = api_response[\"outputs\"][\"ElectricUtility\"][\"electric_to_load_series_kw_bau\"]\n", 363 | "electric_to_load_series_kw = api_response[\"outputs\"][\"ElectricUtility\"][\"electric_to_load_series_kw\"]\n", 364 | "\n", 365 | "plt.plot(loads_kw, label=\"Before\")\n", 366 | "plt.plot(electric_to_load_series_kw, label=\"After\")\n", 367 | "\n", 368 | "plt.xlabel(\"Time\")\n", 369 | "plt.ylabel(\"Power (kW)\")\n", 370 | "plt.title(\"Grid Purchases Before and After Investments\")\n", 371 | "plt.legend()\n", 372 | "plt.show()" 373 | ] 374 | }, 375 | { 376 | "cell_type": "markdown", 377 | "metadata": { 378 | "id": "D_yqh4bJMOPm" 379 | }, 380 | "source": [ 381 | "# Practice Exercises" 382 | ] 383 | }, 384 | { 385 | "cell_type": "markdown", 386 | "metadata": { 387 | "id": "5luyHn5vY6K5" 388 | }, 389 | "source": [ 390 | "## 1. Run a resilience evaluation" 391 | ] 392 | }, 393 | { 394 | "cell_type": "code", 395 | "execution_count": 10, 396 | "metadata": { 397 | "id": "kpozWfr0Y41c" 398 | }, 399 | "outputs": [], 400 | "source": [ 401 | "### Modify the values below for your test case before moving on ###\n", 402 | "\n", 403 | "## Set the duration of the outage you wish to evaluate\n", 404 | "outage_hours = 12\n", 405 | "\n", 406 | "## Define your critical loads\n", 407 | "# Option 1: Specify a percentage of the typical loads as critical\n", 408 | "critical_load_fraction = 0.8 # 80%\n", 409 | "\n", 410 | "# Option 2: Create a critical load profile and upload it to the \"load_profiles\" folder under the \"Files\" in this Colab space\n", 411 | "# Uncomment if you wish to use this approach:\n", 412 | "# critical_load_file_name = \"my_file.csv\"\n", 413 | "# filepath = os.path.join(loads_path, critical_load_file_name)\n", 414 | "# critical_loads_kw = pd.read_csv(filepath, header=None).iloc[:, 0].tolist()\n", 415 | "\n", 416 | "## Specify the lat long\n", 417 | "latitude = 50\n", 418 | "longitude = 30\n", 419 | "\n", 420 | "## Specify the building type and annual load\n", 421 | "doe_reference_name = \"Hospital\" # See options here: https://nrel.github.io/REopt.jl/dev/reopt/inputs/#ElectricLoad\n", 422 | "annual_kwh = 100000\n" 423 | ] 424 | }, 425 | { 426 | "cell_type": "code", 427 | "execution_count": 11, 428 | "metadata": { 429 | "id": "diOEtdl8AsDQ" 430 | }, 431 | "outputs": [], 432 | "source": [ 433 | "# Just run this cell -- no modifications needed.\n", 434 | "root_url = \"https://developer.nrel.gov/api/reopt/stable\"\n", 435 | "\n", 436 | "# Get load profile\n", 437 | "inputs = {\"load_type\": \"electric\",\n", 438 | " \"doe_reference_name\": doe_reference_name,\n", 439 | " \"latitude\": latitude,\n", 440 | " \"longitude\": longitude,\n", 441 | " \"annual_kwh\": annual_kwh\n", 442 | "}\n", 443 | "load_url = root_url + '/simulated_load/?api_key=' + API_KEY\n", 444 | "response = requests.get(url=load_url, params=inputs, verify=False)\n", 445 | "loads_kw = json.loads(response.content)[\"loads_kw\"]\n", 446 | "\n", 447 | "# Get outage start times. Assume outages occur during peak load times\n", 448 | "inputs = {\"seasonal_peaks\": True,\n", 449 | " \"outage_duration\": outage_hours,\n", 450 | " \"critical_load\": loads_kw,\n", 451 | " \"start_not_center_on_peaks\": False\n", 452 | " }\n", 453 | "times_url = root_url + '/peak_load_outage_times/?api_key=' + API_KEY\n", 454 | "response = requests.post(url=times_url, json=inputs)\n", 455 | "outage_times = json.loads(response.content)[\"outage_start_time_steps\"]" 456 | ] 457 | }, 458 | { 459 | "cell_type": "code", 460 | "execution_count": 12, 461 | "metadata": { 462 | "id": "3A7ADI4Xf6sm" 463 | }, 464 | "outputs": [], 465 | "source": [ 466 | "### Modify any values below that are not already variables ###\n", 467 | "### Check all possible inputs here: https://nrel.github.io/REopt.jl/dev/reopt/inputs/ ###\n", 468 | "\n", 469 | "post = {\n", 470 | " \"Site\": {\n", 471 | " # Location of site\n", 472 | " \"latitude\": latitude,\n", 473 | " \"longitude\": longitude,\n", 474 | " # Area available\n", 475 | " \"land_acres\": 1,\n", 476 | " \"roof_squarefeet\": 5000\n", 477 | " },\n", 478 | " \"PV\": {\n", 479 | " # Modify PV cost (fully-installed cost)\n", 480 | " \"installed_cost_per_kw\": 800.0\n", 481 | " },\n", 482 | " # Supply a blank dictionary to evaluate the technology without chaning any defaults\n", 483 | " \"ElectricStorage\" : {},\n", 484 | " \"ElectricLoad\": {\n", 485 | " # Define building type and annual load - to use modeled loads from DOE Commercial Reference Buildings\n", 486 | " \"doe_reference_name\": doe_reference_name,\n", 487 | " \"annual_kwh\": annual_kwh\n", 488 | " # If using a custom load, you can specify loads_kw\n", 489 | " },\n", 490 | " \"ElectricTariff\": {\n", 491 | " # Average energy and demand charges, applied monthly\n", 492 | " \"blended_annual_energy_rate\": 0.20,\n", 493 | " \"blended_annual_demand_rate\": 5\n", 494 | " },\n", 495 | " \"ElectricUtility\" : {\n", 496 | " # Specify grid emissions factor\n", 497 | " \"emissions_factor_series_lb_CO2_per_kwh\": 1.04,\n", 498 | " # Resilience Inputs\n", 499 | " \"outage_durations\": [outage_hours],\n", 500 | " \"outage_start_time_steps\": outage_times,\n", 501 | " # If using Option 2 above: \"critical_loads_kw\": critical_loads\n", 502 | " },\n", 503 | " \"Financial\": {\n", 504 | " # Define financial parameters\n", 505 | " \"elec_cost_escalation_rate_fraction\": 0.05,\n", 506 | " \"offtaker_discount_rate_fraction\": 0.13,\n", 507 | " \"analysis_years\": 20,\n", 508 | " \"offtaker_tax_rate_fraction\": 0.18,\n", 509 | " \"om_cost_escalation_rate_fraction\": 0.025\n", 510 | " }\n", 511 | "}" 512 | ] 513 | }, 514 | { 515 | "cell_type": "code", 516 | "execution_count": null, 517 | "metadata": { 518 | "colab": { 519 | "base_uri": "https://localhost:8080/" 520 | }, 521 | "id": "1lgpxIRUE4jX", 522 | "outputId": "b9d7aed5-54a0-4c3d-c50c-f3c0d5b9cd4c" 523 | }, 524 | "outputs": [], 525 | "source": [ 526 | "# Call the REopt API\n", 527 | "outputs_file_name = f\"resilience_run_{outage_hours}hours\"\n", 528 | "root_url = \"https://developer.nrel.gov/api/reopt/stable\" # /stable == /v3\n", 529 | "\n", 530 | "api_response = get_api_results(post=post,\n", 531 | " API_KEY=API_KEY,\n", 532 | " api_url=root_url,\n", 533 | " results_file=os.path.join(outputs_path, outputs_file_name + \".json\"),\n", 534 | " run_id=None)" 535 | ] 536 | }, 537 | { 538 | "cell_type": "code", 539 | "execution_count": null, 540 | "metadata": { 541 | "colab": { 542 | "base_uri": "https://localhost:8080/" 543 | }, 544 | "id": "SDyJHWgQFDD_", 545 | "outputId": "e6ec8e9f-6bef-401a-82f0-f96c4e43f399" 546 | }, 547 | "outputs": [], 548 | "source": [ 549 | "# Summary of results\n", 550 | "print(f\"Results for Outage Duration of {outage_hours} hours\")\n", 551 | "print(\"NPV ($) = \", api_response[\"outputs\"][\"Financial\"][\"npv\"])\n", 552 | "print(\"Capital Cost, Net ($) = \", api_response[\"outputs\"][\"Financial\"][\"lifecycle_capital_costs\"])\n", 553 | "tech_list = [\"PV\", \"Wind\", \"ElectricStorage\", \"CHP\", \"Generator\", \"HotThermalStorage\", \"ColdThermalStorage\", \"AbsorptionChiller\", \"GHP\", \"Boiler\", \"SteamTurbine\"]\n", 554 | "for tech in tech_list:\n", 555 | " if tech in post.keys():\n", 556 | " if tech == \"GHP\":\n", 557 | " print(\"GHX Number of Boreholes = \", api_response[\"outputs\"][tech][\"ghpghx_chosen_outputs\"].get(\"number_of_boreholes\"))\n", 558 | " print(\"GHP Heat Pump Capacity (ton) = \", api_response[\"outputs\"][tech][\"ghpghx_chosen_outputs\"].get(\"peak_combined_heatpump_thermal_ton\"))\n", 559 | " for size_name_value in [(key, val) for key, val in api_response[\"outputs\"][tech].items() if \"size\" in key]:\n", 560 | " print(tech + \" \" + size_name_value[0], \" = \", size_name_value[1])" 561 | ] 562 | }, 563 | { 564 | "cell_type": "markdown", 565 | "metadata": { 566 | "id": "l9Do0ACKQjc2" 567 | }, 568 | "source": [ 569 | "## 2. Change the outage duration and compare results" 570 | ] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "execution_count": 13, 575 | "metadata": { 576 | "id": "Q8As4_61HPPH" 577 | }, 578 | "outputs": [], 579 | "source": [ 580 | "## Specify a new outage duration and run REopt\n", 581 | "outage_hours = 48 # Modify this value" 582 | ] 583 | }, 584 | { 585 | "cell_type": "code", 586 | "execution_count": 14, 587 | "metadata": { 588 | "colab": { 589 | "base_uri": "https://localhost:8080/" 590 | }, 591 | "id": "sFm1oIk8Qv28", 592 | "outputId": "dea1189a-64f3-4894-dd84-dce6b680dfd2" 593 | }, 594 | "outputs": [ 595 | { 596 | "name": "stderr", 597 | "output_type": "stream", 598 | "text": [ 599 | "main INFO Response OK from https://developer.nrel.gov/api/reopt/stable/job/?api_key=7vZ12k2wwtIzYdR4nHAHrLvdwv3NmMMkumcfnjyQ.\n", 600 | "INFO:main:Response OK from https://developer.nrel.gov/api/reopt/stable/job/?api_key=7vZ12k2wwtIzYdR4nHAHrLvdwv3NmMMkumcfnjyQ.\n", 601 | "main INFO Polling https://developer.nrel.gov/api/reopt/stable/job/177f7ffd-41a7-4266-901d-2fd9eae020ec/results/?api_key=7vZ12k2wwtIzYdR4nHAHrLvdwv3NmMMkumcfnjyQ for results with interval of 5s...\n", 602 | "INFO:main:Polling https://developer.nrel.gov/api/reopt/stable/job/177f7ffd-41a7-4266-901d-2fd9eae020ec/results/?api_key=7vZ12k2wwtIzYdR4nHAHrLvdwv3NmMMkumcfnjyQ for results with interval of 5s...\n", 603 | "main INFO Saved results to ./outputs/resilience_run_48hours.json\n", 604 | "INFO:main:Saved results to ./outputs/resilience_run_48hours.json\n" 605 | ] 606 | } 607 | ], 608 | "source": [ 609 | "# Get new outage start times. Assume outages occur during peak load times\n", 610 | "inputs = {\"seasonal_peaks\": True,\n", 611 | " \"outage_duration\": outage_hours,\n", 612 | " \"critical_load\": loads_kw,\n", 613 | " \"start_not_center_on_peaks\": False\n", 614 | " }\n", 615 | "times_url = root_url + '/peak_load_outage_times/?api_key=' + API_KEY\n", 616 | "response = requests.post(url=times_url, json=inputs)\n", 617 | "outage_times = json.loads(response.content)[\"outage_start_time_steps\"]\n", 618 | "\n", 619 | "# Update the post\n", 620 | "post[\"ElectricUtility\"][\"outage_durations\"] = [outage_hours]\n", 621 | "post[\"ElectricUtility\"][\"outage_start_time_steps\"] = outage_times\n", 622 | "\n", 623 | "# Run REopt\n", 624 | "outputs_file_name = f\"resilience_run_{outage_hours}hours\"\n", 625 | "root_url = \"https://developer.nrel.gov/api/reopt/stable\" # /stable == /v3\n", 626 | "\n", 627 | "api_response = get_api_results(post=post,\n", 628 | " API_KEY=API_KEY,\n", 629 | " api_url=root_url,\n", 630 | " results_file=os.path.join(outputs_path, outputs_file_name + \".json\"),\n", 631 | " run_id=None)" 632 | ] 633 | }, 634 | { 635 | "cell_type": "code", 636 | "execution_count": 15, 637 | "metadata": { 638 | "colab": { 639 | "base_uri": "https://localhost:8080/" 640 | }, 641 | "id": "MUxYYRG2HPXz", 642 | "outputId": "1d9aa1c1-237e-41ab-d652-de96c8c45f39" 643 | }, 644 | "outputs": [ 645 | { 646 | "name": "stdout", 647 | "output_type": "stream", 648 | "text": [ 649 | "Results for Outage Duration of 48 hours\n", 650 | "NPV ($) = -25937.56\n", 651 | "Capital Cost, Net ($) = 123167.8662\n", 652 | "PV size_kw = 97.1641\n", 653 | "ElectricStorage size_kw = 17.26\n", 654 | "ElectricStorage size_kwh = 177.75\n" 655 | ] 656 | } 657 | ], 658 | "source": [ 659 | "# Summary of results\n", 660 | "print(f\"Results for Outage Duration of {outage_hours} hours\")\n", 661 | "print(\"NPV ($) = \", api_response[\"outputs\"][\"Financial\"][\"npv\"])\n", 662 | "print(\"Capital Cost, Net ($) = \", api_response[\"outputs\"][\"Financial\"][\"lifecycle_capital_costs\"])\n", 663 | "tech_list = [\"PV\", \"Wind\", \"ElectricStorage\", \"CHP\", \"Generator\", \"HotThermalStorage\", \"ColdThermalStorage\", \"AbsorptionChiller\", \"GHP\", \"Boiler\", \"SteamTurbine\"]\n", 664 | "for tech in tech_list:\n", 665 | " if tech in post.keys():\n", 666 | " if tech == \"GHP\":\n", 667 | " print(\"GHX Number of Boreholes = \", api_response[\"outputs\"][tech][\"ghpghx_chosen_outputs\"].get(\"number_of_boreholes\"))\n", 668 | " print(\"GHP Heat Pump Capacity (ton) = \", api_response[\"outputs\"][tech][\"ghpghx_chosen_outputs\"].get(\"peak_combined_heatpump_thermal_ton\"))\n", 669 | " for size_name_value in [(key, val) for key, val in api_response[\"outputs\"][tech].items() if \"size\" in key]:\n", 670 | " print(tech + \" \" + size_name_value[0], \" = \", size_name_value[1])" 671 | ] 672 | }, 673 | { 674 | "cell_type": "code", 675 | "execution_count": null, 676 | "metadata": { 677 | "id": "diMyMB1GRUSO" 678 | }, 679 | "outputs": [], 680 | "source": [] 681 | } 682 | ], 683 | "metadata": { 684 | "@webio": { 685 | "lastCommId": null, 686 | "lastKernelId": null 687 | }, 688 | "colab": { 689 | "provenance": [] 690 | }, 691 | "kernelspec": { 692 | "display_name": "Python 3", 693 | "language": "python", 694 | "name": "python3" 695 | }, 696 | "language_info": { 697 | "codemirror_mode": { 698 | "name": "ipython", 699 | "version": 3 700 | }, 701 | "file_extension": ".py", 702 | "mimetype": "text/x-python", 703 | "name": "python", 704 | "nbconvert_exporter": "python", 705 | "pygments_lexer": "ipython3", 706 | "version": "3.8.8" 707 | }, 708 | "latex_envs": { 709 | "LaTeX_envs_menu_present": true, 710 | "autoclose": false, 711 | "autocomplete": true, 712 | "bibliofile": "biblio.bib", 713 | "cite_by": "apalike", 714 | "current_citInitial": 1, 715 | "eqLabelWithNumbers": true, 716 | "eqNumInitial": 1, 717 | "hotkeys": { 718 | "equation": "Ctrl-E", 719 | "itemize": "Ctrl-I" 720 | }, 721 | "labels_anchors": false, 722 | "latex_user_defs": false, 723 | "report_style_numbering": false, 724 | "user_envs_cfg": false 725 | } 726 | }, 727 | "nbformat": 4, 728 | "nbformat_minor": 0 729 | } 730 | -------------------------------------------------------------------------------- /REopt API Scripts/inputs/post_1.json: -------------------------------------------------------------------------------- 1 | {"Site": {"longitude": -118.1164613, "latitude": 34.5794343}, "PV": {"array_type": 0}, "ElectricLoad": {"doe_reference_name": "RetailStore", "annual_kwh": 100000.0, "year": 2017}, "ElectricTariff": {"urdb_label": "5ed6c1a15457a3367add15ae", "urdb_response": {"energyweekdayschedule": [[1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 4, 4, 4, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 4, 4, 4, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 4, 4, 4, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 4, 4, 4, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 4, 4, 4, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 4, 4, 4, 3, 3, 3], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1]], "energyweekendschedule": [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], "energyratestructure": [[{"rate": 0, "unit": "kWh"}], [{"rate": 0.09716, "unit": "kWh"}], [{"rate": 0.1133, "unit": "kWh"}], [{"rate": 0.08981, "unit": "kWh"}], [{"rate": 0.1196, "unit": "kWh"}], [{"rate": 0.16299, "unit": "kWh"}]], "demandratestructure": [[{"rate": 0}], [{"rate": 0.0}], [{"rate": 0.06}], [{"rate": 5.88}], [{"rate": 21.19}]], "demandweekdayschedule": [[1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1]], "demandweekendschedule": [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], "flatdemandstructure": [[{"rate": 0}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}], [{"rate": 21.3}]], "flatdemandmonths": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "demandratchetpercentage": [0, 0, 0, 0, 0, 50.0, 50.0, 50.0, 50.0, 0, 0, 0], "fixedchargefirstmeter": 45.08771, "fixedchargeunits": "$/day"}}, "Financial": {"elec_cost_escalation_rate_fraction": 0.026, "owner_discount_rate_fraction": 0.081, "analysis_years": 20, "offtaker_tax_rate_fraction": 0.4, "owner_tax_rate_fraction": 0.4, "om_cost_escalation_rate_fraction": 0.025}} -------------------------------------------------------------------------------- /REopt API Scripts/single_scenario_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Run a REopt API evaluation" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Initialization" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "import pandas as pd\n", 24 | "import numpy as np\n", 25 | "import json\n", 26 | "import requests\n", 27 | "import copy\n", 28 | "import os\n", 29 | "from src.post_and_poll import get_api_results\n", 30 | "API_KEY = 'DEMO_KEY' # REPLACE WITH YOUR API KEY" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "# following is not necessary but silences warnings:\n", 40 | "# InsecureRequestWarning: Unverified HTTPS request is being made to host 'developer.nrel.gov'. Adding certificate verification is strongly advised.\n", 41 | "import urllib3\n", 42 | "urllib3.disable_warnings()" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "\"\"\"\n", 52 | "Here are some convenience definitions for using the Multi-scenario capabilities\n", 53 | "\"\"\"\n", 54 | "##############################################################################################################\n", 55 | "inputs_path = os.path.join(\".\", 'inputs')\n", 56 | "outputs_path = os.path.join(\".\", 'outputs')\n", 57 | "loads_path = os.path.join(\".\", 'load_profiles')\n", 58 | "rates_path = os.path.join(\".\", 'electric_rates')\n", 59 | "##############################################################################################################" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": {}, 65 | "source": [ 66 | "## Load a previously saved API response .json file instead of running REopt" 67 | ] 68 | }, 69 | { 70 | "cell_type": "raw", 71 | "metadata": {}, 72 | "source": [ 73 | "response_json = 'results'\n", 74 | "with open(os.path.join(outputs_path, response_json + '.json'), 'rb') as handle:\n", 75 | " api_response = json.load(handle)" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "## Scenario Inputs (POST), if wanting to do a new API call" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 4, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "post_1 = {\n", 92 | " \"Site\": {\n", 93 | " \"longitude\": -118.1164613,\n", 94 | " \"latitude\": 34.5794343\n", 95 | " },\n", 96 | " \"PV\": {\n", 97 | " \"array_type\": 0\n", 98 | " },\n", 99 | " \"ElectricLoad\": {\n", 100 | " \"doe_reference_name\": \"RetailStore\",\n", 101 | " \"annual_kwh\": 100000.0,\n", 102 | " \"year\": 2017\n", 103 | " },\n", 104 | " \"ElectricTariff\": {\n", 105 | " \"urdb_label\": \"5ed6c1a15457a3367add15ae\"\n", 106 | " },\n", 107 | " \"Financial\": {\n", 108 | " \"elec_cost_escalation_rate_fraction\": 0.026,\n", 109 | " \"owner_discount_rate_fraction\": 0.081,\n", 110 | " \"analysis_years\": 20,\n", 111 | " \"offtaker_tax_rate_fraction\": 0.4,\n", 112 | " \"owner_tax_rate_fraction\": 0.4,\n", 113 | " \"om_cost_escalation_rate_fraction\": 0.025\n", 114 | " }\n", 115 | "}" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "metadata": {}, 121 | "source": [ 122 | "### Load in a custom electric rate generated from https://reopt.nrel.gov/tool/custom_tariffs" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": 5, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [ 131 | "load_electric_rate = \"PGE_E20\"\n", 132 | "with open(os.path.join(rates_path, load_electric_rate + \".json\"), 'r') as fp:\n", 133 | " rate_1 = json.load(fp)\n", 134 | "post_1[\"ElectricTariff\"][\"urdb_response\"] = rate_1" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "metadata": {}, 140 | "source": [ 141 | "## Save the POST to a .json file for sharing or future loading in" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 6, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "# Convert python dictionary post into json and save to a .json file\n", 151 | "post_save = post_1\n", 152 | "post_name = \"post_1\"\n", 153 | "with open(os.path.join(inputs_path, post_name + \".json\"), 'w') as fp:\n", 154 | " json.dump(post_save, fp)" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "## Or load in a saved .json file for the inputs/POST" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": 7, 167 | "metadata": { 168 | "scrolled": true 169 | }, 170 | "outputs": [], 171 | "source": [ 172 | "# Load a json into a python dictionary\n", 173 | "load_post = \"post_1\"\n", 174 | "with open(os.path.join(inputs_path, load_post + \".json\"), 'r') as fp:\n", 175 | " post_1 = json.load(fp)" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "## POST and poll (periodic GET request) the API to GET a new result, if not loading in a previous response. This may take a while!" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "### Note, the `api_url` in the `get_api_results` function below calls the **production server** hosted API (master/main branch/version, publicly accessible)\n", 190 | "\n", 191 | "#### For calling a locally-hosted (localhost) API, see:\n", 192 | "- https://github.com/NREL/REopt_API/wiki/3.-Calling-Locally-Hosted-API\n", 193 | "\n", 194 | "#### For calling an API hosted on an NREL-internal server (only NREL users can access this), see:\n", 195 | "- https://github.nrel.gov/REopt/API_scripts/wiki/API-URLs-for-NREL-internal-servers" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "`get_api_results` POST's your inputs to the API `job` endpoint, which provides a `run_uuid` if the input is valid, and then polls the `results` endpoint using the `run_uuid` until the results come back with a status other than `Optimizing...`.\n", 203 | "\n", 204 | "`get_api_results` also saves the results (full API response, including inputs) to the `results_file`.\n", 205 | "\n", 206 | "A log file is also created in the current working directory." 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 7, 212 | "metadata": {}, 213 | "outputs": [ 214 | { 215 | "name": "stderr", 216 | "output_type": "stream", 217 | "text": [ 218 | "main INFO Response OK from https://developer.nrel.gov/api/reopt/stable/job/?api_key=DEMO_KEY.\n", 219 | "main INFO Polling https://developer.nrel.gov/api/reopt/stable/job/9dfea231-3209-43eb-9270-30566fcab24c/results/?api_key=DEMO_KEY for results with interval of 5s...\n", 220 | "main INFO Saved results to ./outputs/results_file.json\n" 221 | ] 222 | } 223 | ], 224 | "source": [ 225 | "outputs_file_name = \"results_file\"\n", 226 | "root_url = \"https://developer.nrel.gov/api/reopt/stable\" # /stable == /v3 \n", 227 | "api_response = get_api_results(post=post_1, \n", 228 | " API_KEY=API_KEY, \n", 229 | " api_url=root_url, \n", 230 | " results_file=os.path.join(outputs_path, outputs_file_name + \".json\"), \n", 231 | " run_id=None)" 232 | ] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "metadata": {}, 237 | "source": [ 238 | "### If you get disconnected from the polling function but you think it ran, copy the run_uuid from the log above to manually GET the results:" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 8, 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "data": { 248 | "text/plain": [ 249 | "dict_keys(['run_uuid', 'api_version', 'user_uuid', 'webtool_uuid', 'job_type', 'status', 'created', 'reopt_version', 'inputs', 'outputs', 'messages'])" 250 | ] 251 | }, 252 | "execution_count": 8, 253 | "metadata": {}, 254 | "output_type": "execute_result" 255 | } 256 | ], 257 | "source": [ 258 | "api_response.keys()" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 9, 264 | "metadata": {}, 265 | "outputs": [], 266 | "source": [ 267 | "run_uuid = api_response[\"run_uuid\"]\n", 268 | "results_url = root_url + '/job/' + run_uuid + '/results/?api_key=' + API_KEY\n", 269 | "resp = requests.get(url=results_url, verify=False)\n", 270 | "api_response = json.loads(resp.text)" 271 | ] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "metadata": {}, 276 | "source": [ 277 | "## Get summary of results" 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": 10, 283 | "metadata": {}, 284 | "outputs": [ 285 | { 286 | "name": "stdout", 287 | "output_type": "stream", 288 | "text": [ 289 | "NPV ($) = 47696.79\n", 290 | "Capital Cost, Net ($) = 23587.6414\n", 291 | "PV size_kw = 37.0515\n" 292 | ] 293 | } 294 | ], 295 | "source": [ 296 | "print(\"NPV ($) = \", api_response[\"outputs\"][\"Financial\"][\"npv\"])\n", 297 | "print(\"Capital Cost, Net ($) = \", api_response[\"outputs\"][\"Financial\"][\"lifecycle_capital_costs\"])\n", 298 | "tech_list = [\"PV\", \"Wind\", \"ElectricStorage\", \"CHP\", \"Generator\", \"HotThermalStorage\", \"ColdThermalStorage\", \"AbsorptionChiller\", \"GHP\", \"Boiler\", \"SteamTurbine\"]\n", 299 | "for tech in tech_list:\n", 300 | " if tech in post_1.keys():\n", 301 | " if tech == \"GHP\":\n", 302 | " print(\"GHX Number of Boreholes = \", api_response[\"outputs\"][tech][\"ghpghx_chosen_outputs\"].get(\"number_of_boreholes\"))\n", 303 | " print(\"GHP Heat Pump Capacity (ton) = \", api_response[\"outputs\"][tech][\"ghpghx_chosen_outputs\"].get(\"peak_combined_heatpump_thermal_ton\"))\n", 304 | " for size_name_value in [(key, val) for key, val in api_response[\"outputs\"][tech].items() if \"size\" in key]:\n", 305 | " print(tech + \" \" + size_name_value[0], \" = \", size_name_value[1]) " 306 | ] 307 | }, 308 | { 309 | "cell_type": "markdown", 310 | "metadata": {}, 311 | "source": [ 312 | "### Here are some results keys examples:" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": 19, 318 | "metadata": {}, 319 | "outputs": [ 320 | { 321 | "data": { 322 | "text/plain": [ 323 | "dict_keys(['run_uuid', 'api_version', 'user_uuid', 'webtool_uuid', 'job_type', 'status', 'created', 'reopt_version', 'inputs', 'outputs', 'messages'])" 324 | ] 325 | }, 326 | "execution_count": 19, 327 | "metadata": {}, 328 | "output_type": "execute_result" 329 | } 330 | ], 331 | "source": [ 332 | "api_response.keys()" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": 20, 338 | "metadata": {}, 339 | "outputs": [ 340 | { 341 | "data": { 342 | "text/plain": [ 343 | "'optimal'" 344 | ] 345 | }, 346 | "execution_count": 20, 347 | "metadata": {}, 348 | "output_type": "execute_result" 349 | } 350 | ], 351 | "source": [ 352 | "api_response[\"status\"]" 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": 21, 358 | "metadata": {}, 359 | "outputs": [ 360 | { 361 | "data": { 362 | "text/plain": [ 363 | "{'info': 'When using doe_reference_name or blended_doe_reference_names for ElectricLoad the year is set to 2017 because the DoE load profiles start on a Sunday.',\n", 364 | " 'errors': {},\n", 365 | " 'warnings': {},\n", 366 | " 'has_stacktrace': False}" 367 | ] 368 | }, 369 | "execution_count": 21, 370 | "metadata": {}, 371 | "output_type": "execute_result" 372 | } 373 | ], 374 | "source": [ 375 | "api_response[\"messages\"]" 376 | ] 377 | }, 378 | { 379 | "cell_type": "code", 380 | "execution_count": 22, 381 | "metadata": {}, 382 | "outputs": [ 383 | { 384 | "name": "stdout", 385 | "output_type": "stream", 386 | "text": [ 387 | "Financial\n", 388 | "ElectricTariff\n", 389 | "ElectricUtility\n", 390 | "ElectricLoad\n", 391 | "Site\n", 392 | "PV\n" 393 | ] 394 | } 395 | ], 396 | "source": [ 397 | "for k in api_response[\"outputs\"].keys():\n", 398 | " print(k)" 399 | ] 400 | }, 401 | { 402 | "cell_type": "markdown", 403 | "metadata": {}, 404 | "source": [ 405 | "## Save API response into a JSON file for later use" 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": 23, 411 | "metadata": {}, 412 | "outputs": [], 413 | "source": [ 414 | "response_save = api_response\n", 415 | "file_name_to_save = \"response_1\"\n", 416 | "with open(os.path.join(outputs_path, file_name_to_save + \".json\"), 'w') as fp:\n", 417 | " json.dump(response_save, fp)" 418 | ] 419 | }, 420 | { 421 | "cell_type": "code", 422 | "execution_count": null, 423 | "metadata": {}, 424 | "outputs": [], 425 | "source": [] 426 | } 427 | ], 428 | "metadata": { 429 | "@webio": { 430 | "lastCommId": null, 431 | "lastKernelId": null 432 | }, 433 | "kernelspec": { 434 | "display_name": "Python 3", 435 | "language": "python", 436 | "name": "python3" 437 | }, 438 | "language_info": { 439 | "codemirror_mode": { 440 | "name": "ipython", 441 | "version": 3 442 | }, 443 | "file_extension": ".py", 444 | "mimetype": "text/x-python", 445 | "name": "python", 446 | "nbconvert_exporter": "python", 447 | "pygments_lexer": "ipython3", 448 | "version": "3.8.8" 449 | }, 450 | "latex_envs": { 451 | "LaTeX_envs_menu_present": true, 452 | "autoclose": false, 453 | "autocomplete": true, 454 | "bibliofile": "biblio.bib", 455 | "cite_by": "apalike", 456 | "current_citInitial": 1, 457 | "eqLabelWithNumbers": true, 458 | "eqNumInitial": 1, 459 | "hotkeys": { 460 | "equation": "Ctrl-E", 461 | "itemize": "Ctrl-I" 462 | }, 463 | "labels_anchors": false, 464 | "latex_user_defs": false, 465 | "report_style_numbering": false, 466 | "user_envs_cfg": false 467 | } 468 | }, 469 | "nbformat": 4, 470 | "nbformat_minor": 4 471 | } 472 | -------------------------------------------------------------------------------- /REopt API Scripts/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/REopt-Analysis-Scripts/50dc58078a021ff69f46dfb12ce613e35e1f972b/REopt API Scripts/src/__init__.py -------------------------------------------------------------------------------- /REopt API Scripts/src/create_input_template_with_values_from_help_endpoint.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import pandas as pd 4 | from src.logger import log 5 | from collections import OrderedDict 6 | API_KEY = 'DEMO_KEY' 7 | api_url = 'https://developer.nrel.gov/api/reopt/stable' 8 | 9 | 10 | # these keys are excluded from the input template because their values are arrays that can't be in the DataFrame/CSV 11 | array_inputs_to_exclude = [ 12 | "FuelTariff|boiler_fuel_blended_monthly_rates_us_dollars_per_mmbtu", 13 | "FuelTariff|chp_fuel_blended_monthly_rates_us_dollars_per_mmbtu", 14 | "Site|outdoor_air_temp_degF", 15 | ] 16 | # TODO figure out a work-around for including these values in all_api_inputs.csv that is compatible with multi_site 17 | # analysis 18 | 19 | 20 | def set_default(nested_dict, flat_dict, k, piped_key): 21 | """ 22 | Try to set a default value from nested_input_definitions. If there is no default value, set to None 23 | :param d: dictionary 24 | :param k: key 25 | :return: None 26 | """ 27 | if piped_key.startswith("Scenario"): 28 | piped_key = piped_key.lstrip("Scenario|") 29 | try: 30 | if k not in ['tilt']: 31 | flat_dict[piped_key] = nested_dict[k]['default'] 32 | else: # tilt default is "Site latitude" and is set to such in the api 33 | flat_dict[piped_key] = None 34 | 35 | except KeyError as e: 36 | if 'default' in e.args: 37 | log.debug("No default value exists for {}. Set value to None".format(k)) 38 | flat_dict[piped_key] = None 39 | else: 40 | raise e 41 | 42 | 43 | def flatten_nested_dict(nested_dict, flat_dict=None, obj=None): 44 | """ 45 | recursive function for converting a nested dictionary into a flat one using the piped key convention from 46 | multi_site_inputs_parser.py 47 | :param nested_dict: 48 | :param flat_dict: 49 | :param obj: 50 | :return: 51 | """ 52 | if flat_dict is None: # for initial call to flatten_nested_dict 53 | flat_dict = OrderedDict() 54 | 55 | for k, v in list(sorted(nested_dict.items())): 56 | 57 | piped_key = None 58 | if isinstance(obj, str): 59 | piped_key = obj + '|' + k 60 | 61 | if k[0].islower() and isinstance(v, dict): 62 | 63 | if piped_key is not None: 64 | set_default(nested_dict, flat_dict, k, piped_key) 65 | 66 | if k in nested_dict.keys(): # k could be deleted in set_default if there is no default value 67 | if isinstance(nested_dict[k], dict): 68 | if any([isinstance(i, dict) for i in nested_dict[k].values()]): # nested dict with definitions 69 | flatten_nested_dict(nested_dict[k], flat_dict, obj=str(k)) # dig deeper into nested_dict 70 | return flat_dict 71 | 72 | 73 | def create_input_template_with_values_from_help_endpoint(api_url, API_KEY): 74 | input_definitions = json.loads(requests.get(api_url + '/help?API_KEY=' + API_KEY).content) 75 | flat_dict = flatten_nested_dict(input_definitions) 76 | # fill in enough values to run an example 77 | flat_dict["site_number"] = 1 78 | flat_dict["description"] = "test site" 79 | flat_dict["Site|latitude"] = 34 80 | flat_dict["Site|longitude"] = -118 81 | flat_dict["ElectricTariff|urdb_label"] = "5a3821035457a32645d2dd80" 82 | flat_dict["LoadProfile|doe_reference_name"] = "LargeOffice" 83 | flat_dict["LoadProfile|annual_kwh"] = 1000000 84 | flat_dict.move_to_end("site_number", last=False) 85 | for k in array_inputs_to_exclude: 86 | flat_dict.pop(k, None) 87 | df = pd.DataFrame(flat_dict, index=[1]) 88 | df.to_csv("all_api_inputs.csv", index=False) 89 | 90 | 91 | if __name__ == "__main__": 92 | create_input_template_with_values_from_help_endpoint(api_url, API_KEY) 93 | -------------------------------------------------------------------------------- /REopt API Scripts/src/logger.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom logging set up, with handlers for writing to .log file and console. 3 | 4 | The _handler.setLevel determines the logging level to write to file or console. 5 | Logging levels are: 6 | 7 | Level Numeric value 8 | CRITICAL 50 9 | ERROR 40 10 | WARNING 30 11 | INFO 20 12 | DEBUG 10 13 | NOTSET 0 14 | """ 15 | import logging 16 | 17 | log = logging.getLogger('main') 18 | log.setLevel(logging.DEBUG) 19 | 20 | file_handler = logging.FileHandler(filename='main.log', mode='w') 21 | file_formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s') 22 | file_handler.setFormatter(file_formatter) 23 | file_handler.setLevel(logging.INFO) 24 | 25 | console_handler = logging.StreamHandler() 26 | console_formatter = logging.Formatter('%(name)-12s %(levelname)-8s %(message)s') 27 | console_handler.setFormatter(console_formatter) 28 | console_handler.setLevel(logging.INFO) 29 | 30 | log.addHandler(file_handler) 31 | log.addHandler(console_handler) 32 | -------------------------------------------------------------------------------- /REopt API Scripts/src/multi_site_inputs_parser.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import os 4 | import requests 5 | import json 6 | import copy 7 | from src.logger import log 8 | 9 | 10 | def set_default(d, k): 11 | """ 12 | Try to set a default value from nested_input_definitions. If there is no default value, delete the key (k) 13 | :param d: dictionary 14 | :param k: key 15 | :return: None 16 | """ 17 | try: 18 | if k not in ['tilt']: 19 | d[k] = d[k]['default'] 20 | else: # tilt default is "Site latitude" and is set to such in the api 21 | del d[k] 22 | 23 | except KeyError as e: 24 | if 'default' in e.args: 25 | log.debug("No default value exists for {}.".format(k)) 26 | del d[k] 27 | else: 28 | raise e 29 | 30 | 31 | def make_nested_dict(flat_dict, nested_dict, obj=None): 32 | """ 33 | Use flat_dict, with key:value pairs from csv, and create a dict for posting to reopt api 34 | :param flat_dict: key:value pairs for one site 35 | :param nested_dict: nested_input_definitions from help endpoint 36 | :param obj: upper case key in nested_dict, represents reopt api class and used for '|' parameters from input csv 37 | :return: 38 | """ 39 | 40 | for k, v in list(nested_dict.items()): 41 | 42 | piped_key = None 43 | if isinstance(obj, str): 44 | piped_key = obj + '|' + k 45 | 46 | if k[0].islower() and isinstance(v, dict): # then this key represents an input value 47 | 48 | if k in flat_dict.keys(): 49 | 50 | if pd.isnull(flat_dict[k]): # empty cell in input csv file 51 | set_default(nested_dict, k) 52 | else: # use value from input csv 53 | input_val = flat_dict[k] 54 | # work-around for pd.df.to_dict() converting to numpy types, yargh. 55 | if type(input_val) is np.int64: 56 | input_val = int(input_val) 57 | if isinstance(input_val, np.bool_): 58 | input_val = bool(input_val) 59 | nested_dict[k] = input_val 60 | 61 | elif piped_key in flat_dict.keys() and piped_key is not None: # eg. PV|max_kw NOTE: NO SPACES IN piped_key 62 | 63 | if pd.isnull(flat_dict[piped_key]): # empty cell in input csv file 64 | set_default(nested_dict, k) 65 | else: # use value from input csv 66 | input_val = flat_dict[piped_key] 67 | # work-around for pd.df.to_dict() converting to numpy types, yargh. 68 | if type(input_val) is np.int64: 69 | input_val = int(input_val) 70 | if isinstance(input_val, np.bool_): 71 | input_val = bool(input_val) 72 | nested_dict[k] = input_val 73 | 74 | else: # no column in input csv 75 | set_default(nested_dict, k) 76 | 77 | if k in nested_dict.keys(): # k could be deleted in set_default if there is no default value 78 | if isinstance(nested_dict[k], dict): 79 | if any([isinstance(i, dict) for i in nested_dict[k].values()]): # nested dict with definitions 80 | make_nested_dict(flat_dict, nested_dict[k], obj=str(k)) # dig deeper into nested_dict 81 | 82 | return nested_dict 83 | 84 | 85 | def add_load_profile_inputs(flat_dict, nested_dict, path_to_load_files="../inputs/load_profiles"): 86 | """ 87 | If flat_dict has a "load_file" key (i.e. same column in csv file), 88 | then a custom load profile is added to the inputs (which is used by API even if other optional inputs are filled 89 | in, such as doe_reference_name and annual_kwh) 90 | :param flat_dict: inputs from site(s) data csv file 91 | :param nested_dict: nested_dict that has already passed through make_nested_dict (filled in single value inputs) 92 | :return: None 93 | """ 94 | if "load_file" in flat_dict.keys(): 95 | if not pd.isnull(flat_dict["load_file"]): # case for some sites having custom load profiles 96 | fp = os.path.join(path_to_load_files, flat_dict["load_file"]) 97 | load_profile = pd.read_csv(fp, header=None, squeeze=True).tolist() 98 | load_profile = [float(v) for v in load_profile] # numpy floats are not JSON serializable 99 | assert len(load_profile) in [8760, 17520, 35040] 100 | nested_dict['Scenario']['Site']['LoadProfile']['loads_kw'] = load_profile 101 | else: 102 | log.info("Using built-in profile for Site number {}.".format(flat_dict['site_number'])) 103 | 104 | 105 | def multi_site_csv_parser(path_to_csv, api_url, API_KEY, n_sites=None): 106 | """ 107 | Script to read a multi-sites input csv file and parse it into dictionaries for passing to the API. 108 | :param path_to_csv: path to csv file containing rows for each site and column headers. 109 | :param n_sites: default=None. If integer value is passed then only that many sites will be processed. 110 | :return: list of dictionaries, with length equal to n_sites, which can be posted to API. Additional keys may be 111 | added for creating output summary csv. 112 | TODO: [ ] pass start row and end row for running scenarios. 113 | """ 114 | 115 | df = pd.read_csv(path_to_csv) 116 | 117 | if isinstance(n_sites, int): 118 | df = df.iloc[:n_sites] 119 | 120 | input_definitions = json.loads(requests.get(api_url + '/help?API_KEY=' + API_KEY).content) 121 | if "error" in input_definitions.keys(): 122 | raise BlockingIOError(input_definitions["error"]) 123 | 124 | posts = [] 125 | for i in range(len(df)): 126 | nested_dict = copy.deepcopy(input_definitions) 127 | 128 | site_inputs = df.iloc[i].to_dict() 129 | posts.append(make_nested_dict(site_inputs, nested_dict)) 130 | 131 | path_to_load_files = "./load_profiles" 132 | add_load_profile_inputs(site_inputs, posts[-1], path_to_load_files=path_to_load_files) 133 | 134 | path_to_rate_files = "./electric_rates" 135 | if "urdb_json_file" in df.columns: 136 | if pd.isnull(df.loc[i,"urdb_json_file"]) == False: 137 | rate_i = json.load(open(os.path.join(path_to_rate_files, df["urdb_json_file"][i]), "r")) 138 | posts[i]["Scenario"]["Site"]["ElectricTariff"]["urdb_response"] = rate_i 139 | else: 140 | pass 141 | else: 142 | pass 143 | 144 | posts[i]['Scenario']['Site']['Wind'] = {'max_kw': 0} # hack for Wind not in help endpoint 145 | 146 | return posts 147 | 148 | 149 | test = False 150 | if test: 151 | list_of_posts = multi_site_csv_parser( 152 | './test_inputs/test_scenarios.csv', 153 | api_url='https://developer.nrel.gov/api/reopt/stable', 154 | API_KEY="DEMO_KEY" 155 | ) 156 | -------------------------------------------------------------------------------- /REopt API Scripts/src/parse_api_responses_to_csv.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import pandas as pd 3 | from collections import OrderedDict 4 | 5 | 6 | def get_nested_output(key, resp, obj=None): 7 | 8 | if '|' in key: 9 | obj = key[:key.index('|')] 10 | val = key[key.index('|') + 1:] 11 | 12 | return resp['outputs']['Scenario']['Site'][obj][val] 13 | 14 | else: 15 | site = {k: v for k, v in resp['outputs']['Scenario']['Site'].items() if k[0].isupper()} 16 | # Site now has lower case keys that must be removed 17 | for d in site.values(): 18 | 19 | if key in d.keys(): 20 | return d[key] 21 | 22 | return None 23 | 24 | 25 | def parse_responses_to_csv_with_template(csv_template, responses, output_csv, input_csv=None, n_custom_columns=0): 26 | """ 27 | 28 | :param csv_template: path to csv file with headers for output fields, and rows for input scenarios 29 | :param responses: list of dicts, each dict is API response for input scenario (in same order is input csv) 30 | :param output_csv: str, path to output csv that parser writes summary to. 31 | :param input_csv: str, path to input csv to copy custom columns from 32 | :param n_custom_columns: number of custom columns to copy from input scenarios csv to output_csv 33 | (columns that are not modified by script) 34 | :return: None (writes results to csv). 35 | """ 36 | 37 | results = OrderedDict() 38 | 39 | # open template and initialize results dict with custom cols filled in and black output columns 40 | with open(csv_template, 'r') as f: 41 | reader = csv.reader(f) 42 | outputs = next(reader) # names of parameters to pull out of responses and put in output_csv 43 | 44 | if input_csv is not None: 45 | # copy and paste custom columns' values 46 | 47 | with open(input_csv, 'r') as f: 48 | reader = csv.reader(f) 49 | hdr = next(reader) 50 | for i in range(n_custom_columns): 51 | results[hdr[i]] = [] 52 | 53 | for row in reader: 54 | for i in range(n_custom_columns): 55 | results[hdr[i]].append(row[i]) 56 | 57 | for output in outputs: 58 | results[output] = [] 59 | 60 | for i, resp in enumerate(responses): 61 | 62 | for output in outputs: 63 | results[output].append(get_nested_output(output, resp)) 64 | 65 | df = pd.DataFrame.from_dict(results) 66 | df.index = df.iloc[:, 0] 67 | df.drop(df.columns[0], axis=1, inplace=True) 68 | df.to_csv(output_csv) 69 | 70 | -------------------------------------------------------------------------------- /REopt API Scripts/src/parse_api_responses_to_excel.py: -------------------------------------------------------------------------------- 1 | import openpyxl as xl 2 | 3 | 4 | def dict_to_worksheet(d, wb, row_idx, description, name): 5 | """ 6 | Write dictionary of scalar results to worksheet. Creates the worksheet using `name` as the name if it does not 7 | exist. 8 | :param d: dictionary from API JSON response 9 | :param wb: openpyxl excel workbook 10 | :param row_idx: equivalent to scenario index; the row in the worksheet to write results 11 | :param description: scenario description; used as the left-most entry in the row 12 | :param name: worksheet name 13 | :return: nested dictionary with time-series values for each API "object"; 14 | eg. {'PV': 15 | {'year_one_power_production_series_kw : [ 0.0, ..., 0.0] } 16 | } 17 | """ 18 | time_series_dict = dict() # for collecting lists 19 | if row_idx == 0: 20 | ws = wb.create_sheet(name) 21 | else: 22 | ws = wb[name] 23 | 24 | keys = [k for k in d.keys() if k.islower()] 25 | ws.cell(row=row_idx + 2, column=1, value=description) 26 | 27 | col_idx = 0 28 | for hdr in sorted(keys): 29 | if isinstance(d[hdr], list): 30 | time_series_dict[hdr] = d[hdr] 31 | continue 32 | if isinstance(d[hdr], dict) or 'series' in hdr.lower(): 33 | continue 34 | if row_idx == 0: # write header 35 | ws.cell(row=row_idx + 1, column=col_idx + 2, value=hdr) 36 | val = d[hdr] 37 | ws.cell(row=row_idx + 2, column=col_idx + 2, value=val) 38 | col_idx += 1 39 | 40 | return time_series_dict 41 | 42 | 43 | def time_series_to_worksheet(d, wb, description): 44 | """ 45 | Writes out all time series values from `d` to a worksheet in `wb` with the name: description + '_time_series' 46 | :param d: nested dictionary of time series (from dict_to_worksheet) 47 | :param wb: openpyxl excel workbook 48 | :param description: scenario description; used to name worksheet: description + '_time_series' 49 | :return: None 50 | """ 51 | ws = wb.create_sheet(description + '_time_series') 52 | col_idx = 0 53 | for K in [Key for Key in d.keys() if len(d[Key].keys()) > 0]: 54 | for k in [key for key in d[K].keys() if len(d[K][key]) > 0]: 55 | ws.cell(row=1, column=col_idx + 1, value=K + '|' + k) 56 | for j, val in enumerate(d[K][k]): 57 | ws.cell(row=2 + j, column=col_idx + 1, value=val) 58 | col_idx += 1 59 | 60 | 61 | def parse_api_responses_to_excel(responses, template="template.xlsx", spreadsheet='results_summary.xlsx'): 62 | """ 63 | Writes all inputs, outputs, and dispatches to separate worksheets in an Excel workbook. 64 | REopt object inputs and outputs are on separate sheets (eg. 'PV_inputs' and 'PV_outputs') with each row 65 | corresponding to a scenario. 66 | Dispatch time-series for each scenario are written to one worksheet per scenario. 67 | :param responses: list of dictionaries - one dict = one api response 68 | :param template: str, path/to/template.xlsx which is the template file for the spreadsheet 69 | :param spreadsheet: str, path/to/excel_spreadsheet.xlsx to write results summary 70 | :return: None 71 | """ 72 | wb = xl.load_workbook(template) 73 | ws_sites = wb.active 74 | 75 | for row_idx, resp in enumerate(responses): 76 | time_series_dict = dict() 77 | inputs_dict = resp['inputs']['Scenario'] 78 | outputs_dict = resp['outputs']['Scenario'] 79 | description = inputs_dict['description'] 80 | ws_sites.cell(row=row_idx + 2, column=1, value=description) 81 | scenario_inputs = [k for k in inputs_dict.keys() if k.islower()] 82 | site_inputs = [k for k in inputs_dict['Site'].keys() if k.islower()] 83 | 84 | for col_idx, hdr in enumerate(sorted(scenario_inputs) + sorted(site_inputs)): 85 | if hdr == 'description': 86 | continue 87 | if row_idx == 0: # write header 88 | ws_sites.cell(row=row_idx + 1, column=col_idx + 1, value=hdr) 89 | try: 90 | val = inputs_dict[hdr] 91 | except KeyError: 92 | val = inputs_dict['Site'][hdr] 93 | ws_sites.cell(row=row_idx + 2, column=col_idx + 1, value=val) 94 | 95 | err_msg = resp['messages'].get('error') 96 | if row_idx == 0: # write header 97 | ws_sites.cell(row=row_idx + 1, column=col_idx + 2, value='error_message') 98 | ws_sites.cell(row=row_idx + 2, column=col_idx + 2, value=err_msg) 99 | 100 | for Key in [K for K in inputs_dict['Site'].keys() if K[0].isupper()]: 101 | time_series_dict[Key] = dict_to_worksheet(inputs_dict['Site'][Key], wb, row_idx, description, Key + '_inputs') 102 | 103 | for Key in [K for K in outputs_dict['Site'].keys() if K[0].isupper()]: 104 | time_series_dict[Key] = dict_to_worksheet(outputs_dict['Site'][Key], wb, row_idx, description, Key + '_outputs') 105 | 106 | time_series_to_worksheet(time_series_dict, wb, description) 107 | wb.save(spreadsheet) 108 | 109 | # TODO address "UserWarning: Title is more than 31 characters. Some applications may not be able to read the file" -------------------------------------------------------------------------------- /REopt API Scripts/src/post_and_poll.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from src.logger import log 4 | from src.results_poller import poller, poller_v3 5 | 6 | 7 | def get_api_results(post, API_KEY, api_url, results_file='results.json', run_id=None): 8 | """ 9 | Function for posting job and polling results end-point 10 | :param post: 11 | :param results_file: 12 | :param API_KEY: 13 | :param api_url: 14 | :return: results dictionary / API response 15 | """ 16 | 17 | if run_id is None: 18 | run_id = get_run_uuid(post, API_KEY=API_KEY, api_url=api_url) 19 | 20 | if run_id is not None: 21 | 22 | results_url = api_url + '/job//results/?api_key=' + API_KEY 23 | if "stable" in results_url or "v3" in results_url: 24 | results = poller_v3(url=results_url.replace('', run_id)) 25 | else: # for v1 and v2 26 | results = poller(url=results_url.replace('', run_id)) 27 | 28 | with open(results_file, 'w') as fp: 29 | json.dump(obj=results, fp=fp) 30 | 31 | log.info("Saved results to {}".format(results_file)) 32 | else: 33 | results = None 34 | log.error("Unable to get results: no run_uuid from POST.") 35 | 36 | return results 37 | 38 | 39 | def get_run_uuid(post, API_KEY, api_url): 40 | """ 41 | Function for posting job 42 | :param post: 43 | :param API_KEY: 44 | :param api_url: 45 | :return: job run_uuid 46 | """ 47 | post_url = api_url + '/job/?api_key=' + API_KEY 48 | resp = requests.post(post_url, json=post) 49 | run_id = None 50 | if not resp.ok: 51 | log.error("Status code {}. {}".format(resp.status_code, resp.content)) 52 | else: 53 | log.info("Response OK from {}.".format(post_url)) 54 | 55 | run_id_dict = json.loads(resp.text) 56 | 57 | try: 58 | run_id = run_id_dict['run_uuid'] 59 | except KeyError: 60 | msg = "Response from {} did not contain run_uuid.".format(post_url) 61 | log.error(msg) 62 | 63 | return run_id 64 | 65 | 66 | if __name__ == '__main__': 67 | """ 68 | In case just need to re-save json response 69 | """ 70 | run_uuid = "my_run_id" 71 | file_name = "my_results.json" 72 | api_url = 'https://developer.nrel.gov/api/reopt/stable' 73 | API_KEY = "DEMO_KEY" 74 | 75 | results = get_api_results(post={}, API_KEY=API_KEY, run_id=run_uuid, results_file=file_name, api_url=api_url) 76 | -------------------------------------------------------------------------------- /REopt API Scripts/src/results_poller.py: -------------------------------------------------------------------------------- 1 | """ 2 | function for polling reopt api results url 3 | """ 4 | import requests 5 | import json 6 | import time 7 | from src.logger import log 8 | 9 | 10 | def poller(url, poll_interval=5): 11 | """ 12 | Function for polling the REopt API results URL until status is not "Optimizing..." 13 | :param url: results url to poll 14 | :param poll_interval: seconds 15 | :return: dictionary response (once status is not "Optimizing...") 16 | """ 17 | 18 | key_error_count = 0 19 | key_error_threshold = 3 20 | status = "Optimizing..." 21 | log.info("Polling {} for results with interval of {}s...".format(url, poll_interval)) 22 | while True: 23 | 24 | resp = requests.get(url=url, verify=False) 25 | resp_dict = json.loads(resp.content) 26 | 27 | try: 28 | status = resp_dict['outputs']['Scenario']['status'] 29 | except KeyError: 30 | key_error_count += 1 31 | log.info('KeyError count: {}'.format(key_error_count)) 32 | if key_error_count > key_error_threshold: 33 | log.info('Breaking polling loop due to KeyError count threshold of {} exceeded.' 34 | .format(key_error_threshold)) 35 | break 36 | 37 | if status != "Optimizing...": 38 | break 39 | else: 40 | time.sleep(poll_interval) 41 | 42 | return resp_dict 43 | 44 | 45 | def poller_v3(url, poll_interval=5): 46 | """ 47 | Function for polling the REopt API results URL until status is not "Optimizing..." 48 | :param url: results url to poll 49 | :param poll_interval: seconds 50 | :return: dictionary response (once status is not "Optimizing...") 51 | """ 52 | 53 | key_error_count = 0 54 | key_error_threshold = 3 55 | status = "Optimizing..." 56 | log.info("Polling {} for results with interval of {}s...".format(url, poll_interval)) 57 | while True: 58 | 59 | resp = requests.get(url=url, verify=False) 60 | resp_dict = json.loads(resp.content) 61 | 62 | try: 63 | status = resp_dict['status'] 64 | except KeyError: 65 | key_error_count += 1 66 | log.info('KeyError count: {}'.format(key_error_count)) 67 | if key_error_count > key_error_threshold: 68 | log.info('Breaking polling loop due to KeyError count threshold of {} exceeded.' 69 | .format(key_error_threshold)) 70 | break 71 | 72 | if status != "Optimizing...": 73 | break 74 | else: 75 | time.sleep(poll_interval) 76 | 77 | return resp_dict 78 | 79 | -------------------------------------------------------------------------------- /REopt API Scripts/src/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/REopt-Analysis-Scripts/50dc58078a021ff69f46dfb12ce613e35e1f972b/REopt API Scripts/src/template.xlsx -------------------------------------------------------------------------------- /REopt Julia Package Scripts/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76" 3 | HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" 4 | JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" 5 | JuMP = "4076af6c-e467-56ae-b986-b466b2749572" 6 | REopt = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" 7 | -------------------------------------------------------------------------------- /REopt Julia Package Scripts/scenarios/pv_retail.json: -------------------------------------------------------------------------------- 1 | { 2 | "Site": { 3 | "longitude": -118.1164613, 4 | "latitude": 34.5794343 5 | }, 6 | "PV": { 7 | }, 8 | "ElectricLoad": { 9 | "doe_reference_name": "RetailStore", 10 | "annual_kwh": 200000.0, 11 | "year": 2017 12 | }, 13 | "ElectricTariff": { 14 | "urdb_label": "5ed6c1a15457a3367add15ae" 15 | }, 16 | "ElectricUtility": { 17 | "net_metering_limit_kw": 1000 18 | }, 19 | "Financial": { 20 | "elec_cost_escalation_rate_fraction": 0.026, 21 | "offtaker_discount_rate_fraction": 0.08, 22 | "offtaker_tax_rate_fraction": 0.28, 23 | "om_cost_escalation_rate_fraction": 0.025 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /REopt Julia Package Scripts/scenarios/wind_battery_hospital.json: -------------------------------------------------------------------------------- 1 | { 2 | "Site": { 3 | "longitude": -95.94535, 4 | "latitude": 38.9228 5 | }, 6 | "Wind": { 7 | "max_kw": 1500 8 | }, 9 | "ElectricStorage":{ 10 | }, 11 | "ElectricLoad": { 12 | "doe_reference_name": "Hospital", 13 | "annual_kwh": 1000000.0, 14 | "year": 2017 15 | }, 16 | "ElectricTariff": { 17 | "blended_annual_energy_rate": 0.12, 18 | "blended_annual_demand_rate": 15 19 | }, 20 | "Financial": { 21 | "elec_cost_escalation_rate_fraction": 0.026, 22 | "offtaker_discount_rate_fraction": 0.08, 23 | "offtaker_tax_rate_fraction": 0.28, 24 | "om_cost_escalation_rate_fraction": 0.025 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /REopt Julia Package Scripts/simple_examples.jl: -------------------------------------------------------------------------------- 1 | """ 2 | The REopt Julia package optimizes distributed energy resources to minimize the lifecycle cost of energy at a site. 3 | This file provides an example of how to use REopt.jl. 4 | See the package documentation for more information: https://nrel.github.io/REopt.jl/stable/ 5 | You will need Julia installed to run this. 6 | 7 | ### Follow these steps for this example ### 8 | Step 1: Open a Julia terminal and navigate to this subfolder 9 | julia> cd("path/to/REopt Julia Package Scripts") 10 | can check working directory with: julia> pwd() 11 | Step 2: Activate and instantiate the environment in this directory (hit the "]" key to enter the package manager) 12 | pkg> activate . 13 | (REopt Julia Package Scripts) pkg> instantiate 14 | (You can view all of the packages in this environment with pkg > st) 15 | Step 3: Run this file (leave the package manager with the backspace button) 16 | julia> include("simple_examples.jl") 17 | """ 18 | 19 | # Replace with your API Key 20 | ENV["NREL_DEVELOPER_API_KEY"] = "DEMO_KEY" 21 | 22 | ## Example 1: Run a single model with no business-as-usual (BAU) case with Cbc solver: ## 23 | using REopt 24 | using Cbc # Cbc is a free solver. It may be slow with models that involve binary variables. Replace with your own solver (e.g., Xpress) if desired. 25 | using JSON 26 | using JuMP 27 | # See documentation for also adding GhpGhx 28 | 29 | # Ensure latest REopt version is used 30 | using Pkg 31 | Pkg.update("REopt") 32 | 33 | println("Running a single REopt model with no BAU.") 34 | 35 | # Setup inputs 36 | data_file = "pv_retail.json" # Notice that just the PV key with an empty dictionary is provided to tell REopt to consider PV 37 | data = JSON.parsefile("scenarios/$data_file") 38 | data["Financial"]["analysis_years"] = 20 # Example modifying the scenario 39 | 40 | # Define model 41 | m = Model(Cbc.Optimizer) # Another example, using Xpress: m = Model(optimizer_with_attributes(Xpress.Optimizer, "MIPRELSTOP" => 0.01, "OUTPUTLOG" => 0)) 42 | 43 | # Run REopt 44 | results = run_reopt(m, data) 45 | 46 | # Print some results 47 | println("PV [kW]: ", results["PV"]["size_kw"]) 48 | println("Lifecycle cost [\$]: ", results["Financial"]["lcc"]) 49 | 50 | # Save results json 51 | write("./results/$data_file", JSON.json(results)) 52 | 53 | 54 | ## Example 2: Run a model with a BAU case using HiGHS Solver: ## 55 | using HiGHS # HiGHS is a free solver 56 | 57 | println("Running a REopt model with a BAU.") 58 | 59 | # Setup inputs 60 | data_file = "wind_battery_hospital.json" 61 | data = JSON.parsefile("scenarios/$data_file") 62 | 63 | # Define models 64 | m1 = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) 65 | m2 = Model(optimizer_with_attributes(HiGHS.Optimizer, "output_flag" => false, "log_to_console" => false)) 66 | 67 | # Run REopt 68 | results = run_reopt([m1,m2], data) # Must supply two models to run the BAU (BAU scenario automatically generated based on inputs) 69 | 70 | # Print some results 71 | println("Wind [kW]: ", results["Wind"]["size_kw"]) 72 | println("Battery [kW]: ", results["ElectricStorage"]["size_kw"]) 73 | println("Battery [hours]: ", results["ElectricStorage"]["size_kwh"] / results["ElectricStorage"]["size_kw"]) 74 | println("Lifecycle cost [\$]: ", results["Financial"]["lcc"]) 75 | println("NPV [\$]: ", results["Financial"]["npv"]) # Now you can get a NPV 76 | 77 | # Save results json 78 | write("./results/$data_file", JSON.json(results)) 79 | 80 | 81 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | notebook: 4 | build: 5 | context: . 6 | volumes: 7 | - .:/home/jovyan/work 8 | ports: 9 | - 8888:8888 10 | container_name: datascience-notebook-container -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.10.8 2 | charset-normalizer==2.0.7 3 | et-xmlfile==1.1.0 4 | idna==3.3 5 | numpy==1.22.0 6 | openpyxl==3.0.9 7 | pandas==1.3.4 8 | python-dateutil==2.8.2 9 | pytz==2021.3 10 | requests==2.26.0 11 | six==1.16.0 12 | urllib3==1.26.7 13 | -------------------------------------------------------------------------------- /v2_v3_outputs_map.csv: -------------------------------------------------------------------------------- 1 | v2_key,v2_name,v3_key,v3_name 2 | Generator,average_yearly_energy_exported_kwh,---,(Removed in v3) 3 | LoadProfile,bau_sustained_time_steps,---,(Removed in v3) 4 | LoadProfile,resilience_check_flag,---,(Removed in v3) 5 | LoadProfile,sr_required_series_kw,---,(Removed in v3) 6 | LoadProfile,sustain_hours,---,(Removed in v3) 7 | LoadProfile,total_sr_provided,---,(Removed in v3) 8 | Wind,year_one_energy_produced_kwh,---,(Removed in v3) 9 | Profile,parse_run_outputs_seconds,---,(Removed in v3) 10 | Profile,pre_setup_scenario_seconds,---,(Removed in v3) 11 | Profile,reopt_bau_seconds,(not yet in v3), 12 | Profile,reopt_seconds,(not yet in v3), 13 | Profile,setup_scenario_seconds,---,(Removed in v3) 14 | Scenario,lower_bound,(not yet in v3), 15 | Scenario,optimality_gap,(not yet in v3), 16 | AbsorptionChiller,year_one_absorp_chl_electric_consumption_kwh,AbsorptionChiller,annual_electric_consumption_kwh 17 | AbsorptionChiller,year_one_absorp_chl_thermal_consumption_mmbtu,AbsorptionChiller,annual_thermal_consumption_mmbtu 18 | AbsorptionChiller,year_one_absorp_chl_thermal_production_tonhr,AbsorptionChiller,annual_thermal_production_tonhour 19 | AbsorptionChiller,year_one_absorp_chl_electric_consumption_series_kw,AbsorptionChiller,electric_consumption_series_kw 20 | ---,(Added in v3),AbsorptionChiller,size_kw 21 | AbsorptionChiller,size_ton,AbsorptionChiller,size_ton 22 | AbsorptionChiller,year_one_absorp_chl_thermal_consumption_series_mmbtu_per_hr,AbsorptionChiller,thermal_consumption_series_mmbtu_per_hour 23 | AbsorptionChiller,year_one_absorp_chl_thermal_to_load_series_ton,AbsorptionChiller,thermal_to_load_series_ton 24 | AbsorptionChiller,year_one_absorp_chl_thermal_to_tes_series_ton,AbsorptionChiller,thermal_to_storage_series_ton 25 | Scenario,status,APIMeta (no key),status 26 | Scenario,api_version,APIMeta (no key),api_version 27 | Scenario,run_uuid,APIMeta (no key),run_uuid 28 | NewBoiler,year_one_boiler_fuel_consumption_mmbtu,Boiler,annual_fuel_consumption_mmbtu 29 | NewBoiler,year_one_boiler_thermal_production_mmbtu,Boiler,annual_thermal_production_mmbtu 30 | NewBoiler,year_one_boiler_fuel_consumption_series_mmbtu_per_hr,Boiler,fuel_consumption_series_mmbtu_per_hour 31 | FuelTariff,total_newboiler_fuel_cost_us_dollars,Boiler,lifecycle_fuel_cost_after_tax 32 | ,,Boiler,lifecycle_fuel_cost_after_tax_bau 33 | ---,(Added in v3),Boiler (not in v3 master),lifecycle_per_unit_prod_om_costs 34 | NewBoiler,size_mmbtu_per_hr,Boiler (not in v3 master),size_mmbtu_per_hour 35 | NewBoiler,year_one_boiler_thermal_production_series_mmbtu_per_hr,Boiler (not in v3 master),thermal_production_series_mmbtu_per_hour 36 | NewBoiler,year_one_thermal_to_load_series_mmbtu_per_hour,Boiler (not in v3 master),thermal_to_load_series_mmbtu_per_hour 37 | NewBoiler,year_one_thermal_to_steamturbine_series_mmbtu_per_hour,Boiler (not in v3 master),thermal_to_steamturbine_series_mmbtu_per_hour 38 | NewBoiler,year_one_thermal_to_tes_series_mmbtu_per_hour,Boiler (not in v3 master),thermal_to_storage_series_mmbtu_per_hour 39 | FuelTariff,year_one_newboiler_fuel_cost_us_dollars,Boiler,year_one_fuel_cost_before_tax 40 | ,,Boiler,thermal_production_series_mmbtu_per_hour 41 | CHP,year_one_electric_energy_produced_kwh,CHP,annual_electric_production_kwh 42 | CHP,year_one_fuel_used_mmbtu,CHP,annual_fuel_consumption_mmbtu 43 | CHP,year_one_thermal_energy_produced_mmbtu,CHP,annual_thermal_production_mmbtu 44 | CHP,year_one_electric_production_series_kw,CHP,electric_production_series_kw 45 | CHP,year_one_to_grid_series_kw,CHP,electric_to_grid_series_kw 46 | CHP,year_one_to_load_series_kw,CHP,electric_to_load_series_kw 47 | CHP,year_one_to_battery_series_kw,CHP,electric_to_storage_series_kw 48 | FuelTariff,total_chp_fuel_cost_us_dollars,CHP,lifecycle_fuel_cost_after_tax 49 | ElectricTariff,total_chp_standby_cost_us_dollars,CHP,lifecycle_standby_cost_after_tax 50 | CHP,size_kw,CHP,size_kw 51 | CHP,size_supplementary_firing_kw,CHP,size_supplemental_firing_kw 52 | CHP,year_one_thermal_to_waste_series_mmbtu_per_hour,CHP,thermal_curtailed_series_mmbtu_per_hour 53 | ---,(Added in v3),CHP,thermal_production_series_mmbtu_per_hour 54 | CHP,year_one_thermal_to_load_series_mmbtu_per_hour,CHP,thermal_to_load_series_mmbtu_per_hour 55 | CHP,year_one_thermal_to_steamturbine_series_mmbtu_per_hour,CHP,thermal_to_steamturbine_series_mmbtu_per_hour 56 | CHP,year_one_thermal_to_tes_series_mmbtu_per_hour,CHP,thermal_to_storage_series_mmbtu_per_hour 57 | FuelTariff,year_one_chp_fuel_cost_us_dollars,CHP,year_one_fuel_cost_before_tax 58 | ElectricTariff,year_one_chp_standby_cost_us_dollars,CHP,year_one_standby_cost_before_tax 59 | ColdTES,size_gal,ColdThermalStorage,size_gal 60 | ColdTES,year_one_cold_tes_soc_series_pct,ColdThermalStorage,soc_series_fraction 61 | ColdTES,year_one_thermal_from_cold_tes_series_ton,ColdThermalStorage,storage_to_load_series_ton 62 | ,,CoolingLoad,annual_calculated_tonhour 63 | LoadProfileChillerThermal,annual_calculated_kwh_bau,CoolingLoad,annual_electric_chiller_base_load_kwh 64 | LoadProfileChillerThermal,year_one_chiller_electric_load_series_kw,CoolingLoad,electric_chiller_base_load_series_kw 65 | LoadProfileChillerThermal,year_one_chiller_electric_load_series_kw_bau,CoolingLoad,electric_chiller_base_load_series_kw 66 | LoadProfileChillerThermal,year_one_chiller_thermal_load_series_ton,CoolingLoad,load_series_ton 67 | LoadProfile,annual_calculated_kwh,ElectricLoad,annual_calculated_kwh 68 | ,,ElectricLoad,bau_critical_load_met 69 | ,,ElectricLoad,bau_critical_load_met_time_steps 70 | LoadProfile,critical_load_series_kw,ElectricLoad,critical_load_series_kw 71 | LoadProfile,year_one_electric_load_series_kw,ElectricLoad,load_series_kw 72 | ,,ElectricLoad,offgrid_annual_oper_res_provided_series_kwh 73 | LoadProfile,total_sr_required,ElectricLoad,offgrid_annual_oper_res_required_series_kwh 74 | LoadProfile,load_met_pct,ElectricLoad,offgrid_load_met_fraction 75 | LoadProfile,load_met_series_kw,ElectricLoad,offgrid_load_met_series_kw 76 | ---,(Added in v3),ElectricStorage,initial_capital_cost 77 | ---,(Added in v3),ElectricStorage,maintenance_cost 78 | Storage,size_kw,ElectricStorage,size_kw 79 | Storage,size_kwh,ElectricStorage,size_kwh 80 | Storage,year_one_soc_series_pct,ElectricStorage,soc_series_fraction 81 | ---,(Added in v3),ElectricStorage,state_of_health 82 | Storage,year_one_to_load_series_kw,ElectricStorage,storage_to_load_series_kw 83 | Storage,sr_provided_series_kw,---,(Removed in v3) 84 | Storage,year_one_to_grid_series_kw,---,(Removed in v3) 85 | ,,ElectricTariff,lifecycle_chp_standby_cost_after_tax 86 | ElectricTariff,total_coincident_peak_cost_us_dollars,ElectricTariff,lifecycle_coincident_peak_cost_after_tax 87 | ElectricTariff,total_coincident_peak_cost_bau_us_dollars,ElectricTariff,lifecycle_coincident_peak_cost_after_tax_bau 88 | ElectricTariff,total_demand_cost_us_dollars,ElectricTariff,lifecycle_demand_cost_after_tax 89 | ElectricTariff,total_demand_cost_bau_us_dollars,ElectricTariff,lifecycle_demand_cost_after_tax_bau 90 | ElectricTariff,total_energy_cost_us_dollars,ElectricTariff,lifecycle_energy_cost_after_tax 91 | ElectricTariff,total_energy_cost_bau_us_dollars,ElectricTariff,lifecycle_energy_cost_after_tax_bau 92 | ElectricTariff,total_export_benefit_us_dollars,ElectricTariff,lifecycle_export_benefit_after_tax 93 | ElectricTariff,total_export_benefit_bau_us_dollars,ElectricTariff,lifecycle_export_benefit_after_tax_bau 94 | ElectricTariff,total_fixed_cost_us_dollars,ElectricTariff,lifecycle_fixed_cost_after_tax 95 | ElectricTariff,total_fixed_cost_bau_us_dollars,ElectricTariff,lifecycle_fixed_cost_after_tax_bau 96 | ElectricTariff,total_min_charge_adder_us_dollars,ElectricTariff,lifecycle_min_charge_adder_after_tax 97 | ElectricTariff,total_min_charge_adder_bau_us_dollars,ElectricTariff,lifecycle_min_charge_adder_after_tax_bau 98 | ElectricTariff,year_one_bill_us_dollars,ElectricTariff,year_one_bill_before_tax 99 | ElectricTariff,year_one_bill_bau_us_dollars,ElectricTariff,year_one_bill_before_tax_bau 100 | ,,ElectricTariff,year_one_chp_standby_cost_before_tax 101 | ElectricTariff,year_one_coincident_peak_cost_us_dollars,ElectricTariff,year_one_coincident_peak_cost_before_tax 102 | ElectricTariff,year_one_coincident_peak_cost_bau_us_dollars,ElectricTariff,year_one_coincident_peak_cost_before_tax_bau 103 | ElectricTariff,year_one_demand_cost_us_dollars,ElectricTariff,year_one_demand_cost_before_tax 104 | ElectricTariff,year_one_demand_cost_bau_us_dollars,ElectricTariff,year_one_demand_cost_before_tax_bau 105 | ElectricTariff,year_one_energy_cost_us_dollars,ElectricTariff,year_one_energy_cost_before_tax 106 | ElectricTariff,year_one_energy_cost_bau_us_dollars,ElectricTariff,year_one_energy_cost_before_tax_bau 107 | ElectricTariff,year_one_export_benefit_us_dollars,ElectricTariff,year_one_export_benefit_before_tax 108 | ElectricTariff,year_one_export_benefit_bau_us_dollars,ElectricTariff,year_one_export_benefit_before_tax_bau 109 | ElectricTariff,year_one_fixed_cost_us_dollars,ElectricTariff,year_one_fixed_cost_before_tax 110 | ElectricTariff,year_one_fixed_cost_bau_us_dollars,ElectricTariff,year_one_fixed_cost_before_tax_bau 111 | ElectricTariff,year_one_min_charge_adder_us_dollars,ElectricTariff,year_one_min_charge_adder_before_tax 112 | ElectricTariff,year_one_min_charge_adder_bau_us_dollars,ElectricTariff,year_one_min_charge_adder_before_tax_bau 113 | ElectricTariff,year_one_demand_cost_series_us_dollars_per_kw,ElectricTariff (not yet in v3), 114 | ElectricTariff,year_one_energy_cost_series_us_dollars_per_kwh,ElectricTariff (not yet in v3), 115 | ElectricTariff,year_one_emissions_tCO2,ElectricUtility,annual_emissions_tonnes_CO2 116 | ElectricTariff,year_one_emissions_tCO2_bau,ElectricUtility,annual_emissions_tonnes_CO2_bau 117 | ElectricTariff,year_one_emissions_tNOx,ElectricUtility,annual_emissions_tonnes_NOx 118 | ElectricTariff,year_one_emissions_tNOx_bau,ElectricUtility,annual_emissions_tonnes_NOx_bau 119 | ElectricTariff,year_one_emissions_tPM25,ElectricUtility,annual_emissions_tonnes_PM25 120 | ElectricTariff,year_one_emissions_tPM25_bau,ElectricUtility,annual_emissions_tonnes_PM25_bau 121 | ElectricTariff,year_one_emissions_tSO2,ElectricUtility,annual_emissions_tonnes_SO2 122 | ElectricTariff,year_one_emissions_tSO2_bau,ElectricUtility,annual_emissions_tonnes_SO2_bau 123 | ElectricTariff,year_one_energy_supplied_kwh,ElectricUtility,annual_energy_supplied_kwh 124 | ElectricTariff,year_one_energy_supplied_kwh_bau,ElectricUtility,annual_energy_supplied_kwh_bau 125 | ---,(Added in v3),ElectricUtility,distance_to_emissions_region_meters 126 | ElectricTariff,year_one_to_load_series_kw,ElectricUtility,electric_to_load_series_kw 127 | ElectricTariff,year_one_to_load_series_bau_kw,ElectricUtility,electric_to_load_series_kw_bau 128 | ElectricTariff,year_one_to_battery_series_kw,ElectricUtility,electric_to_storage_series_kw 129 | ElectricTariff,emissions_region,ElectricUtility,emissions_region 130 | ElectricTariff,lifecycle_emissions_tCO2,ElectricUtility,lifecycle_emissions_tonnes_CO2 131 | ElectricTariff,lifecycle_emissions_tCO2_bau,ElectricUtility,lifecycle_emissions_tonnes_CO2_bau 132 | ElectricTariff,lifecycle_emissions_tNOx,ElectricUtility,lifecycle_emissions_tonnes_NOx 133 | ElectricTariff,lifecycle_emissions_tNOx_bau,ElectricUtility,lifecycle_emissions_tonnes_Nox_bau 134 | ElectricTariff,lifecycle_emissions_tPM25,ElectricUtility,lifecycle_emissions_tonnes_PM25 135 | ElectricTariff,lifecycle_emissions_tPM25_bau,ElectricUtility,lifecycle_emissions_tonnes_PM25_bau 136 | ElectricTariff,lifecycle_emissions_tSO2,ElectricUtility,lifecycle_emissions_tonnes_SO2 137 | ElectricTariff,lifecycle_emissions_tSO2_bau,ElectricUtility,lifecycle_emissions_tonnes_SO2_bau 138 | Boiler,year_one_boiler_fuel_consumption_mmbtu,ExistingBoiler,annual_fuel_consumption_mmbtu 139 | Boiler,year_one_boiler_thermal_production_mmbtu,ExistingBoiler,annual_thermal_production_mmbtu 140 | Boiler,year_one_boiler_fuel_consumption_series_mmbtu_per_hr,ExistingBoiler,fuel_consumption_series_mmbtu_per_hour 141 | FuelTariff,total_boiler_fuel_cost_us_dollars,---,(No corresponding v3 output) 142 | FuelTariff,total_boiler_fuel_cost_bau_us_dollars,---,(No corresponding v3 output) 143 | Boiler,year_one_boiler_thermal_production_series_mmbtu_per_hr,ExistingBoiler,thermal_production_series_mmbtu_per_hour 144 | Boiler,year_one_thermal_to_load_series_mmbtu_per_hour,ExistingBoiler,thermal_to_load_series_mmbtu_per_hour 145 | Boiler,year_one_thermal_to_steamturbine_series_mmbtu_per_hour,ExistingBoiler,thermal_to_steamturbine_series_mmbtu_per_hour 146 | Boiler,year_one_thermal_to_tes_series_mmbtu_per_hour,ExistingBoiler,thermal_to_storage_series_mmbtu_per_hour 147 | FuelTariff,year_one_boiler_fuel_cost_us_dollars,ExistingBoiler,year_one_fuel_cost_before_tax 148 | FuelTariff,year_one_boiler_fuel_cost_bau_us_dollars,ExistingBoiler,year_one_fuel_cost_before_tax 149 | Boiler,year_one_boiler_fuel_consumption_mmbtu_bau,ExistingBoiler (not yet in v3), 150 | LoadProfileBoilerFuel,annual_calculated_boiler_fuel_load_mmbtu_bau,HeatingOutputs,annual_calculated_total_heating_thermal_load_mmbtu 151 | LoadProfileBoilerFuel,year_one_boiler_fuel_load_series_mmbtu_per_hr,HeatingOutputs,total_heating_boiler_fuel_load_series_mmbtu_per_hour 152 | LoadProfileBoilerFuel,year_one_boiler_thermal_load_series_mmbtu_per_hr,HeatingOutputs,total_heating_thermal_load_series_mmbtu_per_hour 153 | ElectricChiller,year_one_electric_chiller_electric_consumption_kwh,ExistingChiller,annual_electric_consumption_kwh 154 | ElectricChiller,year_one_electric_chiller_thermal_production_tonhr,ExistingChiller,annual_thermal_production_tonhour 155 | ElectricChiller,year_one_electric_chiller_electric_consumption_series_kw,ExistingChiller,electric_consumption_series_kw 156 | ElectricChiller,year_one_electric_chiller_thermal_to_load_series_ton,ExistingChiller,thermal_to_load_series_ton 157 | ElectricChiller,year_one_electric_chiller_thermal_to_tes_series_ton,ExistingChiller,thermal_to_storage_series_ton 158 | Financial,annualized_payment_to_third_party_us_dollars,Financial,annualized_payment_to_third_party 159 | Site,breakeven_cost_of_emissions_reduction_us_dollars_per_tCO2,Financial,breakeven_cost_of_emissions_reduction_per_tonnes_CO2 160 | Financial,developer_annual_free_cashflow_series_bau_us_dollars,Financial,developer_annual_free_cashflows 161 | Financial,developer_om_and_replacement_present_cost_after_tax_us_dollars,Financial,developer_om_and_replacement_present_cost_after_tax 162 | Financial,initial_capital_costs,Financial,initial_capital_costs 163 | Financial,initial_capital_costs_after_incentives,Financial,initial_capital_costs_after_incentives 164 | Financial,irr_pct,Financial,internal_rate_of_return 165 | Financial,lcc_us_dollars,Financial,lcc 166 | Financial,lcc_bau_us_dollars,Financial,lcc_bau 167 | Financial,net_capital_costs,Financial,lifecycle_capital_costs 168 | Financial,net_capital_costs_plus_om_us_dollars,Financial,lifecycle_capital_costs_plus_om_after_tax 169 | ---,(Added in v3),Financial,lifecycle_chp_standby_cost_after_tax 170 | ---,(Added in v3),Financial,lifecycle_elecbill_after_tax 171 | Site,lifecycle_emissions_cost_CO2,Financial,lifecycle_emissions_cost_climate 172 | Site,lifecycle_emissions_cost_CO2_bau,Financial,lifecycle_emissions_cost_climate_bau 173 | Site,lifecycle_emissions_cost_Health,Financial,lifecycle_emissions_cost_health 174 | Site,lifecycle_emissions_cost_Health_bau,Financial,lifecycle_emissions_cost_health_bau 175 | FuelTariff,total_fuel_cost_us_dollars,Financial,lifecycle_fuel_costs_after_tax 176 | ---,(Added in v3),Financial,lifecycle_generation_tech_capital_costs 177 | Financial,microgrid_upgrade_cost_us_dollars,Financial,lifecycle_MG_upgrade_and_fuel_cost 178 | Financial,total_annual_cost_us_dollars,Financial,lifecycle_offgrid_other_annual_costs_after_tax 179 | Financial,additional_cap_costs_us_dollars,Financial,lifecycle_offgrid_other_capital_costs 180 | Financial,total_om_costs_us_dollars,Financial,lifecycle_om_costs_after_tax 181 | Financial,total_om_costs_bau_us_dollars,Financial (not yet in v3),lifecycle_om_costs_after_tax_bau 182 | ---,(Added in v3),Financial,lifecycle_om_costs_before_tax 183 | ---,(Added in v3),Financial,lifecycle_om_costs_before_tax_bau 184 | ---,(Added in v3),Financial,lifecycle_outage_cost 185 | Financial,total_production_incentive_after_tax,Financial,lifecycle_production_incentive_after_tax 186 | ---,(Added in v3),Financial,lifecycle_storage_capital_costs 187 | ---,(Added in v3),Financial,microgrid_upgrade_capital_cost 188 | Financial,net_present_cost_us_dollars,Financial,net_present_cost 189 | Financial,npv_us_dollars,Financial,npv 190 | Financial,microgrid_lcoe_us_dollars_per_kwh,Financial,offgrid_microgrid_lcoe_dollars_per_kwh 191 | Financial,offtaker_annual_free_cashflow_series_us_dollars,Financial,offtaker_annual_free_cashflows 192 | Financial,offtaker_annual_free_cashflow_series_bau_us_dollars,Financial,offtaker_annual_free_cashflows_bau 193 | Financial,offtaker_discounted_annual_free_cashflow_series_us_dollars,Financial,offtaker_discounted_annual_free_cashflows 194 | Financial,offtaker_discounted_annual_free_cashflow_series_bau_us_dollars,Financial,offtaker_discounted_annual_free_cashflows_bau 195 | Financial,om_and_replacement_present_cost_after_tax_us_dollars,Financial,om_and_replacement_present_cost_after_tax 196 | Financial,replacement_costs,Financial,replacements_future_cost_after_tax 197 | ---,(Added in v3),Financial,replacements_present_cost_after_tax 198 | Financial,simple_payback_years,Financial,simple_payback_years 199 | Financial,year_one_om_costs_us_dollars,Financial,year_one_om_costs_after_tax 200 | Financial,year_one_om_costs_before_tax_us_dollars,Financial,year_one_om_costs_before_tax 201 | Financial,year_one_om_costs_before_tax_bau_us_dollars,Financial,year_one_om_costs_before_tax_bau 202 | Financial,lcoe_component_diesel_capex_us_dollars_per_kwh,Financial (not yet in v3), 203 | Financial,lcoe_component_fuel_us_dollars_per_kwh,Financial (not yet in v3), 204 | Financial,lcoe_component_om_us_dollars_per_kwh,Financial (not yet in v3), 205 | Financial,lcoe_component_other_annual_costs_us_dollars_per_kwh,Financial (not yet in v3), 206 | Financial,lcoe_component_other_capex_us_dollars_per_kwh,Financial (not yet in v3), 207 | Financial,lcoe_component_re_capex_us_dollars_per_kwh,Financial (not yet in v3), 208 | Financial,net_om_us_dollars_bau,---,(Removed in v3) 209 | Generator,average_yearly_energy_produced_kwh,Generator,annual_energy_produced_kwh 210 | Generator,fuel_used_gal,Generator,annual_fuel_consumption_gal 211 | Generator,year_one_to_grid_series_kw,Generator,electric_to_grid_series_kw 212 | Generator,year_one_to_load_series_kw,Generator,electric_to_load_series_kw 213 | Generator,year_one_to_battery_series_kw,Generator,electric_to_storage_series_kw 214 | Generator,fuel_used_gal_bau,Generator,annual_fuel_consumption_gal_bau 215 | Generator,total_fixed_om_cost_us_dollars,Generator,lifecycle_fixed_om_cost_after_tax 216 | Generator,existing_gen_total_fixed_om_cost_us_dollars,Generator,lifecycle_fixed_om_cost_bau 217 | Generator,total_fuel_cost_us_dollars,Generator,lifecycle_fuel_cost_after_tax 218 | Generator,existing_gen_total_fuel_cost_us_dollars,Generator,lifecycle_fuel_cost_after_tax_bau 219 | Generator,total_variable_om_cost_us_dollars,Generator,lifecycle_variable_om_cost_after_tax 220 | Generator,existing_gen_total_variable_om_cost_us_dollars,Generator,lifecycle_variable_om_cost_after_tax_bau 221 | Generator,size_kw,Generator,size_kw 222 | Generator,year_one_energy_produced_kwh,Generator,annual_energy_produced_kwh 223 | Generator,year_one_fixed_om_cost_us_dollars,Generator,year_one_fixed_om_cost_before_tax 224 | ---,(Added in v3),Generator,year_one_fixed_om_cost_before_tax_bau 225 | Generator,year_one_fuel_cost_us_dollars,Generator,year_one_fuel_cost_before_tax 226 | Generator,existing_gen_year_one_fuel_cost_us_dollars,Generator,year_one_fuel_cost_before_tax_bau 227 | Generator,year_one_variable_om_cost_us_dollars,Generator,year_one_variable_om_cost_before_tax 228 | Generator,existing_gen_year_one_variable_om_cost_us_dollars,Generator,year_one_variable_om_cost_before_tax_bau 229 | Generator,fuel_used_series_gal,Generator,annual_fuel_consumption_gal_bau 230 | Generator,sr_provided_series_kw,---,(Removed in v3) 231 | Generator,year_one_power_production_series_kw,Generator,(Removed in v3) 232 | ---,(Added in v3),GHP,cooling_thermal_load_reduction_with_ghp_kw 233 | ---,(Added in v3),GHP,ghp_option_chosen 234 | GHP,cooling_thermal_reduction_series_ton,GHP,cooling_thermal_load_reduction_with_ghp_ton 235 | GHP,ghpghx_chosen_outputs,GHP,ghpghx_chosen_outputs 236 | GHP,size_heat_pump_ton,GHP,size_heat_pump_ton 237 | GHP,space_heating_thermal_reduction_series_mmbtu_per_hr,GHP,space_heating_thermal_load_reduction_with_ghp_mmbtu_per_hour 238 | GHP,ghp_chosen_uuid,---,(Removed in v3) 239 | ,,HeatingLoadOutputs,annual_calculated_dhw_boiler_fuel_load_mmbtu 240 | ---,(Added in v3),HeatingLoadOutputs,annual_calculated_dhw_thermal_load_mmbtu 241 | ,,HeatingLoadOutputs,annual_calculated_space_heating_boiler_fuel_load_mmbtu 242 | ---,(Added in v3),HeatingLoadOutputs,annual_calculated_space_heating_thermal_load_mmbtu 243 | ,,HeatingLoadOutputs,annual_calculated_total_heating_boiler_fuel_load_mmbtu 244 | ---,(Added in v3),HeatingLoadOutputs,annual_calculated_total_heating_thermal_load_mmbtu 245 | ,,HeatingLoadOutputs,dhw_boiler_fuel_load_series_mmbtu_per_hour 246 | ---,(Added in v3),HeatingLoadOutputs,dhw_thermal_load_series_mmbtu_per_hour 247 | ,,HeatingLoadOutputs,space_heating_boiler_fuel_load_series_mmbtu_per_hour 248 | ---,(Added in v3),HeatingLoadOutputs,space_heating_thermal_load_series_mmbtu_per_hour 249 | ,,HeatingLoadOutputs,total_heating_boiler_fuel_load_series_mmbtu_per_hour 250 | ---,(Added in v3),HeatingLoadOutputs,total_heating_thermal_load_series_mmbtu_per_hour 251 | HotTES,size_gal,HotThermalStorage,size_gal 252 | HotTES,year_one_hot_tes_soc_series_pct,HotThermalStorage,soc_series_fraction 253 | HotTES,year_one_thermal_from_hot_tes_series_mmbtu_per_hr,HotThermalStorage,storage_to_load_series_mmbtu_per_hour 254 | ,,Outages,chp_curtailed_series_kw 255 | ,,Outages,chp_fuel_used_per_outage_mmbtu 256 | ,,Outages,chp_microgrid_size_kw 257 | ,,Outages,chp_microgrid_upgrade_cost 258 | ,,Outages,chp_to_load_series_kw 259 | ,,Outages,chp_to_storage_series_kw 260 | ---,(Added in v3),Outages,storage_discharge_series_kw 261 | ---,(Added in v3),Outages,expected_outage_cost 262 | ---,(Added in v3),Outages,generator_microgrid_size_kw 263 | ---,(Added in v3),Outages,max_outage_cost_per_outage_duration 264 | ---,(Added in v3),Outages,generator_fuel_used_per_outage_gal 265 | ---,(Added in v3),Outages,generator_microgrid_upgrade_cost 266 | ---,(Added in v3),Outages,pv_microgrid_upgrade_cost 267 | ---,(Added in v3),Outages,mg_storage_upgrade_cost 268 | ---,(Added in v3),Outages,generator_curtailed_series_kw 269 | ---,(Added in v3),Outages,generator_to_load_series_kw 270 | ---,(Added in v3),Outages,generator_to_storage_series_kw 271 | ---,(Added in v3),Outages,pv_curtailed_series_kw 272 | ---,(Added in v3),Outages,pv_to_load_series_kw 273 | ---,(Added in v3),Outages,pv_to_storage_series_kw 274 | ,,Outages,microgrid_upgrade_capital_cost 275 | ---,(Added in v3),Outages,pv_microgrid_size_kw 276 | ---,(Added in v3),Outages,storage_microgrid_upgrade_cost 277 | ---,(Added in v3),Outages,unserved_load_per_outage_kwh 278 | ---,(Added in v3),Outages,unserved_load_series_kw 279 | PV,average_yearly_energy_exported_kwh,PV,annual_energy_exported_kwh 280 | PV,average_yearly_energy_produced_kwh,PV,annual_energy_produced_kwh 281 | PV,average_yearly_energy_produced_bau_kwh,PV,annual_energy_produced_kwh_bau 282 | PV,year_one_curtailed_production_series_kw,PV,electric_curtailed_series_kw 283 | PV,year_one_to_grid_series_kw,PV,electric_to_grid_series_kw 284 | PV,year_one_to_load_series_kw,PV,electric_to_load_series_kw 285 | PV,year_one_to_battery_series_kw,PV,electric_to_storage_series_kw 286 | ---,(Added in v3),PV,lcoe_per_kwh 287 | ---,(Added in v3),PV,lifecycle_om_cost_after_tax 288 | PV,existing_pv_om_cost_us_dollars,PV,lifecycle_om_cost_after_tax_bau 289 | ---,(Added in v3),PV,lifecycle_om_cost_bau 290 | PV,pv_name,PV,name 291 | ---,(Added in v3),PV,production_factor_series 292 | PV,size_kw,PV,size_kw 293 | PV,year_one_energy_produced_kwh,PV,year_one_energy_produced_kwh 294 | PV,year_one_energy_produced_bau_kwh,PV,year_one_energy_produced_kwh_bau 295 | PV,sr_provided_series_kw,---,(Removed in v3) 296 | PV,sr_required_series_kw,---,(Removed in v3) 297 | PV,station_distance_km,---,(Removed in v3) 298 | PV,station_latitude,---,(Removed in v3) 299 | PV,station_longitude,---,(Removed in v3) 300 | PV,year_one_power_production_series_kw,---,(Removed in v3) 301 | Site,year_one_emissions_from_fuelburn_tCO2,Site,annual_emissions_from_fuelburn_tonnes_CO2 302 | Site,year_one_emissions_from_fuelburn_tCO2_bau,Site,annual_emissions_from_fuelburn_tonnes_CO2_bau 303 | Site,year_one_emissions_from_fuelburn_tNOx,Site,annual_emissions_from_fuelburn_tonnes_NOx 304 | Site,year_one_emissions_from_fuelburn_tNOx_bau,Site,annual_emissions_from_fuelburn_tonnes_NOx_bau 305 | Site,year_one_emissions_from_fuelburn_tPM25,Site,annual_emissions_from_fuelburn_tonnes_PM25 306 | Site,year_one_emissions_from_fuelburn_tPM25_bau,Site,annual_emissions_from_fuelburn_tonnes_PM25_bau 307 | Site,year_one_emissions_from_fuelburn_tSO2,Site,annual_emissions_from_fuelburn_tonnes_SO2 308 | Site,year_one_emissions_from_fuelburn_tSO2_bau,Site,annual_emissions_from_fuelburn_tonnes_SO2_bau 309 | Site,year_one_emissions_tCO2,Site,annual_emissions_tonnes_CO2 310 | Site,year_one_emissions_tCO2_bau,Site,annual_emissions_tonnes_CO2_bau 311 | Site,year_one_emissions_tNOx,Site,annual_emissions_tonnes_NOx 312 | Site,year_one_emissions_tNOx_bau,Site,annual_emissions_tonnes_NOx_bau 313 | Site,year_one_emissions_tPM25,Site,annual_emissions_tonnes_PM25 314 | Site,year_one_emissions_tPM25_bau,Site,annual_emissions_tonnes_PM25_bau 315 | Site,year_one_emissions_tSO2,Site,annual_emissions_tonnes_SO2 316 | Site,year_one_emissions_tSO2_bau,Site,annual_emissions_tonnes_SO2_bau 317 | Site,annual_renewable_electricity_kwh,Site,annual_renewable_electricity_kwh 318 | Site,annual_renewable_electricity_kwh_bau,Site,annual_renewable_electricity_kwh_bau 319 | Site,lifecycle_emissions_from_fuelburn_tCO2,Site,lifecycle_emissions_from_fuelburn_tonnes_CO2 320 | Site,lifecycle_emissions_from_fuelburn_tCO2_bau,Site,lifecycle_emissions_from_fuelburn_tonnes_CO2_bau 321 | Site,lifecycle_emissions_from_fuelburn_tNOx,Site,lifecycle_emissions_from_fuelburn_tonnes_NOx 322 | Site,lifecycle_emissions_from_fuelburn_tNOx_bau,Site,lifecycle_emissions_from_fuelburn_tonnes_NOx_bau 323 | Site,lifecycle_emissions_from_fuelburn_tPM25,Site,lifecycle_emissions_from_fuelburn_tonnes_PM25 324 | Site,lifecycle_emissions_from_fuelburn_tPM25_bau,Site,lifecycle_emissions_from_fuelburn_tonnes_PM25_bau 325 | Site,lifecycle_emissions_from_fuelburn_tSO2,Site,lifecycle_emissions_from_fuelburn_tonnes_SO2 326 | Site,lifecycle_emissions_from_fuelburn_tSO2_bau,Site,lifecycle_emissions_from_fuelburn_tonnes_SO2_bau 327 | Site,lifecycle_emissions_reduction_CO2_pct,Site,lifecycle_emissions_reduction_CO2_fraction 328 | Site,lifecycle_emissions_tCO2,Site,lifecycle_emissions_tonnes_CO2 329 | Site,lifecycle_emissions_tCO2_bau,Site,lifecycle_emissions_tonnes_CO2_bau 330 | Site,lifecycle_emissions_tNOx,Site,lifecycle_emissions_tonnes_NOx 331 | Site,lifecycle_emissions_tNOx_bau,Site,lifecycle_emissions_tonnes_NOx_bau 332 | Site,lifecycle_emissions_tPM25,Site,lifecycle_emissions_tonnes_PM25 333 | Site,lifecycle_emissions_tPM25_bau,Site,lifecycle_emissions_tonnes_PM25_bau 334 | Site,lifecycle_emissions_tSO2,Site,lifecycle_emissions_tonnes_SO2 335 | Site,lifecycle_emissions_tSO2_bau,Site,lifecycle_emissions_tonnes_SO2_bau 336 | Site,annual_renewable_electricity_pct,Site,renewable_electricity_fraction 337 | Site,annual_renewable_electricity_pct_bau,Site,renewable_electricity_fraction_bau 338 | Site,annual_total_renewable_energy_pct,Site,total_renewable_energy_fraction 339 | Site,annual_total_renewable_energy_pct_bau,Site,total_renewable_energy_fraction_bau 340 | SteamTurbine,year_one_electric_energy_produced_kwh,SteamTurbine,annual_electric_production_kwh 341 | SteamTurbine,year_one_thermal_consumption_mmbtu,SteamTurbine,annual_thermal_consumption_mmbtu 342 | SteamTurbine,year_one_thermal_energy_produced_mmbtu,SteamTurbine,annual_thermal_production_mmbtu 343 | SteamTurbine,year_one_electric_production_series_kw,SteamTurbine,electric_production_series_kw 344 | SteamTurbine,year_one_to_grid_series_kw,SteamTurbine,electric_to_grid_series_kw 345 | SteamTurbine,year_one_to_load_series_kw,SteamTurbine,electric_to_load_series_kw 346 | SteamTurbine,year_one_to_battery_series_kw,SteamTurbine,electric_to_storage_series_kw 347 | SteamTurbine,size_kw,SteamTurbine,size_kw 348 | SteamTurbine,year_one_thermal_consumption_series_mmbtu_per_hr,SteamTurbine,thermal_consumption_series_mmbtu_per_hour 349 | SteamTurbine,year_one_thermal_to_load_series_mmbtu_per_hour,SteamTurbine,thermal_to_load_series_mmbtu_per_hour 350 | SteamTurbine,year_one_thermal_to_tes_series_mmbtu_per_hour,SteamTurbine,thermal_to_storage_series_mmbtu_per_hour 351 | Wind,average_yearly_energy_exported_kwh,Wind,annual_energy_exported_kwh 352 | Wind,average_yearly_energy_produced_kwh,Wind,annual_energy_produced_kwh 353 | Wind,year_one_curtailed_production_series_kw,Wind,electric_curtailed_series_kw 354 | Wind,year_one_to_grid_series_kw,Wind,electric_to_grid_series_kw 355 | Wind,year_one_to_load_series_kw,Wind,electric_to_load_series_kw 356 | Wind,year_one_to_battery_series_kw,Wind,electric_to_storage_series_kw 357 | ---,(Added in v3),Wind,lcoe_per_kwh 358 | ---,(Added in v3),Wind,lifecycle_om_cost_after_tax 359 | Wind,size_kw,Wind,size_kw 360 | ---,(Added in v3),Wind,year_one_om_cost_before_tax 361 | Wind,year_one_power_production_series_kw,---,(Removed in v3) 362 | --------------------------------------------------------------------------------