> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dalyenergy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Energy Model Response Contracts

> Canonical response envelopes for POST/GET /energymodels

# Energy Model Response Contracts

This page documents the current public response contract for `/energymodels`,
including the saved-input update/rerun flow on existing rows.

## POST `/energymodels`

Execution modes:

* Default (`async` omitted or `?async=false`): synchronous attempt
* `?async=true`: queued async execution
* sync fan-out can also return a queued `200` response when block work is
  queued before completion

Request-shaping notes:

* `output.query` is rejected with `422`.
* `output.blockIndex` is the canonical selected-block filter and preserves
  zero-based original block numbering in public responses.
* `output.blockResults` controls `blockTimeSeries` visibility and is mutually
  exclusive with `output.fullTimeSeries`.
* `output.lossBreakdownTimestamps` is a top-level boolean again and implies
  block-level results are surfaced.
* object-shaped `output.lossBreakdownTimestamps` values are rejected with
  `422`.
* `output.irradianceLossDetail` is an additive annual-detail flag that may
  surface `losses.irradianceLossDetail` even when `output.fullOutput` is false.
* Deprecated top-level `blockQuery` + `blockQueryIndex` remain compatibility
  aliases to top-level `output.blockIndex`; conflicting canonical and legacy
  block selectors return `422`.

### `200 OK`

Sync-completed responses keep legacy-compatible top-level flattened result
sections.

```json theme={null}
{
  "result": "success",
  "message": "Energy model completed",
  "energyModelId": 56,
  "globalEnergyModelId": "111222333",
  "projectId": 7,
  "globalProjectId": "901234567",
  "revision": "v0003",
  "taskDetails": {
    "message": "Energy model completed",
    "taskId": 1234,
    "schemaMode": "legacy",
    "executionMode": "sync_in_process",
    "calculationTime": 12.5,
    "createdAtUtc": "2026-03-03T16:21:11Z",
    "warnings": [
      "Block 0: Using legacy single-array format. Migrate to 'dcInputs' for per-MPPT control. Legacy format will be removed in schema v4.0."
    ],
    "normalizationWarnings": []
  },
  "energyYieldSummary": {
    "MWh": 1234.56,
    "kWh/kWdc": 1875.4
  }
}
```

Declared top-level result sections:

* `title`
* `energyYieldSummary`
* `locationSummary`
* `plantSummary`
* `losses`
* `blockSummary`
* `settings`
* `monthlySummary`
* `timeSeries`
* `blockTimeSeries`

`blockTimeSeries` notes:

* surfaced when block-level results are effectively requested by
  `output.blockResults=true`, `output.lossBreakdownTimestamps=true`, or
  deprecated `?blockresults=true`
* returns all available blocks when `output.blockIndex` is omitted
* returns only the selected block when `output.blockIndex` is provided
* preserves original zero-based block numbering in public keys
* timestamped loss breakdowns live under
  `blockTimeSeries[<blockIndex>].lossBreakdown`

Legacy compatibility note:

* If the request uses `output.fullTimeSeries=true`, the immediate sync POST
  response filters `timeSeries` back to:
  `date`, `arraySTCDCPower`, `arrayDCPower`, `inverterOutACPower`,
  `gridACPower`, `poiACPower`

Additive irradiance-loss detail:

* When `output.irradianceLossDetail=true`, public responses may include
  `losses.irradianceLossDetail` without requiring `output.fullOutput=true`.
* The detail payload reports annual fractions on the same stage basis as the
  scalar `losses.irradianceLosses` entries.
* Stage keys include `transpositionOnPoaLoss`, `farShadingLoss`,
  `nearShadingLoss`, `aoiIrradianceLoss`, `soilingIrradianceLoss`, and
  `groundReflectedOnFront`.
* The component fields are `direct`, `circumsolar`, `diffuse`, and `albedo`.

### `200 OK` (Queued)

```json theme={null}
{
  "result": "success",
  "message": "Energy model queued for processing",
  "energyModelId": 57,
  "globalEnergyModelId": "111222334",
  "projectId": 7,
  "globalProjectId": "901234567",
  "revision": "v0004",
  "taskDetails": {
    "message": "Energy model queued for processing",
    "taskId": 1235,
    "schemaMode": "legacy",
    "executionMode": "async_fanout",
    "calculationTime": null,
    "createdAtUtc": "2026-03-03T16:22:03Z",
    "warnings": [
      "Deprecated query parameter 'blockresults' was used."
    ],
    "normalizationWarnings": [
      "defaulted output to {}"
    ]
  }
}
```

Queued async and fan-out responses do not expose per-block task IDs in the
public payload.

## Canonical `taskDetails` fields

| Field                   | Type                                                           | Description                                         |
| ----------------------- | -------------------------------------------------------------- | --------------------------------------------------- |
| `message`               | `string \| null`                                               | Run/task status message.                            |
| `taskId`                | `integer \| null`                                              | Workspace-scoped task index for `/tasks/{task_id}`. |
| `schemaMode`            | `"legacy" \| "multi_mppt" \| "hybrid" \| null`                 | Detected input schema mode.                         |
| `executionMode`         | `"sync_in_process" \| "sync_fanout" \| "async_fanout" \| null` | Run execution path.                                 |
| `calculationTime`       | `number \| null`                                               | Runtime in seconds for completed runs.              |
| `createdAtUtc`          | `string \| null`                                               | Resource creation timestamp (UTC ISO-8601).         |
| `warnings`              | `string[] \| null`                                             | Human-readable warning messages.                    |
| `normalizationWarnings` | `string[] \| null`                                             | Input-normalization warnings.                       |

Notes:

* Warnings are always exposed as strings.
* If runtime code generates structured warnings, the API surfaces only their
  `message` text.
* GET/list/detail task details are backed by persisted summary metadata for the
  run.
* `output.query` and `debug_block` are not supported on the public request
  surface; the API returns `422` and directs callers to `output.blockIndex`.

## GET `/energymodels`

List responses remain a light flattened summary shape.

```json theme={null}
[
  {
    "energyModelId": 57,
    "globalEnergyModelId": "111222333",
    "workspaceId": 1,
    "name": "North Project Run",
    "projectId": 7,
    "globalProjectId": "901234567",
    "revision": "v0004",
    "dateCreated": "2026-03-03T16:22:03Z",
    "status": "queued",
    "annualACEnergy": 111.1,
    "taskDetails": {
      "message": "Energy model queued for processing",
      "taskId": 1235,
      "schemaMode": "legacy",
      "executionMode": "async_fanout",
      "calculationTime": null,
      "createdAtUtc": "2026-03-03T16:22:03Z",
      "warnings": ["gate warning"],
      "normalizationWarnings": []
    }
  }
]
```

Compatibility note:

* `include=summary` is accepted as a no-op compatibility token on this
  endpoint.

## GET `/energymodels/{energy_model_id}`

Default detail responses omit optional result sections unless requested with
`include`.

With `include=summary,inputs,fulltimeseries,blocktimeseries`, detail responses
can additionally expose:

* `title`
* `energyYieldSummary`
* `locationSummary`
* `plantSummary`
* `losses`
* `blockSummary`
* `settings`
* `monthlySummary`
* `inputs`
* `normalizedInputs`
* `timeSeries`
* `blockTimeSeries`

With `include=inputs`, detail responses also expose the persisted saved input
JSON needed for edit/rerun workflows.

## PUT `/energymodels/{energy_model_id}`

`PUT /energymodels/{energy_model_id}` accepts a full `EnergyModelInput` body,
replaces the saved input JSON on that same row, clears stale outputs, and
returns an `EnergyModelDetailResponse`.

Typical update response:

```json theme={null}
{
  "energyModelId": 56,
  "projectId": 7,
  "revision": "v0003",
  "status": "draft",
  "inputs": {
    "projectId": 7,
    "blocks": []
  },
  "taskDetails": {
    "message": "Energy model saved",
    "taskId": null,
    "schemaMode": "legacy",
    "executionMode": null,
    "createdAtUtc": "2026-03-03T16:22:03Z"
  }
}
```

Notes:

* This updates the existing energy-model row instead of creating a new one.
* The returned `inputs` payload is the saved JSON after hydration/validation.
* Rows in `pending`, `queued`, or `running` state return `409 Conflict`.

## POST `/energymodels/{energy_model_id}/run`

`POST /energymodels/{energy_model_id}/run` executes the currently saved input
JSON on the same energy-model row in place.

Behavior:

* `?async=false` or omitted attempts sync execution first.
* `?async=true` queues background execution and returns a queued `200`.
* Multi-block reruns can still return queued `200` fan-out responses from the
  sync path.
* The response envelopes match `POST /energymodels`, but `energyModelId`
  remains the existing row instead of a newly created row.

Queued rerun example:

```json theme={null}
{
  "result": "success",
  "message": "Energy model queued for processing",
  "energyModelId": 56,
  "revision": "v0003",
  "taskDetails": {
    "message": "Energy model queued for processing",
    "taskId": 1235,
    "schemaMode": "legacy",
    "executionMode": "async_queued",
    "calculationTime": null,
    "createdAtUtc": "2026-03-03T16:22:03Z",
    "warnings": [],
    "normalizationWarnings": []
  }
}
```

Async notes:

* Poll `GET /tasks/{task_id}` using `taskDetails.taskId`.
* Fetch final results from `GET /energymodels/{energy_model_id}`.
* The rerun updates the same saved row; it does not create a second model row.

## Include token behavior

| Endpoint                                   | Allowed tokens                                           | Current behavior                           |
| ------------------------------------------ | -------------------------------------------------------- | ------------------------------------------ |
| `GET /energymodels`                        | `summary`                                                | Accepted no-op compatibility token         |
| `GET /energymodels/{energy_model_id}`      | `summary`, `inputs`, `fulltimeseries`, `blocktimeseries` | Gates optional detail sections             |
| `PUT /energymodels/{energy_model_id}`      | none                                                     | Request body is the full saved input JSON  |
| `POST /energymodels/{energy_model_id}/run` | none                                                     | Runs the currently saved JSON in place     |
| `POST /energymodels`                       | none                                                     | Query-based include control is unsupported |

Legacy query parameters such as `summary` and `fullresults` are rejected with
endpoint-specific `422` guidance.
