Metadata-Version: 2.5
Name: hydropeak-opendata
Version: 0.1.0
Summary: Async client for Hydro-Québec's public peak events (pointes hivernales) open data
Project-URL: Homepage, https://github.com/Beat-YT/hydropeak-opendata
Project-URL: Issues, https://github.com/Beat-YT/hydropeak-opendata/issues
Author: Beat-YT
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Home Automation
Requires-Python: >=3.11
Requires-Dist: aiohttp>=3.9
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-aiohttp>=1.1; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# hydropeak-opendata

Async Python client for Hydro-Québec's public **peak events** (pointes hivernales) open data. No account, no login — just the open data feed.

Extracted from the [HydroPeak](https://github.com/Beat-YT/hydropeak-ha) Home Assistant integration.

## Data sources

- **Peak events feed** — `pointeshivernales.json`: available offers (canonical, verbatim) and scheduled peak events.
- **Offer descriptions** — the `evenements-de-pointe-offres-disponibles` Opendatasoft dataset (rate limited; intended for occasional use such as setup flows).

## Usage

```python
import aiohttp
from hydropeak_opendata import OpenDataClient

async def main():
    async with aiohttp.ClientSession() as session:
        client = OpenDataClient(session)

        offers = await client.get_available_offers()
        # ('Credit hivernal Residentiel (CPC-D)', 'Flex Residentiel (TPC-DPC)', ...)

        events = await client.get_events(offers[0])
        for event in events:
            print(event.start, event.end, event.period, event.duration)
```

Notes:

- Offer identifiers are the strings published in `offresDisponibles`, used verbatim. The library applies no transformation; `get_events(offer)` filters by exact match.
- The client sends conditional requests (`If-None-Match`) and serves its cached parse on `304 Not Modified`, so frequent polling is cheap for both sides. Concurrent refreshes are serialized on a lock.
- All datetimes from the feed are timezone-aware. `PeakEventsFeed.last_execution` is naive (the feed publishes it without an offset).
- Errors raise typed exceptions: `OpenDataConnectionError`, `OpenDataRateLimitError`, `OpenDataResponseError`, `OpenDataParseError` — all subclasses of `OpenDataError`. Failures never silently return empty data.

## Development

```
pip install -e .[dev]
ruff check .
mypy
pytest
```

## License

MIT
