pluginbattery/scripts/run_oracle.py
Michiel Berger 60e0706736 Initial import: home-battery ROI simulator + cracked thuisbatterijgids calc
- Hourly data exporter (InfluxDB → CSV) for prices, P1, irradiance.
- LP-based 24h-foresight oracle dispatch with SoC-consistent state engine.
- Reverse-engineered thuisbatterijgids.nl formula (matches their quotes
  to within €0.50 across three battery configs).
- Catalog scraper for the 52 batteries on thuisbatterijgids.net via their
  /wp-json REST endpoint.
- Web app (Flask) that ranks every catalog battery by honest payback and
  contrasts with the store's quote, deployable via the included Procfile.
2026-04-30 13:46:27 +02:00

66 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Run the daily 24h-foresight oracle over the full window and write per-hour output."""
from __future__ import annotations
import argparse
from pathlib import Path
from pluginbattery.sim import (
ECOFLOW_STREAM_AC,
Battery,
apply_nl_tariff,
load_hourly,
oracle_daily_schedule,
simulate,
)
# EcoFlow Stream AC retail price (incl. VAT) for payback math.
ECOFLOW_STREAM_AC_PRICE_EUR = 699.0
def run(battery: Battery, price_eur: float, data_dir: Path, out_path: Path) -> None:
df = load_hourly(data_dir)
df = apply_nl_tariff(df) # eur_per_kwh now = consumer price (incl. VAT + tax)
print(f"Loaded {len(df)} hours: {df.index[0]}{df.index[-1]}")
print(f"Battery: cap={battery.capacity_kwh} kWh, "
f"P_in={battery.max_charge_kw} kW, P_out={battery.max_discharge_kw} kW, "
f"η_rt={battery.round_trip_eff:.2f}, allows_export={battery.allows_export}")
print(f"Price (incl. VAT) : €{price_eur:.0f}")
print(f"Tariff: consumer = EPEX × 1.21 + 0.136 EUR/kWh")
print("Solving daily 24h LPs...")
schedule = oracle_daily_schedule(df, battery)
print("Walking SoC...")
out = simulate(df, battery, schedule)
out_path.parent.mkdir(parents=True, exist_ok=True)
out.to_csv(out_path)
cost_no = out["cost_no_battery"].sum()
cost_yes = out["cost_with_battery"].sum()
savings = out["savings"].sum()
cycles = out["discharge_kwh"].sum() / battery.capacity_kwh
payback_years = price_eur / savings if savings > 0 else float("inf")
print()
print(f"Year cost without battery : €{cost_no:8.2f}")
print(f"Year cost with battery : €{cost_yes:8.2f}")
print(f"Annual savings (consumer) : €{savings:8.2f}")
print(f"Equivalent full cycles : {cycles:7.1f}")
print(f"Hours simulated : {len(out)}")
print(f"Simple payback : {payback_years:6.2f} years")
print(f"Output -> {out_path}")
def main() -> None:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--data", default="data/raw")
p.add_argument("--out", default="data/processed/oracle_daily.csv")
p.add_argument("--price", type=float, default=ECOFLOW_STREAM_AC_PRICE_EUR)
args = p.parse_args()
run(ECOFLOW_STREAM_AC, args.price, Path(args.data), Path(args.out))
if __name__ == "__main__":
main()