Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
319 changes: 319 additions & 0 deletions Examples/green_button_data_example/parse_and_run_green_button.ipynb
Original file line number Diff line number Diff line change
@@ -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",
"<IntervalBlock (Interval Block)>\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
}
5 changes: 5 additions & 0 deletions Examples/green_button_data_example/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
certifi
greenbutton_objects
NREL-PySAM
pandas
requests
Loading