92 lines
3.5 KiB
Python
92 lines
3.5 KiB
Python
from __future__ import annotations
|
||
from dataclasses import dataclass
|
||
from datetime import time, datetime
|
||
from typing import Dict, Literal
|
||
|
||
from DistributionCost import DistributionCostBase
|
||
from utils.time_helpers import in_range_local
|
||
from utils.calendar_pl import is_weekend_or_holiday
|
||
|
||
Season = Literal["summer", "winter"]
|
||
DayType = Literal["workday", "dayoff"]
|
||
Band = Literal["peak", "offpeak_day", "night"]
|
||
|
||
RatesDict = Dict[Season, Dict[DayType, Dict[Band, float]]]
|
||
@dataclass
|
||
class TauronG13SProvider(DistributionCostBase):
|
||
"""
|
||
TAURON Dystrybucja – taryfa G13s (od 1 lipca 2025).
|
||
|
||
Strefy czasowe (Europe/Warsaw):
|
||
* Noc: 21:00–07:00 (cały rok)
|
||
* Lato: 07:00–09:00 (szczyt), 09:00–17:00 (pozaszczyt), 17:00–21:00 (szczyt)
|
||
* Zima: 07:00–10:00 (szczyt), 10:00–15:00 (pozaszczyt), 15:00–21:00 (szczyt)
|
||
|
||
Ceny różnią się także rodzajem dnia:
|
||
- workday = pon–pt z wyłączeniem świąt,
|
||
- dayoff = sobota, niedziela i dni ustawowo wolne od pracy.
|
||
|
||
Oczekuje stawek NETTO (PLN/kWh) przekazanych w `rates` według struktury RatesDict.
|
||
"""
|
||
rates: RatesDict = None # wstrzykuj w konstruktorze
|
||
|
||
# stałe zakresy godzin
|
||
NIGHT: tuple[time, time] = (time(21, 0), time(7, 0)) # przez północ
|
||
|
||
SUMMER_PEAK_1: tuple[time, time] = (time(7, 0), time(9, 0))
|
||
SUMMER_OFFDAY: tuple[time, time] = (time(9, 0), time(17, 0))
|
||
SUMMER_PEAK_2: tuple[time, time] = (time(17, 0), time(21, 0))
|
||
|
||
WINTER_PEAK_1: tuple[time, time] = (time(7, 0), time(10, 0))
|
||
WINTER_OFFDAY: tuple[time, time] = (time(10, 0), time(15, 0))
|
||
WINTER_PEAK_2: tuple[time, time] = (time(15, 0), time(21, 0))
|
||
|
||
def __init__(self):
|
||
self.rates= {
|
||
"winter":
|
||
{
|
||
"dayoff": { "peak" : 0.20, "offpeak_day" : 0.12, "night" : 0.11 },
|
||
"workday":{ "peak" : 0.24, "offpeak_day" : 0.2, "night" : 0.11 }
|
||
},
|
||
"summer":
|
||
{
|
||
"dayoff": { "peak": 0.12, "offpeak_day": 0.04, "night": 0.11},
|
||
"workday": { "peak": 0.29, "offpeak_day": 0.1, "night": 0.11 }
|
||
}
|
||
}
|
||
|
||
@staticmethod
|
||
def _season(d: datetime.date) -> Season:
|
||
return "summer" if 4 <= d.month <= 9 else "winter"
|
||
|
||
@staticmethod
|
||
def _daytype(d: datetime.date) -> DayType:
|
||
return "dayoff" if is_weekend_or_holiday(d) else "workday"
|
||
|
||
def _band(self, t: time, season: Season) -> Band:
|
||
if in_range_local(t, self.NIGHT):
|
||
return "night"
|
||
|
||
if season == "summer":
|
||
if in_range_local(t, self.SUMMER_PEAK_1) or in_range_local(t, self.SUMMER_PEAK_2):
|
||
return "peak"
|
||
if in_range_local(t, self.SUMMER_OFFDAY):
|
||
return "offpeak_day"
|
||
else: # winter
|
||
if in_range_local(t, self.WINTER_PEAK_1) or in_range_local(t, self.WINTER_PEAK_2):
|
||
return "peak"
|
||
if in_range_local(t, self.WINTER_OFFDAY):
|
||
return "offpeak_day"
|
||
|
||
return "night"
|
||
|
||
def rate(self, ts: datetime) -> float:
|
||
dt = self.to_local_dt(ts)
|
||
season = self._season(dt.date())
|
||
daytype = self._daytype(dt.date())
|
||
band = self._band(dt.time(), season)
|
||
try:
|
||
return self.rates[season][daytype][band]
|
||
except KeyError as e:
|
||
raise KeyError(f"no price for [{season}][{daytype}][{band}]") from e
|