global: MASSIVE snapshot

This commit is contained in:
nym21
2026-02-23 17:22:12 +01:00
parent be0d749f9c
commit 3b7aa8242a
703 changed files with 29130 additions and 30779 deletions

View File

@@ -132,36 +132,93 @@ pub fn generate_endpoint_class(output: &mut String) {
writeln!(
output,
r#"# Date conversion constants
_GENESIS = date(2009, 1, 3) # dateindex 0, weekindex 0
_DAY_ONE = date(2009, 1, 9) # dateindex 1 (6 day gap after genesis)
_DATE_INDEXES = frozenset(['dateindex', 'weekindex', 'monthindex', 'yearindex', 'quarterindex', 'semesterindex', 'decadeindex'])
_GENESIS = date(2009, 1, 3) # day1 0, week1 0
_DAY_ONE = date(2009, 1, 9) # day1 1 (6 day gap after genesis)
_EPOCH = datetime(2009, 1, 1, tzinfo=timezone.utc)
_DATE_INDEXES = frozenset([
'minute1', 'minute5', 'minute10', 'minute30',
'hour1', 'hour4', 'hour12',
'day1', 'day3', 'week1',
'month1', 'month3', 'month6',
'year1', 'year10',
])
def is_date_index(index: str) -> bool:
"""Check if an index type is date-based."""
return index in _DATE_INDEXES
def index_to_date(index: str, i: int) -> date:
"""Convert an index value to a date for date-based indexes."""
if index == 'dateindex':
def _index_to_date(index: str, i: int) -> Union[date, datetime]:
"""Convert an index value to a date/datetime for date-based indexes."""
if index == 'minute1':
return _EPOCH + timedelta(minutes=i)
elif index == 'minute5':
return _EPOCH + timedelta(minutes=i * 5)
elif index == 'minute10':
return _EPOCH + timedelta(minutes=i * 10)
elif index == 'minute30':
return _EPOCH + timedelta(minutes=i * 30)
elif index == 'hour1':
return _EPOCH + timedelta(hours=i)
elif index == 'hour4':
return _EPOCH + timedelta(hours=i * 4)
elif index == 'hour12':
return _EPOCH + timedelta(hours=i * 12)
elif index == 'day1':
return _GENESIS if i == 0 else _DAY_ONE + timedelta(days=i - 1)
elif index == 'weekindex':
elif index == 'day3':
return _EPOCH.date() + timedelta(days=i * 3)
elif index == 'week1':
return _GENESIS + timedelta(weeks=i)
elif index == 'monthindex':
elif index == 'month1':
return date(2009 + i // 12, i % 12 + 1, 1)
elif index == 'yearindex':
return date(2009 + i, 1, 1)
elif index == 'quarterindex':
elif index == 'month3':
m = i * 3
return date(2009 + m // 12, m % 12 + 1, 1)
elif index == 'semesterindex':
elif index == 'month6':
m = i * 6
return date(2009 + m // 12, m % 12 + 1, 1)
elif index == 'decadeindex':
elif index == 'year1':
return date(2009 + i, 1, 1)
elif index == 'year10':
return date(2009 + i * 10, 1, 1)
else:
raise ValueError(f"{{index}} is not a date-based index")
def _date_to_index(index: str, d: Union[date, datetime]) -> int:
"""Convert a date/datetime to an index value for date-based indexes.
Returns the floor index (latest index whose date is <= the given date).
For sub-day indexes (minute*, hour*), a plain date is treated as midnight UTC.
"""
if index in ('minute1', 'minute5', 'minute10', 'minute30', 'hour1', 'hour4', 'hour12'):
if isinstance(d, datetime):
dt = d if d.tzinfo else d.replace(tzinfo=timezone.utc)
else:
dt = datetime(d.year, d.month, d.day, tzinfo=timezone.utc)
secs = int((dt - _EPOCH).total_seconds())
div = {{'minute1': 60, 'minute5': 300, 'minute10': 600, 'minute30': 1800,
'hour1': 3600, 'hour4': 14400, 'hour12': 43200}}
return secs // div[index]
dd = d.date() if isinstance(d, datetime) else d
if index == 'day1':
if dd < _DAY_ONE:
return 0
return 1 + (dd - _DAY_ONE).days
elif index == 'day3':
return (dd - date(2009, 1, 1)).days // 3
elif index == 'week1':
return (dd - _GENESIS).days // 7
elif index == 'month1':
return (dd.year - 2009) * 12 + (dd.month - 1)
elif index == 'month3':
return (dd.year - 2009) * 4 + (dd.month - 1) // 3
elif index == 'month6':
return (dd.year - 2009) * 2 + (dd.month - 1) // 6
elif index == 'year1':
return dd.year - 2009
elif index == 'year10':
return (dd.year - 2009) // 10
else:
raise ValueError(f"{{index}} is not a date-based index")
@dataclass
class MetricData(Generic[T]):
"""Metric data with range information."""
@@ -173,71 +230,64 @@ class MetricData(Generic[T]):
stamp: str
data: List[T]
def dates(self) -> List[date]:
"""Convert index range to dates. Only works for date-based indexes."""
return [index_to_date(self.index, i) for i in range(self.start, self.end)]
@property
def is_date_based(self) -> bool:
"""Whether this metric uses a date-based index."""
return self.index in _DATE_INDEXES
def dates(self) -> list:
"""Get dates for the index range. Date-based indexes only, throws otherwise."""
return [_index_to_date(self.index, i) for i in range(self.start, self.end)]
def indexes(self) -> List[int]:
"""Get index range as list."""
"""Get raw index numbers."""
return list(range(self.start, self.end))
def to_date_dict(self) -> dict[date, T]:
"""Return data as {{date: value}} dict. Only works for date-based indexes."""
return dict(zip(self.dates(), self.data))
def keys(self) -> list:
"""Get keys: dates for date-based indexes, index numbers otherwise."""
return self.dates() if self.is_date_based else self.indexes()
def to_index_dict(self) -> dict[int, T]:
"""Return data as {{index: value}} dict."""
return dict(zip(range(self.start, self.end), self.data))
def items(self) -> list:
"""Get (key, value) pairs: keys are dates for date-based, numbers otherwise."""
return list(zip(self.keys(), self.data))
def date_items(self) -> List[Tuple[date, T]]:
"""Return data as [(date, value), ...] pairs. Only works for date-based indexes."""
return list(zip(self.dates(), self.data))
def to_dict(self) -> dict:
"""Return {{key: value}} dict: keys are dates for date-based, numbers otherwise."""
return dict(zip(self.keys(), self.data))
def index_items(self) -> List[Tuple[int, T]]:
"""Return data as [(index, value), ...] pairs."""
return list(zip(range(self.start, self.end), self.data))
def iter(self) -> Iterator[Tuple[int, T]]:
"""Iterate over (index, value) pairs."""
return iter(zip(range(self.start, self.end), self.data))
def iter_dates(self) -> Iterator[Tuple[date, T]]:
"""Iterate over (date, value) pairs. Date-based indexes only."""
return iter(zip(self.dates(), self.data))
def __iter__(self) -> Iterator[Tuple[int, T]]:
"""Default iteration over (index, value) pairs."""
return self.iter()
def __iter__(self):
"""Iterate over (key, value) pairs. Keys are dates for date-based, numbers otherwise."""
return iter(zip(self.keys(), self.data))
def to_polars(self, with_dates: bool = True) -> pl.DataFrame:
"""Convert to Polars DataFrame. Requires polars to be installed.
Returns a DataFrame with columns:
- 'date' (date) and 'value' (T) if with_dates=True and index is date-based
- 'index' (int) and 'value' (T) otherwise
- 'date' and 'value' if with_dates=True and index is date-based
- 'index' and 'value' otherwise
"""
try:
import polars as pl # type: ignore[import-not-found]
except ImportError:
raise ImportError("polars is required: pip install polars")
if with_dates and self.index in _DATE_INDEXES:
if with_dates and self.is_date_based:
return pl.DataFrame({{"date": self.dates(), "value": self.data}})
return pl.DataFrame({{"index": list(range(self.start, self.end)), "value": self.data}})
return pl.DataFrame({{"index": self.indexes(), "value": self.data}})
def to_pandas(self, with_dates: bool = True) -> pd.DataFrame:
"""Convert to Pandas DataFrame. Requires pandas to be installed.
Returns a DataFrame with columns:
- 'date' (date) and 'value' (T) if with_dates=True and index is date-based
- 'index' (int) and 'value' (T) otherwise
- 'date' and 'value' if with_dates=True and index is date-based
- 'index' and 'value' otherwise
"""
try:
import pandas as pd # type: ignore[import-not-found]
except ImportError:
raise ImportError("pandas is required: pip install pandas")
if with_dates and self.index in _DATE_INDEXES:
if with_dates and self.is_date_based:
return pd.DataFrame({{"date": self.dates(), "value": self.data}})
return pd.DataFrame({{"index": list(range(self.start, self.end)), "value": self.data}})
return pd.DataFrame({{"index": self.indexes(), "value": self.data}})
# Type alias for non-generic usage
@@ -369,23 +419,36 @@ class MetricEndpointBuilder(Generic[T]):
@overload
def __getitem__(self, key: slice) -> RangeBuilder[T]: ...
def __getitem__(self, key: Union[int, slice]) -> Union[SingleItemBuilder[T], RangeBuilder[T]]:
"""Access single item or slice.
def __getitem__(self, key: Union[int, slice, date, datetime]) -> Union[SingleItemBuilder[T], RangeBuilder[T]]:
"""Access single item or slice. Accepts dates for date-based indexes.
Examples:
endpoint[5] # Single item at index 5
endpoint[:10] # First 10
endpoint[-5:] # Last 5
endpoint[100:110] # Range 100-109
endpoint[5] # Single item at index 5
endpoint[:10] # First 10
endpoint[-5:] # Last 5
endpoint[100:110] # Range 100-109
endpoint[date(2020, 1, 1):date(2023, 1, 1)] # Date range
endpoint[date(2020, 1, 1):] # Since date
"""
if isinstance(key, (date, datetime)):
idx = _date_to_index(self._config.index, key)
return SingleItemBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
idx, idx + 1
))
if isinstance(key, int):
return SingleItemBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
key, key + 1
))
start, stop = key.start, key.stop
if isinstance(start, (date, datetime)):
start = _date_to_index(self._config.index, start)
if isinstance(stop, (date, datetime)):
stop = _date_to_index(self._config.index, stop)
return RangeBuilder(_EndpointConfig(
self._config.client, self._config.name, self._config.index,
key.start, key.stop
start, stop
))
def head(self, n: int = 10) -> RangeBuilder[T]:
@@ -462,7 +525,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
if j > 0 {
write!(output, ", ").unwrap();
}
write!(output, "'{}'", index.serialize_long()).unwrap();
write!(output, "'{}'", index.name()).unwrap();
}
// Single-element tuple needs trailing comma
if pattern.indexes.len() == 1 {
@@ -496,7 +559,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
.unwrap();
for index in &pattern.indexes {
let method_name = index_to_field_name(index);
let index_name = index.serialize_long();
let index_name = index.name();
writeln!(
output,
" def {}(self) -> MetricEndpointBuilder[T]: return _ep(self._c, self._n, '{}')",