<-- Home
BAS Forecast & Dashboard
Python forecasting scripts plus a browser dashboard for validating BACnet trend charts and forecast overlays. Runs offline first on sample JS files, then optionally against live getData from BasData.
On this page
- Overview & quick start
- Pipeline
- forecast.py
- forecast_comp.py
- forecast_output.json
- test.php dashboard
- Troubleshooting
Overview & quick start
| Component | Description |
|---|---|
| forecast.py | Train models, backtest on last 20%, write forecast_output.json |
| forecast_comp.py | Compare target-only vs target + extra points (console MAE) |
| forecast_output.json | Predictions + MAE per point for the dashboard |
| test.php | ApexCharts lab: sparks, heatmap, line, phases, HVAC, forecast overlay |
| js/test.js | Chart colors, wordbank, point → chart mapping |
| js/veris-sample.js | Veris meter sample data |
| js/rtu-sample.js | RTU sample data |
Install & run
pip install pandas scikit-learn skforecast
python forecast.py
View in browser
/test.php?sample=1&forecast=1
Or open test.php, load sample or live data, then click Load forecast.
Default forecast points (forecast.py → JOBS)
| Sample file | Points |
|---|---|
js/veris-sample.js |
P, Ia, Ib, Ic |
js/rtu-sample.js |
ServRmTmp |
test.php URL flags
| Parameter | Example | Effect |
|---|---|---|
sample=1 |
?sample=1 |
Use sample JS; no live fetch |
forecast=1 |
?forecast=1 |
Auto-load forecast_output.json |
interval=N |
?interval=24 |
Live trend window (8-36 hours) |
keyid=N |
?keyid=1 |
Override automation-server id |
embed=1 |
?embed=1 |
Minimal header (iframe) |
Main BasData app (context)
| Page | Role |
|---|---|
| index.php | Production viewer + getData API |
| reports.php | Report JSON builder |
| test.php | Chart/forecast lab before shipping to reports/index |
Pipeline
veris-sample.js / rtu-sample.js
│
▼
forecast.py ──► forecast_output.json
│
▼
test.php ◄── (optional) live getData from index.php
│
▼
ApexCharts (dashed forecast on P line + phase compare)
forecast_comp.py → console MAE only (experiments, no JSON)
forecast.py
Trains one time-series model per BACnet point, evaluates MAE on the last 20% of history, predicts 100 steps ahead, writes forecast_output.json.
Run
python forecast.py
No CLI args — edit constants at the top of the file.
Settings
| Constant | Default | Meaning |
|---|---|---|
FUTURE_STEPS |
100 |
How many future readings to predict |
LAGS |
25 |
How many past readings the model uses each step |
JOBS |
veris + rtu | List of (js_file, [point_names]) |
Default JOBS:
JOBS = [
("js/veris-sample.js", ["P", "Ia", "Ib", "Ic"]),
("js/rtu-sample.js", ["ServRmTmp"]),
]
Algorithm (per point)
- Load — Parse
var xxxData = { ... };from the.jsfile. - Series — pandas
Seriesindexed by timestamp (x= ms,y= float). - Resample — Even spacing from median step; interpolate gaps.
- Backtest — First 80% train, last 20% predict → MAE printed.
- Forecast — Retrain on full series →
FUTURE_STEPSahead. - Write —
{ "mae", "future": [{x,y},...] }per point to JSON.
Model stack
- skforecast
ForecasterRecursive - sklearn
Ridge - Metric: mean absolute error on the hidden 20%
Console example
P — 432 readings
MAE (last 20%): 11.17
Saved forecasts for: P, Ia, Ib, Ic, ServRmTmp
forecast_comp.py
Research script: does adding other points improve predictions for a target?
Compares two models on the same 80/20 backtest:
| Model | Inputs |
|---|---|
| A — alone | Target point history only |
| B — combined | Target + WITH_POINTS at each timestamp |
Does not write JSON or change charts.
Run
python forecast_comp.py
Settings (top of file)
| Constant | Example | Meaning |
|---|---|---|
JS_FILE |
js/rtu-sample.js |
Sample data source |
TARGET |
RaTmp |
Point to predict |
WITH_POINTS |
["FanCmdOvr"] |
Extra columns for model B |
LAGS |
26 |
Lag window |
Output example
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.
Quality warnings
Script warns when MAE is misleading:
- Flat target in test slice (MAE ≈ 0, model learned nothing useful)
- Exog column identical or nearly identical to target
- Exog fully determines target (lookup table)
- Flat exog in test slice
Use forecast_comp.py to experiment; use forecast.py to ship JSON to the dashboard.
forecast_output.json
Generated by forecast.py. Loaded by test.php for dashed forecast overlays.
Schema
{
"P": {
"mae": 11.17,
"future": [
{ "x": 1781713080000, "y": 19.05 },
{ "x": 1781713200000, "y": 19.04 }
]
}
}
| Field | Meaning |
|---|---|
mae |
Backtest error on last 20% (same units as point) |
future |
Predicted readings after last historical sample |
x |
Unix time in milliseconds (matches sample JS / getData) |
y |
Predicted value (2 decimals) |
Keys (default JOBS)
| Key | Source | Dashboard use |
|---|---|---|
P |
veris-sample.js | Power line chart |
Ia, Ib, Ic |
veris-sample.js | Phase compare |
ServRmTmp |
rtu-sample.js | In JSON; RTU chart overlay TBD |
How test.php uses it
fetch("forecast_output.json")→basForecastDatabasForecastSeries(actualData, pointName)→ last real point +futurearray- ApexCharts: dashed red series, "Forecast →" at last live timestamp
Charts with forecast today: Line P, Phase compare.
mae is stored but not shown in UI yet.
test.php dashboard
Device Snapshot — ApexCharts lab for BasData chart prototyping.
Modes
| Mode | How |
|---|---|
| Live | Log in on index.php → pick office / building / asp → open test.php |
| Sample | ?sample=1 loads rtu-sample.js + veris-sample.js |
| Forecast | ?forecast=1 or Load forecast (needs chart data loaded first) |
Hardcoded devices (for now): RTU_3, VerisMeter.
Charts
| Toolbar slot | Chart | Typical points |
|---|---|---|
| sparks | Fan / room / power mini charts | FanSts, ServRmTmp, P |
| heatmap | All Veris points | all keys in bundle |
| lineP | Power trend (+ forecast) | P, Demand |
| phaseCompare | Ia / Ib / Ic (+ forecast) | Ia, Ib, Ic |
| hvacMode | HVAC mode timeline | HVACMode |
| trigger | Fan start counts per bucket | FanSts |
| radialBar | Temp vs setpoint | ServRmTmp + setpoint pair |
Toggle charts with toolbar checkboxes. Point → slot mapping: js/test.js → BAS_CHART_SLOTS, basResolveDashboard().
Live data flow
test.php --POST--> index.php (getData=true&keyid=&name=&interval=&tstamp=)
<--JSON-- { PointName: [{x,y}, ...], ... }
--> basApplyDashboard(rtu, veris)
Same contract as the index sidebar.
Header buttons
| Button | Action |
|---|---|
| Load sample data | Offline JS bundles |
| Load forecast | Fetch JSON, redraw P + phase charts |
| Status bar | Loading / ok / partial fail / errors |
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| "No forecast_output.json" | File missing | Run python forecast.py from project root |
| Forecast button, no lines | No chart data | ?sample=1 or log in and load live data first |
| MAE ≈ 0 in comp script | Flat or duplicate data | Read warnings in console output |
| Live RTU charts empty | Long interval, huge payload |
Shorter window; see server memory notes |
| Point missing from JSON | Not in JOBS |
Add to forecast.py and re-run |
BasData forecast & dashboard wiki — single-page reference.