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

# Hierarchical Reconciliation

> Make forecasts add up across regions, stores, and totals

## The problem

When your data has a hierarchy — stores inside regions inside a national
total — forecasting each series on its own gives you numbers that don't add
up. The total forecast rarely equals the sum of the region forecasts, and
the regions rarely equal the sum of their stores:

```
               Total
              /     \
        Region A    Region B
        /    \       /    \
     S1     S2     S3     S4
```

Reconciliation adjusts the forecasts so every parent equals the sum of its
children — a property called *coherence*. Planning, budgeting, and
allocation all break down without it, because two teams reading different
levels of the same forecast otherwise see different numbers.

## Enabling it

Name the columns that form your hierarchy, top to bottom. You do **not**
need an id column: the bottom-level series id is derived from the hierarchy
values.

```yaml theme={null}
reconciliation:
  enabled: true
  hierarchy_columns: ["region", "store"]   # top → bottom
  method: min_trace                        # bottom_up | top_down | min_trace
  weights: wls_struct                      # MinT weighting
```

Given rows like `region=north, store=store_1`, the pipeline builds:

| Level          | Series in the output                |
| -------------- | ----------------------------------- |
| Grand total    | `total`                             |
| Region         | `north`, `south`                    |
| Store (bottom) | `north/store_1`, `north/store_2`, … |

Aggregate series are summed from your data, forecast alongside the bottom
series, then reconciled. The output CSV carries every level, so one download
serves every audience.

## Choosing a method

| Method      | Forecasts where | Uses all levels | Best when                                               |
| ----------- | --------------- | --------------- | ------------------------------------------------------- |
| `bottom_up` | Bottom only     | No              | Bottom series are long and low-noise                    |
| `top_down`  | Total only      | No              | The aggregate is much cleaner than sparse, noisy leaves |
| `min_trace` | Every node      | Yes             | Almost always — the default                             |

**Bottom-up** trusts the leaves: forecast each store, sum upward. Always
coherent, but noise in the leaves accumulates into the total.

**Top-down** trusts the root: forecast the total and split it by historical
proportions. Very stable, but it assumes those proportions still hold and
can't react to a shock at one store.

**MinT** (Wickramasuriya et al., 2019) trusts everyone, weighted by their
uncertainty. It forecasts every node, then adjusts them all at once to the
coherent solution that minimizes total forecast-error variance — the trace
of `Var(ỹ − y)`, hence *minimum trace*. Because it uses information from
every level, it generally beats both alternatives.

### MinT weights

`weights` picks how MinT estimates the error covariance `W`:

| Value        | `W`                      | Cost                               |
| ------------ | ------------------------ | ---------------------------------- |
| `ols`        | Identity                 | Free                               |
| `wls_struct` | Node degree (default)    | Free                               |
| `wls_var`    | Per-node error variances | One extra pass per residual window |
| `shrink`     | Shrunk full covariance   | One extra pass per residual window |

`wls_var` and `shrink` estimate `W` from real forecast errors, so the
pipeline runs an internal backtest first — each window in
`residual_windows` costs one extra inference pass. `shrink` is the variant
most used in the literature; `wls_struct` is the default here because it
needs no extra compute and holds up well. If the series are too short to
back-test, the run degrades to `wls_struct` and records a warning rather
than failing.

### Top-down allocation

When `method: top_down`, `top_down_method` chooses how the total is split:

| Value                              | How shares are computed                                                                                                                                         |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `proportions_of_historical_totals` | Each series' summed history over the summed total (default). Stable; dominated by high-volume periods.                                                          |
| `average_historical_proportions`   | Mean of the per-period shares. Every period counts equally, so small series are tracked better — noisier when the total is near zero.                           |
| `forecast_proportions`             | Shares taken from the base forecasts themselves, recursively per level. Adapts to the forecast period at the cost of leaning on noisier disaggregate forecasts. |

## Reading the result

Job responses include a `reconciliation` summary:

```json theme={null}
{
  "enabled": true,
  "method": "min_trace",
  "weights": "wls_struct",
  "hierarchy_columns": ["region", "store"],
  "num_levels": 3,
  "num_bottom_series": 4,
  "num_total_series": 7,
  "coherence_max_abs_error": 5.7e-14,
  "warnings": []
}
```

`coherence_max_abs_error` is the largest gap between any parent and the sum
of its children after reconciliation — it should be at floating-point noise.

## Over the API

Pass the same block as a JSON form field. Sending the field at all enables
reconciliation, so `enabled` is optional:

```bash theme={null}
curl -X POST https://api.eomer.ai/forecast \
  -H "Authorization: Bearer $API_KEY" \
  -F "file=@sales.csv" \
  -F "prediction_length=24" \
  -F "presets=eomer_pulse_s" \
  -F 'reconciliation={"hierarchy_columns": ["region", "store"], "method": "min_trace"}'
```

The MCP `forecast` tool takes the same JSON in its `reconciliation`
parameter. `validate_dataset` and `profile_time_series` report a
`hierarchical` flag plus the detected columns, so you can check whether your
data supports reconciliation before submitting.

## Limits in this version

* **Quantiles are reconciled per column.** Each quantile column is coherent
  on its own, but the reconciled quantiles are not a coherent joint
  predictive distribution.
* **No covariates.** Reconciliation rejects user-declared and external
  covariates: an aggregate series has no meaningful value for them. Internal
  feature engineering still works, since those features are derived per
  series after the aggregates are built.
* **No fine-tuning** in combination with reconciliation.
* **Single partition.** A hierarchy can't be split across workers, so a Ray
  or distributed runtime is forced to one partition.
* Hierarchy values must not contain the `separator` (default `/`) or collide
  with `total_label` — the run fails fast with a message naming the column.
