Skip to content
Draft
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
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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", ...]
Expand Down
112 changes: 112 additions & 0 deletions tests/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down Expand Up @@ -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
34 changes: 33 additions & 1 deletion tinygrid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
14 changes: 11 additions & 3 deletions tinygrid/__init__.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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",
)
8 changes: 8 additions & 0 deletions tinygrid/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Loading
Loading