Upload files to "/"
This commit is contained in:
+85
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
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())}")
|
||||
Reference in New Issue
Block a user