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

# DR enrollment and dispatch specification

> Specify DR enrollment, standard peak days, annual dispatch, system-needs valuation, event constraints, program competition and impact interpretation.

DR builds a baseline load representation, layers technology impacts, evaluates resources independently for characterization and economics, then dispatches the portfolio. This specification applies to both a standalone DR study and DR invoked through the optional DERP integration.

## Quantities and signs

| Quantity | Convention |
| - | - |
| Unit end-use curve | Power per participating unit; source inputs are kW |
| Internal aggregated load/impact | MW |
| Signed impact $\Delta L_h$ | Negative for curtailment, positive for pre-charge or rebound |
| Peak reduction | Maximum load before minus maximum load after dispatch |
| Output power | MW by default; `output_power_unit` can select kW |
| Annual horizon | 8,760 hourly positions |

The reporting layer converts power fields to the configured unit even when an internal field name ends in `_mw`. Do not infer the exported unit solely from the internal field name.

## Market, enrollment and calls

For a resource's total market $T$, applicability factor $M$, enrollment propensity $p$ and opt-out rate $o$, the ordinary portfolio path uses:

$$
N_{enrolled}=TMp,\qquad N_{called}=TMp(1-o).
$$

Program-competition allocations, when present, supply the resolved enrolled and called units instead. The independently evaluated single-unit dispatch path sets enrolled and called units to one. Its purpose is characterization; it is not a market forecast.

The standalone propensity function is:

$$
p(I)=\operatorname{clip}\left(\frac{a_0\ln(\max(I+c_0,10^{-10}))+b_0}{100},0,1\right).
$$

The curve is logarithmic, despite a source docstring describing it as logistic regression. Marketing-level selection is year-specific. Incentive strategies include a percentage of benefits, a break-even cost-effective-potential strategy, and a net-benefit maximization strategy. For benefit $A$, non-incentive cost $B$ and incentive $I$, the optimization objective is based on $p(I)(A-B-I)$. Strategy bounds, feasibility and the selected objective determine the result; the break-even strategy is not the same as maximizing net benefits.

Program ramp-up and scenario-specific mappings are resolved before the final enrollment arrays are used. Connected studies also carry DERP propensity and market information in the handoff; do not apply either a second time as an external multiplier.

## Baseline and technology layers

Load layering maintains baseline, current and reporting shapes. The declared layer order is baseline, EE, DG, BE, FS, HP, EV, TVR and DR. The orchestrator applies the active configured layers in sequence, using the appropriate savings distribution or generation shape. DR therefore acts on the residual curve after preceding technology impacts.

Local mode preserves grid targets. Regional-bulk aggregates grid levels within regions. Bulk uses aggregated end-use curves and shared dispatch grouping across regions while retaining market lookups in each measure's real region. These modes do not merge every market into one regionless denominator.

## Standard peak day

For the selected year and period, the calculator:

1. Selects the configured top load hours and their unique dates.
2. Adds dates in descending daily-maximum order if too few dates were selected.
3. Truncates the date set to `fallback_days`.
4. Retains complete 24-hour, positive-maximum profiles for shape averaging.
5. Normalizes each retained profile by its own maximum, averages the normalized profiles, and normalizes that average by its maximum.
6. Scales the normalized shape by the configured percentile of the selected daily maxima.

For retained daily curves $L_{d,h}$, the shape and peak-day profile are:

$$
z_h=\frac{1}{|D|}\sum_{d\in D}\frac{L_{d,h}}{\max_k L_{d,k}},\qquad
SPD_h=\frac{z_h}{\max_k z_k}\operatorname{percentile}_{\alpha}(P).
$$

$P$ contains the selected dates' maxima; the code records these maxima even where a day's shape is not retained as a complete 24-hour profile. Empty usable shapes return zeros. Weekday/weekend standard days use hourly averages rather than this peak construction. Date-plus-hour inputs are reconstructed to hourly timestamps before averaging.

The truncation to `fallback_days` is part of the current implementation. It is not simply a minimum-day condition with every initially selected date retained. Defaults are 100 top hours, the 97.5th percentile and ten fallback days.

## Resource impact geometry

For a validated event with curtailment fractions $c_j$, pre-charge fractions $a_j$, rebound fractions $b_j$ and called-unit weighted power profile $U_h$:

$$
\Delta L_h=\begin{cases}
-c_jU_h,&h\text{ in curtailment},\\
a_jU_h,&h\text{ in pre-charge},\\
b_jU_h,&h\text{ in rebound},\\
0,&\text{otherwise}.
\end{cases}
$$

The updated load is $L'_h=L_h+\Delta L_h$. Typical-day placement uses 24-hour geometry and can wrap hours. Annual placement rejects any candidate whose complete pre-charge/curtailment/rebound window crosses the year boundary.

The annual enrolled-capacity reporting profile uses the **first** relative-curtailment value applied to enrolled capacity. It is not the entire event-shaped curtailment vector. Called impact, enrolled capacity and market size therefore have different denominators and meanings.

## Peak-chasing dispatch

The dispatcher prioritizes eligible resources using the configured economic score, evaluates candidate windows against the current residual load, and retains a feasible placement that improves the descending peak stack lexicographically: first the maximum, then the second-highest value, and so on. A placement can therefore be accepted with zero reduction of the absolute maximum when it improves a lower-ranked peak. Equal candidate peak stacks use lower total load as a further tie-breaker. A full-day curtailment shape without pre-charge/rebound remains pinned to clock hours rather than rotating. After placement, the dispatcher updates the load used for later resources. Screening and missing-market/profile decisions produce explicit non-placement records.

Effective portfolio peak reduction is:

$$
\Delta P_m=\max_h L_h^{before\ m}-\max_h L_h^{after\ m}.
$$

The maximum can move to another hour. Therefore this quantity differs from curtailment at the original peak, total curtailed energy and the resource's independent peak effect. Independent peak reductions generally cannot be summed to recover portfolio reduction.

The strategy registry exposes `peak_chasing`, `system_needs`, `annual_peak_chasing` and `annual_system_needs`, with `greedy` as a peak-chasing alias. A constant or setting mentioning MIP does not establish an active MIP strategy in this registry.

## System-needs dispatch

System-needs dispatch requires eligible grid-service streams, hourly avoided-cost shapes and remaining service caps. Program stream scope further restricts eligibility. In current stream classification:

* A stream with no re-optimized type uses peak chasing.
* A re-optimized demand stream uses peak chasing when its relative standard deviation is below `STREAM_VARIATION_THRESHOLD`.
* Other typed streams, including an energy stream with a flat shape, use system-needs placement.

A zero-mean stream is classified as flat by `is_stream_flat`. This code-level rule is more specific than classifying every flat stream identically.

For value-based placement, candidate windows are valued using the applicable impact and avoided-cost stream, including pre-charge/rebound effects. The dispatcher selects a positive-value feasible assignment, records its stream, updates load and reduces applicable remaining caps. Missing eligibility, exhausted caps and missing avoided-cost shapes are distinct failure reasons. Resources returned as remaining can enter the fallback peak-chasing stage.

Caps and placement order affect later choices. This is sequential resource allocation, not a simultaneous perfect-foresight optimization of every measure and service.

## Annual constraints and call-number sweeps

Annual event placement enforces the measure's available-hour mask, full-window geometry, `annual_max_events`, `daily_max_events` and cooldown. After an event, the blocking helper excludes the complete impact window expanded by cooldown on both sides. Every calendar day touched by pre-charge, curtailment or rebound increments its event counter; reaching the daily maximum blocks the rest of that day.

Defaults are zero cooldown hours and one daily and annual event. Invalid negative cooldown or event maxima below one are normalized by the input reader with warnings. Annual dispatch must be evaluated on the 8,760-hour sequence; it cannot be reproduced by multiplying one 24-hour result by an assumed number of days.

Typical-day `typical_days.number_of_calls` is a nonempty list of positive integers, default `[1]`. Each configured call number evaluates the applicable call eligibility; it is an analysis dimension. Do not sum rows for call numbers `[1,10,25]` as 36 disjoint events or treat them as an annual event schedule.

## Screening and program competition

Dispatch evaluates PACT against the normalized economic threshold, with applicable override and screening settings. This differs from DERP's JST economic screen. When no PACT ratio is available, the current screening method passes the resource rather than forcing exclusion; the placement record reports a numeric ratio of zero in that case. The result retains ratio, pass/fail, override and placement reason for diagnosis.

Program variants can compete for the same underlying market. Competition resolves allocations before portfolio dispatch, preserving the group identity, algorithm, score and post-competition propensity. The allocation can also partition called units across allowed stream scopes. Inspect post-competition rather than pre-competition enrollment when reconciling dispatched units. For a competitive group, the code uses the largest variant market as the common denominator and the maximum standalone callable units as the group ceiling. Scores are nonnegative callable units times the selected per-unit independent energy/demand metric, subject to the minimum eligible score. Winner-takes-all assigns the ceiling to the highest-score variant; proportional allocation divides the same ceiling by score shares. Additive allocation preserves each variant's own callable units. Zero scores or a zero ceiling give zero competitive allocations. Configured competition order or input order breaks ties. Unknown groups and duplicate program names are rejected.

## Results and limitations

Keep independent and effective impacts, enrolled and called units, grid target, program, year, scenario, period/day type and call number with each result. A measure can enroll but fail dispatch because it does not reduce the residual peak or lacks an eligible positive-value window. Its zero effective impact does not imply zero technical unit flexibility.

The annual model assumes the supplied 8,760-hour calendar. Validate leap-day and daylight-saving handling during input preparation. The current registered strategies model planned dispatch on supplied curves; they do not represent stochastic customer response, live control or a network feasibility guarantee unless those effects are explicitly characterized in the inputs.

## Implementation

* [Standalone runner](https://github.com/dunsky-consulting/packages/blob/trunk/derp/src/derp/run_demand_response.py)
* [Standard days](https://github.com/dunsky-consulting/packages/blob/trunk/derp/src/derp/demand_response/standard_day_calculator.py)
* [Strategy registry](https://github.com/dunsky-consulting/packages/blob/trunk/derp/src/derp/demand_response/dispatch_strategy.py)
* [Annual geometry](https://github.com/dunsky-consulting/packages/blob/trunk/derp/src/derp/demand_response/annual_dispatch/geometry.py)
* [Annual event blocking](https://github.com/dunsky-consulting/packages/blob/trunk/derp/src/derp/demand_response/annual_dispatch/event_blocking.py)

## Inputs and execution contract

A standalone study with cost effectiveness supplies DR measures, DER measure characterization, markets, programs, settings, valuations/rates and adoption curves. These are data inputs to the DR runner; they do not require execution of DERP potential calculations. `run_in_memory` and `run_from_files_with_settings` return a ZIP report as `BytesIO` together with a timestamp string. The latter accepts a JSON settings override. `run_write_to_file` writes the file-based report.

| Workbook content | Named tables or contract |
| - | - |
| DR study settings and periods | `TBE_settings`, `TBE_weather_adjustment`, `TBE_period_definitions` |
| DR flexibility | `TBE_relative_measure_potentials`, including curtailment, pre-charge, rebound and event constraints |
| System/end-use curves | `TBE_end_use_profiles` or configured Databricks-backed profiles |
| Unit power curves | `TBE_unit_end_use_profiles`; distinguish from aggregate end-use profiles |
| Optional shape/layer adjustments | `TBE_dg_shape_curves`, `TBE_stack_overrides` |
| Measure characterization | `TBE_measure_characterization`, `TBE_measure_characterization_ts` |
| Markets and forecasts | `TBE_market_characterization`, `TBE_market_characterization_ts` |
| Program mappings and inputs | `TBE_measure_program_mapping`, `TBE_program_inputs_fixed`, `TBE_program_inputs_ts` |
| Shared scenario and finance inputs | DERP settings, valuations and adoption tables, including scenario selectors and propensity parameters |
| Grid services | `TBE_grid_services`, `TBE_grid_eligibility`, `TBE_re_optimized_streams` |

The timestamp reader accepts its implemented date/time or date-plus-hour conventions. Market time-series scenario selectors must match the configured study mapping. Validate grid market totals and end-use grid coverage before interpreting node results. Missing optional grid-service tables are logged and skipped; this does not establish that system-needs dispatch has sufficient data.

| Setting | Current contract |
| - | - |
| `analysis_scope` | `typical days` (default) or `annual`; unknown nonblank values raise an error |
| `typical_days.number_of_calls` | `[1]` by default; nonempty positive-integer list |
| `output_power_unit` | `MW` by default; `kW` selects power conversion |
| `system_level` | `local`, `regional_bulk`, `bulk`; unknown values warn and fall back to local |
| `cooldown_hours` | Nonnegative integer, default zero |
| `daily_max_events`, `annual_max_events` | Positive integers, each default one |
| Databricks end-use source | Enables the local grid-level batch route when local mode is selected |

Local grid-level batch execution uses a processor, checkpoint and output sink. Explicit grid levels or the configured profile source supply the work list. Checkpoint failure counts determine cleanup; an incomplete job must not be interpreted as a complete zero-impact result. `--no-multiprocess` selects sequential grid processing for file-based CLI execution. `--overwrite` refreshes the configured data/memmap caches.

## Output contract

The report exports `Measures Savings Impact` and `Measures Load Impact` worksheets, with report construction supporting file and in-memory results. Retain the exported power-unit convention when interpreting fields.

| Exported field | Interpretation |
| - | - |
| `measure.key`, `program`, `scenario`, `year` | Identity and study selectors |
| `dispatch.grid_level`, `dispatch.group`, `dispatch.day_type`, `dispatch.call_number` | Grid, competition and temporal scope |
| `units`, `units.enrolled`, `units.called` | Market denominator, enrolled participants and callable participants |
| `enrollment.propensity.pre_competition`, `enrollment.propensity.post_competition` | Enrollment before and after program allocation |
| `peak_reduction`, `avg_reduction`, `effective_impact_per_unit` | Effective portfolio impacts |
| `standalone_peak_reduction`, `standalone_avg_reduction`, `standalone_impact_per_unit` | Independent characterization impacts |
| `enrolled_capacity`, `enrolled_capacity_typical_day` | Enrolled capacity metrics, distinct from dispatched reduction |
| `placed`, `placement_hours`, `placement.reason`, `stack_order` | Placement outcome, event timing and ordering |
| `dispatch.method`, `dispatch.stream` | Strategy and selected service stream |
| `PACT.ratio`, `screen`, `screen_override` | Economic screen diagnostics |
| `incentive.yearly`, `incentive.yearly.effective`, incentive input fields | Applied incentives and characterized inputs |
| `program.competition.group`, `.algorithm`, `.metric_name`, `.metric_value` | Allocation diagnostics |

Non-placement reasons distinguish missing market, missing DR characterization, no matching end-use profile, PACT screening, no eligible streams, missing avoided-cost shape, exhausted caps, no positive savings and no peak reduction. Review these reasons before treating every unplaced resource as technically unavailable. Array-valued output fields are serialized; rounding in reporting can prevent exact reconstruction of intermediate floating-point arrays.

## Verification checks

For an ordinary market of 10,000 units with MAF 0.8, propensity 0.25 and opt-out 0.1, enrolled units are 2,000 and called units are 1,800. At 2 kW of applicable unit load and 50% curtailment, the instantaneous reduction is 1.8 MW before portfolio peak migration or rebound effects. A residual curve with peaks 10 MW and 9.8 MW can show only 0.2 MW effective peak reduction after a 1 MW curtailment at the original peak.

Check these dimensions independently: market conservation and program competition, unit-to-aggregate scaling, curtailment/pre-charge/rebound signs, chronological event blocking, non-placement reasons, power-unit conversion, independent versus effective impact, and the [optional DERP writeback](https://docs.models.dunsky.com/en/models/derp/specification.md#optional-dr-integration).


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