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

# LDM allocation specification

> Define LDM workbook tables, market identifiers, propensity weights, starting adoption, Bass inversion, normalization and forecast reconciliation.

LDM partitions supplied regional forecasts into subregions without recalculating the parent model's economics. All formulas below use the current `structure` modules.

## Workbook and identifiers

| Sheet | Named table | Contract |
| - | - | - |
| `market_and_propensity` | `TBE_market_and_propensity` | Market IDs, size parameter, propensity weights, starting-point and partition methods, diffusion parameters |
| `region_forecast` | `TBE_region_forecast` | Forecast values by year and identifying attributes |
| `region_forecast_parameters` | `TBE_region_forecast_parameters` | `column_name` values classified as `id` or `disaggregate` by `parameter_type` |
| `region_parameters` | `TBE_region_parameters` | Parent-region market and propensity denominators |
| `subregion_parameters` | `TBE_subregion_parameters` | Parent/subregion IDs, partition type and local numerator parameters |

`MarketPropensityID` comprises `region`, `sector`, `segment`, `type` and `sub_type`. Forecast rows also carry `scenario.name`, `potential`, `impact_type`, `savings_reporting_level`, additional declared IDs and `variable`. Duplicate combinations can fail the importer's pivot. Every forecast market ID must resolve to a propensity configuration, and every subregion must resolve to a parent region.

Propensity text is parsed as comma-separated key/value entries into float weights; blanks and textual `none`/`nan` yield no weight mapping. The declared column `attribute.partition_type` is stored on subregions; the partition loop does not independently filter on that field.

## Year and unit conventions

The importer takes unique years from the forecast, pivots each disaggregated variable to annual columns and retains metadata. Supply sorted, consecutive integer years. Diffusion advances one unit per array position regardless of gaps in calendar-year labels.

All allocation weights are dimensionless. Market numerator and denominator must have matching units; each propensity numerator/denominator pair must also match. Output forecast units equal input forecast units. No energy, currency or customer-count conversion is performed. Percentages or intensive metrics should not be disaggregated as though they were additive quantities without an explicit interpretation.

## Relative propensity

For subregion $i$, regional parameter $x_{R,k}$ and configured coefficient $a_k$:

$$
B_i=\sum_k a_k\frac{x_{i,k}}{x_{R,k}}
$$

With `adoption_propensity_parameters` equal to `None` or textual `none`, $B_i=1$. Weights are neither normalized nor constrained to be positive by this function. A parameter with a negative behavioural relationship must be transformed or weighted appropriately in inputs. Zero denominators, missing keys and nonnumeric values are not repaired here.

## Starting adoption

For `starting_point_method="sales_and_barriers"`, read market $M_i$ using `<market_share_parameter>.<sector>.<segment>` and starting sales $N_i$ using `starting_sales_parameter`:

$$
s_i=\begin{cases}N_i/M_i,&M_i\ne0\\0,&M_i=0\end{cases},\qquad s_i^0=\min(s_iB_i,0.999)
$$

When market is zero but starting sales are nonzero, the function sets zero and emits a warning. The cap is upper-only: negative starts remain negative and fail later diffusion validation. Literal `"none"` produces no starting point. The conditional written `starting_point_method == "none" or None` does not accept Python `None` as an additional alternative; Python `None` reaches the invalid-method error.

## Raw partition methods

`market_size` uses $r_{i,y}=M_i/M_R$, constant across study years for fixed market inputs.

`barriers_and_market_size` uses $r_{i,y}=(M_i/M_R)B_i$.

`diffusion` uses $r_{i,y}=(M_i/M_R)F_i(y)$, with the starting point derived above. The only accepted `partition_method` strings are `market_size`, `barriers_and_market_size` and `diffusion`; other values raise `ValueError`.

For Bass parameters $p,q,t_0$:

$$
F(t)=\frac{1-e^{-(p+q)(t-t_0)}}{1+(q/p)e^{-(p+q)(t-t_0)}}
$$

The equivalent starting time is:

$$
t_i^0=\frac{\ln(1+s_i^0q/p)-\ln(1-s_i^0)}{p+q}+t_0
$$

For the first forecast position, evaluate at $t_i^0+1$, then increment by one for subsequent positions and relabel the sequence with the supplied study years. Starting fractions and calculated diffusion fractions are checked against $[0,1]$. Exact one is allowed by the initial range check but is singular in the inverse logarithm; the normal starting-point calculation avoids it with the 0.999 cap. Require valid positive $p$, a nonzero $p+q$ and finite inputs.

## Normalization and multiplication

For every market-propensity configuration, the partition routine constructs a row for every supplied subregion, then normalizes each year column across those rows:

$$
w_{i,y}=\frac{r_{i,y}}{\sum_jr_{j,y}},\qquad f_{i,y}=w_{i,y}F_y
$$

The implementation's normalization set is the supplied subregion list, not an additional grouping by `attribute.partition_type` or parent region. A configuration can therefore include unrelated subregions unless the caller restricts inputs. Use one coherent partition for a regional forecast and reconcile separately by parent region.

For finite weights with nonzero column sums, $\sum_i f_{i,y}=F_y$. This equality does not establish geographic correctness. Negative weights, overlapping areas or inconsistent market denominators can still create invalid allocations. There is no general zero-sum fallback, nonnegative clipping or rounding to integer customers.

## Output and execution

`get_disaggregated_forecasts` multiplies partitions by each forecast series and appends market attributes, subregion, scenario, potential, impact type, reporting level, additional IDs and variable. Annual columns follow the forecast's study-year list. Combined output is a concatenated dataframe; the CLI writes a timestamped CSV under `./results/` with the dataframe index included.

The CLI's `--input-data-xlsx` default is `./data/DERP_input.xlsx`; `--log-file` defaults to `./logs/ldm.log`. These are filenames, not restrictions on which model's forecasts can be supplied. Empty forecast lists or missing matches can fail in concatenation or `next(...)`; no general empty-output recovery is implemented.

## Reproducible checks

For $F=100$ GWh, market shares 0.6/0.4 and propensity factors 0.5/1.5, raw weights are 0.3/0.6. Normalization gives 1/3 and 2/3, producing 33.333… and 66.666… GWh. Changing the units to MWh scales forecast outputs only; weights remain identical.

Check named tables, unique IDs, complete geographic coverage, annual ordering, finite/nonnegative weights, valid diffusion starts and per-year conservation. Test zero regional denominators explicitly before production use. Existing tests cover import, dictionary parsing, starting points, diffusion, partitioning and logging. They verify implementation behaviour, not the empirical validity of the selected spatial proxies.


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