Clone
5
forecast.py
willwheeler edited this page 2026-06-18 21:23:13 +00:00

<-- Home

Forecast — Python pipeline

Train short-horizon BACnet trend models from sample JS files, evaluate accuracy on a held-out tail, write chart-ready JSON, and optionally experiment with extra inputs before changing production settings.

Consumer: test.php loads forecast_output.json for dashed forecast lines on the power and phase charts.

Home


On this page


Pipeline

js/veris-sample.js ──┐
js/rtu-sample.js  ──┼──► forecast.py ──► forecast_output.json ──► test.php (Load forecast)
                      │
                      └──► forecast_comp.py ──► console MAE only (experiments)
Step Tool Output
1 Sample JS files { PointName: [{x,y},...], ... }
2 forecast.py forecast_output.json + MAE per point in console
3 test.php Dashed overlay on Line P + Phase compare
(optional) forecast_comp.py Compare single-point vs multi-point MAE (no JSON)

Sample files use the same {x, y} shape as live getData from index.php, so the pipeline matches production data once you point the scripts at live exports.


Prerequisites

pip install pandas scikit-learn skforecast
Package Used for
pandas Time series, resampling, align
scikit-learn Ridge regressor
skforecast ForecasterRecursive (lag-based forecasting)

Run all commands from the project root (folder containing forecast.py).


forecast.py

Primary script. One model per BACnet point, backtest on the last 20% of history, then predict FUTURE_STEPS ahead and write JSON.

Run

python forecast.py

No CLI arguments. Edit constants at the top of the file.

Settings

Constant Default Meaning
FUTURE_STEPS 100 How many future timestamps to predict
LAGS 25 How many past readings the model sees each step
JOBS see below Which file + point names to process

Default JOBS:

JOBS = [
    ("js/veris-sample.js", ["P", "Ia", "Ib", "Ic"]),
    ("js/rtu-sample.js", ["ServRmTmp"]),
]

Add a (js_file, [names…]) tuple to forecast more points. Re-run to refresh JSON.

What it does (per point)

  1. Load — Parse var xxxData = { ... }; from the .js file (regex + JSON).
  2. Series — Build pandas Series: x → datetime (ms), y → float.
  3. Resample — Median gap between samples → even grid; interpolate missing steps.
  4. BacktestFirst 80% train, last 20% predict → print MAE (mean absolute error).
  5. Forecast — Retrain on 100% of history → predict FUTURE_STEPS forward.
  6. Write — Append to forecast_output.json under the point name.

Model

  • ForecasterRecursive (skforecast) — each step uses recent lags; predictions feed forward.
  • Ridge (sklearn) — linear model, fast and stable on smooth BAS trends.
  • MAE — average absolute miss on the hidden 20%; same units as the point (kW, °F, A, etc.).

Console example

P — 432 readings
  MAE (last 20%): 11.17

Ia — 432 readings
  MAE (last 20%): 0.42
...

Saved forecasts for: P, Ia, Ib, Ic, ServRmTmp

Output file

Writes forecast_output.json in the project root (overwrites each run).


forecast_output.json

Generated by forecast.py. Static file the browser fetches — not generated by PHP.

Schema

Top-level keys = point names. Each value:

Field Type Description
mae number Backtest error on last 20%
future array Predicted readings after the last historical sample

Each future item:

Field Type Description
x integer Unix time in milliseconds (same as sample JS / getData)
y number Predicted value (2 decimal places)

Example

{
  "P": {
    "mae": 11.17,
    "future": [
      { "x": 1781713080000, "y": 19.05 },
      { "x": 1781713200000, "y": 19.04 }
    ]
  }
}

Default keys (from current JOBS)

Key Source file Used on dashboard
P veris-sample.js Line P chart (+ forecast)
Ia, Ib, Ic veris-sample.js Phase compare (+ forecast)
ServRmTmp rtu-sample.js In JSON; RTU spark overlay not wired yet

Charts only overlay forecast when:

  1. forecast_output.json has that key, and
  2. Loaded chart data includes the same point name (live or sample).

See test.php#forecast-overlay for how basForecastSeries() attaches the dashed line.

MAE in JSON

Stored for each point but not shown in the UI yet. Useful when comparing runs after changing LAGS or FUTURE_STEPS.


forecast_comp.py

Research / experiment script. Answers: “If I add another BACnet point as input, does the target predict better?”

Does not write JSON. Does not change charts. Run when tuning before updating forecast.py or exog lists.

Run

python forecast_comp.py

Settings (top of file)

Constant Example Meaning
JS_FILE "js/rtu-sample.js" Sample data file
TARGET "RaTmp" Point you want to predict
WITH_POINTS ["FanCmdOvr"] Extra columns for the combined model
LAGS 26 Lag window (can differ from forecast.py)

All points must exist in the same JS file. Rows are time-aligned; timestamps with any missing value are dropped.

Two models (same 80/20 split)

Model Inputs
A — alone TARGET history only (like one point in forecast.py)
B — combined TARGET + every name in WITH_POINTS at each timestamp (exogenous / exog)

Example output

File: js/rtu-sample.js
Target: RaTmp
Also using: FanCmdOvr
Aligned readings: 412

Backtest on last 20% of sample data:
  RaTmp only:                    MAE = 1.24
  RaTmp + [FanCmdOvr]:           MAE = 0.89

Extra points helped — about 0.35 better on average.
Verdict Condition
Extras helped mae_alone - mae_combined > 0.05
Extras hurt difference < -0.05
About the same otherwise

Quality warnings (read before trusting MAE)

The script prints warnings when results can mislead:

  • Flat target in the test slice → MAE ≈ 0 but model learned nothing useful
  • Exog identical to target → combined MAE ~0 is not a breakthrough
  • Very high correlation between exog and target
  • Lookup table — each exog value maps to one target value
  • Flat exog in the test slice

If combined MAE < 0.01 and warnings appeared, treat the win as suspicious.

When to use which script

Goal Script
Ship forecasts to the dashboard forecast.py
Ask “should FanSts / mode / setpoint help predict room temp?” forecast_comp.py
Tune lags or point combos before changing JOBS forecast_comp.py

Workflow cheat sheet

Offline demo (matches sample + forecast):

pip install pandas scikit-learn skforecast
python forecast.py
open test.php?sample=1&forecast=1

Experiment with extra RTU points:

# edit TARGET / WITH_POINTS in forecast_comp.py
python forecast_comp.py
# if helpful, consider adding logic to forecast.py later

Refresh after sample JS changes:

python forecast.py
# in browser: Load forecast (or hard refresh with ?forecast=1)

Troubleshooting

Problem Likely cause Fix
ModuleNotFoundError: skforecast Deps not installed pip install pandas scikit-learn skforecast
Point missing from JSON Not in JOBS Add name to JOBS, re-run
test.php: “No forecast_output.json” File not generated Run forecast.py from project root
Forecast loads, no dashed lines Point name mismatch JSON key must match chart series (e.g. P, not Demand unless that's the key)
MAE = 0.00 in comp script Flat or duplicate data Read warnings in console
TARGET not found in JS_FILE Typo or wrong file Check exact BACnet name in sample JS
Predictions look flat Short history or very stable signal Normal for Ridge on flat power; try more data or different point

  • Home — repo overview and links
  • test.php — dashboard, live getData, forecast overlay UI
  • index.php — live trend API (future: feed export into forecast.py)

Forecast wiki — Python training, JSON output, and experiment scripts.