ranczo-energy-price-scrapers/DistributionCostProvider/TauronG13SProvider.py
2025-08-28 14:15:42 +02:00

92 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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:0007:00 (cały rok)
* Lato: 07:0009:00 (szczyt), 09:0017:00 (pozaszczyt), 17:0021:00 (szczyt)
* Zima: 07:0010:00 (szczyt), 10:0015:00 (pozaszczyt), 15:0021:00 (szczyt)
Ceny różnią się także rodzajem dnia:
- workday = ponpt 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