diff --git a/CLAUDE.md b/CLAUDE.md index 306bc29..c7ee102 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,6 +35,12 @@ Tiny Grid is a Python SDK for accessing electricity grid data from US Independen 5. **Bulk Download Limit**: 1,000 documents per request - Archive bulk downloads limited to 1,000 files per POST request +6. **RTC+B Changes**: December 4, 2024 + - Real-Time Co-optimization + Batteries went live + - ESR (Energy Storage Resource) type added for batteries + - Real-time AS co-optimization replaces legacy ORDC + - All REST API endpoints remain compatible (additive changes only) + ## Architecture The project follows a three-layer architecture: diff --git a/README.md b/README.md index aba6a67..890bf2e 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,55 @@ Important limitations to be aware of: For data before December 2023, use `get_rtm_spp_historical()`, `get_dam_spp_historical()`, or the EIA integration. +## RTC+B (Real-Time Co-optimization + Batteries) Support + +ERCOT launched RTC+B (Real-Time Co-optimization + Batteries) on December 4, 2024. This SDK fully supports both the new RTC+B changes and legacy endpoints: + +### What Changed with RTC+B + +| Change | Description | +|--------|-------------| +| **ESR Resource Type** | Energy Storage Resources (batteries) now participate in energy and AS markets | +| **Real-Time AS Co-optimization** | Ancillary services are now procured in real-time alongside energy | +| **New Price Signals** | RT SCED Price Adders, RT 15-min Price Adders replace legacy ORDC-based adders | +| **ESR Data Fields** | New columns in disclosure reports for ESR capacity, awards, and state of charge | + +### Using RTC+B Data + +```python +from tinygrid import ERCOT, ResourceType, AncillaryServiceType + +ercot = ERCOT(auth=auth) + +# Get ESR data from dashboard (no auth required) +esr_data = ercot.get_energy_storage_resources() + +# Get ancillary service data (includes ESR participation) +as_prices = ercot.get_as_prices(start="today") + +# Access resource type constants for filtering +print(ResourceType.ESR) # "ESR" - Energy Storage Resource +print(ResourceType.GEN) # "GEN" - Generation Resource +print(ResourceType.CLR) # "CLR" - Controllable Load Resource + +# Access ancillary service type constants +print(AncillaryServiceType.REGUP) # "REGUP" - Regulation Up +print(AncillaryServiceType.ECRSM) # "ECRSM" - ECRS Slow (10-minute) +print(AncillaryServiceType.RRSFFR) # "RRSFFR" - RRS Fast Frequency Response +``` + +### Legacy Endpoint Compatibility + +All existing REST API endpoints continue to work unchanged. The RTC+B changes are primarily: + +1. **Additive** - New data columns in existing endpoint responses +2. **EWS Changes** - SOAP/XML web services have structural changes (not used by this SDK) +3. **Deprecations** - SASM-related fields deprecated in EWS (legacy endpoints still work) + +**Key Dates:** +- ESR Integration: June 1, 2024 +- RTC+B Go-Live: December 4, 2024 + ## Available ERCOT Endpoints Direct access to 100+ ERCOT endpoints organized by category: @@ -315,7 +364,14 @@ All methods accept `**kwargs` for additional API parameters like `size`, `page`, ## Constants and Enums ```python -from tinygrid.constants import Market, LocationType, LOAD_ZONES, TRADING_HUBS +from tinygrid.constants import ( + Market, + LocationType, + ResourceType, # New: RTC+B resource types + AncillaryServiceType, # New: AS types + LOAD_ZONES, + TRADING_HUBS, +) # Market types Market.REAL_TIME_SCED # Real-time SCED (5-minute) @@ -328,6 +384,20 @@ LocationType.TRADING_HUB # Trading hubs (HB_*) LocationType.RESOURCE_NODE # Resource nodes LocationType.ELECTRICAL_BUS # Electrical buses +# Resource types (includes RTC+B ESR) +ResourceType.GEN # Generation Resource +ResourceType.ESR # Energy Storage Resource (batteries) +ResourceType.CLR # Controllable Load Resource +ResourceType.WGR # Wind Generation Resource +ResourceType.PVGR # Solar (Photovoltaic) Generation Resource + +# Ancillary service types +AncillaryServiceType.REGUP # Regulation Up +AncillaryServiceType.REGDN # Regulation Down +AncillaryServiceType.NSPIN # Non-Spinning Reserve +AncillaryServiceType.ECRSM # Emergency Contingency Reserve - Slow +AncillaryServiceType.RRSFFR # Responsive Reserve - Fast Frequency Response + # Pre-defined location lists LOAD_ZONES = ["LZ_HOUSTON", "LZ_NORTH", "LZ_SOUTH", "LZ_WEST", ...] TRADING_HUBS = ["HB_HOUSTON", "HB_NORTH", "HB_SOUTH", "HB_WEST", ...] diff --git a/tests/test_constants.py b/tests/test_constants.py index ce8576b..329c6be 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -5,13 +5,17 @@ EMIL_IDS, ENDPOINT_MAPPINGS, ERCOT_TIMEZONE, + ESR_INTEGRATION_DATE, HISTORICAL_THRESHOLD_DAYS, LIVE_API_RETENTION, LOAD_ZONES, PUBLIC_API_BASE_URL, + RTC_B_LAUNCH_DATE, TRADING_HUBS, + AncillaryServiceType, LocationType, Market, + ResourceType, SettlementPointType, ) @@ -236,3 +240,111 @@ def test_column_mappings_values_are_strings(self): """Test that all column mapping values are strings.""" for value in COLUMN_MAPPINGS.values(): assert isinstance(value, str) + + +class TestResourceTypeEnum: + """Test ResourceType enum (RTC+B). + + RTC+B (Real-Time Co-optimization + Batteries) introduced ESR as a new + resource type for batteries in December 2024. + """ + + def test_resource_type_generation_values(self): + """Test generation resource type values.""" + assert ResourceType.GEN == "GEN" + assert ResourceType.WGR == "WGR" + assert ResourceType.PVGR == "PVGR" + assert ResourceType.SMNE == "SMNE" + + def test_resource_type_load_values(self): + """Test load resource type values.""" + assert ResourceType.CLR == "CLR" + assert ResourceType.LR == "LR" + assert ResourceType.DSR == "DSR" + + def test_resource_type_esr_values(self): + """Test ESR resource type values (RTC+B addition).""" + assert ResourceType.ESR == "ESR" + assert ResourceType.DESR == "DESR" + assert ResourceType.DGR == "DGR" + + def test_resource_type_is_string(self): + """Test that ResourceType enum values are strings.""" + assert isinstance(ResourceType.ESR, str) + assert isinstance(ResourceType.GEN, str) + + def test_resource_type_str_representation(self): + """Test string representation of ResourceType enum.""" + assert str(ResourceType.ESR) == "ESR" + assert str(ResourceType.GEN) == "GEN" + + +class TestAncillaryServiceTypeEnum: + """Test AncillaryServiceType enum. + + RTC+B modified how ancillary services are procured in real-time with + co-optimization of energy and AS. + """ + + def test_as_type_regulation_values(self): + """Test regulation service type values.""" + assert AncillaryServiceType.REGUP == "REGUP" + assert AncillaryServiceType.REGDN == "REGDN" + + def test_as_type_rrs_values(self): + """Test responsive reserve service type values.""" + assert AncillaryServiceType.RRSPFR == "RRSPFR" + assert AncillaryServiceType.RRSFFR == "RRSFFR" + assert AncillaryServiceType.RRSUFR == "RRSUFR" + + def test_as_type_nspin_values(self): + """Test non-spinning reserve type values.""" + assert AncillaryServiceType.NSPIN == "NSPIN" + assert AncillaryServiceType.NSPNM == "NSPNM" + assert AncillaryServiceType.ONNS == "ONNS" + assert AncillaryServiceType.OFFNS == "OFFNS" + + def test_as_type_ecrs_values(self): + """Test ECRS type values.""" + assert AncillaryServiceType.ECRSM == "ECRSM" + assert AncillaryServiceType.ECRSS == "ECRSS" + + def test_as_type_is_string(self): + """Test that AncillaryServiceType enum values are strings.""" + assert isinstance(AncillaryServiceType.REGUP, str) + assert isinstance(AncillaryServiceType.NSPIN, str) + + def test_as_type_str_representation(self): + """Test string representation of AncillaryServiceType enum.""" + assert str(AncillaryServiceType.REGUP) == "REGUP" + assert str(AncillaryServiceType.ECRSM) == "ECRSM" + + +class TestRTCBConstants: + """Test RTC+B (Real-Time Co-optimization + Batteries) constants.""" + + def test_rtc_b_launch_date(self): + """Test RTC+B launch date constant.""" + assert RTC_B_LAUNCH_DATE == "2024-12-04" + assert isinstance(RTC_B_LAUNCH_DATE, str) + + def test_esr_integration_date(self): + """Test ESR integration date constant.""" + assert ESR_INTEGRATION_DATE == "2024-06-01" + assert isinstance(ESR_INTEGRATION_DATE, str) + + def test_dates_are_valid_iso_format(self): + """Test that RTC+B dates are valid ISO format.""" + import datetime + + # Should not raise exception + datetime.date.fromisoformat(RTC_B_LAUNCH_DATE) + datetime.date.fromisoformat(ESR_INTEGRATION_DATE) + + def test_esr_integration_before_rtc_b(self): + """Test that ESR integration date is before RTC+B launch.""" + import datetime + + esr_date = datetime.date.fromisoformat(ESR_INTEGRATION_DATE) + rtc_b_date = datetime.date.fromisoformat(RTC_B_LAUNCH_DATE) + assert esr_date < rtc_b_date diff --git a/tinygrid/README.md b/tinygrid/README.md index 780d665..5a7f0f3 100644 --- a/tinygrid/README.md +++ b/tinygrid/README.md @@ -223,10 +223,42 @@ Standalone data transformation functions: - `GridRateLimitError` - Rate limited (429) - `GridRetryExhaustedError` - Max retries exceeded +## RTC+B Support + +The SDK supports ERCOT's RTC+B (Real-Time Co-optimization + Batteries) changes that went live in December 2024: + +### New Resource Types + +```python +from tinygrid import ResourceType, AncillaryServiceType + +# ESR (Energy Storage Resource) for batteries +ResourceType.ESR # "ESR" +ResourceType.DESR # "DESR" - Distributed ESR +ResourceType.DGR # "DGR" - Distributed Generation Resource + +# All ancillary service types +AncillaryServiceType.REGUP # Regulation Up +AncillaryServiceType.REGDN # Regulation Down +AncillaryServiceType.NSPIN # Non-Spinning Reserve +AncillaryServiceType.ECRSM # ECRS Slow (10-minute) +AncillaryServiceType.ECRSS # ECRS Super Slow (30-minute) +AncillaryServiceType.RRSFFR # RRS Fast Frequency Response +``` + +### Key Dates + +- **ESR Integration**: June 1, 2024 +- **RTC+B Go-Live**: December 4, 2024 + +### Legacy Compatibility + +All existing endpoints continue to work. The REST API paths remain unchanged - only the data within responses may include new ESR-related fields. + ## Tests ```bash pytest tests/ ``` -746 tests with 95% code coverage. +821 tests with 96% code coverage. diff --git a/tinygrid/__init__.py b/tinygrid/__init__.py index fe39886..574bce0 100644 --- a/tinygrid/__init__.py +++ b/tinygrid/__init__.py @@ -1,7 +1,13 @@ """Tiny Grid - A unified Python SDK for accessing grid data from all major US ISOs""" from .auth import ERCOTAuth, ERCOTAuthConfig -from .constants import LocationType, Market, SettlementPointType +from .constants import ( + AncillaryServiceType, + LocationType, + Market, + ResourceType, + SettlementPointType, +) from .ercot import ERCOT, ERCOTArchive from .errors import ( GridAPIError, @@ -20,20 +26,22 @@ __all__ = ( # Client "ERCOT", + # Constants/Enums + "AncillaryServiceType", # Historical "ERCOTArchive", # Auth "ERCOTAuth", "ERCOTAuthConfig", + # Errors "GridAPIError", "GridAuthenticationError", - # Errors "GridError", "GridRateLimitError", "GridRetryExhaustedError", "GridTimeoutError", "LocationType", - # Constants/Enums "Market", + "ResourceType", "SettlementPointType", ) diff --git a/tinygrid/constants/__init__.py b/tinygrid/constants/__init__.py index ffef4ae..cfb7a21 100644 --- a/tinygrid/constants/__init__.py +++ b/tinygrid/constants/__init__.py @@ -3,23 +3,31 @@ from .ercot import ( COLUMN_MAPPINGS, ERCOT_TIMEZONE, + ESR_INTEGRATION_DATE, HISTORICAL_THRESHOLD_DAYS, LIVE_API_RETENTION, LOAD_ZONES, + RTC_B_LAUNCH_DATE, TRADING_HUBS, + AncillaryServiceType, LocationType, Market, + ResourceType, SettlementPointType, ) __all__ = [ "COLUMN_MAPPINGS", "ERCOT_TIMEZONE", + "ESR_INTEGRATION_DATE", "HISTORICAL_THRESHOLD_DAYS", "LIVE_API_RETENTION", "LOAD_ZONES", + "RTC_B_LAUNCH_DATE", "TRADING_HUBS", + "AncillaryServiceType", "LocationType", "Market", + "ResourceType", "SettlementPointType", ] diff --git a/tinygrid/constants/ercot.py b/tinygrid/constants/ercot.py index 83e2e6b..f9d2a9a 100644 --- a/tinygrid/constants/ercot.py +++ b/tinygrid/constants/ercot.py @@ -77,6 +77,43 @@ def __str__(self): # Rate limit (requests per minute) API_RATE_LIMIT = 30 +# ============================================================================ +# RTC+B (Real-Time Co-optimization + Batteries) Changes +# ============================================================================ +# +# RTC+B went live in December 2024 and includes: +# +# 1. ESR (Energy Storage Resource) Integration +# - Batteries can now participate in energy and AS markets +# - New resource type: ESR +# - Bidirectional dispatch (charge/discharge) +# - State of Charge (SOC) constraints +# +# 2. Real-Time AS Co-optimization +# - AS is now procured in real-time alongside energy +# - New price signals: RT SCED Price Adders, RT 15-min Price Adders +# - Replaced legacy ORDC-based price adders +# +# 3. New Data Fields +# - ESR-specific capacity and award columns in disclosure reports +# - New HDL/LDL fields for ESR base point adjustments +# - New MCPC fields for RT AS clearing prices +# +# Legacy Endpoint Support: +# - All existing REST API endpoints continue to work +# - Data returned may include new ESR-related columns +# - SASM (Supplemental AS Market) fields deprecated in EWS +# +# For more information: +# - ERCOT Technical Reference: https://www.ercot.com/mktrules/guides +# - API Specs: https://github.com/ercot/api-specs + +# Date when RTC+B went live +RTC_B_LAUNCH_DATE = "2024-12-04" + +# Date when ESR integration was enabled +ESR_INTEGRATION_DATE = "2024-06-01" + class Market(StrEnum): """ERCOT market types for price data.""" @@ -103,6 +140,58 @@ class SettlementPointType(StrEnum): RN = "RN" # Resource Node +class ResourceType(StrEnum): + """ERCOT resource types. + + RTC+B (Real-Time Co-optimization + Batteries) introduced ESR as a new + resource type in December 2024. ESRs can provide both energy and + ancillary services with bidirectional dispatch (charge/discharge). + """ + + # Generation Resources + GEN = "GEN" # Generation Resource + WGR = "WGR" # Wind Generation Resource + PVGR = "PVGR" # Photovoltaic (Solar) Generation Resource + SMNE = "SMNE" # Small Non-Exempt Generation Resource + + # Load Resources + CLR = "CLR" # Controllable Load Resource + LR = "LR" # Load Resource + DSR = "DSR" # Demand Side Response Resource + + # Energy Storage Resources (RTC+B) + ESR = "ESR" # Energy Storage Resource (batteries, added in RTC+B) + DESR = "DESR" # Distributed Energy Storage Resource + DGR = "DGR" # Distributed Generation Resource + + +class AncillaryServiceType(StrEnum): + """ERCOT Ancillary Service types. + + RTC+B modified how these services are procured in real-time with + co-optimization of energy and AS. ESRs can now provide most AS types. + """ + + # Regulation Services + REGUP = "REGUP" # Regulation Up + REGDN = "REGDN" # Regulation Down + + # Responsive Reserve Services (RRS) + RRSPFR = "RRSPFR" # Responsive Reserve - Primary Frequency Response + RRSFFR = "RRSFFR" # Responsive Reserve - Fast Frequency Response + RRSUFR = "RRSUFR" # Responsive Reserve - Ultra-Fast Frequency Response + + # Non-Spinning Reserve + NSPIN = "NSPIN" # Non-Spinning Reserve (Online) + NSPNM = "NSPNM" # Non-Spinning Reserve (Non-Market) + ONNS = "ONNS" # Online Non-Spinning Reserve + OFFNS = "OFFNS" # Offline Non-Spinning Reserve + + # Emergency Contingency Reserve Service (ECRS) + ECRSM = "ECRSM" # ECRS - Slow (10-minute) + ECRSS = "ECRSS" # ECRS - Super Slow (30-minute) + + # ERCOT Load Zones LOAD_ZONES = [ "LZ_HOUSTON",