diff --git a/CODEOWNERS b/CODEOWNERS index 89417c4ca56978..6714c948402bde 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -140,6 +140,7 @@ homeassistant/components/google_translate/* @awarecan homeassistant/components/google_travel_time/* @robbiet480 homeassistant/components/gpsd/* @fabaff homeassistant/components/greeneye_monitor/* @jkeljo +homeassistant/components/griddy/* @bdraco homeassistant/components/group/* @home-assistant/core homeassistant/components/growatt_server/* @indykoning homeassistant/components/gtfs/* @robbiet480 diff --git a/homeassistant/components/griddy/.translations/en.json b/homeassistant/components/griddy/.translations/en.json new file mode 100644 index 00000000000000..bedd85e750893c --- /dev/null +++ b/homeassistant/components/griddy/.translations/en.json @@ -0,0 +1,21 @@ +{ + "config" : { + "error" : { + "cannot_connect" : "Failed to connect, please try again", + "unknown" : "Unexpected error" + }, + "title" : "Griddy", + "step" : { + "user" : { + "description" : "Your Load Zone is in your Griddy account under “Account > Meter > Load Zone.”", + "data" : { + "loadzone" : "Load Zone (Settlement Point)" + }, + "title" : "Setup your Griddy Load Zone" + } + }, + "abort" : { + "already_configured" : "This Load Zone is already configured" + } + } +} diff --git a/homeassistant/components/griddy/__init__.py b/homeassistant/components/griddy/__init__.py new file mode 100644 index 00000000000000..fb5079b00f8c66 --- /dev/null +++ b/homeassistant/components/griddy/__init__.py @@ -0,0 +1,96 @@ +"""The Griddy Power integration.""" +import asyncio +from datetime import timedelta +import logging + +from griddypower.async_api import LOAD_ZONES, AsyncGriddy +import voluptuous as vol + +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import CONF_LOADZONE, DOMAIN, UPDATE_INTERVAL + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_LOADZONE): vol.In(LOAD_ZONES)})}, + extra=vol.ALLOW_EXTRA, +) + +PLATFORMS = ["sensor"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Griddy Power component.""" + + hass.data.setdefault(DOMAIN, {}) + conf = config.get(DOMAIN) + + if not conf: + return True + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={CONF_LOADZONE: conf.get(CONF_LOADZONE)}, + ) + ) + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Griddy Power from a config entry.""" + + entry_data = entry.data + + async_griddy = AsyncGriddy( + aiohttp_client.async_get_clientsession(hass), + settlement_point=entry_data[CONF_LOADZONE], + ) + + async def async_update_data(): + """Fetch data from API endpoint.""" + return await async_griddy.async_getnow() + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name="Griddy getnow", + update_method=async_update_data, + update_interval=timedelta(seconds=UPDATE_INTERVAL), + ) + + await coordinator.async_refresh() + + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + hass.data[DOMAIN][entry.entry_id] = coordinator + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/griddy/config_flow.py b/homeassistant/components/griddy/config_flow.py new file mode 100644 index 00000000000000..56284384ee0a11 --- /dev/null +++ b/homeassistant/components/griddy/config_flow.py @@ -0,0 +1,75 @@ +"""Config flow for Griddy Power integration.""" +import asyncio +import logging + +from aiohttp import ClientError +from griddypower.async_api import LOAD_ZONES, AsyncGriddy +import voluptuous as vol + +from homeassistant import config_entries, core, exceptions +from homeassistant.helpers import aiohttp_client + +from .const import CONF_LOADZONE +from .const import DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema({vol.Required(CONF_LOADZONE): vol.In(LOAD_ZONES)}) + + +async def validate_input(hass: core.HomeAssistant, data): + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + client_session = aiohttp_client.async_get_clientsession(hass) + + try: + await AsyncGriddy( + client_session, settlement_point=data[CONF_LOADZONE] + ).async_getnow() + except (asyncio.TimeoutError, ClientError): + raise CannotConnect + + # Return info that you want to store in the config entry. + return {"title": f"Load Zone {data[CONF_LOADZONE]}"} + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Griddy Power.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + info = None + if user_input is not None: + try: + info = await validate_input(self.hass, user_input) + except CannotConnect: + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + if "base" not in errors: + await self.async_set_unique_id(user_input[CONF_LOADZONE]) + self._abort_if_unique_id_configured() + return self.async_create_entry(title=info["title"], data=user_input) + + return self.async_show_form( + step_id="user", data_schema=DATA_SCHEMA, errors=errors + ) + + async def async_step_import(self, user_input): + """Handle import.""" + await self.async_set_unique_id(user_input[CONF_LOADZONE]) + self._abort_if_unique_id_configured() + + return await self.async_step_user(user_input) + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/griddy/const.py b/homeassistant/components/griddy/const.py new file mode 100644 index 00000000000000..034567a806e150 --- /dev/null +++ b/homeassistant/components/griddy/const.py @@ -0,0 +1,7 @@ +"""Constants for the Griddy Power integration.""" + +DOMAIN = "griddy" + +UPDATE_INTERVAL = 90 + +CONF_LOADZONE = "loadzone" diff --git a/homeassistant/components/griddy/manifest.json b/homeassistant/components/griddy/manifest.json new file mode 100644 index 00000000000000..d17ed846fd923c --- /dev/null +++ b/homeassistant/components/griddy/manifest.json @@ -0,0 +1,14 @@ +{ + "domain": "griddy", + "name": "Griddy Power", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/griddy", + "requirements": ["griddypower==0.1.0"], + "ssdp": [], + "zeroconf": [], + "homekit": {}, + "dependencies": [], + "codeowners": [ + "@bdraco" + ] +} diff --git a/homeassistant/components/griddy/sensor.py b/homeassistant/components/griddy/sensor.py new file mode 100644 index 00000000000000..31488650dc201c --- /dev/null +++ b/homeassistant/components/griddy/sensor.py @@ -0,0 +1,76 @@ +"""Support for August sensors.""" +import logging + +from homeassistant.helpers.entity import Entity + +from .const import CONF_LOADZONE, DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the August sensors.""" + coordinator = hass.data[DOMAIN][config_entry.entry_id] + + settlement_point = config_entry.data[CONF_LOADZONE] + + async_add_entities([GriddyPriceSensor(settlement_point, coordinator)], True) + + +class GriddyPriceSensor(Entity): + """Representation of an August sensor.""" + + def __init__(self, settlement_point, coordinator): + """Initialize the sensor.""" + self._coordinator = coordinator + self._settlement_point = settlement_point + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return "¢/kWh" + + @property + def name(self): + """Device Name.""" + return f"{self._settlement_point} Price Now" + + @property + def icon(self): + """Device Ice.""" + return "mdi:currency-usd" + + @property + def unique_id(self): + """Device Uniqueid.""" + return f"{self._settlement_point}_price_now" + + @property + def available(self): + """Return True if entity is available.""" + return self._coordinator.last_update_success + + @property + def state(self): + """Get the current price.""" + return round(float(self._coordinator.data.now.price_cents_kwh), 4) + + @property + def should_poll(self): + """Return False, updates are controlled via coordinator.""" + return False + + async def async_update(self): + """Update the entity. + + Only used by the generic entity update service. + """ + await self._coordinator.async_request_refresh() + + async def async_added_to_hass(self): + """Subscribe to updates.""" + self._coordinator.async_add_listener(self.async_write_ha_state) + + async def async_will_remove_from_hass(self): + """Undo subscription.""" + self._coordinator.async_remove_listener(self.async_write_ha_state) diff --git a/homeassistant/components/griddy/strings.json b/homeassistant/components/griddy/strings.json new file mode 100644 index 00000000000000..bedd85e750893c --- /dev/null +++ b/homeassistant/components/griddy/strings.json @@ -0,0 +1,21 @@ +{ + "config" : { + "error" : { + "cannot_connect" : "Failed to connect, please try again", + "unknown" : "Unexpected error" + }, + "title" : "Griddy", + "step" : { + "user" : { + "description" : "Your Load Zone is in your Griddy account under “Account > Meter > Load Zone.”", + "data" : { + "loadzone" : "Load Zone (Settlement Point)" + }, + "title" : "Setup your Griddy Load Zone" + } + }, + "abort" : { + "already_configured" : "This Load Zone is already configured" + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 3d752a955c5872..91fda9f1c32106 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -36,6 +36,7 @@ "gios", "glances", "gpslogger", + "griddy", "hangouts", "heos", "hisense_aehw4a1", diff --git a/requirements_all.txt b/requirements_all.txt index 1a0a7c1785557b..0dd4920e9445ac 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -641,6 +641,9 @@ greeneye_monitor==2.0 # homeassistant.components.greenwave greenwavereality==0.5.1 +# homeassistant.components.griddy +griddypower==0.1.0 + # homeassistant.components.growatt_server growattServer==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1ea989ce82cb9a..5ae7cb1c5821f2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -235,6 +235,9 @@ google-api-python-client==1.6.4 # homeassistant.components.google_pubsub google-cloud-pubsub==0.39.1 +# homeassistant.components.griddy +griddypower==0.1.0 + # homeassistant.components.ffmpeg ha-ffmpeg==2.0 diff --git a/tests/components/griddy/__init__.py b/tests/components/griddy/__init__.py new file mode 100644 index 00000000000000..415ddc3ba5cd9c --- /dev/null +++ b/tests/components/griddy/__init__.py @@ -0,0 +1 @@ +"""Tests for the Griddy Power integration.""" diff --git a/tests/components/griddy/test_config_flow.py b/tests/components/griddy/test_config_flow.py new file mode 100644 index 00000000000000..1ab29aebece5eb --- /dev/null +++ b/tests/components/griddy/test_config_flow.py @@ -0,0 +1,54 @@ +"""Test the Griddy Power config flow.""" +import asyncio + +from asynctest import MagicMock, patch + +from homeassistant import config_entries, setup +from homeassistant.components.griddy.const import DOMAIN + + +async def test_form(hass): + """Test we get the form.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + assert result["type"] == "form" + assert result["errors"] == {} + + with patch( + "homeassistant.components.griddy.config_flow.AsyncGriddy.async_getnow", + return_value=MagicMock(), + ), patch( + "homeassistant.components.griddy.async_setup", return_value=True + ) as mock_setup, patch( + "homeassistant.components.griddy.async_setup_entry", return_value=True, + ) as mock_setup_entry: + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"loadzone": "LZ_HOUSTON"}, + ) + + assert result2["type"] == "create_entry" + assert result2["title"] == "Load Zone LZ_HOUSTON" + assert result2["data"] == {"loadzone": "LZ_HOUSTON"} + await hass.async_block_till_done() + assert len(mock_setup.mock_calls) == 1 + assert len(mock_setup_entry.mock_calls) == 1 + + +async def test_form_cannot_connect(hass): + """Test we handle cannot connect error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.griddy.config_flow.AsyncGriddy.async_getnow", + side_effect=asyncio.TimeoutError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], {"loadzone": "LZ_NORTH"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} diff --git a/tests/components/griddy/test_sensor.py b/tests/components/griddy/test_sensor.py new file mode 100644 index 00000000000000..995327a9b562fa --- /dev/null +++ b/tests/components/griddy/test_sensor.py @@ -0,0 +1,39 @@ +"""The sensor tests for the griddy platform.""" +import json +import os + +from asynctest import patch +from griddypower.async_api import GriddyPriceData + +from homeassistant.components.griddy import CONF_LOADZONE, DOMAIN +from homeassistant.setup import async_setup_component + +from tests.common import load_fixture + + +async def _load_json_fixture(hass, path): + fixture = await hass.async_add_executor_job( + load_fixture, os.path.join("griddy", path) + ) + return json.loads(fixture) + + +def _mock_get_config(): + """Return a default griddy config.""" + return {DOMAIN: {CONF_LOADZONE: "LZ_HOUSTON"}} + + +async def test_houston_loadzone(hass): + """Test creation of the houston load zone.""" + + getnow_json = await _load_json_fixture(hass, "getnow.json") + griddy_price_data = GriddyPriceData(getnow_json) + with patch( + "homeassistant.components.griddy.AsyncGriddy.async_getnow", + return_value=griddy_price_data, + ): + assert await async_setup_component(hass, DOMAIN, _mock_get_config()) + await hass.async_block_till_done() + + sensor_lz_houston_price_now = hass.states.get("sensor.lz_houston_price_now") + assert sensor_lz_houston_price_now.state == "1.269" diff --git a/tests/fixtures/griddy/getnow.json b/tests/fixtures/griddy/getnow.json new file mode 100644 index 00000000000000..2bf685dac44547 --- /dev/null +++ b/tests/fixtures/griddy/getnow.json @@ -0,0 +1,600 @@ +{ + "now": { + "date": "2020-03-08T18:10:16Z", + "hour_num": "18", + "min_num": "10", + "settlement_point": "LZ_HOUSTON", + "price_type": "lmp", + "price_ckwh": "1.26900000000000000000", + "value_score": "11", + "mean_price_ckwh": "1.429706", + "diff_mean_ckwh": "-0.160706", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.544065", + "price_display": "1.3", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T13:10:16-05:00" + }, + "forecast": [ + { + "date": "2020-03-08T19:00:00Z", + "hour_num": "19", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.32000000", + "value_score": "12", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.113030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.3", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T14:00:00-05:00" + }, + { + "date": "2020-03-08T20:00:00Z", + "hour_num": "20", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.37400000", + "value_score": "12", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.059030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.4", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T15:00:00-05:00" + }, + { + "date": "2020-03-08T21:00:00Z", + "hour_num": "21", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.44700000", + "value_score": "13", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.013970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.4", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T16:00:00-05:00" + }, + { + "date": "2020-03-08T22:00:00Z", + "hour_num": "22", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.52600000", + "value_score": "13", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.092970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.5", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T17:00:00-05:00" + }, + { + "date": "2020-03-08T23:00:00Z", + "hour_num": "23", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "2.05100000", + "value_score": "17", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.617970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "2.1", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T18:00:00-05:00" + }, + { + "date": "2020-03-09T00:00:00Z", + "hour_num": "0", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "2.07400000", + "value_score": "17", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.640970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "2.1", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T19:00:00-05:00" + }, + { + "date": "2020-03-09T01:00:00Z", + "hour_num": "1", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.94400000", + "value_score": "16", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.510970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.9", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T20:00:00-05:00" + }, + { + "date": "2020-03-09T02:00:00Z", + "hour_num": "2", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.57500000", + "value_score": "14", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.141970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.6", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T21:00:00-05:00" + }, + { + "date": "2020-03-09T03:00:00Z", + "hour_num": "3", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.23700000", + "value_score": "11", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.196030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.2", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T22:00:00-05:00" + }, + { + "date": "2020-03-09T04:00:00Z", + "hour_num": "4", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "0.96200000", + "value_score": "9", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.471030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1", + "price_display_sign": "¢", + "date_local_tz": "2020-03-08T23:00:00-05:00" + }, + { + "date": "2020-03-09T05:00:00Z", + "hour_num": "5", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "0.80000000", + "value_score": "8", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.633030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "0.8", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T00:00:00-05:00" + }, + { + "date": "2020-03-09T06:00:00Z", + "hour_num": "6", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "0.70000000", + "value_score": "7", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.733030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "0.7", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T01:00:00-05:00" + }, + { + "date": "2020-03-09T07:00:00Z", + "hour_num": "7", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "0.70000000", + "value_score": "7", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.733030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "0.7", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T02:00:00-05:00" + }, + { + "date": "2020-03-09T08:00:00Z", + "hour_num": "8", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "0.70000000", + "value_score": "7", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.733030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "0.7", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T03:00:00-05:00" + }, + { + "date": "2020-03-09T09:00:00Z", + "hour_num": "9", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "0.70000000", + "value_score": "7", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.733030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "0.7", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T04:00:00-05:00" + }, + { + "date": "2020-03-09T10:00:00Z", + "hour_num": "10", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "0.90000000", + "value_score": "8", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.533030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "0.9", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T05:00:00-05:00" + }, + { + "date": "2020-03-09T11:00:00Z", + "hour_num": "11", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.00000000", + "value_score": "9", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.433030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T06:00:00-05:00" + }, + { + "date": "2020-03-09T12:00:00Z", + "hour_num": "12", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.10000000", + "value_score": "10", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.333030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.1", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T07:00:00-05:00" + }, + { + "date": "2020-03-09T13:00:00Z", + "hour_num": "13", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.10000000", + "value_score": "10", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.333030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.1", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T08:00:00-05:00" + }, + { + "date": "2020-03-09T14:00:00Z", + "hour_num": "14", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.20000000", + "value_score": "11", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.233030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.2", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T09:00:00-05:00" + }, + { + "date": "2020-03-09T15:00:00Z", + "hour_num": "15", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.20000000", + "value_score": "11", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.233030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.2", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T10:00:00-05:00" + }, + { + "date": "2020-03-09T16:00:00Z", + "hour_num": "16", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.30000000", + "value_score": "11", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.133030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.3", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T11:00:00-05:00" + }, + { + "date": "2020-03-09T17:00:00Z", + "hour_num": "17", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.40000000", + "value_score": "12", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.033030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.4", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T12:00:00-05:00" + }, + { + "date": "2020-03-09T18:00:00Z", + "hour_num": "18", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.50000000", + "value_score": "13", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.066970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.5", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T13:00:00-05:00" + }, + { + "date": "2020-03-09T19:00:00Z", + "hour_num": "19", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.60000000", + "value_score": "14", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.166970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.6", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T14:00:00-05:00" + }, + { + "date": "2020-03-09T20:00:00Z", + "hour_num": "20", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.60000000", + "value_score": "14", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.166970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.6", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T15:00:00-05:00" + }, + { + "date": "2020-03-09T21:00:00Z", + "hour_num": "21", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.60000000", + "value_score": "14", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.166970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.6", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T16:00:00-05:00" + }, + { + "date": "2020-03-09T22:00:00Z", + "hour_num": "22", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "2.10000000", + "value_score": "18", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.666970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "2.1", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T17:00:00-05:00" + }, + { + "date": "2020-03-09T23:00:00Z", + "hour_num": "23", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "3.20000000", + "value_score": "27", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "1.766970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "3.2", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T18:00:00-05:00" + }, + { + "date": "2020-03-10T00:00:00Z", + "hour_num": "0", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "2.40000000", + "value_score": "20", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.966970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "2.4", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T19:00:00-05:00" + }, + { + "date": "2020-03-10T01:00:00Z", + "hour_num": "1", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "2.00000000", + "value_score": "17", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.566970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "2", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T20:00:00-05:00" + }, + { + "date": "2020-03-10T02:00:00Z", + "hour_num": "2", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.70000000", + "value_score": "15", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "0.266970", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.7", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T21:00:00-05:00" + }, + { + "date": "2020-03-10T03:00:00Z", + "hour_num": "3", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.40000000", + "value_score": "12", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.033030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.4", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T22:00:00-05:00" + }, + { + "date": "2020-03-10T04:00:00Z", + "hour_num": "4", + "min_num": "0", + "settlement_point": "LZ_HOUSTON", + "price_type": "dam", + "price_ckwh": "1.20000000", + "value_score": "11", + "mean_price_ckwh": "1.433030", + "diff_mean_ckwh": "-0.233030", + "high_ckwh": "3.200000", + "low_ckwh": "0.700000", + "std_dev_ckwh": "0.552149", + "price_display": "1.2", + "price_display_sign": "¢", + "date_local_tz": "2020-03-09T23:00:00-05:00" + } + ], + "seconds_until_refresh": "26" +}