Skip to content
Open
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
8 changes: 4 additions & 4 deletions .github/workflows/black.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: psf/black@stable
- uses: actions/setup-python@v5
with:
options: "--check --verbose"
src: "./solcast"
jupyter: false
python-version: "3.12"
- run: python -m pip install black==24.8.0
- run: black --check --verbose ./solcast
4 changes: 3 additions & 1 deletion docs/forecast.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ requested locations or resources, including premium PV and wind power forecasts,
nowcasted for approx. four hours ahead) and numerical weather models (other data and longer horizons).
More information in the [API docs](https://docs.solcast.com.au/#49090b36-66db-4d0f-89d5-87d19f00bec1).

The module `forecast` has 5 available methods:
The module `forecast` has 7 available methods:

| Endpoint | API Docs |
|-------------------------|-------------------------------------------------------------------------------------------|
Expand All @@ -13,6 +13,8 @@ The module `forecast` has 5 available methods:
| `advanced_pv_power` | [details](https://docs.solcast.com.au/?#0c9d3ccf-e2a4-4583-86a3-f89c8d658fde){.md-button} |
| `premium_pv_power` | [details](https://docs.solcast.com.au/docs/section/premium#getDataForecastPremiumPvPower){.md-button} |
| `premium_wind_power` | [details](https://docs.solcast.com.au/docs/section/premium#getDataForecastPremiumWindPower){.md-button} |
| `soiling_hsu` | [details](https://docs.solcast.com.au/docs/section/soiling#getDataForecastSoilingHsu){.md-button} |
| `soiling_kimber` | [details](https://docs.solcast.com.au/docs/section/soiling#getDataForecastSoilingKimber){.md-button} |

### Example

Expand Down
24 changes: 22 additions & 2 deletions docs/pv_power_site_measurements.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Premium PV Site Measurements
# PV Site Measurements

The `pv_power_site_measurements` module manages measured power data for Premium PV Power sites.
The `pv_power_site_measurements` module manages measured power data for Premium PV Power sites and utility-scale measurements for Advanced PV sites.

All measurement records use ISO-8601 timestamps. Site measurement power is measured in MW.

Expand All @@ -11,6 +11,7 @@ See the Premium API schemas for [site upload](https://docs.solcast.com.au/docs/s
| Method | Purpose |
|---|---|
| `create_pv_site_measurements` | Submit site measurements. |
| `create_utility_scale_site_measurements` | Submit measured power data for an Advanced PV site. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this name will lead to confusion. It could be taken to mean that the other measurements are not utility scale, whereas in fact they are. That utility-scale endpoint is mainly used for the super_rapid product.

This is a bit of a mess now, because we have to clearly express what is different between create_utility_scale_pv_site_measurements and create_pv_site_measurements, with it not being clear that the latter is for premium.

Also, I think it is unfortunate that we have ended up with repetitive naming, with the same information in the module name and the function name. Compare with forecasts where it is forecast.premium_pv_power, not forecast.premium_pv_power_forecast. I should have picked up on that with previous reviews.

Analogous with that would be

measurement.create_premium_pv
measurement.create_premium_wind
measurement.create_standard_pv <- could be what we call non-premium usage?
measurement.create_premium_pv_sub_site
etc.

That would be a major breaking change. If we are doing that, I think we should more fully plan it out before we proceed. E.g. when we introduce premium aggregations, do their forecasts go under the aggregations module or the forecasts module?

| `get_pv_site_measurements` | Retrieve site measurements, with optional `start` and `end` filters. |
| `delete_pv_site_measurements` | Delete site measurements between required `start` and `end` timestamps. |
| `create_pv_sub_unit_measurements` | Submit measurements for labelled site sub-units. |
Expand Down Expand Up @@ -40,6 +41,25 @@ response = pv_power_site_measurements.create_pv_site_measurements(
)
```

## Create utility-scale site measurements

Submit measured power records for an Advanced PV site:

```python
response = pv_power_site_measurements.create_utility_scale_site_measurements(
resource_id="your-advanced-pv-resource",
measurements=[
{
"period_end": "2026-01-01T00:05:00Z",
"period": "PT5M",
"total_power": 1.25,
}
],
)

response.to_dict()
```

Create sub-unit measurements with a `sub_unit` value on every record:

```python
Expand Down
1 change: 1 addition & 0 deletions docs/pv_power_sites.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ res = pv_power_sites.get_pv_power_site('ba75-e17a-7374-95ed')

res.to_dict()
```

```
{
'resource_id': 'ba75-e17a-7374-95ed',
Expand Down
51 changes: 51 additions & 0 deletions docs/schedules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Schedules

Retrieve and update availability or curtailment schedules for Premium PV and Wind resources.

| Method | Purpose | API Docs |
|---|---|---|
| `get_resource_schedule` | Retrieve schedule intervals for a time window. | [details](https://docs.solcast.com.au/docs/section/premium#getManageSchedules) |
| `patch_resource_schedule` | Merge availability and/or curtailment intervals into the schedule. | [details](https://docs.solcast.com.au/docs/section/premium#patchManageSchedules) |
| `delete_resource_schedule` | Remove schedule data for a time window and schedule type(s). | [details](https://docs.solcast.com.au/docs/section/premium#deleteManageSchedules) |

## Retrieve a schedule

```python
from solcast import schedules

response = schedules.get_resource_schedule(
resource_id="your-premium-site",
period_start="2026-01-01T00:00:00Z",
period_end="2026-01-02T00:00:00Z",
)

data = response.to_dict()
```

## Patch schedule intervals

```python
response = schedules.patch_resource_schedule(
resource_id="your-premium-site",
availability_schedule=[
{
"period_start": "2026-01-01T00:00:00Z",
"period_end": "2026-01-01T01:00:00Z",
"availability": 75,
}
],
)
```

## Delete schedule data

```python
response = schedules.delete_resource_schedule(
resource_id="your-premium-site",
period_start="2026-01-01T00:00:00Z",
period_end="2026-01-02T00:00:00Z",
schedule_type="availability,curtailment",
)
```

All methods return the SDK `Response` object. Use `response.to_dict()` to inspect the JSON response.
14 changes: 13 additions & 1 deletion docs/tmy.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ TMY (Typical Meteorological Year) is a collation of historical weather data for
The dataset is derived from a multi-year time series specifically selected so that it presents the unique weather
phenomena for the location, and provides annual averages that are consistent with long term averages.
See the [API docs](https://docs.solcast.com.au/#2144b738-a1f7-4647-9067-6e7f6eca32a8)
The `tmy` module has 2 methods:
The `tmy` module has 3 methods:

| Endpoint | API Docs |
|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| `radiation_and_weather` | [details](https://docs.solcast.com.au/?#e75039fa-86ec-43e4-99a3-389d3d7a4a99){.md-button} |
| `rooftop_pv_power` | [details](https://docs.solcast.com.au/?#d117fde9-e410-4df0-bd07-e6168efd23cf){.md-button} |
| `advanced_pv_power` | [details](https://docs.solcast.com.au/docs/section/advanced-pv-power#getDataTmyAdvancedPvPower){.md-button} |

### Example

Expand All @@ -25,6 +26,17 @@ res = tmy.rooftop_pv_power(
res.to_pandas().head()
```

Retrieve a Typical Meteorological Year for a registered Advanced PV site:

```python
res = tmy.advanced_pv_power(
resource_id="your-advanced-pv-resource",
output_parameters=["pv_power_advanced"],
)

res.to_pandas().head()
```

| period_end | pv_power_rooftop |
|:--------------------------|-------------------:|
| 2059-01-01 01:00:00+00:00 | 1.461 |
Expand Down
71 changes: 71 additions & 0 deletions docs/wind_power_sites.md
Comment thread
nik-solcast marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Wind Power Sites

Manage Premium Wind power site resources.

| Method | Purpose | API Docs |
|---|---|---|
| `list_wind_power_sites` | List accessible sites, with optional search and filters. | [details](https://docs.solcast.com.au/docs/section/premium#getResourcesWindPowerSites) |
| `get_wind_power_site` | Retrieve a site by resource ID. | [details](https://docs.solcast.com.au/docs/section/premium#getResourcesWindPowerSite) |
| `create_wind_power_site` | Create a site. Name, coordinates, and capacity are required. | [details](https://docs.solcast.com.au/docs/section/premium#postResourcesWindPowerSite) |
| `patch_wind_power_site` | Partially update fields supplied in the request. | [details](https://docs.solcast.com.au/docs/section/premium#patchResourcesWindPowerSite) |
| `update_wind_power_site` | Replace a site definition. Resource ID, name, coordinates, and capacity are required. | [details](https://docs.solcast.com.au/docs/section/premium#putResourcesWindPowerSite) |
| `delete_wind_power_site` | Delete a site by resource ID. | [details](https://docs.solcast.com.au/docs/section/premium#deleteResourcesWindPowerSite) |

## List and retrieve

```python
from solcast import wind_power_sites

res = wind_power_sites.list_wind_power_sites(query="Sydney")
sites = res.to_dict()["results"]

site = wind_power_sites.get_wind_power_site(
resource_id="your-wind-resource-id",
)
site.to_dict()
```

## Create and update

Create a site with the required fields and any optional turbine settings:

```python
res = wind_power_sites.create_wind_power_site(
name="Example wind site",
latitude=-33.8567,
longitude=151.2152,
capacity=5,
rotor_diameter=120,
hub_height=100,
number_of_turbines=2,
)
resource_id = res.to_dict()["resource_id"]
```

PATCH changes only the fields supplied. PUT replaces the definition and requires the full set of required fields:

```python
wind_power_sites.patch_wind_power_site(
resource_id=resource_id,
allow_negative_power=True,
)

wind_power_sites.update_wind_power_site(
resource_id=resource_id,
name="Example wind site",
latitude=-33.8567,
longitude=151.2152,
capacity=5,
rotor_diameter=120,
hub_height=100,
number_of_turbines=2,
)
```

## Delete

```python
wind_power_sites.delete_wind_power_site(resource_id=resource_id)
```

Each method returns the SDK `Response` object. Use `response.to_dict()` to inspect the JSON response.
4 changes: 3 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ nav:
- Live: live.md
- Historic: historic.md
- TMY: tmy.md
- Schedules: schedules.md
- PV Power Sites: pv_power_sites.md
- Premium PV Site Measurements: pv_power_site_measurements.md
- Wind Power Sites: wind_power_sites.md
- PV Site Measurements: pv_power_site_measurements.md
- Premium Wind Site Measurements: wind_power_site_measurements.md
- Aggregations: aggregations.md
- Example Notebooks:
Expand Down
4 changes: 4 additions & 0 deletions solcast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
pv_power_sites,
pv_power_site_measurements,
wind_power_site_measurements,
wind_power_sites,
schedules,
tmy,
unmetered_locations,
)
Expand All @@ -20,6 +22,8 @@
"pv_power_sites",
"pv_power_site_measurements",
"wind_power_site_measurements",
"wind_power_sites",
"schedules",
"tmy",
"unmetered_locations",
]
14 changes: 10 additions & 4 deletions solcast/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,36 +195,41 @@ def post(

Args:
params: a dictionary of parameters that are passed in the POST request
json_body: an optional JSON object to send as the request body

Returns:
a Response object.

"""
return self._make_request(params, method="POST", json_body=json_body)

def patch(self, params: dict) -> Response:
def patch(
self, params: dict, json_body: Optional[Dict[str, Any]] = None
) -> Response:
"""Wrap _make_request to make a PATCH request

Args:
params: a dictionary of parameters that are passed in the PATCH request
json_body: an optional JSON object to send as the request body

Returns:
a Response object.

"""
return self._make_request(params, method="PATCH")
return self._make_request(params, method="PATCH", json_body=json_body)

def put(self, params: dict) -> Response:
def put(self, params: dict, json_body: Optional[Dict[str, Any]] = None) -> Response:
"""Wrap _make_request to make a PUT request

Args:
params: a dictionary of parameters that are passed in the PUT request
json_body: an optional JSON object to send as the request body

Returns:
a Response object.

"""
return self._make_request(params, method="PUT")
return self._make_request(params, method="PUT", json_body=json_body)

def delete(self, params: dict) -> Response:
"""Wrap _make_request to make a DEL request
Expand All @@ -246,6 +251,7 @@ def _make_request(
Args:
params: a dictionary of parameters that are passed in the request
method: HTTP method to use
json_body: an optional JSON object to serialize as the request body

Returns:
a Response object.
Expand Down
25 changes: 21 additions & 4 deletions solcast/pv_power_site_measurements.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from typing import Any, Dict, List
from urllib.parse import quote

from ._site_measurements import MeasurementInput, measurement_records
from .api import Client, PandafiableResponse, Response
from .urls import (
base_url,
pv_power_site_measurements,
pv_power_site_measurements_sub_units,
utility_scale_site_measurements,
)


Expand Down Expand Up @@ -41,6 +45,21 @@ def create_pv_site_measurements(
)


def create_utility_scale_site_measurements(
resource_id: str,
measurements: List[Dict[str, Any]],
**kwargs,
) -> Response:
"""Submit one or more measurements for an Advanced PV Power site."""
endpoint = utility_scale_site_measurements.format(
resource_id=quote(resource_id, safe="")
)
url = kwargs.pop("base_url", base_url)
client = Client(base_url=url, endpoint=endpoint, response_type=Response)

return client.post(kwargs, json_body={"measurements": measurements})


def get_pv_site_measurements(resource_id: str, **kwargs) -> PandafiableResponse:
"""
Retrieve historical measurement data for a Premium PV Power site. Supports
Expand All @@ -65,7 +84,7 @@ def delete_pv_site_measurements(
resource_id: str, start: str, end: str, **kwargs
) -> Response:
"""
Delete site measurements
Delete Site Measurements

Args:
resource_id: The unique identifier of the Premium PV Power resource.
Expand All @@ -86,7 +105,6 @@ def delete_pv_site_measurements(
"resource_id": resource_id,
"start": start,
"end": end,
"format": "json",
**kwargs,
}
)
Expand Down Expand Up @@ -151,7 +169,7 @@ def delete_pv_sub_unit_measurements(
resource_id: str, start: str, end: str, **kwargs
) -> Response:
"""
Delete sub-unit site measurements
Delete Sub-Unit Site Measurements

Args:
resource_id: The unique identifier of the Premium PV Power resource.
Expand All @@ -172,7 +190,6 @@ def delete_pv_sub_unit_measurements(
"resource_id": resource_id,
"start": start,
"end": end,
"format": "json",
**kwargs,
}
)
Loading
Loading