diff --git a/Examples/green_button_data_example/parse_and_run_green_button.ipynb b/Examples/green_button_data_example/parse_and_run_green_button.ipynb new file mode 100644 index 0000000..9cd6e6f --- /dev/null +++ b/Examples/green_button_data_example/parse_and_run_green_button.ipynb @@ -0,0 +1,319 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d979ed0f", + "metadata": {}, + "source": [ + "This script takes Green Button Data XML (see https://greenbuttondata.org/index.html)\n", + "and transforms it into a format usable by SAM for bill savings calculations.\n", + "\n", + "Note that this was written based on data with a 15 minute timestep and at least one full calendar year of data\n", + "Future improvements to this script may allow for more flexible data inputs\n", + "\n", + "Also note: during testing we encountered data with PV generation where PV generation steps had a 0 for that timestep (when PV was exporting to the grid)\n", + "This script assumes load data does not include a generator that can export to the grid" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "79a8ae7a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7.0.0\n" + ] + } + ], + "source": [ + "import certifi\n", + "import greenbutton_objects\n", + "import greenbutton_objects.parse\n", + "import json\n", + "import os\n", + "import pandas as pd\n", + "import PySAM\n", + "from PySAM import ResourceTools as tools\n", + "import PySAM.Battery as battery_model\n", + "import PySAM.Pvsamv1 as pv_model\n", + "import PySAM.Utilityrate5 as utility_rate\n", + "import PySAM.Cashloan as cashloan\n", + "import PySAM.UtilityRateTools\n", + "import requests\n", + "\n", + "print(PySAM.__version__)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3018da0a", + "metadata": {}, + "outputs": [], + "source": [ + "# Set up required info for NSRDB and other API calls\n", + "\n", + "nrel_api_key = \"YOUR_API_KEY\"\n", + "nrel_email = \"YOUR_EMAIL\"" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a97d245e", + "metadata": {}, + "outputs": [], + "source": [ + "# Code to extract the relevant data from the interval reading object\n", + "# Adapted from https://github.com/JenDobson/greenbutton/tree/master/greenbutton\n", + "\n", + "def parse_reading(interval_reading):\n", + " start = interval_reading.timePeriod.start\n", + " duration = interval_reading.timePeriod.duration\n", + " value = int(interval_reading.value)\n", + " return (start, duration, value)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ac7d265", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2022-09-01 00:00:00+00:00\n", + "\n" + ] + } + ], + "source": [ + "filename = \"path_to_greenbutton_xml\"\n", + "\n", + "\n", + "# Existing libraries deal with units and scaling factors within the Green Button data strucutre\n", + "data = greenbutton_objects.parse.parse_feed(filename)\n", + "\n", + "# Quick printouts to check what is in each level of data in this structure\n", + "mr = list(data[0].meterReadings)[0]\n", + "print(mr.intervalBlocks[0].intervalReadings[0].timePeriod.start)\n", + "print(mr.intervalBlocks[0])\n", + "\n", + "readings = []\n", + "\n", + "for interval_reading in mr.intervalBlocks[0].intervalReadings:\n", + " readings.append(parse_reading(interval_reading))\n", + "\n", + "# Create dataframe from parsed data in Wh\n", + "df = pd.DataFrame(readings,columns=['Start Time','Duration','Wh'])\n", + "df = df.set_index('Start Time')" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "e55fa809", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Duration Wh\n", + "2022-12-31 00:00:00+00:00 0 days 00:15:00 1036\n", + "2022-12-31 00:15:00+00:00 0 days 00:15:00 639\n", + "2022-12-31 00:30:00+00:00 0 days 00:15:00 176\n", + "2022-12-31 00:45:00+00:00 0 days 00:15:00 192\n", + "2022-12-31 01:00:00+00:00 0 days 00:15:00 157\n", + "... ... ...\n", + "2024-01-01 22:45:00+00:00 0 days 00:15:00 77\n", + "2024-01-01 23:00:00+00:00 0 days 00:15:00 104\n", + "2024-01-01 23:15:00+00:00 0 days 00:15:00 78\n", + "2024-01-01 23:30:00+00:00 0 days 00:15:00 132\n", + "2024-01-01 23:45:00+00:00 0 days 00:15:00 114\n", + "\n", + "[35232 rows x 2 columns]\n", + "35040\n" + ] + } + ], + "source": [ + "# Want one calendar year of data for PySAM run - choose 2023 based on the example file we had\n", + "df_23 = df.loc['2022-12-31':'2024-01-01']\n", + "\n", + "# The example xml had some missing data so fill it with the nearest value\n", + "idx = pd.period_range(min(df_23.index), max(df_23.index), freq='15min')\n", + "df_23 = df_23.reindex(idx.astype('datetime64[ns, UTC]'), method='nearest')\n", + "print(df_23.loc['2022-12-31':'2024-01-01'])\n", + "\n", + "# Convert Wh to kW (This equation only works for 15 minute data)\n", + "kW_data = df_23.loc['2023-01-01 07:00:00':'2024-01-01 06:59:00']['Wh'] / 250.0\n", + "print(len(kW_data))\n", + "kW_data.to_csv('15_min_load.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "4534f881", + "metadata": {}, + "outputs": [], + "source": [ + "# Optional: example conversion from 15 minute data in Wh to hourly data in kW\n", + "\n", + "hourly_data = [0] * 8760\n", + "for i, v in enumerate(df_23.loc['2023-01-01 07:00:00':'2024-01-01 06:59:00']['Wh'].values):\n", + " hourly_data[int(i / 4)] += v / 1000.0\n", + "\n", + "idx = range(0, 8760)\n", + "df_hrly = pd.DataFrame.from_dict({'time' : idx, 'kW' : hourly_data})\n", + "df_hrly.to_csv('hourly_load.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "789bb8b6", + "metadata": {}, + "outputs": [], + "source": [ + "# Download rate from URDB and save as file. If rate has already been downloaded, use file\n", + "def get_urdb_rate_data(page, key):\n", + "\n", + " # Full API can be viewed at: https://openei.org/services/doc/rest/util_rates/?version=8\n", + " urdb_url = 'https://api.openei.org/utility_rates?format=json&detail=full&version=8'\n", + " get_url = urdb_url + '&api_key={api_key}&getpage={page_id}'.format(api_key=key, page_id=page)\n", + " print(get_url)\n", + "\n", + " filename = \"urdb_rate_{}.json\".format(page)\n", + " print(filename)\n", + "\n", + " if not os.path.isfile(filename):\n", + " print(get_url)\n", + " resp = requests.get(get_url, verify=certifi.where())\n", + " data = resp.text\n", + " # Cache rate as file\n", + " if \"error\" not in data:\n", + " with open(filename, 'w') as f:\n", + " f.write(json.dumps(data, sort_keys=True, indent=2, separators=(',', ': ')))\n", + " else:\n", + " with open(filename, 'r') as f:\n", + " data = json.load(f)\n", + "\n", + " return data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b0d90f6c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Starting data download for solar using 1 thread workers.\n", + "File already exists. Skipping download: .\\nsrdb_39.74_-105.17_nsrdb-GOES-conus-v4-0-0_15_2023.csv\n", + "https://api.openei.org/utility_rates?format=json&detail=full&version=8&api_key=RkdVzEGa7mlEGqV9jzwjhrh95ACZOr6PFr0K0yTc&getpage=67a65130767b93f0b5044b9a\n", + "urdb_rate_67a65130767b93f0b5044b9a.json\n", + "Year one savings 876.0264276434835\n" + ] + } + ], + "source": [ + "# Download 15 minute data from NSRDB\n", + "building_path = \".\"\n", + "year = 2023\n", + "\n", + "nsrdbfetcher = tools.FetchResourceFiles(\n", + " tech='solar',\n", + " nrel_api_key=sam_api_key,\n", + " nrel_api_email=sam_email,\n", + " resource_type='nsrdb-GOES-conus-v4-0-0',\n", + " resource_interval_min=15,\n", + " resource_year=str(year),\n", + " resource_dir=building_path)\n", + "\n", + "lon = \"-105.17\"\n", + "lat = \"39.74\"\n", + "\n", + "lon_lats = []\n", + "\n", + "lon_lat = (float(lon), float(lat))\n", + "lon_lats.append(lon_lat)\n", + "\n", + "nsrdbfetcher.fetch(lon_lats)\n", + "\n", + "# Get rate from URDB\n", + "path = os.getcwd() + os.path.sep\n", + "page = \"67a65130767b93f0b5044b9a\" # https://apps.openei.org/USURDB/rate/view/67a65130767b93f0b5044b9a (residential time of use)\n", + "\n", + "urdb_response = get_urdb_rate_data(page, sam_api_key)\n", + "urdb_response_json = json.loads(urdb_response)\n", + "if 'error' in urdb_response_json.keys():\n", + " raise Exception(urdb_response_json['error'])\n", + "rates = PySAM.UtilityRateTools.URDBv8_to_ElectricityRates(urdb_response_json[\"items\"][0]) \n", + "\n", + "# Run PV + Battery code with defaults\n", + "pv = pv_model.default(\"PVBatteryResidential\") # This runs both PV and battery\n", + "ur = utility_rate.from_existing(pv, \"PVBatteryResidential\")\n", + "cl = cashloan.from_existing(ur, \"PVBatteryResidential\")\n", + "\n", + "for k, v in rates.items():\n", + " try:\n", + " # from_existing above updates this both for PV (battery dispatch) and the utility rates code\n", + " pv.value(k, v)\n", + " except AttributeError:\n", + " if \"batt_adjust\" in k:\n", + " pass\n", + " else:\n", + " print(\"Failed to assign PV key \" + str(k))\n", + "\n", + "weather_file = \"nsrdb_\" + lat + \"_\" + lon + \"_nsrdb-GOES-conus-v4-0-0_15_2023.csv\"\n", + "\n", + "pv.value(\"solar_resource_file\", str(weather_file))\n", + "pv.value(\"batt_dispatch_choice\", 4) # Retail rates dispatch\n", + "pv.value(\"load\", kW_data.values)\n", + "pv.value(\"crit_load\", kW_data.values) # Not used, but need to set this to 15 minute values to pass checks\n", + "pv.value(\"en_batt\", 1)\n", + "cl.value(\"en_batt\",1)\n", + "\n", + "pv.execute()\n", + "ur.execute()\n", + "cl.execute()\n", + "\n", + "# Print out bill savings\n", + "print(\"Year one savings \", ur.Outputs.savings_year1)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "green_button_tests", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Examples/green_button_data_example/requirements.txt b/Examples/green_button_data_example/requirements.txt new file mode 100644 index 0000000..2995f5d --- /dev/null +++ b/Examples/green_button_data_example/requirements.txt @@ -0,0 +1,5 @@ +certifi +greenbutton_objects +NREL-PySAM +pandas +requests \ No newline at end of file