eb-adapters 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ eb_adapters.
5
+
6
+ Adapter classes for integrating external forecasting and regression engines
7
+ (Prophet, statsmodels, CatBoost, LightGBM, etc.) into the ElectricBarometer
8
+ ecosystem using a consistent scikit-learn-like interface.
9
+
10
+ All adapters exposed by this package implement:
11
+
12
+ - `fit(X, y, sample_weight=None)` returning `self`
13
+ - `predict(X)` returning a one-dimensional numpy array
14
+
15
+ This allows ElectricBarometer evaluation, selection, and cloning utilities to
16
+ treat native scikit-learn estimators and wrapped external models uniformly.
17
+ """
18
+
19
+ from .base import BaseAdapter, _clone_model, clone_model
20
+ from .prophet import ProphetAdapter
21
+ from .statsmodels import SarimaxAdapter, ArimaAdapter
22
+ from .catboost import CatBoostAdapter
23
+ from .lightgbm import LightGBMRegressorAdapter
24
+
25
+ __all__ = [
26
+ "BaseAdapter",
27
+ "_clone_model",
28
+ "clone_model",
29
+ "ProphetAdapter",
30
+ "SarimaxAdapter",
31
+ "ArimaAdapter",
32
+ "CatBoostAdapter",
33
+ "LightGBMRegressorAdapter",
34
+ ]
eb_adapters/base.py ADDED
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Base adapter interfaces and cloning utilities.
5
+
6
+ This module defines the minimal adapter contract used throughout the
7
+ ElectricBarometer ecosystem, along with a lightweight cloning helper for
8
+ estimator-like objects.
9
+
10
+ Adapters are intended to wrap non-scikit-learn forecasting or regression
11
+ libraries (for example, statsmodels, Prophet, or custom models) and expose
12
+ a scikit-learn-like interface so they can be used interchangeably inside
13
+ ElectricBarometer evaluation and selection workflows.
14
+ """
15
+
16
+ from typing import Any, Optional
17
+
18
+ import numpy as np
19
+
20
+
21
+ def _clone_model(model: Any) -> Any:
22
+ """
23
+ Lightweight cloning utility for estimator-like or adapter-like objects.
24
+
25
+ This function attempts to reconstruct a fresh instance of a model using a
26
+ best-effort strategy that favors compatibility with scikit-learn-style APIs
27
+ while remaining usable for custom adapters.
28
+
29
+ Cloning strategy
30
+ ----------------
31
+ The following steps are attempted in order:
32
+
33
+ 1. If scikit-learn is available, call `sklearn.base.clone(model)`.
34
+ 2. Otherwise, if the object implements `get_params()`, re-instantiate via::
35
+
36
+ model.__class__(**model.get_params())
37
+
38
+ 3. As a final fallback, instantiate the class with no arguments::
39
+
40
+ model.__class__()
41
+
42
+ Notes
43
+ -----
44
+ For custom adapters, the most reliable approach is to make the adapter
45
+ configuration-only at initialization time and implement `get_params()`
46
+ so that the instance can be reconstructed deterministically.
47
+
48
+ If a model cannot be meaningfully cloned using parameters alone, callers
49
+ may choose to bypass this helper and explicitly construct fresh adapter
50
+ instances before passing them into ElectricBarometer workflows.
51
+ """
52
+ # Try sklearn.clone if available
53
+ try: # pragma: no cover - optional dependency path
54
+ from sklearn.base import clone as sk_clone # type: ignore
55
+
56
+ return sk_clone(model)
57
+ except Exception:
58
+ pass
59
+
60
+ # Fallback: re-create via class + get_params
61
+ if hasattr(model, "get_params"):
62
+ try:
63
+ params = model.get_params() # type: ignore[assignment]
64
+ return model.__class__(**params)
65
+ except Exception:
66
+ # If get_params exists but reconstruction fails, fall through
67
+ # to the final fallback below.
68
+ pass
69
+
70
+ # Last resort: call class with no args
71
+ return model.__class__()
72
+
73
+
74
+ # Optional public alias for convenience / backwards compatibility
75
+ clone_model = _clone_model
76
+
77
+
78
+ class BaseAdapter:
79
+ """
80
+ Minimal base class defining the adapter contract for ElectricBarometer.
81
+
82
+ This class documents the expected interface for wrapping non-scikit-learn
83
+ forecasting or regression engines so they can be evaluated and selected
84
+ alongside native scikit-learn estimators.
85
+
86
+ Subclasses are expected to present a scikit-learn-like API:
87
+
88
+ - `fit(X, y, sample_weight=None)` returning `self`
89
+ - `predict(X)` returning a one-dimensional numpy array
90
+
91
+ The ElectricBarometer engine does not distinguish between native
92
+ scikit-learn estimators and adapters; it simply calls `fit` and `predict`.
93
+ This base class serves as a clear, documented contract for adapter authors.
94
+ """
95
+
96
+ def fit(
97
+ self,
98
+ X: np.ndarray,
99
+ y: np.ndarray,
100
+ sample_weight: Optional[np.ndarray] = None,
101
+ ) -> "BaseAdapter":
102
+ """
103
+ Fit the underlying forecasting or regression model.
104
+
105
+ Parameters
106
+ ----------
107
+ X : numpy.ndarray
108
+ Feature matrix. For pure time-series models, this may be ignored
109
+ or used only for alignment.
110
+ y : numpy.ndarray
111
+ One-dimensional target vector.
112
+ sample_weight : numpy.ndarray | None
113
+ Optional per-sample weights. Adapters may ignore this argument if
114
+ weighting is not supported by the underlying model.
115
+
116
+ Returns
117
+ -------
118
+ BaseAdapter
119
+ The fitted adapter instance (self).
120
+
121
+ Raises
122
+ ------
123
+ NotImplementedError
124
+ If the subclass does not override this method.
125
+ """
126
+ raise NotImplementedError(
127
+ "BaseAdapter subclasses must implement fit(X, y, sample_weight=None)."
128
+ )
129
+
130
+ def predict(self, X: np.ndarray) -> np.ndarray:
131
+ """
132
+ Generate predictions from the fitted model.
133
+
134
+ Parameters
135
+ ----------
136
+ X : numpy.ndarray
137
+ Feature matrix used to generate predictions.
138
+
139
+ Returns
140
+ -------
141
+ numpy.ndarray
142
+ One-dimensional array of predictions.
143
+
144
+ Raises
145
+ ------
146
+ NotImplementedError
147
+ If the subclass does not override this method.
148
+ """
149
+ raise NotImplementedError(
150
+ "BaseAdapter subclasses must implement predict(X)."
151
+ )
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ CatBoost adapter.
5
+
6
+ This module provides `CatBoostAdapter`, a thin wrapper around
7
+ `catboost.CatBoostRegressor` with a scikit-learn-like interface (`fit`, `predict`).
8
+
9
+ The adapter is designed for use within the ElectricBarometer ecosystem and aims to be:
10
+
11
+ - Lightweight: minimal behavior beyond input normalization and parameter storage.
12
+ - Cloneable: constructor parameters are preserved in `self.params` so cloning utilities
13
+ can reconstruct the instance consistently.
14
+ - Optional-dependency safe: importing this module does not require CatBoost, but
15
+ instantiating `CatBoostAdapter` does.
16
+
17
+ """
18
+
19
+ from typing import Any, Dict, Optional
20
+
21
+ import numpy as np
22
+
23
+ from .base import BaseAdapter
24
+
25
+
26
+ # Optional CatBoost dependency guard -----------------------------------------
27
+ try: # pragma: no cover - optional dependency
28
+ from catboost import CatBoostRegressor # type: ignore
29
+
30
+ HAS_CATBOOST = True
31
+ except Exception: # pragma: no cover - optional dependency
32
+ CatBoostRegressor = None
33
+ HAS_CATBOOST = False
34
+
35
+
36
+ class CatBoostAdapter(BaseAdapter):
37
+ """
38
+ Adapter for `catboost.CatBoostRegressor`.
39
+
40
+ This adapter exposes a scikit-learn-like API and stores initialization parameters
41
+ so the instance can be reconstructed by cloning utilities (for example, an internal
42
+ `clone_model()` helper or `sklearn.base.clone`).
43
+
44
+ Parameters
45
+ ----------
46
+ **params
47
+ Keyword arguments forwarded to `catboost.CatBoostRegressor`.
48
+
49
+ Notes
50
+ -----
51
+ - `X` and `y` are treated as standard tabular regression inputs.
52
+ - If provided, `sample_weight` is passed through to CatBoost training.
53
+ - Training verbosity is disabled by default (`verbose=False`) unless the caller
54
+ supplies `verbose` explicitly.
55
+ - All initialization parameters are stored in `self.params`.
56
+
57
+ Examples
58
+ --------
59
+ >>> model = CatBoostAdapter(
60
+ ... depth=4,
61
+ ... learning_rate=0.1,
62
+ ... iterations=200,
63
+ ... loss_function="RMSE",
64
+ ... )
65
+ >>> # X, y are numpy arrays (or array-like)
66
+ >>> # model.fit(X, y).predict(X)
67
+
68
+ """
69
+
70
+ def __init__(self, **params: Any) -> None:
71
+ if not HAS_CATBOOST:
72
+ raise ImportError(
73
+ "CatBoostAdapter requires the optional 'catboost' package. "
74
+ "Install it via `pip install catboost`."
75
+ )
76
+
77
+ # Store params for clone() compatibility
78
+ self.params: Dict[str, Any] = dict(params)
79
+
80
+ # Default: no spammy training logs
81
+ if "verbose" not in self.params:
82
+ self.params["verbose"] = False
83
+
84
+ # Instantiate the underlying CatBoost model
85
+ self.model: Optional[CatBoostRegressor] = CatBoostRegressor(**self.params)
86
+
87
+ # ------------------------------------------------------------------
88
+ # Fit
89
+ # ------------------------------------------------------------------
90
+ def fit(
91
+ self,
92
+ X: np.ndarray,
93
+ y: np.ndarray,
94
+ sample_weight: Optional[np.ndarray] = None,
95
+ ) -> "CatBoostAdapter":
96
+ """
97
+ Fit the underlying `catboost.CatBoostRegressor`.
98
+
99
+ Parameters
100
+ ----------
101
+ X : numpy.ndarray
102
+ Feature matrix of shape (n_samples, n_features).
103
+ y : numpy.ndarray
104
+ Target vector of shape (n_samples,).
105
+ sample_weight : numpy.ndarray | None
106
+ Optional per-sample weights of shape (n_samples,). If provided, this is
107
+ forwarded to CatBoost training.
108
+
109
+ Returns
110
+ -------
111
+ CatBoostAdapter
112
+ The fitted adapter (self), allowing method chaining.
113
+
114
+ Raises
115
+ ------
116
+ RuntimeError
117
+ If CatBoost is not available or the internal model is not initialized.
118
+
119
+ """
120
+ if not HAS_CATBOOST or self.model is None:
121
+ raise RuntimeError(
122
+ "CatBoostAdapter cannot train: CatBoost is not available or "
123
+ "the internal model was not initialized correctly."
124
+ )
125
+
126
+ X_arr = np.asarray(X)
127
+ y_arr = np.asarray(y, dtype=float)
128
+
129
+ if sample_weight is not None:
130
+ sw_arr = np.asarray(sample_weight, dtype=float)
131
+ self.model.fit(X_arr, y_arr, sample_weight=sw_arr)
132
+ else:
133
+ self.model.fit(X_arr, y_arr)
134
+
135
+ return self
136
+
137
+ # ------------------------------------------------------------------
138
+ # Predict
139
+ # ------------------------------------------------------------------
140
+ def predict(self, X: np.ndarray) -> np.ndarray:
141
+ """
142
+ Predict using the fitted CatBoost model.
143
+
144
+ Parameters
145
+ ----------
146
+ X : numpy.ndarray
147
+ Feature matrix of shape (n_samples, n_features).
148
+
149
+ Returns
150
+ -------
151
+ numpy.ndarray
152
+ Predicted values of shape (n_samples,).
153
+
154
+ Raises
155
+ ------
156
+ RuntimeError
157
+ If the adapter has not been fit yet.
158
+
159
+ """
160
+ if self.model is None:
161
+ raise RuntimeError("CatBoostAdapter has not been fit yet. Call `fit(...)` first.")
162
+
163
+ X_arr = np.asarray(X)
164
+ preds = self.model.predict(X_arr)
165
+ return np.asarray(preds, dtype=float).ravel()
166
+
167
+ # ------------------------------------------------------------------
168
+ # Param API for clone_model() compatibility
169
+ # ------------------------------------------------------------------
170
+ def get_params(self, deep: bool = True) -> Dict[str, Any]:
171
+ """
172
+ Return initialization parameters for cloning utilities.
173
+
174
+ Parameters
175
+ ----------
176
+ deep : bool
177
+ Included for scikit-learn compatibility. This adapter does not expose
178
+ nested estimators, so the value does not change the output.
179
+
180
+ Returns
181
+ -------
182
+ dict[str, Any]
183
+ A shallow copy of the stored initialization parameters.
184
+
185
+ """
186
+ _ = deep # intentionally unused; kept for API compatibility
187
+ return dict(self.params)
188
+
189
+ def set_params(self, **params: Any) -> "CatBoostAdapter":
190
+ """
191
+ Update parameters and rebuild the underlying CatBoost model.
192
+
193
+ Parameters
194
+ ----------
195
+ **params
196
+ Keyword parameters to merge into the stored initialization parameters.
197
+
198
+ Returns
199
+ -------
200
+ CatBoostAdapter
201
+ The updated adapter instance (self).
202
+
203
+ Notes
204
+ -----
205
+ This method updates `self.params` and then re-instantiates
206
+ `catboost.CatBoostRegressor` using the merged parameter set.
207
+
208
+ """
209
+ self.params.update(params)
210
+ if HAS_CATBOOST:
211
+ self.model = CatBoostRegressor(**self.params)
212
+ return self
213
+
214
+ # ------------------------------------------------------------------
215
+ # Representation
216
+ # ------------------------------------------------------------------
217
+ def __repr__(self) -> str:
218
+ return f"CatBoostAdapter(params={self.params})"
@@ -0,0 +1,213 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ LightGBM adapter.
5
+
6
+ This module provides `LightGBMRegressorAdapter`, a thin wrapper around
7
+ `lightgbm.LGBMRegressor` with a scikit-learn-like interface (`fit`, `predict`).
8
+
9
+ The adapter is designed for use within the ElectricBarometer ecosystem and aims to be:
10
+
11
+ - Lightweight: minimal behavior beyond input normalization and parameter storage.
12
+ - Cloneable: constructor parameters are preserved so cloning utilities can
13
+ reconstruct the instance consistently.
14
+ - Optional-dependency safe: importing this module does not require LightGBM, but
15
+ instantiating `LightGBMRegressorAdapter` does.
16
+
17
+ """
18
+
19
+ from typing import Any, Dict, Optional
20
+
21
+ import numpy as np
22
+
23
+ from .base import BaseAdapter
24
+
25
+
26
+ # Optional LightGBM dependency guard ------------------------------------------
27
+ try: # pragma: no cover - optional dependency
28
+ from lightgbm import LGBMRegressor # type: ignore
29
+
30
+ HAS_LIGHTGBM = True
31
+ except Exception: # pragma: no cover - optional dependency
32
+ LGBMRegressor = None
33
+ HAS_LIGHTGBM = False
34
+
35
+
36
+ class LightGBMRegressorAdapter(BaseAdapter):
37
+ """
38
+ Adapter for `lightgbm.LGBMRegressor`.
39
+
40
+ This adapter exposes a scikit-learn-like API and stores initialization parameters
41
+ so the instance can be reconstructed by cloning utilities (for example, an internal
42
+ `clone_model()` helper or `sklearn.base.clone`).
43
+
44
+ Parameters
45
+ ----------
46
+ **lgbm_params
47
+ Keyword arguments forwarded to `lightgbm.LGBMRegressor`.
48
+
49
+ Notes
50
+ -----
51
+ - `X` and `y` are treated as standard tabular regression inputs.
52
+ - If provided, `sample_weight` is passed through to LightGBM training.
53
+ - All initialization parameters are stored in `self.lgbm_params`.
54
+
55
+ Examples
56
+ --------
57
+ >>> model = LightGBMRegressorAdapter(
58
+ ... n_estimators=200,
59
+ ... learning_rate=0.05,
60
+ ... max_depth=-1,
61
+ ... )
62
+ >>> # X, y are numpy arrays (or array-like)
63
+ >>> # model.fit(X, y).predict(X)
64
+
65
+ """
66
+
67
+ def __init__(self, **lgbm_params: Any) -> None:
68
+ if not HAS_LIGHTGBM:
69
+ raise ImportError(
70
+ "LightGBMRegressorAdapter requires the optional 'lightgbm' package. "
71
+ "Install it via `pip install lightgbm`."
72
+ )
73
+
74
+ # Store init params so the adapter is cloneable.
75
+ self.lgbm_params: Dict[str, Any] = dict(lgbm_params)
76
+
77
+ # Underlying LightGBM model instance
78
+ self.model: Optional[LGBMRegressor] = LGBMRegressor(**self.lgbm_params)
79
+
80
+ # ------------------------------------------------------------------
81
+ # Fit
82
+ # ------------------------------------------------------------------
83
+ def fit(
84
+ self,
85
+ X: np.ndarray,
86
+ y: np.ndarray,
87
+ sample_weight: Optional[np.ndarray] = None,
88
+ ) -> "LightGBMRegressorAdapter":
89
+ """
90
+ Fit the underlying `lightgbm.LGBMRegressor`.
91
+
92
+ Parameters
93
+ ----------
94
+ X : numpy.ndarray
95
+ Feature matrix of shape (n_samples, n_features).
96
+ y : numpy.ndarray
97
+ Target vector of shape (n_samples,).
98
+ sample_weight : numpy.ndarray | None
99
+ Optional per-sample weights of shape (n_samples,). If provided, this is
100
+ forwarded to LightGBM training.
101
+
102
+ Returns
103
+ -------
104
+ LightGBMRegressorAdapter
105
+ The fitted adapter (self), allowing method chaining.
106
+
107
+ Raises
108
+ ------
109
+ RuntimeError
110
+ If LightGBM is not available or the internal model is not initialized.
111
+
112
+ """
113
+ if not HAS_LIGHTGBM or self.model is None:
114
+ raise RuntimeError(
115
+ "LightGBMRegressorAdapter cannot train: LightGBM is not available "
116
+ "or the internal model was not initialized."
117
+ )
118
+
119
+ X_arr = np.asarray(X)
120
+ y_arr = np.asarray(y, dtype=float)
121
+
122
+ if sample_weight is not None:
123
+ sw_arr = np.asarray(sample_weight, dtype=float)
124
+ self.model.fit(X_arr, y_arr, sample_weight=sw_arr)
125
+ else:
126
+ self.model.fit(X_arr, y_arr)
127
+
128
+ return self
129
+
130
+ # ------------------------------------------------------------------
131
+ # Predict
132
+ # ------------------------------------------------------------------
133
+ def predict(self, X: np.ndarray) -> np.ndarray:
134
+ """
135
+ Predict using the fitted LightGBM model.
136
+
137
+ Parameters
138
+ ----------
139
+ X : numpy.ndarray
140
+ Feature matrix of shape (n_samples, n_features).
141
+
142
+ Returns
143
+ -------
144
+ numpy.ndarray
145
+ Predicted values of shape (n_samples,).
146
+
147
+ Raises
148
+ ------
149
+ RuntimeError
150
+ If the adapter has not been fit yet.
151
+
152
+ """
153
+ if self.model is None:
154
+ raise RuntimeError(
155
+ "LightGBMRegressorAdapter has not been fit yet. Call `fit(...)` first."
156
+ )
157
+
158
+ X_arr = np.asarray(X)
159
+ preds = self.model.predict(X_arr)
160
+ return np.asarray(preds, dtype=float).ravel()
161
+
162
+ # ------------------------------------------------------------------
163
+ # Param API for clone_model() compatibility
164
+ # ------------------------------------------------------------------
165
+ def get_params(self, deep: bool = True) -> Dict[str, Any]:
166
+ """
167
+ Return initialization parameters for cloning utilities.
168
+
169
+ Parameters
170
+ ----------
171
+ deep : bool
172
+ Included for scikit-learn compatibility. This adapter does not expose
173
+ nested estimators, so the value does not change the output.
174
+
175
+ Returns
176
+ -------
177
+ dict[str, Any]
178
+ A shallow copy of the stored initialization parameters.
179
+
180
+ """
181
+ _ = deep # intentionally unused; kept for API compatibility
182
+ return dict(self.lgbm_params)
183
+
184
+ def set_params(self, **params: Any) -> "LightGBMRegressorAdapter":
185
+ """
186
+ Update parameters and rebuild the underlying LightGBM model.
187
+
188
+ Parameters
189
+ ----------
190
+ **params
191
+ Keyword parameters to merge into the stored initialization parameters.
192
+
193
+ Returns
194
+ -------
195
+ LightGBMRegressorAdapter
196
+ The updated adapter instance (self).
197
+
198
+ Notes
199
+ -----
200
+ This method updates `self.lgbm_params` and then re-instantiates
201
+ `lightgbm.LGBMRegressor` using the merged parameter set.
202
+
203
+ """
204
+ self.lgbm_params.update(params)
205
+ if HAS_LIGHTGBM:
206
+ self.model = LGBMRegressor(**self.lgbm_params)
207
+ return self
208
+
209
+ # ------------------------------------------------------------------
210
+ # Representation
211
+ # ------------------------------------------------------------------
212
+ def __repr__(self) -> str:
213
+ return f"LightGBMRegressorAdapter(params={self.lgbm_params})"
eb_adapters/prophet.py ADDED
@@ -0,0 +1,171 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Prophet adapter.
5
+
6
+ This module provides `ProphetAdapter`, a thin wrapper around `prophet.Prophet`
7
+ with a scikit-learn-like interface (`fit`, `predict`).
8
+
9
+ Unlike tabular regressors, Prophet expects a pandas DataFrame with a timestamp
10
+ column named `ds` and a target column named `y`. This adapter converts common
11
+ array-like inputs into that canonical format for use within the
12
+ ElectricBarometer ecosystem.
13
+ """
14
+
15
+ from typing import Any, Optional
16
+
17
+ import numpy as np
18
+
19
+ from .base import BaseAdapter
20
+
21
+
22
+ class ProphetAdapter(BaseAdapter):
23
+ """
24
+ Adapter for `prophet.Prophet`.
25
+
26
+ This adapter enables Prophet models to be used inside ElectricBarometer or
27
+ other CWSL-based evaluation workflows by exposing a scikit-learn-like API.
28
+
29
+ Parameters
30
+ ----------
31
+ model : Any | None
32
+ Optional pre-configured `prophet.Prophet` instance. If None, a default
33
+ Prophet model is constructed. If the `prophet` package is not installed,
34
+ constructing a default model will raise `ImportError`.
35
+
36
+ Notes
37
+ -----
38
+ Input conventions:
39
+
40
+ - `X` encodes the time index as either:
41
+ - shape (n_samples,) of datetime-like values, or
42
+ - shape (n_samples, n_features) where the first column is datetime-like
43
+ - `y` is a one-dimensional array-like of numeric targets.
44
+
45
+ At fit time, the adapter constructs a DataFrame with columns:
46
+
47
+ - `ds`: timestamps parsed from `X`
48
+ - `y`: targets from `y`
49
+
50
+ and calls `Prophet.fit(df)`.
51
+
52
+ At predict time, the adapter constructs a DataFrame with column `ds` and
53
+ returns the `yhat` predictions as a one-dimensional numpy array.
54
+
55
+ Examples
56
+ --------
57
+ >>> from prophet import Prophet
58
+ >>> base = Prophet()
59
+ >>> model = ProphetAdapter(model=base)
60
+ >>> # X contains datetimes, y contains numeric targets
61
+ >>> # model.fit(X, y).predict(X)
62
+
63
+ """
64
+
65
+ def __init__(self, model: Optional[Any] = None) -> None:
66
+ if model is None:
67
+ try:
68
+ from prophet import Prophet as _Prophet # type: ignore
69
+ except Exception as e: # pragma: no cover - import failure path
70
+ raise ImportError(
71
+ "ProphetAdapter requires the optional 'prophet' package. "
72
+ "Install it via `pip install prophet`."
73
+ ) from e
74
+
75
+ model = _Prophet()
76
+
77
+ self.model = model
78
+
79
+ # ------------------------------------------------------------------
80
+ # Fit
81
+ # ------------------------------------------------------------------
82
+ def fit(
83
+ self,
84
+ X: np.ndarray,
85
+ y: np.ndarray,
86
+ sample_weight: Optional[np.ndarray] = None, # ignored
87
+ ) -> "ProphetAdapter":
88
+ """
89
+ Fit the underlying Prophet model.
90
+
91
+ Parameters
92
+ ----------
93
+ X : numpy.ndarray
94
+ Time index values. Accepted forms are:
95
+ - shape (n_samples,) of datetime-like values, or
96
+ - shape (n_samples, n_features) where the first column is datetime-like
97
+ y : numpy.ndarray
98
+ Target vector of shape (n_samples,).
99
+ sample_weight : numpy.ndarray | None
100
+ Accepted for API compatibility but ignored by this adapter.
101
+
102
+ Returns
103
+ -------
104
+ ProphetAdapter
105
+ The fitted adapter (self), allowing method chaining.
106
+
107
+ Notes
108
+ -----
109
+ This method imports pandas locally to avoid making pandas a hard
110
+ dependency at module import time.
111
+ """
112
+ _ = sample_weight # intentionally unused; kept for API compatibility
113
+
114
+ # Local import to avoid making pandas a hard dependency for the module
115
+ import pandas as pd
116
+
117
+ X_arr = np.asarray(X)
118
+
119
+ # Use the first column if 2D
120
+ if X_arr.ndim > 1:
121
+ X_arr = X_arr[:, 0]
122
+
123
+ ds = pd.to_datetime(X_arr)
124
+ y_arr = np.asarray(y, dtype=float)
125
+
126
+ df = pd.DataFrame({"ds": ds, "y": y_arr})
127
+ self.model.fit(df)
128
+ return self
129
+
130
+ # ------------------------------------------------------------------
131
+ # Predict
132
+ # ------------------------------------------------------------------
133
+ def predict(self, X: np.ndarray) -> np.ndarray:
134
+ """
135
+ Predict using the fitted Prophet model.
136
+
137
+ Parameters
138
+ ----------
139
+ X : numpy.ndarray
140
+ Time index values in the same format accepted by `fit`.
141
+
142
+ Returns
143
+ -------
144
+ numpy.ndarray
145
+ Predicted values of shape (n_samples,), taken from Prophet's `yhat`
146
+ output column.
147
+
148
+ Raises
149
+ ------
150
+ RuntimeError
151
+ If the Prophet forecast output does not contain the `yhat` column.
152
+
153
+ Notes
154
+ -----
155
+ This method imports pandas locally to avoid making pandas a hard
156
+ dependency at module import time.
157
+ """
158
+ import pandas as pd
159
+
160
+ X_arr = np.asarray(X)
161
+ if X_arr.ndim > 1:
162
+ X_arr = X_arr[:, 0]
163
+
164
+ ds = pd.to_datetime(X_arr)
165
+ df_future = pd.DataFrame({"ds": ds})
166
+ forecast = self.model.predict(df_future)
167
+
168
+ if "yhat" not in forecast.columns:
169
+ raise RuntimeError("ProphetAdapter: expected 'yhat' column in forecast output.")
170
+
171
+ return np.asarray(forecast["yhat"], dtype=float)
@@ -0,0 +1,366 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Statsmodels adapters.
5
+
6
+ This module provides thin wrappers around univariate statsmodels time-series models so
7
+ they can be used inside the ElectricBarometer ecosystem via a scikit-learn-like API.
8
+
9
+ Adapters in this module intentionally treat `X` as an index placeholder:
10
+
11
+ - `fit(X, y)` fits the underlying time-series model to `y` only.
12
+ - `predict(X)` forecasts `len(X)` steps ahead from the end of the training sample.
13
+
14
+ This design supports evaluation workflows that expect the `predict(X)` signature while
15
+ remaining faithful to how classic univariate ARIMA-family models operate.
16
+ """
17
+
18
+ from typing import Any, Dict, Optional
19
+
20
+ import numpy as np
21
+
22
+ from .base import BaseAdapter
23
+
24
+ # Optional statsmodels support ------------------------------------------------
25
+ try: # pragma: no cover - import guard
26
+ import statsmodels.api as _sm # type: ignore[import]
27
+
28
+ HAS_STATSMODELS = True
29
+ except Exception: # pragma: no cover - import guard
30
+ _sm = None
31
+ HAS_STATSMODELS = False
32
+
33
+
34
+ class SarimaxAdapter(BaseAdapter):
35
+ """
36
+ Adapter for `statsmodels` SARIMAX.
37
+
38
+ This wrapper fits a univariate SARIMAX model on `y` and produces forecasts for
39
+ `len(X)` steps ahead when `predict(X)` is called.
40
+
41
+ Parameters
42
+ ----------
43
+ order : tuple[int, int, int], default (1, 0, 0)
44
+ ARIMA (p, d, q) order.
45
+ seasonal_order : tuple[int, int, int, int], default (0, 0, 0, 0)
46
+ Seasonal (P, D, Q, s) order.
47
+ trend : str | None, default None
48
+ Trend specification forwarded to SARIMAX.
49
+ enforce_stationarity : bool, default True
50
+ Whether to enforce stationarity in the SARIMAX model.
51
+ enforce_invertibility : bool, default True
52
+ Whether to enforce invertibility in the SARIMAX model.
53
+
54
+ Notes
55
+ -----
56
+ - `X` is ignored during fitting. It is only used at prediction time to
57
+ determine the forecast horizon (`n_steps = len(X)`).
58
+ - This adapter stores initialization parameters via `get_params()` so cloning
59
+ utilities can reconstruct the adapter.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ order: tuple[int, int, int] = (1, 0, 0),
65
+ seasonal_order: tuple[int, int, int, int] = (0, 0, 0, 0),
66
+ trend: Optional[str] = None,
67
+ enforce_stationarity: bool = True,
68
+ enforce_invertibility: bool = True,
69
+ ) -> None:
70
+ super().__init__()
71
+ self.order = order
72
+ self.seasonal_order = seasonal_order
73
+ self.trend = trend
74
+ self.enforce_stationarity = enforce_stationarity
75
+ self.enforce_invertibility = enforce_invertibility
76
+
77
+ self._result = None
78
+
79
+ # Failure is intentionally delayed until fit(); this flag supports
80
+ # feature detection and optional-dependency behavior.
81
+ if not HAS_STATSMODELS:
82
+ pass
83
+
84
+ def fit(
85
+ self,
86
+ X: np.ndarray,
87
+ y: np.ndarray,
88
+ sample_weight: Optional[np.ndarray] = None,
89
+ ) -> "SarimaxAdapter":
90
+ """
91
+ Fit a univariate SARIMAX model on `y`.
92
+
93
+ Parameters
94
+ ----------
95
+ X : numpy.ndarray
96
+ Ignored. Present for API compatibility.
97
+ y : numpy.ndarray
98
+ Target series of shape (n_samples,).
99
+ sample_weight : numpy.ndarray | None
100
+ Accepted for API compatibility but ignored by this adapter.
101
+
102
+ Returns
103
+ -------
104
+ SarimaxAdapter
105
+ The fitted adapter (self), allowing method chaining.
106
+
107
+ Raises
108
+ ------
109
+ ImportError
110
+ If `statsmodels` is not installed.
111
+ """
112
+ _ = X # intentionally unused; kept for API compatibility
113
+ _ = sample_weight # intentionally unused; kept for API compatibility
114
+
115
+ if not HAS_STATSMODELS:
116
+ raise ImportError(
117
+ "SarimaxAdapter requires the optional 'statsmodels' package. "
118
+ "Install it via `pip install statsmodels`."
119
+ )
120
+
121
+ y_arr = np.asarray(y, dtype=float)
122
+
123
+ model = _sm.tsa.statespace.SARIMAX(
124
+ y_arr,
125
+ order=self.order,
126
+ seasonal_order=self.seasonal_order,
127
+ trend=self.trend,
128
+ enforce_stationarity=self.enforce_stationarity,
129
+ enforce_invertibility=self.enforce_invertibility,
130
+ )
131
+
132
+ # Keep fitting lightweight for typical adapter usage and tests.
133
+ self._result = model.fit(disp=False, maxiter=50)
134
+ return self
135
+
136
+ def predict(self, X: np.ndarray) -> np.ndarray:
137
+ """
138
+ Forecast `len(X)` steps ahead from the end of the training sample.
139
+
140
+ Parameters
141
+ ----------
142
+ X : numpy.ndarray
143
+ Array-like placeholder used to determine the forecast horizon.
144
+
145
+ Returns
146
+ -------
147
+ numpy.ndarray
148
+ Forecast values of shape (len(X),).
149
+
150
+ Raises
151
+ ------
152
+ RuntimeError
153
+ If the adapter has not been fit yet.
154
+ """
155
+ if self._result is None:
156
+ raise RuntimeError("SarimaxAdapter has not been fit yet. Call `fit(X, y)` first.")
157
+
158
+ n_steps = len(X)
159
+ if n_steps <= 0:
160
+ return np.array([], dtype=float)
161
+
162
+ forecast = self._result.forecast(steps=n_steps)
163
+ return np.asarray(forecast, dtype=float)
164
+
165
+ def get_params(self, deep: bool = True) -> Dict[str, Any]:
166
+ """
167
+ Return initialization parameters for cloning utilities.
168
+
169
+ Parameters
170
+ ----------
171
+ deep : bool
172
+ Included for scikit-learn compatibility.
173
+
174
+ Returns
175
+ -------
176
+ dict[str, Any]
177
+ Initialization parameters that can be passed back to `__init__`.
178
+ """
179
+ _ = deep # intentionally unused; kept for API compatibility
180
+ return {
181
+ "order": self.order,
182
+ "seasonal_order": self.seasonal_order,
183
+ "trend": self.trend,
184
+ "enforce_stationarity": self.enforce_stationarity,
185
+ "enforce_invertibility": self.enforce_invertibility,
186
+ }
187
+
188
+ def set_params(self, **params: Any) -> "SarimaxAdapter":
189
+ """
190
+ Update adapter parameters.
191
+
192
+ Parameters
193
+ ----------
194
+ **params
195
+ Parameters to set as attributes on the adapter instance.
196
+
197
+ Returns
198
+ -------
199
+ SarimaxAdapter
200
+ The updated adapter instance (self).
201
+ """
202
+ for k, v in params.items():
203
+ setattr(self, k, v)
204
+ return self
205
+
206
+ def __repr__(self) -> str:
207
+ return (
208
+ f"SarimaxAdapter(order={self.order}, "
209
+ f"seasonal_order={self.seasonal_order}, trend={self.trend!r})"
210
+ )
211
+
212
+
213
+ class ArimaAdapter(BaseAdapter):
214
+ """
215
+ Adapter for `statsmodels` ARIMA.
216
+
217
+ This wrapper fits a univariate ARIMA model on `y` and produces forecasts for
218
+ `len(X)` steps ahead when `predict(X)` is called.
219
+
220
+ Parameters
221
+ ----------
222
+ order : tuple[int, int, int], default (1, 0, 0)
223
+ ARIMA (p, d, q) order.
224
+ trend : str | None, default None
225
+ Trend specification forwarded to `statsmodels.tsa.ARIMA`.
226
+
227
+ Notes
228
+ -----
229
+ - `X` is ignored during fitting. It is only used at prediction time to
230
+ determine the forecast horizon (`n_steps = len(X)`).
231
+ - This adapter stores initialization parameters via `get_params()` so cloning
232
+ utilities can reconstruct the adapter.
233
+ """
234
+
235
+ def __init__(
236
+ self,
237
+ order: tuple[int, int, int] = (1, 0, 0),
238
+ trend: Optional[str] = None,
239
+ ) -> None:
240
+ super().__init__()
241
+ self.order = order
242
+ self.trend = trend
243
+ self._result = None
244
+
245
+ # Failure is intentionally delayed until fit(); this flag supports
246
+ # feature detection and optional-dependency behavior.
247
+ if not HAS_STATSMODELS:
248
+ pass
249
+
250
+ def fit(
251
+ self,
252
+ X: np.ndarray,
253
+ y: np.ndarray,
254
+ sample_weight: Optional[np.ndarray] = None,
255
+ ) -> "ArimaAdapter":
256
+ """
257
+ Fit a univariate ARIMA model on `y`.
258
+
259
+ Parameters
260
+ ----------
261
+ X : numpy.ndarray
262
+ Ignored. Present for API compatibility.
263
+ y : numpy.ndarray
264
+ Target series of shape (n_samples,).
265
+ sample_weight : numpy.ndarray | None
266
+ Accepted for API compatibility but ignored by this adapter.
267
+
268
+ Returns
269
+ -------
270
+ ArimaAdapter
271
+ The fitted adapter (self), allowing method chaining.
272
+
273
+ Raises
274
+ ------
275
+ ImportError
276
+ If `statsmodels` is not installed.
277
+ """
278
+ _ = X # intentionally unused; kept for API compatibility
279
+ _ = sample_weight # intentionally unused; kept for API compatibility
280
+
281
+ if not HAS_STATSMODELS:
282
+ raise ImportError(
283
+ "ArimaAdapter requires the optional 'statsmodels' package. "
284
+ "Install it via `pip install statsmodels`."
285
+ )
286
+
287
+ y_arr = np.asarray(y, dtype=float)
288
+
289
+ model = _sm.tsa.ARIMA(
290
+ y_arr,
291
+ order=self.order,
292
+ trend=self.trend,
293
+ )
294
+
295
+ self._result = model.fit()
296
+ return self
297
+
298
+ def predict(self, X: np.ndarray) -> np.ndarray:
299
+ """
300
+ Forecast `len(X)` steps ahead from the end of the training sample.
301
+
302
+ Parameters
303
+ ----------
304
+ X : numpy.ndarray
305
+ Array-like placeholder used to determine the forecast horizon.
306
+
307
+ Returns
308
+ -------
309
+ numpy.ndarray
310
+ Forecast values of shape (len(X),).
311
+
312
+ Raises
313
+ ------
314
+ RuntimeError
315
+ If the adapter has not been fit yet.
316
+ """
317
+ if self._result is None:
318
+ raise RuntimeError("ArimaAdapter has not been fit yet. Call `fit(X, y)` first.")
319
+
320
+ n_steps = len(X)
321
+ if n_steps <= 0:
322
+ return np.array([], dtype=float)
323
+
324
+ forecast = self._result.forecast(steps=n_steps)
325
+ return np.asarray(forecast, dtype=float)
326
+
327
+ def get_params(self, deep: bool = True) -> Dict[str, Any]:
328
+ """
329
+ Return initialization parameters for cloning utilities.
330
+
331
+ Parameters
332
+ ----------
333
+ deep : bool
334
+ Included for scikit-learn compatibility.
335
+
336
+ Returns
337
+ -------
338
+ dict[str, Any]
339
+ Initialization parameters that can be passed back to `__init__`.
340
+ """
341
+ _ = deep # intentionally unused; kept for API compatibility
342
+ return {
343
+ "order": self.order,
344
+ "trend": self.trend,
345
+ }
346
+
347
+ def set_params(self, **params: Any) -> "ArimaAdapter":
348
+ """
349
+ Update adapter parameters.
350
+
351
+ Parameters
352
+ ----------
353
+ **params
354
+ Parameters to set as attributes on the adapter instance.
355
+
356
+ Returns
357
+ -------
358
+ ArimaAdapter
359
+ The updated adapter instance (self).
360
+ """
361
+ for k, v in params.items():
362
+ setattr(self, k, v)
363
+ return self
364
+
365
+ def __repr__(self) -> str:
366
+ return f"ArimaAdapter(order={self.order}, trend={self.trend!r})"
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.4
2
+ Name: eb-adapters
3
+ Version: 0.1.0
4
+ Summary: Adapter layer for third-party forecasting libraries in Electric Barometer
5
+ License: BSD-3-Clause
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: numpy
10
+ Requires-Dist: pandas
11
+ Provides-Extra: prophet
12
+ Requires-Dist: prophet>=1.1; extra == "prophet"
13
+ Provides-Extra: statsmodels
14
+ Requires-Dist: statsmodels>=0.14; extra == "statsmodels"
15
+ Provides-Extra: catboost
16
+ Requires-Dist: catboost>=1.2; extra == "catboost"
17
+ Provides-Extra: lightgbm
18
+ Requires-Dist: lightgbm>=4.0; extra == "lightgbm"
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: pytest-cov; extra == "dev"
22
+ Requires-Dist: mypy; extra == "dev"
23
+ Requires-Dist: ruff; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # Electric Barometer Adapters (`eb-adapters`)
27
+
28
+ ![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)
29
+ ![Python Versions](https://img.shields.io/badge/Python-3.10%2B-blue)
30
+ [![Docs](https://img.shields.io/badge/docs-electric--barometer-blue)](https://economistician.github.io/eb-docs/)
31
+ ![Project Status](https://img.shields.io/badge/Status-Alpha-yellow)
32
+
33
+ This repository contains the **model adapter layer** of the *Electric Barometer*
34
+ ecosystem.
35
+
36
+ `eb-adapters` provides standardized interfaces that adapt common forecasting and
37
+ machine-learning libraries to the Electric Barometer evaluation and readiness
38
+ frameworks, enabling consistent metric computation and model comparison across
39
+ heterogeneous modeling approaches.
40
+
41
+ Conceptual definitions and evaluation philosophy are maintained in the companion
42
+ research repository:
43
+ **`eb-papers`**.
44
+
45
+ ---
46
+
47
+ ## Naming convention
48
+
49
+ Electric Barometer packages follow standard Python packaging conventions:
50
+
51
+ - **Distribution names** (used with `pip install`) use hyphens
52
+ e.g. `pip install eb-adapters`
53
+ - **Python import paths** use underscores
54
+ e.g. `import eb_adapters`
55
+
56
+ This distinction is intentional and consistent across the Electric Barometer
57
+ ecosystem.
58
+
59
+ ---
60
+
61
+ ## Role Within Electric Barometer
62
+
63
+ Within the Electric Barometer ecosystem:
64
+
65
+ - **`eb-papers`** defines *concepts, frameworks, and meaning*
66
+ - **`eb-metrics`** implements *individual metrics*
67
+ - **`eb-evaluation`** orchestrates *evaluation workflows*
68
+ - **`eb-adapters`** standardizes *model interfaces*
69
+
70
+ This repository focuses on bridging external modeling libraries into a common,
71
+ evaluation-ready form.
72
+
73
+ ---
74
+
75
+ ## What This Library Provides
76
+
77
+ - **A common adapter base class** defining a unified interface for forecast models
78
+ - **Library-specific adapters** for popular forecasting and ML frameworks
79
+ - **Consistent prediction and evaluation hooks** compatible with EB metrics
80
+ - **Extensible patterns** for adding new model families without changing
81
+ downstream evaluation logic
82
+
83
+ Current adapters include support for:
84
+ - CatBoost
85
+ - LightGBM
86
+ - Prophet
87
+ - Statsmodels-based models
88
+
89
+ ---
90
+
91
+ ## Scope
92
+
93
+ This repository focuses on **model adaptation**, not model training algorithms or
94
+ metric definitions.
95
+
96
+ **In scope:**
97
+ - Wrapping external model APIs behind a consistent adapter interface
98
+ - Standardizing prediction outputs for evaluation
99
+ - Providing compatibility with EB evaluation pipelines
100
+
101
+ **Out of scope:**
102
+ - Metric definitions and loss formulations (see `eb-metrics`)
103
+ - Evaluation orchestration logic (see `eb-evaluation`)
104
+ - Model training methodology or hyperparameter optimization
105
+ - Conceptual framework definitions (see `eb-papers`)
106
+
107
+ ---
108
+
109
+ ## Installation
110
+
111
+ Install from PyPI:
112
+
113
+ ```bash
114
+ pip install eb-adapters
115
+ ```
116
+
117
+ For development or local use:
118
+
119
+ ```bash
120
+ pip install -e .
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Package Structure
126
+
127
+ The repository follows a clean, modern Python package layout:
128
+
129
+ ```text
130
+ eb-adapters/
131
+ ├── src/eb_adapters/
132
+ │ ├── base.py # Abstract adapter base class
133
+ │ ├── catboost.py # CatBoost model adapter
134
+ │ ├── lightgbm.py # LightGBM model adapter
135
+ │ ├── prophet.py # Prophet model adapter
136
+ │ └── statsmodels.py # Statsmodels-based adapters
137
+ │
138
+ ├── tests/
139
+ │ └── adapters/ # Unit tests for adapter implementations
140
+ │
141
+ ├── pyproject.toml # Build and dependency configuration
142
+ ├── README.md # Project documentation
143
+ └── LICENSE # BSD-3-Clause license
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Relationship to Other EB Repositories
149
+
150
+ - **`eb-papers`**
151
+ Source of truth for conceptual definitions and evaluation philosophy.
152
+
153
+ - **`eb-metrics`**
154
+ Provides the metric implementations used during evaluation.
155
+
156
+ - **`eb-evaluation`**
157
+ Orchestrates evaluation workflows using adapted models.
158
+
159
+ - **`eb-adapters`**
160
+ Ensures heterogeneous models can be evaluated consistently.
161
+
162
+ When discrepancies arise, conceptual intent in `eb-papers` should be treated as
163
+ authoritative.
164
+
165
+ ---
166
+
167
+ ## Development and Testing
168
+
169
+ Tests are located under the `tests/` directory and mirror adapter coverage.
170
+
171
+ To run the test suite:
172
+
173
+ ```bash
174
+ pytest
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Status
180
+
181
+ This package is under active development.
182
+ Public APIs may evolve prior to the first stable release.
@@ -0,0 +1,11 @@
1
+ eb_adapters/__init__.py,sha256=aFGCWDxo03BMHND98rf-ZGV1h2LPAgVn9OS2T9hP7PM,1036
2
+ eb_adapters/base.py,sha256=-ZZ8-rPMZhhTNJ-aqA4TnmKciRxGCUaLsjupcXNJHZM,5020
3
+ eb_adapters/catboost.py,sha256=R7U7cUFD4jJuZRHGAitmZ9scPCbg1k6edCjTZPLDFk4,7149
4
+ eb_adapters/lightgbm.py,sha256=kHH7ea49PiarEmkE5kKjCcZtrQAKB0PX5lk4p5ITHhU,6997
5
+ eb_adapters/prophet.py,sha256=6FBoBG-zFhg9XDxGTiy5OjcJpvLeD4K5iddZ6sG_JD0,5459
6
+ eb_adapters/statsmodels.py,sha256=IhaZbY04bX7gyerM_fQEjR4HTAn-697vhDHpBT8ss88,11365
7
+ eb_adapters-0.1.0.dist-info/licenses/LICENSE,sha256=qFjBKWIfPVLU4ZK4DgROjINVdZVLuNznTfyVaEgCa9w,1526
8
+ eb_adapters-0.1.0.dist-info/METADATA,sha256=MhIHfGjhT-Piv7SE5mG2mGPUbqCRmW1ouKOH3FKHZ1E,5275
9
+ eb_adapters-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ eb_adapters-0.1.0.dist-info/top_level.txt,sha256=uVNItEBTrYVjvjMvKEpz91-Pzhc5oNT8vZB8wPOMP9w,12
11
+ eb_adapters-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Kyle Corrie
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ eb_adapters