Metadata-Version: 2.3
Name: comed-hourly-pricing
Version: 0.1.0
Summary: Library to retrieve ComEd BESH (Basic Electric Service-Hourly) supply-rate prices.
Requires-Dist: aiohttp>=3.9
Requires-Python: >=3.13
Description-Content-Type: text/markdown

# comed-hourly-pricing

Async Python library to retrieve ComEd **BESH** (Basic Electric Service–Hourly) supply-rate pricing, both the estimated and the actual settled prices.

Prices are exposed as **dollars/kWh** (floats). The upstream feed reports cents/kWh (e.g. `1.8¢` → `0.018`).

## Endpoints

### HTML ServletFeed (hourly BESH rates)

| Method | Source feed | Columns |
| --- | --- | --- |
| `get_dual(day)` | `pricingtabledual&date=YYYYMMDD` | hour-ending, estimated, actual settled (`n/a` until settled) |
| `get_next_day()` | `pricingtabledaynexttomorrow` | hour-ending, estimated (empty until ~4:30pm Central) |

Timestamps mark the **end** of the hour and are timezone-aware in `America/Chicago`.

### JSON API (live real-time pricing)

| Method | Source feed | Returns |
| --- | --- | --- |
| `get_five_minute_feed(start, end)` | `api?type=5minutefeed` | `tuple[PricePoint, ...]` — last 24h by default, or a custom range |
| `get_current_hour_average()` | `api?type=currenthouraverage` | `PricePoint \| None` |

`start`/`end` are optional Central-local `datetime`s (sent as `YYYYMMDDhhmm`).

See [`ComEd's API documentation`](https://hourlypricing.comed.com/hp-api/) for details.

## Install / run

```bash
uv sync
```

## Usage

```python
import asyncio
from comed_hourly_pricing import Client

async def main():
    async with Client() as client:
        today = await client.get_dual()          # defaults to today (Central)
        for hour in today.settled:
            print(hour.hour_ending, hour.estimated, hour.actual)

        tomorrow = await client.get_next_day()
        if not tomorrow:
            print("Next-day prices not published yet.")

        recent = await client.get_five_minute_feed()   # last 24h, 5-min points
        for point in recent:
            print(point.timestamp, point.price)

        now = await client.get_current_hour_average()
        if now:
            print("Current hour avg:", now.price)

asyncio.run(main())
```

You may also pass your own session: `Client(session=my_session)` — a borrowed session is never closed by the client.

### Data model

- `HourlyPrice(hour_ending: datetime, estimated: float | None, actual: float | None)` — one hour's row.
- `HourlyDayPrices(date, hours)` — returned by `get_dual`; iterable/len/bool, plus `.settled` and `.unsettled` views.
- `HourlyNextDayEstimatedPrices(date, hours)` — returned by `get_next_day`; estimate-only (every row's `actual` is `None`).
- `PricePoint(timestamp: datetime, price: float)` — one live JSON-API reading; `timestamp` is tz-aware `America/Chicago`, `price` is dollars/kWh. Returned by `get_five_minute_feed` (a tuple) and `get_current_hour_average` (one, or `None`).

## CLI

```bash
uv run python -m comed_hourly_pricing --date 2026-08-02   # dual feed for a date
uv run python -m comed_hourly_pricing --next-day          # next-day estimates
uv run python -m comed_hourly_pricing --five-minute       # last 24h 5-minute feed
uv run python -m comed_hourly_pricing --current-hour      # current-hour average
```

## Polling

These feeds should **not** be polled frequently. Each method makes a single request. Throttling is the caller's responsibility.

## Tests

```bash
uv run pytest
```

Tests run fully offline against captured fixtures in `tests/fixtures/`.

## Development

Requires Python ≥ 3.13 and `aiohttp`.
