Source code for gridstatus.tests.test_caiso

import math

import pandas as pd
import pytest

from gridstatus import CAISO, Markets
from gridstatus.base import NoDataFoundException
from gridstatus.caiso import (
    REAL_TIME_DISPATCH_MARKET_RUN_ID,
)
from gridstatus.tests.base_test_iso import BaseTestISO
from gridstatus.tests.decorators import with_markets


[docs]class TestCAISO(BaseTestISO): iso = CAISO() trading_hub_locations = iso.trading_hub_locations """get_as"""
[docs] def test_get_as_prices(self): date = "Oct 15, 2022" df = self.iso.get_as_prices(date) assert df.shape[0] > 0 assert df.columns.tolist() == [ "Time", "Interval Start", "Interval End", "Region", "Market", "Non-Spinning Reserves", "Regulation Down", "Regulation Mileage Down", "Regulation Mileage Up", "Regulation Up", "Spinning Reserves", ]
[docs] def test_get_as_procurement(self): date = "Oct 15, 2022" for market in ["DAM", "RTM"]: df = self.iso.get_as_procurement(date, market=market) self._check_as_data(df, market)
"""get_fuel_mix"""
[docs] def test_fuel_mix_across_dst_transition(self): # these dates are across the DST transition # and caused a bug in the past date = ( pd.Timestamp("2023-11-05 09:55:00+0000", tz="UTC"), pd.Timestamp("2023-11-05 20:49:26.038069+0000", tz="UTC"), ) df = self.iso.get_fuel_mix(date=date) self._check_fuel_mix(df)
"""get_load_forecast"""
[docs] def test_get_load_forecast_publish_time(self): df = self.iso.get_load_forecast("today") assert df.columns.tolist() == [ "Time", "Interval Start", "Interval End", "Publish Time", "Load Forecast", ] assert df["Publish Time"].nunique() == 1 assert df["Publish Time"].max() < self.local_now()
"""get_solar_and_wind_forecast_dam""" def _check_solar_and_wind_forecast(self, df, expected_count_unique_publish_times): assert df.shape[0] > 0 assert df.columns.tolist() == [ "Interval Start", "Interval End", "Publish Time", "Location", "Solar MW", "Wind MW", ] assert df["Location"].unique().tolist() == ["CAISO", "NP15", "SP15", "ZP26"] totals = df.loc[df["Location"] == "CAISO"] non_totals = df.loc[df["Location"] != "CAISO"] assert math.isclose( totals["Solar MW"].sum(), non_totals["Solar MW"].sum(), rel_tol=0.01, ) assert math.isclose( totals["Wind MW"].sum(), non_totals["Wind MW"].sum(), rel_tol=0.01, ) self._check_time_columns( df, instant_or_interval="interval", skip_column_named_time=True, ) # Make sure there are no future publish times assert df["Publish Time"].max() < self.local_now() assert df["Publish Time"].nunique() == expected_count_unique_publish_times
[docs] def test_get_solar_and_wind_forecast_dam_today(self): df = self.iso.get_solar_and_wind_forecast_dam("today") self._check_solar_and_wind_forecast(df, 1) assert df["Interval Start"].min() == self.local_start_of_today() assert df["Interval Start"].max() == self.local_start_of_today() + pd.Timedelta( hours=23, )
[docs] def test_get_solar_and_wind_forecast_dam_latest(self): assert self.iso.get_solar_and_wind_forecast_dam("latest").equals( self.iso.get_solar_and_wind_forecast_dam("today"), )
[docs] def test_get_solar_and_wind_forecast_dam_historical_date(self): df = self.iso.get_solar_and_wind_forecast_dam("2024-02-20") self._check_solar_and_wind_forecast(df, 1) assert df["Interval Start"].min() == self.local_start_of_day("2024-02-20") assert df["Interval Start"].max() == self.local_start_of_day( "2024-02-20", ) + pd.Timedelta(hours=23)
[docs] def test_get_solar_and_wind_forecast_dam_historical_range(self): start = pd.Timestamp("2023-08-15") end = pd.Timestamp("2023-08-21") df = self.iso.get_solar_and_wind_forecast_dam(start, end=end) # Only 6 days of data because the end date is exclusive self._check_solar_and_wind_forecast(df, 6) assert df["Interval Start"].min() == self.local_start_of_day(start) assert df["Interval Start"].max() == self.local_start_of_day( end, ) - pd.Timedelta(hours=1)
[docs] def test_get_solar_and_wind_forecast_dam_future_date_range(self): start = self.local_today() + pd.Timedelta(days=1) end = start + pd.Timedelta(days=2) df = self.iso.get_solar_and_wind_forecast_dam(start, end=end) self._check_solar_and_wind_forecast(df, 1)
"""get_curtailment""" def _check_curtailment(self, df): assert df.shape[0] > 0 assert df.columns.tolist() == [ "Time", "Interval Start", "Interval End", "Curtailment Type", "Curtailment Reason", "Fuel Type", "Curtailment (MWh)", "Curtailment (MW)", ] self._check_time_columns(df)
[docs] def test_get_curtailment(self): date = "Oct 15, 2022" df = self.iso.get_curtailment(date) assert df.shape == (31, 8) self._check_curtailment(df)
[docs] def test_get_curtailment_2_pages(self): # test that the function can handle 3 pages of data date = "March 15, 2022" df = self.iso.get_curtailment(date) assert df.shape == (55, 8) self._check_curtailment(df)
[docs] def test_get_curtailment_3_pages(self): # test that the function can handle 3 pages of data date = "March 16, 2022" df = self.iso.get_curtailment(date) assert df.shape == (76, 8) self._check_curtailment(df)
"""get_gas_prices"""
[docs] def test_get_gas_prices(self): date = "Oct 15, 2022" # no fuel region df = self.iso.get_gas_prices(date=date) n_unique = 153 assert df["Fuel Region Id"].nunique() == n_unique assert len(df) == n_unique * 24 # single fuel region test_region_1 = "FRPGE2GHG" df = self.iso.get_gas_prices(date=date, fuel_region_id=test_region_1) assert df["Fuel Region Id"].unique()[0] == test_region_1 assert len(df) == 24 # list of fuel regions test_region_2 = "FRSCE8GHG" df = self.iso.get_gas_prices( date=date, fuel_region_id=[ test_region_1, test_region_2, ], ) assert set(df["Fuel Region Id"].unique()) == set( [test_region_1, test_region_2], ) assert len(df) == 24 * 2
"""get_fuel_regions"""
[docs] def test_get_fuel_regions(self): df = self.iso.get_fuel_regions() assert df.columns.tolist() == [ "Fuel Region Id", "Pricing Hub", "Transportation Cost", "Fuel Reimbursement Rate", "Cap and Trade Credit", "Miscellaneous Costs", "Balancing Authority", ] assert df.shape[0] > 180
"""get_ghg_allowance"""
[docs] def test_get_ghg_allowance(self): date = "Oct 15, 2022" df = self.iso.get_ghg_allowance(date) assert len(df) == 1 assert df.columns.tolist() == [ "Time", "Interval Start", "Interval End", "GHG Allowance Price", ]
"""get_lmp""" @with_markets( Markets.DAY_AHEAD_HOURLY, )
[docs] def test_lmp_date_range(self, market): super().test_lmp_date_range(market=market)
@with_markets( Markets.DAY_AHEAD_HOURLY, Markets.REAL_TIME_15_MIN, Markets.REAL_TIME_5_MIN, )
[docs] def test_get_lmp_historical(self, market): super().test_get_lmp_historical(market=market)
@with_markets( Markets.DAY_AHEAD_HOURLY, Markets.REAL_TIME_15_MIN, Markets.REAL_TIME_5_MIN, )
[docs] def test_get_lmp_latest(self, market): super().test_get_lmp_latest(market=market)
[docs] def test_get_lmp_locations_must_be_list(self): date = "today" with pytest.raises(AssertionError): self.iso.get_lmp(date, locations="foo", market="REAL_TIME_5_MIN")
@with_markets( Markets.DAY_AHEAD_HOURLY, Markets.REAL_TIME_15_MIN, Markets.REAL_TIME_5_MIN, )
[docs] def test_get_lmp_today(self, market): super().test_get_lmp_today(market=market)
[docs] def test_get_lmp_with_locations_range_dam(self): end = pd.Timestamp("today").normalize() start = end - pd.Timedelta(days=3) locations = self.iso.trading_hub_locations df = self.iso.get_lmp( start=start, end=end, locations=locations, market="DAY_AHEAD_HOURLY", ) # assert all days are present assert df["Location"].nunique() == len(locations)
# all nodes having problems # also not working on oasis web portal # as of may 11, 2023 # def test_get_lmp_all_locations_dam(self): # yesterday = pd.Timestamp("today").normalize() - pd.Timedelta(days=1) # df = self.iso.get_lmp( # date=yesterday, # locations="ALL", # market="DAY_AHEAD_HOURLY", # verbose=True, # ) # # assert approx 16000 locations # assert df["Location"].nunique() > 16000
[docs] def test_get_lmp_all_ap_nodes_locations(self): yesterday = pd.Timestamp("today").normalize() - pd.Timedelta(days=1) df = self.iso.get_lmp( date=yesterday, locations="ALL_AP_NODES", market="DAY_AHEAD_HOURLY", ) # assert approx 2300 locations assert df["Location"].nunique() > 2300
[docs] def test_get_lmp_with_all_locations_range(self): end = pd.Timestamp("today").tz_localize( self.iso.default_timezone, ).normalize() - pd.Timedelta(days=2) start = end - pd.Timedelta(days=3) df = self.iso.get_lmp( start=start, end=end, locations="ALL_AP_NODES", market="DAY_AHEAD_HOURLY", ) # assert all days are present assert df["Time"].dt.date.nunique() == 3
[docs] def test_get_lmp_all_locations_real_time_2_hour(self): # test two hours start = pd.Timestamp("now").tz_localize("UTC").normalize() - pd.Timedelta( days=1, ) end = start + pd.Timedelta(hours=2) df = self.iso.get_lmp( start=start, end=end, locations="ALL_AP_NODES", market="REAL_TIME_15_MIN", verbose=True, ) # assert approx 2300 locations assert df["Location"].nunique() > 2300 assert df["Interval Start"].dt.hour.nunique() == 2
[docs] def test_get_lmp_too_far_in_past_raises_custom_exception(self): too_old_date = pd.Timestamp.now().date() - pd.Timedelta(days=1201) with pytest.raises(NoDataFoundException): self.iso.get_lmp( date=too_old_date, locations=self.trading_hub_locations, market="REAL_TIME_15_MIN", ) valid_date = pd.Timestamp.now().date() - pd.Timedelta(days=1000) df = self.iso.get_lmp( date=valid_date, locations=self.trading_hub_locations, market="REAL_TIME_15_MIN", ) assert not df.empty
[docs] def test_warning_no_end_date(self): start = pd.Timestamp("2021-04-01T03:00").tz_localize("UTC") with ( pytest.warns( UserWarning, match="Only 1 hour of data will be returned for real time markets if end is not specified and all nodes are requested", # noqa ), ): try: self.iso.get_lmp( start=start, locations="ALL_AP_NODES", market="REAL_TIME_15_MIN", ) except NoDataFoundException: pass
@staticmethod def _check_as_data(df, market): columns = [ "Time", "Interval Start", "Interval End", "Region", "Market", "Non-Spinning Reserves Procured (MW)", "Non-Spinning Reserves Self-Provided (MW)", "Non-Spinning Reserves Total (MW)", "Non-Spinning Reserves Total Cost", "Regulation Down Procured (MW)", "Regulation Down Self-Provided (MW)", "Regulation Down Total (MW)", "Regulation Down Total Cost", "Regulation Mileage Down Procured (MW)", "Regulation Mileage Down Self-Provided (MW)", "Regulation Mileage Down Total (MW)", "Regulation Mileage Down Total Cost", "Regulation Mileage Up Procured (MW)", "Regulation Mileage Up Self-Provided (MW)", "Regulation Mileage Up Total (MW)", "Regulation Mileage Up Total Cost", "Regulation Up Procured (MW)", "Regulation Up Self-Provided (MW)", "Regulation Up Total (MW)", "Regulation Up Total Cost", "Spinning Reserves Procured (MW)", "Spinning Reserves Self-Provided (MW)", "Spinning Reserves Total (MW)", "Spinning Reserves Total Cost", ] assert df.columns.tolist() == columns assert df["Market"].unique()[0] == market assert df.shape[0] > 0
[docs] def test_get_curtailed_non_operational_generator_report(self): columns = [ "Publish Time", "Outage MRID", "Resource Name", "Resource ID", "Outage Type", "Nature of Work", "Curtailment Start Time", "Curtailment End Time", "Curtailment MW", "Resource PMAX MW", "Net Qualifying Capacity MW", ] start_of_data = pd.Timestamp("2021-06-17") df = self.iso.get_curtailed_non_operational_generator_report( date=start_of_data, ) assert df.shape[0] > 0 assert df.columns.tolist() == columns two_days_ago = pd.Timestamp("today") - pd.Timedelta(days=2) df = self.iso.get_curtailed_non_operational_generator_report( date=two_days_ago.normalize(), ) assert df.shape[0] > 0 assert df.columns.tolist() == columns date_with_duplicates = pd.Timestamp("2021-11-07") df = self.iso.get_curtailed_non_operational_generator_report( date=date_with_duplicates, ) assert df.shape[0] > 0 assert df.columns.tolist() == columns # errors for a date before 2021-06-17 with pytest.raises(ValueError): self.iso.get_curtailed_non_operational_generator_report( date="2021-06-16", ) assert df.shape[0] > 0
"""get_tie_flows_real_time""" def _check_tie_flows_real_time(self, df): assert df.columns.tolist() == [ "Interval Start", "Interval End", "Interface ID", "Tie Name", "From BAA", "To BAA", "Market", "MW", ] assert (df["Interval End"] - df["Interval Start"]).unique() == pd.Timedelta( minutes=5, ) assert df["Market"].unique() == REAL_TIME_DISPATCH_MARKET_RUN_ID assert not df.duplicated( subset=["Interval Start", "Tie Name", "From BAA", "To BAA"], ).any()
[docs] def test_get_tie_flows_real_time_latest(self): df = self.iso.get_tie_flows_real_time("latest") self._check_tie_flows_real_time(df) assert df["Interval Start"].min() == pd.Timestamp.utcnow().round("5min") assert df["Interval End"].max() == pd.Timestamp.utcnow().round( "5min", ) + pd.Timedelta(minutes=5)
[docs] def test_get_tie_flows_real_time_today(self): df = self.iso.get_tie_flows_real_time("today") self._check_tie_flows_real_time(df) assert df["Interval Start"].min() == self.local_start_of_today()
[docs] def test_get_tie_flows_real_time_historical_date_range(self): start = self.local_start_of_today() - pd.DateOffset(days=100) end = start + pd.DateOffset(days=2) df = self.iso.get_tie_flows_real_time(start, end=end) self._check_tie_flows_real_time(df) assert df["Interval Start"].min() == start assert df["Interval End"].max() == end
"""other"""
[docs] def test_oasis_no_data(self): df = self.iso.get_oasis_dataset( dataset="as_clearing_prices", date=pd.Timestamp.now() + pd.Timedelta(days=7), ) assert df.empty
[docs] def test_get_pnodes(self): df = self.iso.get_pnodes() assert df.shape[0] > 0