Update forecast.py

2026-06-18 21:22:27 +00:00
parent 10a20e9c49
commit b424bede20
+280 -1
@@ -1,2 +1,281 @@
<-- [[Home]] <-- [[Home]]
will finish tomorrow
# 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](#pipeline)
- [Prerequisites](#prerequisites)
- [forecast.py](#forecastpy)
- [forecast_output.json](#forecast_outputjson)
- [forecast_comp.py](#forecast_comppy)
- [Workflow cheat sheet](#workflow-cheat-sheet)
- [Troubleshooting](#troubleshooting)
---
## 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. **Backtest****First 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 &lt; -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 &lt; 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 thats 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 |
---
## Related
- [[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.*