diff --git a/src/pluginbattery/sim.py b/src/pluginbattery/sim.py index 6371014..964baca 100644 --- a/src/pluginbattery/sim.py +++ b/src/pluginbattery/sim.py @@ -238,9 +238,20 @@ def oracle_daily_schedule(df: pd.DataFrame, battery: Battery) -> np.ndarray: # Cost = sum(import_price * gI - export_price * gE). # When import_price > export_price, the LP automatically picks # min(gI, gE) = 0 because keeping both > 0 costs (imp - exp) > 0. + # + # Tiny tie-breakers: when several surplus hours are equally good for + # charging (true whenever export_price is constant and we're capturing + # surplus), the simplex would otherwise pick an arbitrary subset. + # Adding a microscopic penalty proportional to hour-index makes early + # charging strictly cheaper than late charging by a fraction of a + # cent — so the battery visibly fills as soon as surplus appears. + # Symmetric for discharge: prefer late hours (when prices are + # typically highest anyway, but ties happen during evening plateaus). + EPS = 1e-6 + hr = np.arange(m) f = np.concatenate([ - np.zeros(m), # c — already paid for via gI - np.zeros(m), # d — earnings reach us via reduced gI / increased gE + EPS * hr, # c: prefer earlier charging (smaller h ⇒ less cost) + EPS * (m - 1 - hr), # d: prefer later discharging import_price, # pay for imports -export_price, # earn export credit ]) diff --git a/src/pluginbattery/templates/index.html b/src/pluginbattery/templates/index.html index 01dd5e6..dcce9cf 100644 --- a/src/pluginbattery/templates/index.html +++ b/src/pluginbattery/templates/index.html @@ -5,7 +5,7 @@ Honest battery payback — vs thuisbatterijgids.nl - + @@ -194,6 +194,6 @@ - + diff --git a/src/pluginbattery/web.py b/src/pluginbattery/web.py index e610beb..ca8b2f7 100644 --- a/src/pluginbattery/web.py +++ b/src/pluginbattery/web.py @@ -326,11 +326,12 @@ DEFAULTS = dict( # demand_kwh / ANNUAL_DEMAND_BASE; ANNUAL_DEMAND_BASE annualises the # 8-month window's 1010 kWh → 1518 kWh-equivalent/yr, so set the # default to 1518 to keep the per-hour P1 values unscaled (scale = 1.0). - # Dad's actual full-year net is 2351 kWh (from his complete CSV) — so - # using 1518 effectively represents 'an 8-month-extrapolated 2025 year'. + # export_rate = 0 by default to keep the model simple. Dad's actual + # situation has terugleveringskosten of −€0.106/kWh; set the field + # explicitly to that value when modelling his real bill. demand_kwh=1518.0, retail=0.25, pv_kwp=0.0, pv_yield=875.0, fixed_rate=False, saldering=False, eta=0.88, inflation=0.03, - export_rate=-0.106, + export_rate=0.0, )