> ## 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.

# PV and Battery Simulations

> Explicit AC- and DC-coupled plants, chronological dispatch, and optional electrical analysis

# PV and Battery Simulations

The opt-in `hybrid` engine simulates PV, standalone storage, or a combined plant
with a single chronological battery and dispatch execution layer. Submit the
`hybrid-1` input contract to
`POST /v2/energymodels?calculationEngine=hybrid`. The service must have hybrid
admission enabled; otherwise submissions return `503`.

Existing PV submissions keep their default engine and request contract. Hybrid
inputs require the explicit selector on both create and saved rerun. The selector
does not convert an existing PV-only saved model into a hybrid model.
For an existing solar template, use the explicit
[solar preparation flow](https://docs.dalyenergy.com/energy-models/solar-template-preparation.md) to build
and review a native solar-only request before submission.

Use the `/v2` base path for hybrid authentication, equipment reads, task polling,
results, saved updates and reruns. The original API remains available for
existing solar models. The versioned API initially requires the hybrid selector
for simulations, including PV-only studies; it returns `409` for historical
solar mutations. Historical detail reads redirect to the original endpoint
after authentication and workspace checks, preserving the stored model's
original input and output contract.

## Choose an execution mode

| `options.preset` | Calculation | Appropriate use |
| - | - | - |
| `fast` | Battery inventory and dispatch with branch-specific losses at prescribed AC voltages | Repeated energy studies on radial networks |
| `electrical_audit` | The same reduced calculation, plus independent pandapower snapshots at selected accepted operating points | Checking voltage, branch loading, and the reduced approximation |
| `revenue_optimized` | Rolling Pyomo/HiGHS scheduling followed by the same physical executor | Price-driven studies with explicit price and forecast assumptions |
| `full_electrical` | Pandapower AC power flow inside the physical constraint evaluation throughout the chronology | Detailed studies where solved voltages and network constraints matter |

For physical networks, `options.electrical_model` can explicitly select
`reduced`, `audit`, or `full`. The separate `collection_equivalent` option is
restricted to [solar-template screening](https://docs.dalyenergy.com/energy-models/solar-template-preparation.md)
with supported collection loss laws and controls; it does not provide a
physical topology or battery model.
`dispatch.method="optimized"` can be combined with a detailed electrical mode.
The audit mode reports independent checks; it does not replace the reduced
trajectory with the audit result. Without explicit `audit_interval_indices`, it
retains first/last points, electrical extrema, and a bounded set of topology
states, with at most 28 snapshots. Explicit indices retain accepted physical
subintervals within the selected intervals and remain subject to the output budget.

The default hourly solar-only path has a tens-of-seconds execution requirement;
validate its performance separately from BESS and optional detailed calculations.
BESS runtime depends on interval count, equipment count, state-boundary events,
and numerical iterations. Optimization and full electrical runs use the long-running
compute path and can take substantially longer. Larger fast and audit studies also
use that path: routing counts intervals
times the largest equipment, PV-group, or AC-network count and switches above
200,000. This is a scheduling threshold, not a runtime or accuracy guarantee.
A client polling timeout does not cancel a calculation.

## Define physical connections and units

Every asset ID is supplied by the caller and must be unique across the plant.
References identify actual buses and converters, rather than accounting groups.

| Input or output | Unit or convention |
| - | - |
| `time.timestamps` | Timezone-aware interval **starts**; adjacent intervals must be contiguous |
| `time.duration_seconds` | Positive seconds, scalar or one value per interval |
| `source_timezone` | Valid IANA name; timestamps still carry their own offsets |
| `*_mw`, `*_mvar`, `rated_mva` | MW, MVAr, MVA; input power is held over each interval |
| `*_mwh` | MWh; `capacity_mwh` is nominal DC stored-energy inventory |
| `nominal_kv` | AC line-to-line kV, or DC bus kV |
| `resistance_ohm_per_phase`, `reactance_ohm_per_phase` | Total branch impedance per phase, not per kilometre |
| `capacitance_nf_per_phase` | Total line capacitance per phase in nF |
| `max_current_a` | Amperes |
| `*_efficiency`, `soc_min`, `soc_max`, `*_fraction` | Fractions, not percentages |
| Transformer `*_percent` | Percent, not fraction; `no_load_loss_kw` is kW |
| `temperature_c` | Degrees Celsius; explicitly supplied cell temperature |
| `battery_dc_mw` dispatch | Positive discharge, negative charge, at the battery DC terminal before the DC/DC converter |
| `poi_target_mw` / output `poi_mw` | Positive export, negative import at the point of interconnection |
| `export_per_mwh`, `import_per_mwh` | Currency per MWh; positive import prices are costs |

Scalar time-series fields repeat over the entire study. Array fields must match
the time axis. JSON inputs accept the modeled snake\_case names and their camelCase
aliases, for example `duration_seconds` / `durationSeconds` and
`capacity_mwh` / `capacityMwh`. Continue using camelCase if desired. Send each
property once; do not submit duplicate aliases. Query parameters such as
`calculationEngine` retain their documented spelling. Arbitrary asset IDs,
dictionary keys, and saved input JSON are not recursively renamed.

For AC coupling, storage has its own bidirectional PCS and DC bus, separate from
the PV inverter. For controlled DC coupling, PV and storage reference the same
converter and DC bus, and the battery uses `connection="controlled_dc_dc"`.
The physical converter's active-power, apparent-power, current, and voltage
limits apply once to its combined operating point. DC/DC, cell, converter,
auxiliary, and network losses have separate ownership.

## Synthetic AC-coupled example

Save this as `hybrid-ac.json`. The four hourly intervals deliberately use ideal
equipment and a single AC bus to illustrate boundaries. These assumptions are
not manufacturer equipment data. No project or location is required when PV
availability is supplied explicitly.

```json theme={null}
{
  "contract_version": "hybrid-1",
  "name": "Synthetic AC-coupled example",
  "time": {
    "timestamps": [
      "2026-01-01T10:00:00Z", "2026-01-01T11:00:00Z",
      "2026-01-01T12:00:00Z", "2026-01-01T13:00:00Z"
    ],
    "duration_seconds": 3600,
    "source_timezone": "UTC"
  },
  "plant": {
    "buses": [
      {"id": "poi", "domain": "ac", "nominal_kv": 20},
      {"id": "pv_dc", "domain": "dc", "nominal_kv": 1},
      {"id": "battery_dc", "domain": "dc", "nominal_kv": 1}
    ],
    "poi_bus_id": "poi",
    "frequency_hz": 60,
    "export_limit_mw": 1,
    "import_limit_mw": 0,
    "allow_grid_charging": false,
    "converters": [
      {
        "id": "pv_inverter", "ac_bus_id": "poi", "dc_bus_id": "pv_dc",
        "rated_ac_mw": 2, "rated_mva": 2,
        "charge_efficiency": 1, "discharge_efficiency": 1
      },
      {
        "id": "battery_pcs", "ac_bus_id": "poi", "dc_bus_id": "battery_dc",
        "rated_ac_mw": 1, "rated_mva": 1,
        "charge_efficiency": 1, "discharge_efficiency": 1,
        "bidirectional": true
      }
    ],
    "pv_sources": [
      {"id": "pv", "converter_id": "pv_inverter", "available_ac_mw": [1.5, 1.5, 0, 0]}
    ],
    "batteries": [
      {
        "id": "battery", "converter_id": "battery_pcs", "dc_bus_id": "battery_dc",
        "connection": "ac", "capacity_mwh": 2, "initial_energy_mwh": 0.5,
        "soc_min": 0, "soc_max": 1,
        "max_charge_dc_mw": 0.5, "max_discharge_dc_mw": 0.5,
        "cell_charge_efficiency": 1, "cell_discharge_efficiency": 1,
        "temperature_c": 25
      }
    ]
  },
  "dispatch": {
    "method": "battery_schedule",
    "battery_dc_mw": {"battery": [-0.5, -0.5, 0.5, 0.5]},
    "charge_source": "pv_only"
  },
  "options": {
    "preset": "fast", "data_quality": "synthetic_fixture", "output_detail": "component",
    "assumptions": ["Ideal efficiencies; no collector losses; prescribed 25 C cell temperature"]
  }
}
```

`available_ac_mw` is availability at the inverter AC output, before collector and
POI losses. Its inaccessible upstream DC power cannot be used to charge a
DC-coupled battery. This example charges 1 MWh and discharges 1 MWh, returns to
0.5 MWh stored inventory, and exports 3 MWh over the four hours.

## Synthetic DC-coupled example

This complete transformation of the previous example creates `hybrid-dc.json`.
It puts PV and storage behind one 1 MW shared inverter and exposes PV availability
at its DC bus. The first two intervals can charge storage with the 0.5 MW that
would otherwise exceed the shared inverter's capacity.

```python theme={null}
import json
from pathlib import Path

payload = json.loads(Path("hybrid-ac.json").read_text())
payload["name"] = "Synthetic controlled DC-coupled example"
plant = payload["plant"]
plant["buses"] = [bus for bus in plant["buses"] if bus["id"] != "pv_dc"]
shared = plant["converters"][1]
shared["id"] = "shared_inverter"
plant["converters"] = [shared]
plant["pv_sources"] = [{
    "id": "pv", "converter_id": "shared_inverter",
    "available_dc_mw": [1.5, 1.5, 0, 0]
}]
battery = plant["batteries"][0]
battery.update(
    converter_id="shared_inverter", connection="controlled_dc_dc",
    dc_dc_charge_efficiency=1, dc_dc_discharge_efficiency=1
)
Path("hybrid-dc.json").write_text(json.dumps(payload, indent=2))
```

## Submit, poll, edit, and rerun

Submit either JSON file using your usual authenticated workspace credentials:

```http theme={null}
POST /energymodels?calculationEngine=hybrid
Content-Type: application/json
```

A successful submission returns `202` with `energyModelId`,
`taskDetails.taskId`, `calculationEngine`, `calculationRunId`, `contractVersion`,
and `preset`. Standalone models omit project and location identity fields.

1. Poll `GET /tasks/{taskId}` until a terminal status.
2. Read `GET /energymodels/{energyModelId}?include=inputs` for the saved request,
   normalized request, and raw hybrid outputs.
3. Save an edited request with `PUT /energymodels/{energyModelId}`. This resets
   the saved result and leaves the row in draft status.
4. Queue it again with
   `POST /energymodels/{energyModelId}/run?calculationEngine=hybrid`.

Update, rerun, and delete reject an active hybrid model with `409`. Use
`POST /tasks/{taskId}/cancel` to cancel an active run. Cancellation records a
durable cancelled status and requests compute shutdown; shutdown is asynchronous.
An invalid input returns `422`; physical infeasibility or solver failure found
during execution is reported on the failed task. Existing read/write workspace
permissions apply throughout.

Hybrid input representation limits default to 32 MiB for request JSON and
128 MiB for the normalized request plus hydrated PV runtime. Exceeding either
returns `413`. Explicitly selected hybrid POST bodies are bounded before JSON
parsing, including streamed uploads. Saved-input PUT bodies are parsed before
the service identifies the hybrid row and applies its semantic size check.
PV hydration likewise precedes the runtime-artifact check. These limits prevent
oversized representations from being saved or queued; they are not a guarantee
of peak memory use or a change to the scientific time resolution.

The Python SDK forwards the payload without changing its keys:

```python theme={null}
import json
import time
from pathlib import Path

# client is an authenticated DalyClient for your workspace.
payload = json.loads(Path("hybrid-ac.json").read_text())
accepted = client.energy_models.create(payload, calculation_engine="hybrid")
deadline = time.monotonic() + 1800
while True:
    task = client.tasks.get(accepted["taskDetails"]["taskId"])
    if task["status"] in {"completed", "failed", "cancelled"}:
        break
    if time.monotonic() >= deadline:
        raise TimeoutError("Polling stopped; the server task may still be running")
    time.sleep(5)
if task["status"] != "completed":
    raise RuntimeError(f"Simulation {task['status']}")
saved = client.energy_models.get(accepted["energyModelId"], include="inputs")
# After any desired saved-input update:
rerun = client.energy_models.run_saved(accepted["energyModelId"], calculation_engine="hybrid")
```

The CLI uses the same explicit selector:

```bash theme={null}
daly energy-models run hybrid-ac.json --calculation-engine hybrid --wait --wait-timeout 1800
daly energy-models rerun MODEL_ID --calculation-engine hybrid --wait --wait-timeout 1800
```

Omitting the new SDK argument or CLI flag preserves the existing engine default.

Hybrid models also appear in the UI's energy-model list. Their detail page shows
study-period export, import, stored energy, and revenue when prices are supplied,
with input/result JSON downloads and rerun controls. The standard PV editor and
quick-run shortcuts retain their solar contract. The import comparison flow
explicitly prepares a new native v2 solar study; it does not convert or rewrite
an existing run's saved result.

## Dispatch, prices, and physical execution

`surplus` uses available PV and plant headroom. `battery_schedule` requests
battery-terminal power, and `poi_schedule` requests a signed POI target. Each
request is projected onto equipment, energy inventory, charging permissions, and
network limits. Requested schedules are not guarantees of delivered power;
inspect the executed results and limiting reasons.

Charging policies are `unrestricted`, `pv_only`, or `curtailed_pv_only`.
Grid charging also requires `plant.allow_grid_charging=true` and a physical
bidirectional path. Storage availability, connection state, commissioning and
retirement timestamps, auxiliaries, temperature limits, and capacity retention
can further restrict operation.

Accepted event segments cover each declared input interval, including its final
remaining fraction. Commissioning and retirement boundaries use elapsed UTC
time within that interval. Energy, inventory and aging use the same accepted
durations; displayed Unix timestamps do not determine the integration duration.

Source attribution tracks PV, grid, and initial stored inventory. With
`unrestricted` charging, contemporaneous supplies mix proportionally at each DC
bus and the connected AC pool. PV-only policies reserve fresh local PV for
charging first, then fresh PV arriving through the AC network. Energy discharged
by another battery retains its historical tags but does not count as fresh PV
for charging eligibility. These are explicit inventory-accounting conventions;
they do not trace individual electrons. Physical source permissions and equipment
limits are enforced separately before the inventory advances.

`curtailed_pv_only` measures the physical export opportunity without requested
storage dispatch, using realized PV and equipment, POI, and network limits.
A separate economic extraction cap neither creates eligible clipping nor removes
clipping already present. Accepted charging must still use actual fresh PV and
respect the extraction cap. AC-coupled storage can recover downstream collector
or POI curtailment; upstream inverter clipping requires access at a supported DC
boundary.

Optimization requires explicit `prices` with currency, import/export prices,
source, market, and location. It has bounded rolling horizons, solve-time and
relative-gap settings, a terminal inventory policy, and optional discharged-energy
wear cost. Revenue uses executed POI import/export; the planning objective and
predicted trajectory are reported separately. Finite-time solutions need not be
proven optimal.

`forecast_mode` defaults to `perfect_foresight`; `realized_rolling_proxy` is also
an explicitly optimistic use of realized inputs. For historical forecast replay,
choose `forecast_replay` and supply unambiguous, already-issued forecast snapshots
covering each planning horizon. Missing time-varying forecasts do not silently
fall back to realized data. The planner uses radial, prescribed-voltage branch
bounds and simplified efficiencies; the executor applies actual losses and
physical restrictions before advancing battery state.

## Calculate PV from weather and mixed modules

Instead of inline availability, a source can specify a zero-based
`runtime_block_index` and the request can include an ordinary Daly PV input under
`solar_input`. Referenced equipment, project, weather, and layout data are resolved
within the authenticated workspace. Every source must select exactly one of
`available_dc_mw`, `available_ac_mw`, or `runtime_block_index`.

The calculated-PV adapter uses `options.pv_policy="subarray_allocation"`.
Homogeneous groups in a block's `dcInputs` can specify different `moduleId` values,
with one module type within each string. It allocates the inverter's capacity
among those groups and combines their results. This is a labeled allocation
approximation; it does not establish numerical equivalence to an external
simulator. Direct common-voltage mixed-module IV solving is not yet an available
optional policy.

The new hybrid contract defaults `options.pv_rear_coordinate_convention` to
`front_consistent`, aligning the default bifacial model's rear transverse axis
with the front tracker-rotation convention. The default rear model always uses
a flat axis internally, while tracking, backtracking and front irradiance
transposition retain the actual signed source axis tilt. Tracker rotation and
fixed-panel tilt are preserved; imported and shared inputs are not overwritten.
The corrected convention rejects other bifacial models. Select `retained`
explicitly for those models or to reproduce the existing rear irradiance
coordinates; there is no silent fallback. This choice changes bifacial rear
irradiance and yield and is recorded
in simulation metadata and PV cache identity. The maintained calculator resolves
the physical front surface for both monofacial and bifacial tilted-axis trackers.
The frozen V1 deployment and its default calculation remain unchanged.

Without a source's optional `spatial` configuration, the adapter requires one
resolved orientation and shading domain per block. Independent MPPT buses cannot
be bridged by a DC-connected battery; DC storage requires a supported
single common bus. Calculated PV timestamps must be regular interval centres,
with at least two samples, matching the hybrid interval starts plus half their
durations. Do not shift or relabel an incompatible weather time axis.

### Multiple orientations on one inverter

Set `plant.pv_sources[i].spatial.orientations` to a map of names to full inline
Daly layouts. Reference those names with `orientationKey` on that source's
`solar_input.blocks[runtime_block_index].dcInputs` groups. Omitted group keys use
the block's original layout. Every named layout must be used, and every referenced
name must resolve. One MPPT may contain several module groups but must have one
orientation and tracker-control policy. Different MPPTs on the same inverter
can face east and west. Mixed orientations on one MPPT remain unsupported.

For example, this source fragment accompanies groups with `orientationKey` values
`east` and `west` on separate `mpptIndex` values:

```json theme={null}
{
  "id": "pv-roof",
  "converter_id": "roof-inverter",
  "runtime_block_index": 0,
  "spatial": {
    "orientations": {
      "east": {"arrayType": "fixed", "tilt": 20, "azimuth": 90, "gcr": 0.35},
      "west": {"arrayType": "fixed", "tilt": 20, "azimuth": 270, "gcr": 0.35}
    },
    "power_sharing": "daily_irradiance"
  }
}
```

The default `fixed_nominal` sharing policy retains the declared nominal group
fractions. `daily_irradiance` multiplies each nominal fraction by that group's
pre-loss incident plane-of-array irradiance and normalizes each time step;
dark periods use nominal fractions. Inverter current limits remain tied to the
physical MPPT, even when its AC capacity share changes. Physical auxiliary
consumption is counted once. Virtual startup limits scale with capacity share.
This allocation approximation is reported in metadata; numerical agreement with
an external simulator is not established. Optimization still uses simplified
linear conversion for planning and the actual conversion laws for execution.

### Optional table-to-string shading

Read `GET /v2/shadingscenes/{scene_id}/spatial-manifest` after a shading calculation
completes. The authenticated manifest returns `sceneInputSha256`, stable
`tableHash` values, canonical receiver outlines, and geometric partition counts.
An unavailable or stale binding returns an error requiring a scene refresh.
Geometric partitions are **not** module or electrical string counts.

Add a `table_shading` object under the source's `spatial` configuration. This
fragment describes one 20-module string on one declared table; use the actual
manifest hash and table identity rather than the illustrative values:

```json theme={null}
{
  "shading_scene_id": 1,
  "scene_input_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
  "inverter_scope": "single_physical_inverter",
  "model": "table_partition",
  "diffuse_model": "regular_rows",
  "tables": [{"table_hash": "table-A", "module_count": 20}],
  "mappings": [{
    "dc_input_id": 1,
    "string_id": "string-A",
    "segments": [{"table_hash": "table-A", "module_start_index": 0, "module_count": 20}]
  }]
}
```

Declare module capacity explicitly and cover each table's zero-based module
ranges exactly once. Each string's segment count must match its DC group's
`stringLength`, and every group must have its declared `stringCount` mappings.
A string may span tables. Initially the mapped block must contain one physical
inverter, and a table cannot be split across sources. Table planes and actual
tracker axes must match the associated layout. Terrain-aware tracker scenes are
supported when their shading run produced the v2 terrain artifact; the scene's
per-table angles and receiver base-plane offsets are then applied per DC group,
and a scene computed before that controller must be refreshed first. A scene can
still contain unmapped tables and obstructions that shade mapped receivers. Editing the scene or tracking settings invalidates
its binding and requires recalculation and an updated hash.

This is a **table-uniform partition approximation**: every module assigned to a
table receives that table's mean direct/circumsolar and partition factors. Module
positions identify wiring; they do not resolve local shadows or bypass-diode
I-V curves. Positive partition-minus-linear deficits are evaluated per table
before module-weighted aggregation, avoiding double-counted linear shading.
Diffuse and ground shading retain the explicit regular-row approximation.
Bifacial rear irradiance retains the row model and does not consume scene
obstructions. Old string maps, shading profile identifiers, and explicit
directional fields cannot be combined with this mode.

Use `options.output_detail="component"` for per-interval
`components[].pv_availability.blocks` diagnostics. Each converter's block lists
group available DC, PV-only AC availability, temperatures, irradiance, allocation
fractions and table shading losses. `metadata.pv.spatial_topology` carries the
DC-input, MPPT, module, orientation, string and table identities once. These
values precede battery dispatch, AC network losses and plant curtailment;
`components[].converters` and plant time series describe actual executed flows.
Component detail has a larger output budget, so use shorter diagnostic windows
for large plants and summary output for annual studies.

Both spatial features are opt-in. Omitting `spatial` preserves the existing
calculation path unless the separate whole-scene screening policy below is
explicitly selected. Enabling nonuniform table shading can change total plant energy
through losses and inverter clipping; results are not rescaled to force the old
plant total. Documented snake\_case names and camelCase aliases are both accepted.

### Whole-scene irradiance screening without electrical mappings

[Solar-template preparation](https://docs.dalyenergy.com/energy-models/solar-template-preparation.md) accepts
`options.pv_scene_shading_policy="whole_scene_irradiance"` only with explicit
`electrical_model="collection_equivalent"`, fast screening and plant or summary
output. The default remains `explicit_spatial`; an imported scene link alone
does not select this approximation.

This policy requires a linked 3D scene with `electricalImpact: false`, homogeneous
conventional trackers and source controls matching the completed scene,
including axis tilt, GCR, backtracking and rotation limit. Active wind stow,
diffuse irradiance optimization, terrain-aware tracking, string maps,
shading-profile identifiers and per-source `spatial` mappings are incompatible.
The source template is preserved; preparation fails instead of disabling an
incompatible setting.

Preparation authenticates the scene's original input snapshot, completed task
and exact result receipt. The complete planned sun-position grid must include
every receiver. One receiver-area-weighted beam-loss grid applies to each native
block linked to that scene. The same geometry's directional artifact supplies
ground-reflected shading; sky diffuse and rear irradiance retain regular-row
approximations. This does not recover table-to-string wiring, per-inverter
shading or electrical mismatch.

Each prepared PV source receives a `scene_screening` binding with algorithm
version `whole-scene-irradiance-1`. It identifies the scene inputs, result,
directional artifact, runtime layout and compact beam grid by their hashes.
Submission authenticates and resolves the same resources again; changed inputs
or artifacts require fresh preparation. The worker checks its calculation
content against the submitted binding.

The separate `options.pv_bifacial_row_policy="scene_geometry"` opt-in can supply
a missing default-bifacial rear row count from authenticated parallel tracker
geometry. Its default, `source`, still requires declared row geometry. The
derived policy groups occupied longitudinal slices into transverse row runs
and averages their edge fractions by collector area:

```text theme={null}
edgeFraction = sum(collectorAreaFraction_j / rowCount_j)
effectiveRowCount = 1 / edgeFraction
```

Disconnected runs and singleton slices remain in that mean. The effective count
may be fractional but must be at least two; the table total is not a row count.
Geometry must agree with the declared axis, chord and pitch. Declared source
rows are never overwritten. The derivation supplies only a runtime finite-field
approximation with provenance, preserving source GCR, pitch, tracker axis and
table coordinates. It preserves averaged irradiance terms, not separate
nonlinear energy calculations for each run. The default rear model alone uses
an internal 0° axis; actual-axis tracking and transposition remain unchanged.

Collection policy `solar-collection-equivalent-2` retains monthly source PF
magnitudes and recurring availability periods (at most 1,000 periods, each
no longer than 8,784 hours / 366 days, validated before queuing). PF affects native inverter
conversion and current-dependent collection losses. Non-unity PF does not
establish a reactive direction: signed POI Q is unavailable, while inverter
reactive magnitude is reported separately. Outages use partial overlaps with
centered source-local weather intervals and reduce positive final POI export
after collection losses and auxiliaries. Night imports and upstream physics
remain unchanged. See the preparation guide for the energy-ledger fields and
supported scope. The default reduced-network behavior is unchanged.

## Read results and understand limits

The resource envelope uses camelCase; its `outputs` preserves the scientific
`hybrid-result-1` result, including snake\_case keys and caller-supplied asset IDs.
It contains `summary`, `timeseries`, `battery_timeseries`, `battery_summary`,
`components`, `electrical_audits`, `dispatch_plans`, `final_state`, and `metadata`.
`options.output_detail` controls summary, plant, or component detail. Energy
balance residuals, separate losses, source attribution, actual revenue, limiting
reasons, model assumptions, and algorithm version are available for review.

Component detail also includes `components[].operating_diagnostics` for each
accepted segment. Battery records distinguish beginning/end inventory and
capacity, commandable DC-terminal charge/discharge capability, service availability,
and actual charging, discharging or idle operation. Commandable capability includes
battery availability and SOC reserve restrictions; required auxiliary discharge
has a separate allowance. Both exclude shared converter, network and
charging-source restrictions; neither promises power deliverable at the POI.

Signed margins report headroom to configured battery, converter, branch, bus and
POI limits. Positive means headroom; negative means an exceeded bound. Margins
are neither clipped to zero nor classified using a feasibility tolerance. `null`
means not evaluated because a limit is absent, equipment is inactive, or the
quantity is not modeled. Cell voltage/current remain unmodeled in the inventory
battery. Reduced-network voltages are imposed, whereas full-network voltages are
solved; converter DC voltage uses the specified nominal bus voltage. Read
`metadata.operating_diagnostics` for these shared boundary and sign conventions.
Summary and plant detail do not construct these per-segment diagnostic records.

`metadata.runtime_breakdown` separates local validation, preparation, chronology,
post-chronology audits and result assembly. Optional optimizer, full electrical
solve and audit subtotals are already included in those stages; do not add them
again. Preparation includes native PV calculations and solver initialization.
The timing scope excludes prior imports, final result-budget serialization,
external serialization, persistence and orchestration; it is not API latency.

Task status and list responses keep the run identity and result summary compact.
Read `GET /energymodels/{id}` for the complete scientific result in `outputs`;
`GET /tasks/{id}` does not duplicate its interval and component arrays.

For optimized dispatch, `planned_revenue` values the applied plan at its forecast
prices. `forecast_value_of_executed_energy` values the actual accepted energy at
those same prices, while `realized_revenue` uses actual settlement prices.
`execution_correction_revenue` and `settlement_price_error_revenue` separate these
two differences. These totals cover applied intervals; do not sum overlapping
full-horizon values from `dispatch_plans`.

`metadata.electrical_coverage` identifies checked snapshots and compact ranges of
unchecked intervals. Default electrical audits retain observed extremes of PV,
charging, discharge, reactive power, internal power flow, SOC, and the bounded
set of encountered equipment states described by `metadata.audit_selection`.
An audited snapshot does not certify unchecked subsegments or transient behavior.

To assess storage recovery, compare otherwise identical executed studies with an
explicit no-recovery counterfactual. Keep captured charge, discharged energy,
incremental net POI energy, initial/final inventory, and losses separate. Adding
charge and discharge counts the same stored energy twice; their annual ratio is
not an intrinsic round-trip efficiency.

Curtailment energy is attributed sequentially to outage, an optimized economic
PV command, converter, POI, then network constraints. If later physical adjustments recover PV, earlier assigned
curtailment is removed in reverse order. All encountered binding flags remain
available. This is a consistent accounting rule, not an independent
counterfactual estimate of each constraint's marginal effect.

Results have a 32 MiB compact-JSON budget. Admission estimates the combined study
length, equipment count, audits, and retained optimization plans; an oversized
request returns `422`. Choose `summary` for large studies, or explicitly reduce
the study window or retained audit/plan frequency. Summary output retains solver
diagnostics but omits predicted optimization trajectory arrays. An exact byte
check also runs during execution; exceeding it fails the task without publishing
a partial result. The engine does not silently change temporal resolution.

Each optimization horizon is limited to 50,000 interval-asset cells, using the
largest battery, converter, or branch count. Reduce `horizon_intervals` explicitly
when necessary; rolling execution still covers the requested study. API-run
metadata includes the immutable input artifact reference and submission
fingerprint so the stored result can be traced to its exact runtime inputs.

Use `GET /energymodels/{id}?include=inputs` to retrieve the saved normalized
configuration together with the result, and the model/task status to establish
execution status. These API records complement the scientific result: the local
executor does not duplicate the full input time series in its output. Model
assumptions, optional solver status and fallback metadata, and the validity
matrix below describe the interpretation and limits of the calculation.

The inventory model does not solve cell electrochemistry or a dynamic thermal
balance. Prescribed temperature and ambient-plus-offset are explicit assumptions;
optional HVAC thermal service has an electrical cost but does not calculate cell
temperature. The capacity-retention model is a configured calendar/throughput
approximation. Equipment evidence and efficiency/derating curves improve input
traceability, but synthetic examples are not validated equipment models.

Reduced electrical analysis requires a connected radial AC network and imposes
voltage rather than solving regulation. Full electrical analysis solves balanced
AC operating points; it is not electromagnetic-transient or unbalanced three-phase
simulation, and the DC side remains a controlled terminal/inventory model. Graph
version 1 supports one AC/DC converter per shared DC bus. Study-specific accuracy
and runtime should be established with representative equipment and operating
points before relying on results for a design decision.

## Model validity and validation coverage

The following matrix defines the supported calculation boundaries. Its covered
checks describe analytical, differential, and execution tests; they do not certify
a manufacturer's equipment or establish accuracy for every accepted parameter
combination. Input validation establishes a permissible model domain, not an
equipment operating guarantee. A converged electrical solve alone is not a
validation result.

| Model and topology | Equipment and parameter domain | Time interpretation | Covered phenomena and limits |
| - | - | - | - |
| Native mixed-module PV | Homogeneous strings, explicit subarray allocation; optional layouts with one orientation per MPPT and bound table-uniform partition shading; retained module and inverter laws | Regular weather interval centres aligned with hybrid interval starts and durations | Uniform-group differential checks, mixed-module optics/temperature, sharing/current limits, clipping, receiver identity, partition attribution and cache invalidation. External reference parity for new orientation sharing is not established. Common-voltage mixed-string IV physics is unsupported. |
| Bifacial rear coordinates | `front_consistent` for the default model's internal flat-axis geometry; explicit `retained` for other supported configurations | Same time axis as native PV; no inferred subinterval weather | The source axis remains unchanged for tracking and transposition; the rear model retains tracker rotation and fixed-panel tilt. Other bifacial models require `retained`. |
| Whole-scene solar screening | Explicit `whole_scene_irradiance` with collection-equivalent execution; homogeneous conventional trackers and electrical shading disabled | Complete authenticated beam grid interpolated onto the original native weather clock | Receiver-area-weighted optical losses without an electrical map; bound directional ground shading and regular-row diffuse/rear approximations. Optional `scene_geometry` supplies only missing rear rows through an area-weighted edge fraction. Source settings remain unchanged. |
| Collection-equivalent plant controls | Native solar, source AC chain, monthly PF magnitudes, supported auxiliary/cap/night rules and recurring outages; no batteries or solved physical topology | Monthly PF and partial outage overlaps use the source local clock; executed intervals retain their UTC alignment | PF-scaled collection losses and unsigned inverter reactive magnitude; non-unity signed POI Q unavailable. Availability reduces final positive export separately without reducing night import or upstream losses. |
| Inventory battery: standalone, AC or controlled DC/DC | Positive nominal DC inventory; ordered SOC bounds in \[0, 1]; directional efficiencies in (0, 1]; explicit terminal power limits and nonnegative standing loss | Inputs are held over each declared interval; analytical inventory/cohort events can split it | Energy/source ledgers, asymmetric efficiency, exact loaded decay against an independent differential equation, full/empty boundaries and restart. Hourly and five-minute leap-year integration are covered. Cell electrochemistry, direct battery-to-PV coupling and cell voltage dynamics are unsupported. |
| Prescribed thermal conditions and HVAC service | Explicit cell temperature or ambient-plus-offset; independent charge/discharge temperature limits; prescribed heating/cooling demand, positive COP and finite thermal capacity | Supplied temperatures and service are held over each input interval | Directional temperature blocking, service capacity, required electrical supply, disconnection and auxiliary loss ownership. HVAC does not calculate cell temperature; there is no dynamic thermal balance. |
| Capacity retention and lifecycle | `none` or configured calendar/discharged-throughput retention, positive SOH floor, explicit inventory mapping and commissioning/retirement dates | Aging commits at input-interval ends or the cohort's retirement; state is carried through chunks | Cohort isolation, retained inventory, capacity shrinkage, physical dates and restart are covered. The reference-year duration is configured; retention is an approximation. Rainflow, electrochemical aging and calibrated lifetime prediction are unsupported. |
| Converter hardware and controls | Combined physical MW/MVA/current limits; fixed Q or fixed power factor; scalar efficiency or piecewise-linear efficiency tables spanning load fractions 0–1 with monotone, invertible directional power transfer | Quasi-static accepted operating points; explicit equipment-temperature series | Both power directions, apparent-power projection, current and voltage bounds, startup, standby, reactive losses and directional derating. Temperature tables reject out-of-domain values unless endpoint extension is explicitly selected. No switching transient, ramp or protection dynamics are modeled. |
| Fast reduced network | Connected radial AC graph, prescribed voltage, per-branch impedance/current limits and transformer parameters; explicit frequency for capacitance | One steady operating evaluation per physical trial; chronological battery state advances only after acceptance | Analytical three-phase copper/core losses, reverse flow, internal circulation and both branch-terminal currents. Voltage regulation is not solved. Disconnected islands and meshes are rejected. |
| Selected electrical audit | Reduced chronology plus independently solved balanced AC snapshots; explicit frequency and selected or bounded automatically chosen points | Audit snapshots do not change dispatch or battery chronology | Snapshot selection/coverage, voltage and loading differences, solver failure and state preservation. Unchecked points, transients and automatic corrective replay are outside the audit result. |
| Full electrical chronology | Balanced AC power flow with lines, transformers and explicit frequency; connected networks, including tested parallel branches | Solves accepted quasi-static points inside the chronological executor; trials do not commit battery state | Both flow directions, transformer/current/voltage constraints, retry failure without stale results and unchanged caller inputs. The DC side remains the controlled terminal/inventory model; unbalanced phases, electromagnetic transients and islanded adequacy are unsupported. |
| Optimized dispatch | Bounded LP/MILP rolling horizons with explicit prices, forecasts, source permissions, terminal policy and supported fixed-coefficient planning controls | Plans cover input intervals and restart from executed state; lifecycle events used by the planner must align to intervals | Small analytical optima, negative prices, terminal infeasibility, forecast issue-time rules, lifecycle bounds and realized settlement. The physical executor can correct a plan. Plant Q allocation, prescribed HVAC, directional efficiency curves, fixed-power-factor control and other incompatible planning controls fail explicitly; solver success is not a whole-plant accuracy guarantee. |

All chronological inputs require timezone-aware, contiguous intervals with their
actual durations. A five-minute or hourly input represents conditions held over
that interval; event splitting does not reconstruct faster weather, prices or
electrical dynamics. Tests spanning a time axis do not establish a universal
runtime bound or equipment calibration for that resolution.

The minimum SOC is a dispatch reserve. Standing loss and physically supplied
auxiliaries can reduce inventory below that reserve; the model does not invent
energy to maintain it. Physical inventory still cannot become negative.

Equipment evidence marked `manufacturer_sourced` or `calibrated` must identify
its source, version, test conditions and operating domain; calibrated claims also
require separate calibration and validation references. Daly validates that these
descriptors exist, not their authenticity or applicability to a particular plant.
Evaluate the chosen parameter domain, topology and timestep with independent
equipment/reference evidence before treating a screening model as calibrated.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.