- 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.
66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
#!/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()
|