Part of a set of structural observations — see #1695 (index) for context.
Description
pandas is a direct dependency (pyproject.toml) and is imported in 15 files, but it's used almost exclusively for CSV ingestion convenience. The actual backbone that carries a time axis through every computation is a hand-rolled Period/Periods dataclass (common/time_utils.py), used throughout common/variables.py, core/result/results.py, and presentation/json_result/result/emission.py, rather than a pandas.DatetimeIndex. Domain-specific logic (calendar-day vs. stream-day rate conventions, cumulative-volume semantics) could be a thin wrapper class around a Series/DataFrame instead.
Some concrete costs of the current approach:
Period.__contains__ implements half-open interval membership by hand, and get_period_indices finds indices by calling self.start_dates.index(...) / self.all_dates.index(...) — an O(n) linear scan through a Python list, every time it's invoked (a pattern repeated at several other call sites in common/utils/rates.py too). A pandas.DatetimeIndex gives interval membership, boundary handling, and O(log n) lookup (slice_indexer, get_indexer) as built-ins.
libecalc/common/utils/rates.py implements its own reindex, for_periods, and resampling logic (TimeSeriesBoolean.resample, TimeSeriesVolumesCumulative.resample, etc.) on top of the same Period/Periods types — essentially re-deriving Series.reindex/Series.resample/Series.asfreq one method at a time, without the correctness properties (frequency inference, DST handling, vectorized alignment) that pandas already tests for.
presentation/yaml/mappers/variables_mapper/get_global_time_vector.py:get_global_time_vector builds the single shared "global" time axis that every input source ultimately gets resampled onto — the merge point for however many independent sources a model has. The INFLUENCE_TIME_VECTOR keyword controlling that merge is a per-source boolean, defaulting to TRUE, so a reader has to scan every TIME_SERIES entry and mentally OR the flags to know what timesteps the whole model will actually be evaluated at.
Examples
—
Business/User Value
Removes O(n) linear scans on the hot path of every period lookup, removes a hand-maintained reimplementation of reindex/resample logic that pandas already tests for frequency inference, DST handling, and vectorized alignment, and would make the global-time-vector merge logic inspectable via standard pandas index operations instead of manually scanning keyword flags.
Files
common/time_utils.py
common/variables.py
core/result/results.py
presentation/json_result/result/emission.py
libecalc/common/utils/rates.py
presentation/yaml/mappers/variables_mapper/get_global_time_vector.py
Part of a set of structural observations — see #1695 (index) for context.
Description
pandasis a direct dependency (pyproject.toml) and is imported in 15 files, but it's used almost exclusively for CSV ingestion convenience. The actual backbone that carries a time axis through every computation is a hand-rolledPeriod/Periodsdataclass (common/time_utils.py), used throughoutcommon/variables.py,core/result/results.py, andpresentation/json_result/result/emission.py, rather than apandas.DatetimeIndex. Domain-specific logic (calendar-day vs. stream-day rate conventions, cumulative-volume semantics) could be a thin wrapper class around aSeries/DataFrameinstead.Some concrete costs of the current approach:
Period.__contains__implements half-open interval membership by hand, andget_period_indicesfinds indices by callingself.start_dates.index(...)/self.all_dates.index(...)— an O(n) linear scan through a Python list, every time it's invoked (a pattern repeated at several other call sites incommon/utils/rates.pytoo). Apandas.DatetimeIndexgives interval membership, boundary handling, and O(log n) lookup (slice_indexer,get_indexer) as built-ins.libecalc/common/utils/rates.pyimplements its ownreindex,for_periods, and resampling logic (TimeSeriesBoolean.resample,TimeSeriesVolumesCumulative.resample, etc.) on top of the samePeriod/Periodstypes — essentially re-derivingSeries.reindex/Series.resample/Series.asfreqone method at a time, without the correctness properties (frequency inference, DST handling, vectorized alignment) that pandas already tests for.presentation/yaml/mappers/variables_mapper/get_global_time_vector.py:get_global_time_vectorbuilds the single shared "global" time axis that every input source ultimately gets resampled onto — the merge point for however many independent sources a model has. TheINFLUENCE_TIME_VECTORkeyword controlling that merge is a per-source boolean, defaulting toTRUE, so a reader has to scan everyTIME_SERIESentry and mentally OR the flags to know what timesteps the whole model will actually be evaluated at.Examples
—
Business/User Value
Removes O(n) linear scans on the hot path of every period lookup, removes a hand-maintained reimplementation of reindex/resample logic that pandas already tests for frequency inference, DST handling, and vectorized alignment, and would make the global-time-vector merge logic inspectable via standard pandas index operations instead of manually scanning keyword flags.
Files
common/time_utils.pycommon/variables.pycore/result/results.pypresentation/json_result/result/emission.pylibecalc/common/utils/rates.pypresentation/yaml/mappers/variables_mapper/get_global_time_vector.py