# daly-energy

Python SDK client for the [Daly Energy](https://dalyenergy.com) REST API.

## Installation

```bash
pip install daly-energy
```

Install the query helpers with pandas support when you want DataFrame input:

```bash
pip install "daly-energy[query]"
```

## Quick Start

```python
from dalysdk import DalyClient

client = DalyClient(
    workspace_api_key="wk_...",
    user_api_key="uk_...",
)

# List locations
locations = client.locations.list()

# Create a project
project = client.projects.create({
    "name": "My Solar Project",
    "location_index": 1,
})

# Always close when done
client.close()
```

Or use as a context manager:

```python
with DalyClient(workspace_api_key="wk_...", user_api_key="uk_...") as client:
    projects = client.projects.list()
```

## Saved Energy Model Edit/Rerun

```python
with DalyClient(workspace_api_key="wk_...", user_api_key="uk_...") as client:
    saved = client.energy_models.get_with_inputs(56)
    edited = dict(saved["inputs"])
    edited["output"] = {"name": "Variant A - Updated"}

    client.energy_models.update(56, edited)
    queued = client.energy_models.run_saved(56, async_mode=True)

    assert queued["energyModelId"] == 56
```

`run_saved()` reruns the same saved energy-model row in place rather than
creating a new one. If you need different output controls for the rerun, update
the saved model first and then call `run_saved()`. The rerun endpoint does not
take a request body override.

Energy-model submissions are async-only. The SDK submits `create()` and
`run_saved()` requests as accepted jobs and treats any `2xx` response,
including `202 Accepted`, as success. Poll `client.tasks` for completion if
you need finished results.

## Energy Model Query Workflows

For modern SDK usage, use `client.energy_models.query(...)` when you want the
SDK to normalize an inline weather dataset into the canonical API
`weatherData` payload. Use `client.energy_models.create(...)` when you already
have the full API request body prepared.

```python
with DalyClient(workspace_api_key="wk_...", user_api_key="uk_...") as client:
    accepted = client.energy_models.query(
        {
            "locationId": 9,
            "blocks": [
                {
                    "name": "Block A",
                    "inverterDefinition": {"inverterIndex": 1},
                    "moduleDefinition": {"moduleIndex": 1},
                    "surfaceDefinition": {
                        "surfaceType": "fixed_tilt",
                        "surfaceTilt": 20,
                        "surfaceAzimuth": 180,
                    },
                    "moduleStringingDefinition": {
                        "modulesPerString": 20,
                        "stringsPerInverter": 5,
                    },
                }
            ],
        },
        [
            {
                "timestamp": "2024-01-01T00:00:00Z",
                "ghi": 0.0,
                "dhi": 0.0,
                "temp": 15.0,
                "wind_speed": 1.2,
            },
            {
                "timestamp": "2024-01-01T01:00:00Z",
                "ghi": 10.2,
                "dhi": 8.3,
                "temp": 14.8,
                "wind_speed": 1.1,
            },
        ],
        time_step_format="utc",
    )
```

`query()` accepts weather rows keyed by `date`, `timestamp`, or `datetime` and
normalizes them to the API's canonical `weatherData.date` array before
submission. If you want to inspect the exact payload before sending it, use
`client.energy_models.build_query_payload(...)`.

If you already have the final API payload, submit it directly:

```python
with DalyClient(workspace_api_key="wk_...", user_api_key="uk_...") as client:
    result = client.energy_models.create(
        {
            "location": {
                "latitude": 33.4,
                "longitude": -112.0,
                "elevation": 337,
                "timeZone": -7,
            },
            "weatherData": {
                "date": [
                    1704067200000,
                    1704070800000,
                    1704074400000,
                ],
                "ghi": [0.0, 0.0, 10.2],
                "dhi": [0.0, 0.0, 8.3],
                "windSpeed": [1.2, 1.1, 0.8],
                "temperature": [15.0, 14.8, 14.6],
            },
            "blocks": [
                {
                    "name": "Block A",
                    "inverterDefinition": {"inverterIndex": 1},
                    "moduleDefinition": {"moduleIndex": 1},
                    "surfaceDefinition": {
                        "surfaceType": "fixed_tilt",
                        "surfaceTilt": 20,
                        "surfaceAzimuth": 180,
                    },
                    "moduleStringingDefinition": {
                        "modulesPerString": 20,
                        "stringsPerInverter": 5,
                    },
                }
            ],
        },
        async_mode=True,
    )
```

### Output controls and returned time-series

Use the canonical API `output` fields directly in the payload you pass to
`client.energy_models.create(...)`:

- `output.timeSeries` requests plant-level `timeSeries`
- `output.fullTimeSeries` requests extended plant-level series
- `output.blockResults` requests `blockTimeSeries`
- `output.blockIndex` filters the returned `blockTimeSeries` map to one
  zero-based block
- `output.lossBreakdownTimestamps` implies block-level results and adds
  timestamped loss detail under `blockTimeSeries[<blockIndex>].lossBreakdown`
- `output.irradianceLossDetail` adds annual detail under
  `losses.irradianceLossDetail`

Important behavior:

- `output.blockIndex` filters the returned response; it does not trim the
  submitted `blocks` array down to single-block execution.
- Returned `blockTimeSeries` keys preserve the API's original zero-based block
  numbering, including filtered single-block responses.
- If you need to change output controls for `run_saved()`, update the saved
  row first with `client.energy_models.update(...)`.
- The SDK no longer assumes inline sync completion for these submissions; use
  task polling if your workflow needs final outputs.

### Legacy query adapters (compatibility only)

- `client.energy_models.create_legacy_query(...)`
- `client.run_energy_model(...)`
- `LegacyEnergyModel.run_query(...)`

These helpers are provided only for backward compatibility. They preserve the
old block-query controls (`block_results`, `block_results_index`) by
translating them into legacy request fields while keeping the full model block
list intact. They are **not** the recommended modern SDK workflow.

Legacy block-query behavior now follows the current API contract:

- `block_results_index` maps to deprecated selected-block compatibility fields
- the API treats that selection as filtered `blockTimeSeries` output, not
  reduced execution scope
- returned block keys still use the original zero-based block indexes
- compatibility inputs such as `run_async`, `block_results`, and
  `block_results_index` remain accepted, but they do not restore inline sync
  execution semantics

`LegacyEnergyModel.run_query(...)` also keeps legacy method signatures for
compatibility, but it does **not** apply date/time shaping from
`start_date`, `end_date`, or `time_step_format`; those arguments are accepted
and ignored.

### Migration note

If you are migrating from older legacy query code:

1. Prefer direct `client.energy_models.create(...)` calls with explicit,
   complete request payloads.
2. Keep using `LegacyEnergyModel.run_query(...)` only as a temporary bridge
   while removing legacy block-query assumptions.
3. Do not assume `LegacyEnergyModel.run_query(...)` reproduces historical
   date-window query behavior; validate payload shaping in your own code before
   request submission.

## Resources

The client exposes the following resource namespaces:

- `client.locations` — Location management
- `client.projects` — Project CRUD
- `client.modules` — PV module library
- `client.inverters` — Inverter library
- `client.energy_models` — Energy model runs
- `client.shading_scenes` — 3D shading analysis
- `client.weather_data` — Weather data management
- `client.tasks` — Async task tracking
- `client.workflows` — Multi-step workflow orchestration
- `client.workspaces` — Workspace management

## License

MIT
