Update test.php
+284
-1
@@ -1 +1,284 @@
|
||||
<-- [[Home]]
|
||||
<-- [[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](#overview--quick-start)
|
||||
- [Pipeline](#pipeline)
|
||||
- [forecast.py](#forecastpy)
|
||||
- [forecast_comp.py](#forecast_comppy)
|
||||
- [forecast_output.json](#forecast_outputjson)
|
||||
- [test.php dashboard](#testphp-dashboard)
|
||||
- [Troubleshooting](#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–72 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)
|
||||
|
||||
1. **Load** — Parse `var xxxData = { ... };` from the `.js` file.
|
||||
2. **Series** — pandas `Series` indexed by timestamp (`x` = ms, `y` = float).
|
||||
3. **Resample** — Even spacing from median step; interpolate gaps.
|
||||
4. **Backtest** — First **80%** train, last **20%** predict → **MAE** printed.
|
||||
5. **Forecast** — Retrain on **full** series → `FUTURE_STEPS` ahead.
|
||||
6. **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
|
||||
|
||||
1. `fetch("forecast_output.json")` → `basForecastData`
|
||||
2. `basForecastSeries(actualData, pointName)` → last real point + `future` array
|
||||
3. 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.*
|
||||
Reference in New Issue
Block a user