3 Commits
9 ... 12

Author SHA1 Message Date
model-plugin-warehouse CI
f82718ec38 Release 12 (from main) 2026-10-28 13:46:05 +00:00
model-plugin-warehouse CI
efe522474f Release 11 (from main) 2026-10-23 18:04:02 +00:00
model-plugin-warehouse CI
dc12d4e35c Release 10 (from main) 2026-09-14 21:30:42 +00:00
11 changed files with 134 additions and 68 deletions

View File

@@ -1 +1 @@
store_name: SUSE Store<lb>version: 9<lb>models:<lb>- name: linear_regression<lb> version: 1.2.0<lb>- name: xgboost<lb> version: 1.2.0<lb>runtimes:<lb> basic:<lb> path: runtime/basic.yaml<lb> version: 7<lb> single:<lb> path: runtime/single.yaml<lb> version: 1<lb> xgboost:<lb> path: runtime/xgboost.yaml<lb> version: 7<lb> store_name: SUSE Store<lb>version: 12<lb>models:<lb>- name: linear_regression<lb> version: 1.2.3<lb>- name: xgboost<lb> version: 1.2.1<lb>runtimes:<lb> basic:<lb> path: runtime/basic.yaml<lb> version: 8<lb> single:<lb> path: runtime/single.yaml<lb> version: 1<lb> xgboost:<lb> path: runtime/xgboost.yaml<lb> version: 8<lb>

View File

@@ -1,17 +1,17 @@
store_name: SUSE Store store_name: SUSE Store
version: 9 version: 12
models: models:
- name: linear_regression - name: linear_regression
version: 1.2.0 version: 1.2.3
- name: xgboost - name: xgboost
version: 1.2.0 version: 1.2.1
runtimes: runtimes:
basic: basic:
path: runtime/basic.yaml path: runtime/basic.yaml
version: 7 version: 8
single: single:
path: runtime/single.yaml path: runtime/single.yaml
version: 1 version: 1
xgboost: xgboost:
path: runtime/xgboost.yaml path: runtime/xgboost.yaml
version: 7 version: 8

View File

@@ -11,8 +11,8 @@ These parameters are defined in the `model.yaml` under `model.input_map` and are
| `degree` | `2` | Degree of the polynomial used to create polynomial features. | | `degree` | `2` | Degree of the polynomial used to create polynomial features. |
| `interaction_only` | `true` | Whether to include only interaction features in polynomial features. | | `interaction_only` | `true` | Whether to include only interaction features in polynomial features. |
| `verbose` | `true` | Enable verbose output during model execution. | | `verbose` | `true` | Enable verbose output during model execution. |
| `clipping_max` | `100` | Maximum value for clipping prediction output. | | `clipping_max` | `100` | Optional maximum for prediction output (number, decimals allowed). |
| `clipping_min` | `0` | Minimum value for clipping prediction output. | | `clipping_min` | `0` | Optional minimum for prediction output (number, decimals allowed). |
## Data Preprocessor (`DataPreprocessor`) ## Data Preprocessor (`DataPreprocessor`)
@@ -23,7 +23,7 @@ These parameters are defined in the `model.yaml` under `data_model.input_map` an
| `scaler__name` | `'Standard Scaler'` | Name of the scaler to use for scaling the data. Options: 'Standard Scaler' | | `scaler__name` | `'Standard Scaler'` | Name of the scaler to use for scaling the data. Options: 'Standard Scaler' |
| `verbose` | `true` | Enable verbose output during model execution. | | `verbose` | `true` | Enable verbose output during model execution. |
| `steps_order` | `['Discontinuity Treatment', 'Lag Selection', 'Range Selection & Data Removal', 'Static Window Removal', 'Define Variables Limits', 'Normalization', 'Feature Creation', 'Lag Creation']` | Order of the steps to be executed in the pipeline. | | `steps_order` | `['Discontinuity Treatment', 'Lag Selection', 'Range Selection & Data Removal', 'Static Window Removal', 'Define Variables Limits', 'Normalization', 'Feature Creation', 'Lag Creation']` | Order of the steps to be executed in the pipeline. |
| `nan_treatment` | `'drop'` | The treatment for missing values. Options: 'drop', 'linear interpolation' | | `nan_treatment` | `'drop'` | The treatment for missing values. Options: 'drop' (default), 'linear interpolation', 'fill linear' (alias) |
| `lag_train` | `{'variable_name': 1}` | The lags for each variable to be applyed during training. | | `lag_train` | `{'variable_name': 1}` | The lags for each variable to be applyed during training. |
| `lag_transform` | `{'variable_name': 1}` | The lags for each variable to be applyed during transformation. | | `lag_transform` | `{'variable_name': 1}` | The lags for each variable to be applyed during transformation. |
| `start_date` | `'2020-01-01 00:00:00'` | The start date for the dataset. Format: 'YYYY-MM-DD HH:MM:SS' | | `start_date` | `'2020-01-01 00:00:00'` | The start date for the dataset. Format: 'YYYY-MM-DD HH:MM:SS' |
@@ -58,7 +58,8 @@ Neither method mutates the passed DataFrames.
- If `variable_columns` is not set, it is inferred during `fit` as all columns except the target. - If `variable_columns` is not set, it is inferred during `fit` as all columns except the target.
- Polynomial features are created automatically when `degree > 1`. - Polynomial features are created automatically when `degree > 1`.
- `clipping` uses Q1/Q3 of the training target when replacing out-of-bounds predictions. - Rows with NaN in any model input get a NaN prediction (the output always has one value per input row); rows with NaN features or NaN target are left out of the fit.
- `clipping` uses Q1/Q3 of the training target when replacing out-of-bounds predictions: a prediction above `clipping_max` becomes Q3, one below `clipping_min` becomes Q1 (a prediction equal to the limit is kept). Each limit is optional and independent — set only one and only that side is checked.
- The wrapper enforces that `wrapper.target` is set before training or retraining; otherwise, a `ValueError` is raised to fail fast when the configuration is incomplete. - The wrapper enforces that `wrapper.target` is set before training or retraining; otherwise, a `ValueError` is raised to fail fast when the configuration is incomplete.
All parameters documented above **must stay in sync** between: All parameters documented above **must stay in sync** between:

View File

@@ -8,8 +8,8 @@ requirements:
- cloudpickle==3.1.2 - cloudpickle==3.1.2
- mlflow==3.8.1 - mlflow==3.8.1
- git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.11.0 - git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.11.0
- git+ssh://git@github.com/Aignosi/sientia-model-library.git@0.14.0 - git+ssh://git@github.com/Aignosi/sientia-model-library.git@0.14.3
version: 1.2.0 version: 1.2.3
type: model type: model
model: model:
class: LinearRegressionModel class: LinearRegressionModel

View File

@@ -64,6 +64,24 @@ class LinearRegressionModel(BaseEstimator, TransformerMixin):
""" """
return self.regr return self.regr
def _check_model_features(self, columns: pd.Index, model_features: list) -> None:
"""Raise when columns lack any feature the regressor was fitted with."""
missing_model_features = [c for c in model_features if c not in columns]
if missing_model_features:
raise ValueError(
'Prediction data is missing required transformed feature columns: '
+ ', '.join(missing_model_features)
)
def _apply_clipping(self, predictions: np.ndarray) -> None:
"""Replace in place predictions above max with Q3 and below min with Q1."""
for i in range(len(predictions)):
# Each limit is optional on its own (SIENTIAPDE-2219)
if 'max' in self.clipping and predictions[i] > self.clipping['max']:
predictions[i] = self.q3_target
elif 'min' in self.clipping and predictions[i] < self.clipping['min']:
predictions[i] = self.q1_target
def create_poly_features(self, input_data: pd.DataFrame, fit: bool = False) -> pd.DataFrame: def create_poly_features(self, input_data: pd.DataFrame, fit: bool = False) -> pd.DataFrame:
""" """
Function to create polynomial features Function to create polynomial features
@@ -110,15 +128,55 @@ class LinearRegressionModel(BaseEstimator, TransformerMixin):
return input_data return input_data
def _fittable_rows(
self, features: pd.DataFrame, target: pd.Series
) -> tuple[pd.DataFrame, pd.Series]:
"""
Keep only what LinearRegression can fit: inf becomes NaN, all-null columns are dropped
(degree 1 only) and rows with a NaN feature or a NaN target are removed.
Raises:
ValueError: If no rows are left.
"""
# Handle infinite values
features = features.astype(float).replace([np.inf, -np.inf], np.nan)
# Drop columns with all null values. With degree > 1 they are kept (PolynomialFeatures
# needs every variable column), so the row mask below leaves no rows and fit raises.
if self.degree == 1:
all_null_cols = features.columns[features.isnull().all()].tolist()
features = features.drop(columns=all_null_cols)
if self.verbose and all_null_cols:
print(f'{len(all_null_cols)} columns with all null values were dropped.')
# Rows with NaN in any feature or in the target cannot be fitted (SIENTIAPDE-2215).
# Masked before PolynomialFeatures, which rejects NaN.
mask = features.notna().all(axis=1) & target.notna()
features = features[mask]
target = target[mask]
if features.empty:
raise ValueError(
'No training rows left after removing rows with NaN features or NaN target.'
)
if self.verbose:
print(f'X_train shape after removing NaN rows: {features.shape}')
print(f'y_train shape after removing NaN rows: {target.shape}')
return features, target
def fit(self, input_data: pd.DataFrame) -> 'LinearRegressionModel': def fit(self, input_data: pd.DataFrame) -> 'LinearRegressionModel':
""" """
Function to fit the model Function to fit the model. Rows with NaN features or a NaN target are left out of the fit.
Args: Args:
input_data (pandas.DataFrame): The data used to fit the Linear Regression model input_data (pandas.DataFrame): The data used to fit the Linear Regression model
Returns: Returns:
LinearRegressionModel: The prediction model object LinearRegressionModel: The prediction model object
Raises:
ValueError: If target_variable is unset, a feature column is missing, or no
training rows are left after removing rows with NaN features or NaN target.
KeyError: If the target column is missing from input_data.
""" """
if self.verbose: if self.verbose:
# Display the header # Display the header
@@ -153,30 +211,14 @@ class LinearRegressionModel(BaseEstimator, TransformerMixin):
print(f'X_train shape: {X_train.shape}') print(f'X_train shape: {X_train.shape}')
print(f'y_train shape: {y_train.shape}') print(f'y_train shape: {y_train.shape}')
X_train, y_train = self._fittable_rows(X_train, y_train)
# Create polynomial features # Create polynomial features
if self.degree > 1: if self.degree > 1:
X_train = self.create_poly_features(X_train, fit=True) X_train = self.create_poly_features(X_train, fit=True)
# Handle infinite values self.q1_target = float(y_train.quantile(0.25))
X_train = X_train.replace([np.inf, -np.inf], np.nan) self.q3_target = float(y_train.quantile(0.75))
# Drop columns with all null values
all_null_cols = X_train.columns[X_train.isnull().all()].tolist()
X_train = X_train.drop(columns=all_null_cols)
if self.verbose and all_null_cols:
print(f'{len(all_null_cols)} columns with all null values were dropped.')
# Get mask of rows with any NaN values
mask = X_train.notna().all(axis=1)
X_train = X_train[mask]
y_train = y_train[mask]
if self.verbose:
print(f'X_train shape after removing NaN rows: {X_train.shape}')
print(f'y_train shape after removing NaN rows: {y_train.shape}')
self.q1_target = y_train.quantile(0.25)
self.q3_target = y_train.quantile(0.75)
# Fit the model # Fit the model
self.regr.fit(X_train, y_train) self.regr.fit(X_train, y_train)
@@ -211,7 +253,7 @@ class LinearRegressionModel(BaseEstimator, TransformerMixin):
input_data (pandas.DataFrame): The data used to predict the target variable input_data (pandas.DataFrame): The data used to predict the target variable
Returns: Returns:
numpy.ndarray: The predicted target variable numpy.ndarray: One prediction per input row; NaN where any model input is NaN
""" """
if self.verbose: if self.verbose:
# Display the header # Display the header
@@ -228,38 +270,36 @@ class LinearRegressionModel(BaseEstimator, TransformerMixin):
+ ', '.join(missing_features) + ', '.join(missing_features)
) )
X_test = input_data[self.variable_columns] # astype(float) first: laborious sends object columns with None, and replace() on
# object dtype raises a pandas downcasting FutureWarning on every call.
X_test = input_data[self.variable_columns].astype(float).replace([np.inf, -np.inf], np.nan)
if self.verbose: if self.verbose:
print(f'X_test shape: {X_test.shape}') print(f'X_test shape: {X_test.shape}')
# Create polynomial features model_features = list(self.regr.feature_names_in_)
# Rows with any NaN model input get a NaN prediction, so the output keeps the input
# length (SIENTIAPDE-2215). With degree > 1 the mask uses the raw variable columns,
# because PolynomialFeatures rejects NaN.
if self.degree > 1: if self.degree > 1:
X_test = self.create_poly_features(X_test, fit=False) row_mask = X_test.notna().all(axis=1)
X_test = X_test.replace([np.inf, -np.inf], np.nan) else:
self._check_model_features(X_test.columns, model_features)
row_mask = X_test[model_features].notna().all(axis=1)
model_features = self.regr.feature_names_in_ y_pred = np.full(len(input_data), np.nan)
# Ensure required model features exist if not row_mask.any():
missing_model_features = [c for c in model_features if c not in X_test.columns] return y_pred
if missing_model_features:
raise ValueError(
'Prediction data is missing required transformed feature columns: '
+ ', '.join(missing_model_features)
)
# Get mask of rows with any NaN values x_valid = X_test[row_mask]
mask = X_test.notna().all(axis=1) if self.degree > 1:
X_test = X_test[mask] x_valid = self.create_poly_features(x_valid, fit=False)
self._check_model_features(x_valid.columns, model_features)
if self.verbose: if self.verbose:
print(f'X_test shape after removing NaN rows: {X_test.shape}') print(f'X_test shape after removing NaN rows: {x_valid.shape}')
# For simplicity and to keep behavior consistent with prior tests, drop NaN rows before prediction
y_pred = self.regr.predict(X_test[model_features])
valid_pred = self.regr.predict(x_valid[model_features])
if self.clipping: if self.clipping:
for i in range(len(y_pred)): self._apply_clipping(valid_pred)
if y_pred[i] > self.clipping['max']:
y_pred[i] = self.q3_target
elif y_pred[i] < self.clipping['min']:
y_pred[i] = self.q1_target
y_pred[row_mask.to_numpy()] = valid_pred
return y_pred return y_pred

View File

@@ -23,12 +23,12 @@ components:
example: true example: true
description: "Enable verbose output during model execution." description: "Enable verbose output during model execution."
clipping_max: clipping_max:
type: integer type: number
default: null default: null
example: 100 example: 100
description: "Maximum value for clipping prediction output." description: "Maximum value for clipping prediction output."
clipping_min: clipping_min:
type: integer type: number
default: null default: null
example: 0 example: 0
description: "Minimum value for clipping prediction output." description: "Minimum value for clipping prediction output."
@@ -61,10 +61,10 @@ components:
description: "Order of the steps to be executed in the pipeline." description: "Order of the steps to be executed in the pipeline."
nan_treatment: nan_treatment:
type: string type: string
enum: ["drop", "linear interpolation"] enum: ["drop", "linear interpolation", "fill linear"]
default: "drop" default: "drop"
example: "drop" example: "drop"
description: "The treatment for missing values." description: "The treatment for missing values. 'fill linear' is an alias of 'linear interpolation'."
lag_train: lag_train:
type: object type: object
x-feature: true x-feature: true

View File

@@ -11,8 +11,8 @@ requirements:
- rich==14.3.2 - rich==14.3.2
- joblib==1.5.3 - joblib==1.5.3
- git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.11.0 - git+ssh://git@github.com/Aignosi/sientia-dataops-library.git@1.11.0
- git+ssh://git@github.com/Aignosi/sientia-model-library.git@0.14.0 - git+ssh://git@github.com/Aignosi/sientia-model-library.git@0.14.3
version: 1.2.0 version: 1.2.1
type: model type: model
model: model:
external: true external: true

View File

@@ -8,7 +8,7 @@ libraries:
version: 1.0.4 version: 1.0.4
- name: sientia_model - name: sientia_model
source: Aignosi/sientia-model-library source: Aignosi/sientia-model-library
version: ">=0.14.0" version: ">=0.14.3"
- name: sientia_do - name: sientia_do
source: Aignosi/sientia-dataops-library source: Aignosi/sientia-dataops-library
version: ">=1.11.0" version: ">=1.11.0"

View File

@@ -1,10 +1,10 @@
runtimes: runtimes:
basic: basic:
path: runtime/basic.yaml path: runtime/basic.yaml
version: 7 version: 8
single: single:
path: runtime/single.yaml path: runtime/single.yaml
version: 1 version: 1
xgboost: xgboost:
path: runtime/xgboost.yaml path: runtime/xgboost.yaml
version: 7 version: 8

View File

@@ -8,7 +8,7 @@ libraries:
version: 1.0.4 version: 1.0.4
- name: sientia_model - name: sientia_model
source: Aignosi/sientia-model-library source: Aignosi/sientia-model-library
version: ">=0.14.0" version: ">=0.14.3"
- name: sientia_do - name: sientia_do
source: Aignosi/sientia-dataops-library source: Aignosi/sientia-dataops-library
version: ">=1.11.0" version: ">=1.11.0"

25
suse-store_DIFF.md Normal file
View File

@@ -0,0 +1,25 @@
# Store Update Diff Report
## Version Change
- **Previous version**: 11
- **New version**: 12
## Model Changes
### ✅ Inserted Models (0)
*No new models inserted.*
### 🔄 Updated Models (2)
- **linear_regression** (version: 1.2.3)
- **xgboost** (version: 1.2.1)
## Summary
- Total models in store: 2
- New models: 0
- Updated models: 2