Files
2026-06-17 20:32:58 +00:00

86 lines
3.1 KiB
Python

"""
Predict chart points from sample BAS data.
Run:
python forecast.py
Output:
forecast_output.json (test.php reads this for dashed forecast lines)
"""
import json
import re
from pathlib import Path
import pandas as pd
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_absolute_error
from skforecast.recursive import ForecasterRecursive
# --- settings you can change ---
FUTURE_STEPS = 100 # how many readings to predict ahead
LAGS = 25 # how many past readings the model looks at each step
# (sample file, point names) — one model per point
JOBS = [
("js/veris-sample.js", ["P", "Ia", "Ib", "Ic"]),
("js/rtu-sample.js", ["ServRmTmp"]), # RTU room temp (spark chart later)
]
folder = Path(__file__).parent
def load_sample_js(path: str) -> dict:
text = (folder / path).read_text(encoding="utf-8")
return json.loads(re.search(r"=\s*(\{.*\})\s*;?\s*$", text, re.DOTALL).group(1))
def forecast_point(data: dict, point: str) -> dict:
"""Train one model for one point. Returns mae + future [{x, y}, ...]."""
# 1) BUILD TIME SERIES from [{x, y}, ...] rows
rows = data[point]
times = pd.to_datetime([r["x"] for r in rows], unit="ms") # x = timestamp in milliseconds
values = [float(r["y"]) for r in rows] # y = value as a number
series = pd.Series(values, index=times, name=point).sort_index() # oldest first
step = series.index.to_series().diff().median() # typical gap (~2 min Veris, ~32s RTU)
series = series.resample(step).mean().interpolate() # even spacing so lags line up
print(f"\n{point}{len(series)} readings")
# 2) TRAIN + TEST on the last 20% (see how wrong the model is)
split = int(len(series) * 0.8)
train, test = series.iloc[:split], series.iloc[split:] # first 80% train, last 20% test
model = ForecasterRecursive(estimator=Ridge(), lags=LAGS) # uses the lag values to make new predictions make sense
model.fit(y=train)
guesses = model.predict(steps=len(test)) # try to predict the hidden 20%
guesses.index = test.index
mae = mean_absolute_error(test, guesses) # average error from the test data
print(f" MAE (last 20%): {mae:.2f}")
# 3) PREDICT THE FUTURE (retrain on all data, then forecast ahead)
model.fit(y=series)
future = model.predict(steps=FUTURE_STEPS)
future.index = pd.date_range(series.index[-1] + step, periods=FUTURE_STEPS, freq=step)
# 4) FORMAT for charts — same {x, y} shape as veris-sample.js
return {
"mae": round(mae, 2),
"future": [
{"x": int(t.timestamp() * 1000), "y": round(float(v), 2)}
for t, v in future.items()
],
}
output = {}
for js_path, points in JOBS:
data = load_sample_js(js_path)
for point in points:
output[point] = forecast_point(data, point)
out_file = folder / "forecast_output.json"
out_file.write_text(json.dumps(output, indent=2), encoding="utf-8")
print(f"\nSaved forecasts for: {', '.join(output.keys())}")