mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-21 05:11:57 +00:00
fix: unify mypy environments for local checks and CI (#1291)
The isolated pre-commit mypy hook previously omitted runtime type information that make mypy used, hiding errors involving dependencies such as Pydantic and Pendulum. Makefile, pre-commit and CI now run the same full-project typing policy in the development environment defined by uv.lock. - Use uv run --locked --exact --extra dev and the same mypy arguments for Makefile and the local hook. Check all of src and tests, including on configuration-only changes. - Pin Python 3.13 for local development and the pre-commit CI job, and install the locked pre-commit version in CI. - Disable incremental analysis because existing Pendulum cache state changes mypy 2.3.1 diagnostics. Document the policy, the performance tradeoff and the existing typing debt. - Add a regression test that exercises Makefile, the hook and the CI command in a temporary project, accepting valid dependency types and detecting deliberate Pydantic/Pendulum assignment errors. Resolve the newly detected mypy diagnostics. - Enable the numpydantic and Pydantic mypy plugins, retaining strict Pydantic constructor typing with init_typed = true. Validate raw/coercible payloads through model_validate. - Propagate concrete record, provider and time-window types through generic collections, factories and lookup methods. Preserve runtime field inspection and generated time-window documentation. - Align Pendulum annotations with actual factory/arithmetic results while retaining Pydantic validation adapters at runtime. Correct optional values, array boundaries, REST handlers and plotting interfaces. - Add pinned scipy-stubs and types-psutil, update uv.lock, and supply the plugins' dependencies. - Add runtime regression coverage for validated path defaults, normalized time-series metadata, generic field inspection, invalid timestamps and unsupported provider imports. Runtime and compatibility details: - Validate path defaults as Path objects while retaining raw string defaults needed by migration serialization with exclude_defaults. - Normalize feed-in tariff lists and default charge rates to NumPy arrays; reject missing timestamps/uninitialized values explicitly. Importing into a provider without import support returns HTTP 400. - Public JSON schemas and OpenAPI structure match main (excluding the generated version). Signed-off-by: dr-dimitry Signed-off-by: dr-dimitry Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com> Co-authored-by: dr-dimitri <87113560+dr-dimitri@users.noreply.github.com> Co-authored-by: Normann <github@koldrack.com>
This commit is contained in:
co-authored by
dr-dimitri
Normann
parent
5b584cbb57
commit
1abdd345c4
@@ -104,7 +104,7 @@ class ElecFeeDataRecord(PredictionRecord):
|
||||
return self.elecfee_feedin_amt_wh * 1000.0
|
||||
|
||||
|
||||
class ElecFeeProvider(PredictionProvider):
|
||||
class ElecFeeProvider(PredictionProvider[ElecFeeDataRecord]):
|
||||
"""Abstract base class for electricity fee providers.
|
||||
|
||||
Electricity fee providers predict fees on consumed and feed-in electricity to be used by
|
||||
|
||||
@@ -49,7 +49,7 @@ class ElecPriceDataRecord(PredictionRecord):
|
||||
return self.elecprice_marketprice_wh * 1000.0
|
||||
|
||||
|
||||
class ElecPriceProvider(PricePredictionProviderBase):
|
||||
class ElecPriceProvider(PricePredictionProviderBase[ElecPriceDataRecord]):
|
||||
"""Abstract base class for electricity price providers.
|
||||
|
||||
ElecPriceProvider is a thread-safe singleton, ensuring only one instance of this class is created.
|
||||
|
||||
@@ -49,7 +49,7 @@ class FeedInTariffDataRecord(PredictionRecord):
|
||||
return self.feed_in_tariff_wh * 1000.0
|
||||
|
||||
|
||||
class FeedInTariffProvider(PricePredictionProviderBase):
|
||||
class FeedInTariffProvider(PricePredictionProviderBase[FeedInTariffDataRecord]):
|
||||
"""Abstract base class for feed in tariff providers.
|
||||
|
||||
FeedInTariffProvider is a thread-safe singleton, ensuring only one instance of this class is created.
|
||||
|
||||
@@ -123,10 +123,10 @@ class FeedInTariffAkkudoktor(FeedInTariffProvider):
|
||||
history = np.asarray(
|
||||
await self.key_to_array(
|
||||
key="feed_in_tariff_wh",
|
||||
end_datetime=self.highest_orig_datetime,
|
||||
end_datetime=to_datetime(self.highest_orig_datetime),
|
||||
fill_method="linear",
|
||||
),
|
||||
dtype=float,
|
||||
dtype=np.float64,
|
||||
)
|
||||
covered_hours = (
|
||||
int((self.highest_orig_datetime - self.ems_start_datetime).total_seconds() // 3600) + 1
|
||||
|
||||
@@ -279,7 +279,7 @@ class FeedInTariffEnergyCharts(FeedInTariffProvider):
|
||||
# above, so ETS/median always trains on the true wholesale-price signal.
|
||||
history = await self.key_to_array(
|
||||
key="feed_in_tariff_raw_wh",
|
||||
end_datetime=self.highest_orig_datetime,
|
||||
end_datetime=to_datetime(self.highest_orig_datetime),
|
||||
interval=to_duration(f"{resolution_seconds} seconds"),
|
||||
fill_method="linear",
|
||||
)
|
||||
|
||||
@@ -137,11 +137,11 @@ class FeedInTariffTibber(FeedInTariffProvider):
|
||||
history = np.asarray(
|
||||
await self.key_to_array(
|
||||
key="feed_in_tariff_wh",
|
||||
end_datetime=self.highest_orig_datetime,
|
||||
end_datetime=to_datetime(self.highest_orig_datetime),
|
||||
interval=to_duration(f"{interval_seconds} seconds"),
|
||||
fill_method="linear",
|
||||
),
|
||||
dtype=float,
|
||||
dtype=np.float64,
|
||||
)
|
||||
covered_slots = 0
|
||||
if self.highest_orig_datetime >= self.ems_start_datetime:
|
||||
|
||||
@@ -5,7 +5,7 @@ Notes:
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import List, Optional
|
||||
from typing import Generic, List, Optional, TypeVar
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
@@ -20,7 +20,10 @@ class LoadDataRecord(PredictionRecord):
|
||||
)
|
||||
|
||||
|
||||
class LoadProvider(PredictionProvider):
|
||||
LoadDataRecordT = TypeVar("LoadDataRecordT", bound=LoadDataRecord)
|
||||
|
||||
|
||||
class LoadProvider(PredictionProvider[LoadDataRecordT], Generic[LoadDataRecordT]):
|
||||
"""Abstract base class for load providers.
|
||||
|
||||
LoadProvider is a thread-safe singleton, ensuring only one instance of this class is created.
|
||||
@@ -41,7 +44,7 @@ class LoadProvider(PredictionProvider):
|
||||
"""
|
||||
|
||||
# overload
|
||||
records: List[LoadDataRecord] = Field(
|
||||
records: List[LoadDataRecordT] = Field(
|
||||
default_factory=list, json_schema_extra={"description": "List of LoadDataRecord records"}
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class LoadAkkudoktorDataRecord(LoadDataRecord):
|
||||
)
|
||||
|
||||
|
||||
class LoadAkkudoktor(LoadProvider):
|
||||
class LoadAkkudoktor(LoadProvider[LoadAkkudoktorDataRecord]):
|
||||
"""Fetch Load forecast data from Akkudoktor load profiles."""
|
||||
|
||||
records: list[LoadAkkudoktorDataRecord] = Field(
|
||||
|
||||
@@ -119,40 +119,42 @@ weather_openmeteo = WeatherOpenMeteo()
|
||||
weather_import = WeatherImport()
|
||||
|
||||
|
||||
def prediction_providers() -> list[
|
||||
Union[
|
||||
ElecFeeFixed,
|
||||
ElecFeeImport,
|
||||
ElecPriceAkkudoktor,
|
||||
ElecPriceEnergyCharts,
|
||||
ElecPriceFixed,
|
||||
ElecPriceImport,
|
||||
ElecPriceSMARD,
|
||||
ElecPriceTibber,
|
||||
FeedInTariffAkkudoktor,
|
||||
FeedInTariffDvhubOnline,
|
||||
FeedInTariffEnergyCharts,
|
||||
FeedInTariffFixed,
|
||||
FeedInTariffImport,
|
||||
FeedInTariffSMARD,
|
||||
FeedInTariffTibber,
|
||||
LoadAkkudoktor,
|
||||
LoadAkkudoktorAdjusted,
|
||||
LoadImport,
|
||||
LoadVrm,
|
||||
PVForecastAkkudoktor,
|
||||
PVForecastForecastSolar,
|
||||
PVForecastImport,
|
||||
PVForecastPVLib,
|
||||
PVForecastPVNode,
|
||||
PVForecastSolcast,
|
||||
PVForecastVrm,
|
||||
WeatherBrightSky,
|
||||
WeatherClearOutside,
|
||||
WeatherImport,
|
||||
WeatherOpenMeteo,
|
||||
]
|
||||
]:
|
||||
PredictionProviderType = Union[
|
||||
ElecFeeFixed,
|
||||
ElecFeeImport,
|
||||
ElecPriceAkkudoktor,
|
||||
ElecPriceEnergyCharts,
|
||||
ElecPriceFixed,
|
||||
ElecPriceImport,
|
||||
ElecPriceSMARD,
|
||||
ElecPriceTibber,
|
||||
FeedInTariffAkkudoktor,
|
||||
FeedInTariffDvhubOnline,
|
||||
FeedInTariffEnergyCharts,
|
||||
FeedInTariffFixed,
|
||||
FeedInTariffImport,
|
||||
FeedInTariffSMARD,
|
||||
FeedInTariffTibber,
|
||||
LoadAkkudoktor,
|
||||
LoadAkkudoktorAdjusted,
|
||||
LoadImport,
|
||||
LoadVrm,
|
||||
PVForecastAkkudoktor,
|
||||
PVForecastForecastSolar,
|
||||
PVForecastHomeAssistant,
|
||||
PVForecastImport,
|
||||
PVForecastPVLib,
|
||||
PVForecastPVNode,
|
||||
PVForecastSolcast,
|
||||
PVForecastVrm,
|
||||
WeatherBrightSky,
|
||||
WeatherClearOutside,
|
||||
WeatherImport,
|
||||
WeatherOpenMeteo,
|
||||
]
|
||||
|
||||
|
||||
def prediction_providers() -> list[PredictionProviderType]:
|
||||
"""Return list of prediction providers.
|
||||
|
||||
Factory for prediction container.
|
||||
@@ -229,44 +231,10 @@ def prediction_providers() -> list[
|
||||
]
|
||||
|
||||
|
||||
class Prediction(PredictionContainer):
|
||||
class Prediction(PredictionContainer[PredictionProviderType]):
|
||||
"""Prediction container to manage multiple prediction providers."""
|
||||
|
||||
providers: list[
|
||||
Union[
|
||||
ElecFeeFixed,
|
||||
ElecFeeImport,
|
||||
ElecPriceAkkudoktor,
|
||||
ElecPriceEnergyCharts,
|
||||
ElecPriceFixed,
|
||||
ElecPriceImport,
|
||||
ElecPriceSMARD,
|
||||
ElecPriceTibber,
|
||||
FeedInTariffAkkudoktor,
|
||||
FeedInTariffDvhubOnline,
|
||||
FeedInTariffEnergyCharts,
|
||||
FeedInTariffFixed,
|
||||
FeedInTariffImport,
|
||||
FeedInTariffSMARD,
|
||||
FeedInTariffTibber,
|
||||
LoadAkkudoktor,
|
||||
LoadAkkudoktorAdjusted,
|
||||
LoadImport,
|
||||
LoadVrm,
|
||||
PVForecastAkkudoktor,
|
||||
PVForecastForecastSolar,
|
||||
PVForecastHomeAssistant,
|
||||
PVForecastImport,
|
||||
PVForecastPVLib,
|
||||
PVForecastPVNode,
|
||||
PVForecastSolcast,
|
||||
PVForecastVrm,
|
||||
WeatherBrightSky,
|
||||
WeatherClearOutside,
|
||||
WeatherImport,
|
||||
WeatherOpenMeteo,
|
||||
]
|
||||
] = Field(
|
||||
providers: list[PredictionProviderType] = Field(
|
||||
default_factory=prediction_providers,
|
||||
json_schema_extra={"description": "List of prediction providers"},
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ This module is designed for use in predictive modeling workflows, facilitating t
|
||||
and manipulation of configuration and prediction data in a clear, scalable, and structured manner.
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
from typing import Generic, List, Optional, TypeVar
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import Field, computed_field
|
||||
@@ -20,6 +20,7 @@ from akkudoktoreos.core.dataabc import (
|
||||
DataImportProvider,
|
||||
DataProvider,
|
||||
DataRecord,
|
||||
DataRecordT,
|
||||
DataSequence,
|
||||
)
|
||||
from akkudoktoreos.utils.datetimeutil import DateTime, Duration, to_duration
|
||||
@@ -52,7 +53,10 @@ class PredictionRecord(DataRecord):
|
||||
pass
|
||||
|
||||
|
||||
class PredictionSequence(DataSequence):
|
||||
PredictionRecordT = TypeVar("PredictionRecordT", bound=PredictionRecord)
|
||||
|
||||
|
||||
class PredictionSequence(DataSequence[PredictionRecordT], Generic[PredictionRecordT]):
|
||||
"""A managed sequence of PredictionRecord instances with list-like behavior.
|
||||
|
||||
The PredictionSequence class provides an ordered, mutable collection of PredictionRecord
|
||||
@@ -90,7 +94,7 @@ class PredictionSequence(DataSequence):
|
||||
"""
|
||||
|
||||
# To be overloaded by derived classes.
|
||||
records: List[PredictionRecord] = Field(
|
||||
records: List[PredictionRecordT] = Field(
|
||||
default_factory=list, json_schema_extra={"description": "List of prediction records"}
|
||||
)
|
||||
|
||||
@@ -185,7 +189,9 @@ class PredictionStartEndKeepMixin(PredictionABC):
|
||||
return int(duration.total_hours())
|
||||
|
||||
|
||||
class PredictionProvider(PredictionStartEndKeepMixin, DataProvider):
|
||||
class PredictionProvider(
|
||||
PredictionStartEndKeepMixin, DataProvider[DataRecordT], Generic[DataRecordT]
|
||||
):
|
||||
"""Abstract base class for prediction providers with singleton thread-safety and configurable prediction parameters.
|
||||
|
||||
This class serves as a base for managing prediction data, providing an interface for derived
|
||||
@@ -249,7 +255,9 @@ class PredictionProvider(PredictionStartEndKeepMixin, DataProvider):
|
||||
await self._update_data(force_update=force_update)
|
||||
|
||||
|
||||
class PredictionImportProvider(PredictionProvider, DataImportProvider):
|
||||
class PredictionImportProvider(
|
||||
PredictionProvider[DataRecordT], DataImportProvider[DataRecordT], Generic[DataRecordT]
|
||||
):
|
||||
"""Abstract base class for prediction providers that import prediction data.
|
||||
|
||||
This class is designed to handle prediction data provided in the form of a key-value dictionary.
|
||||
@@ -264,7 +272,12 @@ class PredictionImportProvider(PredictionProvider, DataImportProvider):
|
||||
pass
|
||||
|
||||
|
||||
class PredictionContainer(PredictionStartEndKeepMixin, DataContainer):
|
||||
PredictionProviderT = TypeVar("PredictionProviderT", bound=PredictionProvider)
|
||||
|
||||
|
||||
class PredictionContainer(
|
||||
PredictionStartEndKeepMixin, DataContainer[PredictionProviderT], Generic[PredictionProviderT]
|
||||
):
|
||||
"""A container for managing multiple PredictionProvider instances.
|
||||
|
||||
This class enables access to data from multiple prediction providers, supporting retrieval and
|
||||
@@ -277,6 +290,6 @@ class PredictionContainer(PredictionStartEndKeepMixin, DataContainer):
|
||||
"""
|
||||
|
||||
# To be overloaded by derived classes.
|
||||
providers: List[PredictionProvider] = Field(
|
||||
providers: List[PredictionProviderT] = Field(
|
||||
default_factory=list, json_schema_extra={"description": "List of prediction providers"}
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Shared base for price-like predictions (electricity price, feed-in tariff)."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import cast
|
||||
from typing import Generic, cast
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@@ -9,11 +9,14 @@ from loguru import logger
|
||||
from statsmodels.tsa.holtwinters import ExponentialSmoothing
|
||||
|
||||
from akkudoktoreos.core.coreabc import PredictionMixin
|
||||
from akkudoktoreos.core.dataabc import DataRecordT
|
||||
from akkudoktoreos.prediction.predictionabc import PredictionProvider
|
||||
from akkudoktoreos.utils.datetimeutil import DateTime, to_datetime, to_duration
|
||||
|
||||
|
||||
class PricePredictionProviderBase(PredictionMixin, PredictionProvider):
|
||||
class PricePredictionProviderBase(
|
||||
PredictionMixin, PredictionProvider[DataRecordT], Generic[DataRecordT]
|
||||
):
|
||||
"""Common forecasting + fee-application logic shared by price-like providers.
|
||||
|
||||
Subclasses must supply the raw/gross record keys, the fee keys to pull from
|
||||
|
||||
@@ -5,7 +5,7 @@ Notes:
|
||||
"""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import List, Optional
|
||||
from typing import Generic, List, Optional, TypeVar
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import Field
|
||||
@@ -24,7 +24,10 @@ class PVForecastDataRecord(PredictionRecord):
|
||||
)
|
||||
|
||||
|
||||
class PVForecastProvider(PredictionProvider):
|
||||
PVForecastDataRecordT = TypeVar("PVForecastDataRecordT", bound=PVForecastDataRecord)
|
||||
|
||||
|
||||
class PVForecastProvider(PredictionProvider[PVForecastDataRecordT], Generic[PVForecastDataRecordT]):
|
||||
"""Abstract base class for pvforecast providers.
|
||||
|
||||
PVForecastProvider is a thread-safe singleton, ensuring only one instance of this class is created.
|
||||
@@ -45,7 +48,7 @@ class PVForecastProvider(PredictionProvider):
|
||||
"""
|
||||
|
||||
# overload
|
||||
records: List[PVForecastDataRecord] = Field(
|
||||
records: List[PVForecastDataRecordT] = Field(
|
||||
default_factory=list,
|
||||
json_schema_extra={"description": "List of PVForecastDataRecord records"},
|
||||
)
|
||||
|
||||
@@ -188,7 +188,7 @@ class PVForecastAkkudoktorDataRecord(PVForecastDataRecord):
|
||||
return self.pvforecast_ac_power
|
||||
|
||||
|
||||
class PVForecastAkkudoktor(PVForecastProvider):
|
||||
class PVForecastAkkudoktor(PVForecastProvider[PVForecastAkkudoktorDataRecord]):
|
||||
"""Fetch and process PV forecast data from akkudoktor.net.
|
||||
|
||||
PVForecastAkkudoktor is a singleton-based class that retrieves weather forecast data
|
||||
|
||||
@@ -72,6 +72,8 @@ class PVForecastForecastSolar(PVForecastProvider):
|
||||
return to_datetime(s)
|
||||
tz = iana_tz or str(self.config.general.timezone)
|
||||
dt = pendulum.parse(s, tz=tz)
|
||||
if not isinstance(dt, pendulum.DateTime):
|
||||
raise ValueError(f"Expected a datetime, got {local_ts!r}")
|
||||
return to_datetime(dt.isoformat())
|
||||
|
||||
def _plane_url(self, plane: Any) -> str:
|
||||
|
||||
@@ -100,6 +100,8 @@ class PVForecastPVNode(PVForecastProvider):
|
||||
tz = iana_tz or str(self.config.general.timezone)
|
||||
# Interpret the naive wall-clock string AS local time in tz, then resolve.
|
||||
dt = pendulum.parse(s, tz=tz)
|
||||
if not isinstance(dt, pendulum.DateTime):
|
||||
raise ValueError(f"Expected a datetime, got {local_ts!r}")
|
||||
return to_datetime(dt.isoformat())
|
||||
|
||||
def _extract_values(self, body: Any) -> list[tuple[Any, float]]:
|
||||
|
||||
@@ -112,7 +112,7 @@ class WeatherDataRecord(PredictionRecord):
|
||||
)
|
||||
|
||||
|
||||
class WeatherProvider(PredictionProvider):
|
||||
class WeatherProvider(PredictionProvider[WeatherDataRecord]):
|
||||
"""Abstract base class for weather providers.
|
||||
|
||||
WeatherProvider is a thread-safe singleton, ensuring only one instance of this class is created.
|
||||
|
||||
@@ -246,12 +246,15 @@ class WeatherBrightSky(WeatherProvider):
|
||||
logger.debug(debug_msg)
|
||||
return
|
||||
data = pvlib.atmosphere.gueymard94_pw(temperature, humidity)
|
||||
end_datetime = self.end_datetime
|
||||
if end_datetime is None:
|
||||
raise ValueError("Prediction end datetime is not available")
|
||||
pwat = pd.Series(
|
||||
data=data,
|
||||
index=pd.DatetimeIndex(
|
||||
pd.date_range(
|
||||
start=self.ems_start_datetime,
|
||||
end=self.end_datetime,
|
||||
end=end_datetime,
|
||||
freq="1h",
|
||||
inclusive="left",
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ Notes:
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
import requests
|
||||
@@ -232,7 +232,7 @@ class WeatherClearOutside(WeatherProvider):
|
||||
p_detail_tables.pop(0)
|
||||
|
||||
# Create clearout data
|
||||
clearout_data = {}
|
||||
clearout_data: dict[str, Any] = {}
|
||||
# Number of detail values. On last day may be less than 24.
|
||||
detail_values_count = None
|
||||
# Add data values
|
||||
@@ -258,7 +258,7 @@ class WeatherClearOutside(WeatherProvider):
|
||||
raise ValueError(error_msg)
|
||||
|
||||
# Scrape the detail values
|
||||
detail_data = []
|
||||
detail_data: list[float | str] = []
|
||||
extra_detail_name = None
|
||||
extra_detail_data = []
|
||||
for p_detail_value in p_detail_values:
|
||||
@@ -281,9 +281,10 @@ class WeatherClearOutside(WeatherProvider):
|
||||
and hasattr(p_detail_value, "title")
|
||||
and p_detail_value.title
|
||||
):
|
||||
value_str = p_detail_value.title.string
|
||||
value_str = p_detail_value.title.get_text()
|
||||
else:
|
||||
value_str = p_detail_value.get_text()
|
||||
value: float | str
|
||||
try:
|
||||
value = float(value_str)
|
||||
except ValueError:
|
||||
@@ -336,9 +337,9 @@ class WeatherClearOutside(WeatherProvider):
|
||||
if key is None:
|
||||
continue
|
||||
if detail_name in clearout_data:
|
||||
value = clearout_data[detail_name][row_index]
|
||||
record_value = clearout_data[detail_name][row_index]
|
||||
corr_factor = clearoutside_key_mapping[detail_name][1]
|
||||
if corr_factor:
|
||||
value = value * corr_factor
|
||||
setattr(weather_record, key, value)
|
||||
record_value = record_value * corr_factor
|
||||
setattr(weather_record, key, record_value)
|
||||
await self.insert_by_datetime(weather_record)
|
||||
|
||||
@@ -342,12 +342,15 @@ class WeatherOpenMeteo(WeatherProvider):
|
||||
return
|
||||
|
||||
data = pvlib.atmosphere.gueymard94_pw(temperature, humidity)
|
||||
end_datetime = self.end_datetime
|
||||
if end_datetime is None:
|
||||
raise ValueError("Prediction end datetime is not available")
|
||||
pwat = pd.Series(
|
||||
data=data,
|
||||
index=pd.DatetimeIndex(
|
||||
pd.date_range(
|
||||
start=self.ems_start_datetime,
|
||||
end=self.end_datetime,
|
||||
end=end_datetime,
|
||||
freq="1h",
|
||||
inclusive="left",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user