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.
This commit is contained in:
commit
60e0706736
29 changed files with 30358 additions and 0 deletions
66
scripts/run_oracle.py
Normal file
66
scripts/run_oracle.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue