uprava toolu pro battery sizing
This commit is contained in:
@@ -127,6 +127,30 @@ Marže se konfigurují v `site_market_config`:
|
|||||||
|
|
||||||
Denní ekonomika v DB (`ems.fn_economics_daily_for_window`, repeatable `R__068_fn_economics_daily_month.sql`) musí používat stejnou kombinaci jako `fn_effective_buy_price` (komentář ve funkci).
|
Denní ekonomika v DB (`ems.fn_economics_daily_for_window`, repeatable `R__068_fn_economics_daily_month.sql`) musí používat stejnou kombinaci jako `fn_effective_buy_price` (komentář ve funkci).
|
||||||
|
|
||||||
|
### Screening skript pro dimenzování baterie
|
||||||
|
|
||||||
|
Analytický skript `scripts/analysis/battery_sizing_screen.py` umí pro nákup v režimu spot simulovat dva užitečné screening režimy bez vazby na konkrétní `site_market_config`:
|
||||||
|
|
||||||
|
- `--buy-spot-add-fixed-kwh X`: základ nákupu = `raw_ote + X`
|
||||||
|
- `--buy-spot-asym-pct P`: základ nákupu = `raw_ote × (1 + P/100)` pro `raw_ote >= 0`, resp. `raw_ote × (1 - P/100)` pro `raw_ote < 0`
|
||||||
|
|
||||||
|
V obou případech skript ke každému importnímu slotu fixně přičte:
|
||||||
|
|
||||||
|
- `--buy-distribution-kwh`
|
||||||
|
- `--buy-other-fees-kwh`
|
||||||
|
|
||||||
|
Volitelně pak na celý součet aplikuje:
|
||||||
|
|
||||||
|
- `--buy-vat-multiplier` (např. `1.21`)
|
||||||
|
|
||||||
|
Tato logika je implementovaná přímo ve `build_buy_prices_96()` v `scripts/analysis/battery_sizing_screen.py`. Účel je screening nové lokality nebo obchodního modelu ještě před seedem do DB; nejde o náhradu `ems.fn_effective_buy_price`.
|
||||||
|
|
||||||
|
Ověření:
|
||||||
|
|
||||||
|
- spusť skript nad krátkým vzorkem OTE (`--price-csv` nebo `--db`) a zkontroluj vypsané shrnutí režimu nákupu
|
||||||
|
- pro asymetrickou variantu ověř, že záporné ceny používají faktor `1 - P/100`, nikoli `1 + P/100`
|
||||||
|
- pro arbitráž bez FVE použij `--pv-daily-kwh-summer 0 --pv-daily-kwh-winter 0 --load-kw 0`
|
||||||
|
|
||||||
**Zelený bonus** není součástí `fn_effective_sell_price` ani view efektivní prodejní ceny – jde o samostatný příjem z výroby, viz níže.
|
**Zelený bonus** není součástí `fn_effective_sell_price` ani view efektivní prodejní ceny – jde o samostatný příjem z výroby, viz níže.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -26,14 +26,23 @@ Příklad (PVGIS měsíční E_d + NT/VT):
|
|||||||
--buy-nt-kwh 5.25 --buy-vt-surcharge-kwh 2.0 --nt-from-hour 22 --nt-to-hour 6 \\
|
--buy-nt-kwh 5.25 --buy-vt-surcharge-kwh 2.0 --nt-from-hour 22 --nt-to-hour 6 \\
|
||||||
... (ostatní jako výše)
|
... (ostatní jako výše)
|
||||||
|
|
||||||
|
Příklad (čistá arbitráž, nákup = spot + fixní adder + distribuce/poplatky):
|
||||||
|
python3 scripts/analysis/battery_sizing_screen.py --db \\
|
||||||
|
--load-kw 0 --pv-daily-kwh-summer 0 --pv-daily-kwh-winter 0 \\
|
||||||
|
--buy-spot-add-fixed-kwh 0.25 \\
|
||||||
|
--buy-distribution-kwh 1.80 --buy-other-fees-kwh 0.20 --buy-vat-multiplier 1.21 \\
|
||||||
|
... (ostatní jako výše)
|
||||||
|
|
||||||
Vyžaduje: pip install pulp (volitelně psycopg2 pro --db).
|
Vyžaduje: pip install pulp (volitelně psycopg2 pro --db).
|
||||||
|
|
||||||
Omezení modelu: FVE buď syntetický denní tvar (--pv-daily-kwh-*), nebo součet měsíčních
|
Omezení modelu: FVE buď syntetický denní tvar (--pv-daily-kwh-*), nebo součet měsíčních
|
||||||
E_d z PVGIS CSV (--pvgis-csv, opakovat pro více orientací); denní energie = E_d měsíce
|
E_d z PVGIS CSV (--pvgis-csv, opakovat pro více orientací); denní energie = E_d měsíce
|
||||||
× normalizovaný tvar (stejný profil každý den v měsíci). Nákup: buď flat (--buy-vat-kwh),
|
× normalizovaný tvar (stejný profil každý den v měsíci). Nákup: flat (--buy-vat-kwh),
|
||||||
nebo NT/VT podle hodin Europe/Prague: --buy-nt-kwh, VT = NT + --buy-vt-surcharge-kwh,
|
NT/VT podle hodin Europe/Prague (--buy-nt-kwh, VT = NT + --buy-vt-surcharge-kwh),
|
||||||
okno NT --nt-from-hour až --nt-to-hour (přes půlnoc, pokud from > to). Mikroinvertory / GEN
|
nebo od raw OTE spotu: --buy-spot-add-fixed-kwh / --buy-spot-asym-pct; u všech režimů
|
||||||
nejsou; zelený bonus není v účelové funkci. Výsledek = screening, ne nabídka.
|
lze přičíst --buy-distribution-kwh a --buy-other-fees-kwh a výslednou cenu násobit
|
||||||
|
--buy-vat-multiplier. Mikroinvertory / GEN nejsou; zelený bonus není v účelové funkci.
|
||||||
|
Výsledek = screening, ne nabídka.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -69,6 +78,21 @@ class SiteLimits:
|
|||||||
soc_max_frac: float = 0.95
|
soc_max_frac: float = 0.95
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class BuyPricingConfig:
|
||||||
|
mode: str = "flat"
|
||||||
|
flat_kwh: float = 4.443
|
||||||
|
nt_kwh: float | None = None
|
||||||
|
vt_kwh: float | None = None
|
||||||
|
nt_from_hour: int = 22
|
||||||
|
nt_to_hour: int = 6
|
||||||
|
spot_add_fixed_kwh: float | None = None
|
||||||
|
spot_asym_pct: float | None = None
|
||||||
|
distribution_kwh: float = 0.0
|
||||||
|
other_fees_kwh: float = 0.0
|
||||||
|
vat_multiplier: float = 1.0
|
||||||
|
|
||||||
|
|
||||||
def batt_power_cap_w(usable_kwh: float, site: SiteLimits) -> float:
|
def batt_power_cap_w(usable_kwh: float, site: SiteLimits) -> float:
|
||||||
return min(site.c_rate * usable_kwh * 1000.0, site.inv_batt_max_w)
|
return min(site.c_rate * usable_kwh * 1000.0, site.inv_batt_max_w)
|
||||||
|
|
||||||
@@ -173,6 +197,51 @@ def effective_sell_kc_kwh(raw_ote: float, margin_fixed: float, margin_pct: float
|
|||||||
return raw_ote + margin_fixed + (raw_ote * margin_pct / 100.0)
|
return raw_ote + margin_fixed + (raw_ote * margin_pct / 100.0)
|
||||||
|
|
||||||
|
|
||||||
|
def effective_buy_spot_add_fixed_kc_kwh(raw_ote: float, add_fixed_kwh: float) -> float:
|
||||||
|
return raw_ote + add_fixed_kwh
|
||||||
|
|
||||||
|
|
||||||
|
def effective_buy_spot_asym_pct_kc_kwh(raw_ote: float, asym_pct: float) -> float:
|
||||||
|
if raw_ote >= 0:
|
||||||
|
return raw_ote * (1.0 + asym_pct / 100.0)
|
||||||
|
return raw_ote * (1.0 - asym_pct / 100.0)
|
||||||
|
|
||||||
|
|
||||||
|
def build_buy_prices_96(raw_ote_96: Sequence[float], cfg: BuyPricingConfig) -> list[float]:
|
||||||
|
fixed_fees_kwh = cfg.distribution_kwh + cfg.other_fees_kwh
|
||||||
|
if cfg.mode == "spot_add_fixed":
|
||||||
|
if cfg.spot_add_fixed_kwh is None:
|
||||||
|
raise ValueError("Pro mode=spot_add_fixed chybí spot_add_fixed_kwh")
|
||||||
|
return [
|
||||||
|
(effective_buy_spot_add_fixed_kc_kwh(px, cfg.spot_add_fixed_kwh) + fixed_fees_kwh)
|
||||||
|
* cfg.vat_multiplier
|
||||||
|
for px in raw_ote_96
|
||||||
|
]
|
||||||
|
if cfg.mode == "spot_asym_pct":
|
||||||
|
if cfg.spot_asym_pct is None:
|
||||||
|
raise ValueError("Pro mode=spot_asym_pct chybí spot_asym_pct")
|
||||||
|
return [
|
||||||
|
(effective_buy_spot_asym_pct_kc_kwh(px, cfg.spot_asym_pct) + fixed_fees_kwh)
|
||||||
|
* cfg.vat_multiplier
|
||||||
|
for px in raw_ote_96
|
||||||
|
]
|
||||||
|
if cfg.mode == "nt_vt":
|
||||||
|
if cfg.nt_kwh is None or cfg.vt_kwh is None:
|
||||||
|
raise ValueError("Pro mode=nt_vt chybí NT/VT cena")
|
||||||
|
return [
|
||||||
|
(base + fixed_fees_kwh) * cfg.vat_multiplier
|
||||||
|
for base in buy_prices_96_nt_vt(
|
||||||
|
cfg.nt_kwh,
|
||||||
|
cfg.vt_kwh,
|
||||||
|
cfg.nt_from_hour,
|
||||||
|
cfg.nt_to_hour,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if cfg.mode != "flat":
|
||||||
|
raise ValueError(f"Neznámý buy mode: {cfg.mode}")
|
||||||
|
return [(cfg.flat_kwh + fixed_fees_kwh) * cfg.vat_multiplier] * SLOTS_PER_DAY
|
||||||
|
|
||||||
|
|
||||||
def load_env_file(path: Path) -> None:
|
def load_env_file(path: Path) -> None:
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
return
|
return
|
||||||
@@ -211,11 +280,30 @@ def sync_pg_env_from_db_vars() -> None:
|
|||||||
def load_prices_csv(path: str) -> list[tuple[datetime, float]]:
|
def load_prices_csv(path: str) -> list[tuple[datetime, float]]:
|
||||||
out: list[tuple[datetime, float]] = []
|
out: list[tuple[datetime, float]] = []
|
||||||
with open(path, newline="", encoding="utf-8") as f:
|
with open(path, newline="", encoding="utf-8") as f:
|
||||||
|
first_line = f.readline()
|
||||||
|
f.seek(0)
|
||||||
|
if "interval_start" in first_line and "sell_raw_price_czk_kwh" in first_line:
|
||||||
r = csv.DictReader(f)
|
r = csv.DictReader(f)
|
||||||
for row in r:
|
for row in r:
|
||||||
ts = datetime.fromisoformat(row["interval_start"].replace("Z", "+00:00"))
|
ts = datetime.fromisoformat(row["interval_start"].replace("Z", "+00:00"))
|
||||||
px = float(row["sell_raw_price_czk_kwh"])
|
px = float(row["sell_raw_price_czk_kwh"])
|
||||||
out.append((ts, px))
|
out.append((ts, px))
|
||||||
|
else:
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
prg = ZoneInfo("Europe/Prague")
|
||||||
|
r = csv.reader(f)
|
||||||
|
for row in r:
|
||||||
|
if len(row) < 3:
|
||||||
|
continue
|
||||||
|
date_s = row[0].strip()
|
||||||
|
time_s = row[1].strip()
|
||||||
|
price_s = row[2].strip()
|
||||||
|
if not date_s or not time_s or not price_s:
|
||||||
|
continue
|
||||||
|
ts = datetime.fromisoformat(f"{date_s}T{time_s}").replace(tzinfo=prg)
|
||||||
|
px = float(price_s)
|
||||||
|
out.append((ts, px))
|
||||||
out.sort(key=lambda x: x[0])
|
out.sort(key=lambda x: x[0])
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -356,8 +444,7 @@ def simulate_year(
|
|||||||
site: SiteLimits,
|
site: SiteLimits,
|
||||||
sell_margin_fixed: float,
|
sell_margin_fixed: float,
|
||||||
sell_margin_pct: float,
|
sell_margin_pct: float,
|
||||||
buy_flat_kwh: float,
|
buy_cfg: BuyPricingConfig,
|
||||||
buy_prices_96: Sequence[float] | None,
|
|
||||||
summer_kwh: float,
|
summer_kwh: float,
|
||||||
winter_kwh: float,
|
winter_kwh: float,
|
||||||
load_kw: float,
|
load_kw: float,
|
||||||
@@ -367,12 +454,6 @@ def simulate_year(
|
|||||||
e_wh = usable_kwh * 1000.0
|
e_wh = usable_kwh * 1000.0
|
||||||
p_batt = batt_power_cap_w(usable_kwh, site)
|
p_batt = batt_power_cap_w(usable_kwh, site)
|
||||||
load_wh = daily_load_wh(load_kw)
|
load_wh = daily_load_wh(load_kw)
|
||||||
if buy_prices_96 is not None:
|
|
||||||
if len(buy_prices_96) != SLOTS_PER_DAY:
|
|
||||||
raise ValueError("buy_prices_96 musí mít 96 hodnot")
|
|
||||||
p_buy_day: Sequence[float] = buy_prices_96
|
|
||||||
else:
|
|
||||||
p_buy_day = [buy_flat_kwh] * SLOTS_PER_DAY
|
|
||||||
cash_total = 0.0
|
cash_total = 0.0
|
||||||
curt_total = 0.0
|
curt_total = 0.0
|
||||||
dis_total = 0.0
|
dis_total = 0.0
|
||||||
@@ -383,12 +464,13 @@ def simulate_year(
|
|||||||
continue
|
continue
|
||||||
raw = px_day[d]
|
raw = px_day[d]
|
||||||
p_sell = [effective_sell_kc_kwh(x, sell_margin_fixed, sell_margin_pct) for x in raw]
|
p_sell = [effective_sell_kc_kwh(x, sell_margin_fixed, sell_margin_pct) for x in raw]
|
||||||
|
p_buy = build_buy_prices_96(raw, buy_cfg)
|
||||||
if monthly_ed_kwh is not None:
|
if monthly_ed_kwh is not None:
|
||||||
pv_wh = daily_pv_wh_monthly(d, monthly_ed_kwh, shape)
|
pv_wh = daily_pv_wh_monthly(d, monthly_ed_kwh, shape)
|
||||||
else:
|
else:
|
||||||
pv_wh = daily_pv_wh(d, summer_kwh, winter_kwh, shape)
|
pv_wh = daily_pv_wh(d, summer_kwh, winter_kwh, shape)
|
||||||
cash, soc_state, curt, dis = solve_one_day(
|
cash, soc_state, curt, dis = solve_one_day(
|
||||||
pv_wh, load_wh, p_sell, p_buy_day, e_wh, p_batt, site, soc_state
|
pv_wh, load_wh, p_sell, p_buy, e_wh, p_batt, site, soc_state
|
||||||
)
|
)
|
||||||
cash_total += cash
|
cash_total += cash
|
||||||
curt_total += curt
|
curt_total += curt
|
||||||
@@ -419,7 +501,12 @@ def main() -> None:
|
|||||||
default="",
|
default="",
|
||||||
help="Přepíše PGHOST (např. stejná IP jako EMS_DB_BIND ve compose)",
|
help="Přepíše PGHOST (např. stejná IP jako EMS_DB_BIND ve compose)",
|
||||||
)
|
)
|
||||||
ap.add_argument("--price-csv", type=str, default="", help="CSV: interval_start, sell_raw_price_czk_kwh")
|
ap.add_argument(
|
||||||
|
"--price-csv",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
help="CSV buď s hlavičkou interval_start,sell_raw_price_czk_kwh, nebo legacy bez hlavičky: date,time,price",
|
||||||
|
)
|
||||||
ap.add_argument("--date-from", type=str, required=True)
|
ap.add_argument("--date-from", type=str, required=True)
|
||||||
ap.add_argument("--date-to", type=str, required=True)
|
ap.add_argument("--date-to", type=str, required=True)
|
||||||
ap.add_argument("--battery-kwh", type=float, nargs="+", required=True, help="Užitkové kWh (např. 12.5 32 48)")
|
ap.add_argument("--battery-kwh", type=float, nargs="+", required=True, help="Užitkové kWh (např. 12.5 32 48)")
|
||||||
@@ -432,7 +519,7 @@ def main() -> None:
|
|||||||
"--buy-vat-kwh",
|
"--buy-vat-kwh",
|
||||||
type=float,
|
type=float,
|
||||||
default=4.443,
|
default=4.443,
|
||||||
help="Flat nákup Kč/kWh (když není --buy-nt-kwh)",
|
help="Flat základní nákup Kč/kWh (když není spotový ani NT/VT režim)",
|
||||||
)
|
)
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"--buy-nt-kwh",
|
"--buy-nt-kwh",
|
||||||
@@ -448,6 +535,36 @@ def main() -> None:
|
|||||||
)
|
)
|
||||||
ap.add_argument("--nt-from-hour", type=int, default=22, help="Začátek NT (hodina 0–23)")
|
ap.add_argument("--nt-from-hour", type=int, default=22, help="Začátek NT (hodina 0–23)")
|
||||||
ap.add_argument("--nt-to-hour", type=int, default=6, help="Konec NT: první hodina VT (0–23); přes půlnoc pokud from > to")
|
ap.add_argument("--nt-to-hour", type=int, default=6, help="Konec NT: první hodina VT (0–23); přes půlnoc pokud from > to")
|
||||||
|
ap.add_argument(
|
||||||
|
"--buy-spot-add-fixed-kwh",
|
||||||
|
type=float,
|
||||||
|
default=None,
|
||||||
|
help="Základ nákupu = raw OTE + tento fixní adder Kč/kWh; pak se přičtou distribuce a ostatní poplatky",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--buy-spot-asym-pct",
|
||||||
|
type=float,
|
||||||
|
default=None,
|
||||||
|
help="Základ nákupu = raw OTE × (1 + p/100) pro raw >= 0, raw OTE × (1 - p/100) pro raw < 0",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--buy-distribution-kwh",
|
||||||
|
type=float,
|
||||||
|
default=0.0,
|
||||||
|
help="Fixně přičtená distribuční složka Kč/kWh ke každému nákupnímu slotu",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--buy-other-fees-kwh",
|
||||||
|
type=float,
|
||||||
|
default=0.0,
|
||||||
|
help="Fixně přičtené ostatní poplatky Kč/kWh (OTE, systémové služby apod.) ke každému nákupnímu slotu",
|
||||||
|
)
|
||||||
|
ap.add_argument(
|
||||||
|
"--buy-vat-multiplier",
|
||||||
|
type=float,
|
||||||
|
default=1.0,
|
||||||
|
help="Násobitel DPH aplikovaný na finální nákupní cenu po přičtení distribuce a ostatních poplatků (např. 1.21)",
|
||||||
|
)
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"--pvgis-csv",
|
"--pvgis-csv",
|
||||||
action="append",
|
action="append",
|
||||||
@@ -464,6 +581,18 @@ def main() -> None:
|
|||||||
|
|
||||||
d0 = date.fromisoformat(args.date_from)
|
d0 = date.fromisoformat(args.date_from)
|
||||||
d1 = date.fromisoformat(args.date_to)
|
d1 = date.fromisoformat(args.date_to)
|
||||||
|
base_buy_modes = [
|
||||||
|
args.buy_nt_kwh is not None,
|
||||||
|
args.buy_spot_add_fixed_kwh is not None,
|
||||||
|
args.buy_spot_asym_pct is not None,
|
||||||
|
]
|
||||||
|
if sum(base_buy_modes) > 1:
|
||||||
|
ap.error("Zvol jen jeden režim základu nákupu: flat, NT/VT, --buy-spot-add-fixed-kwh nebo --buy-spot-asym-pct")
|
||||||
|
if args.buy_vat_multiplier <= 0:
|
||||||
|
ap.error("--buy-vat-multiplier musí být > 0")
|
||||||
|
for hour_arg, hour_value in (("nt-from-hour", args.nt_from_hour), ("nt-to-hour", args.nt_to_hour)):
|
||||||
|
if not (0 <= hour_value <= 23):
|
||||||
|
ap.error(f"--{hour_arg} musí být v rozsahu 0..23")
|
||||||
if args.db:
|
if args.db:
|
||||||
if not args.no_auto_env:
|
if not args.no_auto_env:
|
||||||
apply_auto_env_files()
|
apply_auto_env_files()
|
||||||
@@ -491,18 +620,42 @@ def main() -> None:
|
|||||||
if args.pvgis_csv:
|
if args.pvgis_csv:
|
||||||
monthly_ed = merge_pvgis_monthly_ed_kwh([Path(p) for p in args.pvgis_csv])
|
monthly_ed = merge_pvgis_monthly_ed_kwh([Path(p) for p in args.pvgis_csv])
|
||||||
|
|
||||||
if args.buy_nt_kwh is not None:
|
if args.buy_spot_add_fixed_kwh is not None:
|
||||||
vt = args.buy_nt_kwh + args.buy_vt_surcharge_kwh
|
buy_cfg = BuyPricingConfig(
|
||||||
buy_prices_96 = buy_prices_96_nt_vt(
|
mode="spot_add_fixed",
|
||||||
args.buy_nt_kwh,
|
spot_add_fixed_kwh=args.buy_spot_add_fixed_kwh,
|
||||||
vt,
|
distribution_kwh=args.buy_distribution_kwh,
|
||||||
args.nt_from_hour,
|
other_fees_kwh=args.buy_other_fees_kwh,
|
||||||
args.nt_to_hour,
|
vat_multiplier=args.buy_vat_multiplier,
|
||||||
|
)
|
||||||
|
elif args.buy_spot_asym_pct is not None:
|
||||||
|
buy_cfg = BuyPricingConfig(
|
||||||
|
mode="spot_asym_pct",
|
||||||
|
spot_asym_pct=args.buy_spot_asym_pct,
|
||||||
|
distribution_kwh=args.buy_distribution_kwh,
|
||||||
|
other_fees_kwh=args.buy_other_fees_kwh,
|
||||||
|
vat_multiplier=args.buy_vat_multiplier,
|
||||||
|
)
|
||||||
|
elif args.buy_nt_kwh is not None:
|
||||||
|
vt = args.buy_nt_kwh + args.buy_vt_surcharge_kwh
|
||||||
|
buy_cfg = BuyPricingConfig(
|
||||||
|
mode="nt_vt",
|
||||||
|
nt_kwh=args.buy_nt_kwh,
|
||||||
|
vt_kwh=vt,
|
||||||
|
nt_from_hour=args.nt_from_hour,
|
||||||
|
nt_to_hour=args.nt_to_hour,
|
||||||
|
distribution_kwh=args.buy_distribution_kwh,
|
||||||
|
other_fees_kwh=args.buy_other_fees_kwh,
|
||||||
|
vat_multiplier=args.buy_vat_multiplier,
|
||||||
)
|
)
|
||||||
buy_flat = args.buy_vat_kwh
|
|
||||||
else:
|
else:
|
||||||
buy_prices_96 = None
|
buy_cfg = BuyPricingConfig(
|
||||||
buy_flat = args.buy_vat_kwh
|
mode="flat",
|
||||||
|
flat_kwh=args.buy_vat_kwh,
|
||||||
|
distribution_kwh=args.buy_distribution_kwh,
|
||||||
|
other_fees_kwh=args.buy_other_fees_kwh,
|
||||||
|
vat_multiplier=args.buy_vat_multiplier,
|
||||||
|
)
|
||||||
|
|
||||||
day_list = [d0 + timedelta(days=i) for i in range((d1 - d0).days)]
|
day_list = [d0 + timedelta(days=i) for i in range((d1 - d0).days)]
|
||||||
|
|
||||||
@@ -515,8 +668,7 @@ def main() -> None:
|
|||||||
site,
|
site,
|
||||||
args.sell_margin_fixed,
|
args.sell_margin_fixed,
|
||||||
args.sell_margin_pct,
|
args.sell_margin_pct,
|
||||||
buy_flat,
|
buy_cfg,
|
||||||
buy_prices_96,
|
|
||||||
args.pv_daily_kwh_summer,
|
args.pv_daily_kwh_summer,
|
||||||
args.pv_daily_kwh_winter,
|
args.pv_daily_kwh_winter,
|
||||||
args.load_kw,
|
args.load_kw,
|
||||||
@@ -529,14 +681,28 @@ def main() -> None:
|
|||||||
base = dict(results)[baseline_kwh]
|
base = dict(results)[baseline_kwh]
|
||||||
|
|
||||||
print("Parametry: prodej = OTE + sell_margin_fixed (+ %)")
|
print("Parametry: prodej = OTE + sell_margin_fixed (+ %)")
|
||||||
if buy_prices_96 is not None:
|
if buy_cfg.mode == "nt_vt":
|
||||||
vt_show = args.buy_nt_kwh + args.buy_vt_surcharge_kwh
|
vt_show = args.buy_nt_kwh + args.buy_vt_surcharge_kwh
|
||||||
print(
|
print(
|
||||||
f" Nákup = NT/VT: NT {args.buy_nt_kwh} Kč/kWh, VT {vt_show} Kč/kWh "
|
f" Nákup = NT/VT: NT {args.buy_nt_kwh} Kč/kWh, VT {vt_show} Kč/kWh "
|
||||||
f"(okno NT {args.nt_from_hour:02d}–{args.nt_to_hour:02d} h lokální)"
|
f"(okno NT {args.nt_from_hour:02d}–{args.nt_to_hour:02d} h lokální)"
|
||||||
)
|
)
|
||||||
|
elif buy_cfg.mode == "spot_add_fixed":
|
||||||
|
print(f" Nákup = raw OTE + {args.buy_spot_add_fixed_kwh} Kč/kWh")
|
||||||
|
elif buy_cfg.mode == "spot_asym_pct":
|
||||||
|
print(
|
||||||
|
f" Nákup = raw OTE × (1 + {args.buy_spot_asym_pct}/100) pro raw >= 0, "
|
||||||
|
f"raw OTE × (1 - {args.buy_spot_asym_pct}/100) pro raw < 0"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(f" Nákup = flat {args.buy_vat_kwh} Kč/kWh")
|
print(f" Nákup = flat {args.buy_vat_kwh} Kč/kWh")
|
||||||
|
if args.buy_distribution_kwh or args.buy_other_fees_kwh:
|
||||||
|
print(
|
||||||
|
f" Fixní add-on k nákupu: distribuce {args.buy_distribution_kwh} Kč/kWh, "
|
||||||
|
f"ostatní poplatky {args.buy_other_fees_kwh} Kč/kWh"
|
||||||
|
)
|
||||||
|
if args.buy_vat_multiplier != 1.0:
|
||||||
|
print(f" DPH násobitel na finální nákupní cenu: {args.buy_vat_multiplier}")
|
||||||
if monthly_ed is not None:
|
if monthly_ed is not None:
|
||||||
edv = [monthly_ed[m] for m in range(1, 13)]
|
edv = [monthly_ed[m] for m in range(1, 13)]
|
||||||
print(
|
print(
|
||||||
|
|||||||
Reference in New Issue
Block a user