External Feature Covariates — Technical Test Guide¶
ForecastAI 2026.01 · May 2026
1. Overview¶
The External Feature (Covariate) system allows ForecastAI to incorporate time-series signals beyond historical demand when generating forecasts. These signals — called covariates — can be future-available (known in advance, e.g. planned holidays, weather forecasts) or history-only (observed in the past, e.g. customer backlog).
Future-available covariates are injected directly into NeuralForecast models as futr_exog inputs. History-only covariates are used as lagged features in the ML feature matrix.
All covariate data is stored in ClickHouse, partitioned by (pipeline_id, scenario_id), so each forecast run uses its own isolated dataset. A reliability gate prevents low-quality or uncorrelated features from entering the models.
2. Inputs¶
2.1 PostgreSQL Feature Definitions¶
Two PostgreSQL tables in the scenario schema define available features and their activation configuration per pipeline.
| Table | Key Columns |
|---|---|
scenario.feature_definitions |
id, name, type (global / item-specific), future_available (bool), source_table, source_column, description |
scenario.feature_parameter_sets |
id, pipeline_id, name, params (JSONB with activated_features and min_reliability) |
Example params JSONB for feature_parameter_sets:
{
"activated_features": [
{"feature_id": 1, "enabled": true, "weight": 1.0},
{"feature_id": 2, "enabled": true, "weight": 0.8}
],
"min_reliability": 0.4
}
2.2 ClickHouse Feature Values Table¶
| Property | Value |
|---|---|
| Table Name | PIPE_feature_values |
| Engine | ReplacingMergeTree(loaded_at) |
| Partition Key | (pipeline_id, scenario_id) |
| Column | Type | Description |
|---|---|---|
pipeline_id |
Int64 | Pipeline identifier |
scenario_id |
Int64 | Scenario identifier |
feature_id |
Int64 | References feature_definitions.id |
feature_name |
String | Denormalized name for fast queries |
feature_type |
LowCardinality(String) | global or item-specific |
future_available |
UInt8 | 1 = futr_exog, 0 = hist_exog |
date |
Date | ISO week start date (Monday) |
unique_id |
String | Series identifier (item_id_site_id) |
value |
Float64 | Covariate signal value |
loaded_at |
DateTime | Upsert timestamp for ReplacingMergeTree |
2.3 Demo Features (Seeded via API)¶
| Feature Name | Type | future_available |
Behaviour |
|---|---|---|---|
| Holiday Index | global | true | 1.0 normal weeks; 2.5 on bank holiday weeks (25 Dec, 1 Jan, Easter Friday) |
| Weather Index | global | true | Sinusoidal seasonal: 0.7 in winter, 1.3 in summer (northern hemisphere) |
| Customer Backlog | item-specific | false | Random walk bounded [0.5, 2.0] per unique_id — history-only lag feature |
3. Data Flow and Computation¶
3.1 FeatureLoader (files/forecasting/feature_loader.py)¶
On first call per Dask worker batch (lazy init), loads all rows for (pipeline_id, scenario_id) from ClickHouse into a pandas DataFrame in memory.
get_series_features(unique_id, hist_dates, future_dates) returns:
hist_df— history-only signals aligned tohist_datesfutr_df— future-available signals aligned tofuture_datesfutr_exog_list— column names for NeuralForecast constructorshist_exog_list— column names for ML feature matrix
Missing values: forward-fill within range; flat extrapolation beyond loaded window.
3.2 NeuralForecast Integration (files/forecasting/neural_models.py)¶
After the calendar feature block, if a FeatureLoader is attached:
- Calls
get_series_features()to retrievefutr_dfandfutr_exog_list - Merges
futr_exogcolumns into the series DataFrame passed to NeuralForecast - Passes
futr_exog=futr_exog_listto each model constructor
Models benefiting from future-available covariates: NHITS, NBEATS, PatchTST, TFT, DeepAR.
3.3 ML Model Integration (files/forecasting/ml_models.py)¶
In _forecast_method():
- Calls
get_series_features()to retrievehist_dfandhist_exog_list - Merges
hist_exogcolumns into the lag feature matrix - For each forecast horizon step
h, substitutesfutr_exogvalues fromfutr_dfat the target date
3.4 Reliability Gating (feature-reliability pipeline step)¶
For each (feature, unique_id) pair, a composite reliability score in [0, 1] is computed:
| Component | Weight | Formula |
|---|---|---|
| Pearson correlation | 50% | Correlation between feature values and demand_corrected values |
| Naive MAPE improvement | 30% | MAPE improvement vs. no feature |
| Bias contribution inverted | 20% | Lower bias = higher score |
Pairs with a composite score below the configured min_reliability threshold are excluded for that series. The gating result is persisted in scenario.feature_reliability_scores.
| Score Range | Signal Quality | Action |
|---|---|---|
| ≥ 0.7 | Strong signal | Feature injected for this series |
| 0.4 – 0.7 | Moderate signal | Injected if min_reliability ≤ 0.4 (default) |
| < 0.4 | Weak / uncorrelated | Excluded for this series |
4. Database Tables¶
| Table Name | Location | Purpose | Key Columns |
|---|---|---|---|
feature_definitions |
PostgreSQL scenario |
Defines available external signals | id, name, type, future_available |
feature_parameter_sets |
PostgreSQL scenario |
Per-pipeline activation config | id, pipeline_id, name, params (JSONB) |
feature_reliability_scores |
PostgreSQL scenario |
Per-(feature, series) reliability result | feature_id, unique_id, pipeline_id, score, gated_in |
PIPE_feature_values |
ClickHouse | Actual covariate values per run | pipeline_id, scenario_id, feature_id, date, value |
demand_corrected |
PostgreSQL scenario |
Outlier-corrected demand for correlation | item_id, site_id, date, corrected_value, pipeline_id |
forecast_results |
PostgreSQL scenario |
Forecast outputs (covariate-enhanced) | unique_id, method, point_forecast, pipeline_id |
5. User Configuration (Step-by-Step)¶
Step 1 — Select Your Pipeline¶
In the left toolbar, choose the pipeline (e.g. "Bicycle — Base Pipeline"). All configuration is scoped to this pipeline.
Step 2 — Open the Feature Params Tab¶
Navigate to Processes (left menu) and click the Feature Params tab (puzzle-piece icon). Three panels appear: Feature Coverage, Feature Activation, Data Management.
Step 3 — Seed Demo Data¶
In the Data Management panel, click "Seed Demo Data". This calls POST /api/features/seed-demo and loads the three demo features into ClickHouse.
Step 4 — Configure Activation¶
Toggle each feature on or off, adjust weights (0.0 to 1.0), and set the Min Reliability threshold (default 0.4). Changes are saved automatically when you click outside the field.
Step 5 — Run the Pipeline¶
In the Run Pipeline panel, ensure both feature-reliability and forecast steps are selected. Click Run. The feature-reliability step must precede the forecast step to gate low-quality signals.
6. Observing Results¶
6.1 Feature Coverage Table¶
After seeding, the Feature Coverage panel shows one row per feature with: Feature Name, Type, Future Available, Row Count, Date Range, Unique Series Count. A green tick indicates sufficient data coverage; a warning icon appears when fewer than 52 rows are present for a feature.
6.2 Reliability Score Interpretation¶
See Section 3.4 above for the score ranges and what they mean.
6.3 Forecast Quality Comparison¶
Compare backtest metrics (MAE, MAPE, SMAPE) between a run without features and a run with features. Navigate to a series in the Dashboard, open the Series Viewer, and compare the backtest metric columns across methods.
7. Testable SKUs¶
The following SKUs from the Bicycle tenant are recommended for testing covariate integration:
unique_id |
Item Name | Site | Why Test | Expected Benefit |
|---|---|---|---|---|
10_301 |
Mountain Bike | Paris | High volume, strong seasonality. Holiday and weather signals correlate with spring/summer peaks. | NHITS MAPE reduction 15–25% |
18_305 |
Wheelset | Bordeaux | Assembly component — tracks finished goods. Customer backlog adds lag predictive power. | ML feature importance increase |
5_320 |
Road Bike | Lyon | Strong summer seasonality, intermittent winters. Weather Index alignment expected. | Croston less competitive vs. NHITS |
12_310 |
E-Bike | Marseille | Growing trend. Holiday Index spikes (Black Friday, Christmas) drive clear signal. | MASE improvement on TFT |
3_335 |
Frame Set | Verona | Low ADI, sporadic demand. Features expected below reliability threshold — tests gating. | Reliability score < 0.4; features excluded |
8. API Reference¶
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/features/pipeline/{pipeline_id}/scenario/{scenario_id}/summary |
Feature coverage summary from ClickHouse |
| GET | /api/features/pipeline/{pipeline_id}/scenario/{scenario_id}/list |
Feature definitions with activation status |
| POST | /api/features/pipeline/{pipeline_id}/scenario/{scenario_id}/upload |
Upload CSV (columns: feature_name, date, unique_id, value) |
| POST | /api/features/pipeline/{pipeline_id}/scenario/{scenario_id}/push |
JSON array push to ClickHouse |
| DELETE | /api/features/pipeline/{pipeline_id}/scenario/{scenario_id} |
Drop ClickHouse partition for this run |
| POST | /api/features/seed-demo |
Seed demo features (query params: pipeline_id, scenario_id) |
| GET | /api/features/scenarios/{pipeline_id}/parameter-sets |
Get activation parameter sets for pipeline |
| POST | /api/features/scenarios/{pipeline_id}/parameter-sets |
Create activation parameter set |
9. Troubleshooting¶
| Issue | Resolution |
|---|---|
| Coverage table shows 0 rows after seeding | Check ClickHouse connection in config.yaml. Verify ensure_ch_feature_tables() ran on startup. Re-run seed-demo with correct pipeline_id/scenario_id. |
| All reliability scores below threshold | demand_corrected may be empty. Run the forecast step first (which embeds outlier detection) to populate demand_corrected before re-running feature-reliability. |
| NeuralForecast models ignore features | Check that pipeline_id > 0 is passed to NeuralForecaster. FeatureLoader is only instantiated when pipeline_id > 0. Verify futr_exog_list is non-empty after get_series_features(). |
| ML models show no improvement | hist_exog needs at least 52 weeks of history. Verify Customer Backlog covers the full demand_actuals date range for the series under test. |
| Login fails with 'database unavailable' | HTTP 503 is expected when the PostgreSQL pool is exhausted. Wait 30 seconds or restart the API. This is not related to the feature system. |