feat: enhance configuration and error handling in project setup
- Added new ignore rule for Ruff to allow temporary paths in tests. - Introduced MyPy overrides for specific modules to ignore errors. - Refactored `Cleanup` and `ExperimentTracking` classes to remove async keywords from methods, improving consistency in method signatures. - Updated `Training` class methods to handle synchronous operations, enhancing performance and clarity. - Adjusted `requirements.txt` to remove unnecessary Git dependency, streamlining project setup.
This commit is contained in:
@@ -62,8 +62,8 @@ class TrainModelParams:
|
||||
|
||||
# New Parameters
|
||||
val_file_name: str | None
|
||||
data_model_kwargs: dict | None # Removed params used in DataPreprocessor here
|
||||
model_kwargs: dict | None # Removed params used in Linear Regression Model here
|
||||
data_model_kwargs: dict | None # Removed params used in DataPreprocessor here
|
||||
model_kwargs: dict | None # Removed params used in Linear Regression Model here
|
||||
opt_params: dict | None
|
||||
model_type: str
|
||||
model_id: str | None
|
||||
@@ -102,12 +102,16 @@ class TrainModelParams:
|
||||
model_name = cls._check_none(data.get('model_name'), str, 'model_name')
|
||||
|
||||
return cls(
|
||||
variable_columns=cls._check_none(data.get('variable_columns'), list, 'variable_columns'),
|
||||
variable_columns=cls._check_none(
|
||||
data.get('variable_columns'), list, 'variable_columns'
|
||||
),
|
||||
target_variable=cls._check_none(data.get('target_variable'), str, 'target_variable'),
|
||||
bucket_name=cls._check_none(data.get('bucket_name'), str, 'bucket_name'),
|
||||
file_name=cls._check_none(data.get('file_name'), str, 'file_name'),
|
||||
line_separator=cls._check_none(data.get('line_separator'), str, 'line_separator'),
|
||||
decimal_separator=cls._check_none(data.get('decimal_separator'), str, 'decimal_separator'),
|
||||
decimal_separator=cls._check_none(
|
||||
data.get('decimal_separator'), str, 'decimal_separator'
|
||||
),
|
||||
date_column=data.get('date_column'),
|
||||
date_format=data.get('date_format'),
|
||||
train_size=cls._check_none(data.get('train_size'), int, 'train_size'),
|
||||
@@ -117,12 +121,13 @@ class TrainModelParams:
|
||||
model_name=model_name,
|
||||
experiment_name=model_name + '_experiment',
|
||||
val_file_name=data.get('val_file_name'),
|
||||
data_model_kwargs=cls._check_none(data.get('data_model_kwargs'), dict, 'data_model_kwargs'),
|
||||
data_model_kwargs=cls._check_none(
|
||||
data.get('data_model_kwargs'), dict, 'data_model_kwargs'
|
||||
),
|
||||
model_kwargs=cls._check_none(data.get('model_kwargs'), dict, 'model_kwargs'),
|
||||
opt_params=cls._check_none(data.get('opt_params'), dict, 'opt_params'),
|
||||
model_type=cls._check_none(data.get('model_type'), str, 'model_type'),
|
||||
model_id=data.get('model_id'),
|
||||
|
||||
model_metadata=cls._parse_optional_model_metadata(data.get('model_metadata')),
|
||||
)
|
||||
|
||||
@@ -233,9 +238,7 @@ class TrainModelParams:
|
||||
return None
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
raise TypeError(
|
||||
f'model_metadata must be a dict or None, but got {type(value).__name__}.'
|
||||
)
|
||||
raise TypeError(f'model_metadata must be a dict or None, but got {type(value).__name__}.')
|
||||
|
||||
def validate_business_rules(self) -> None:
|
||||
"""
|
||||
@@ -261,21 +264,20 @@ class TrainModelParams:
|
||||
|
||||
if not self.variable_columns:
|
||||
raise ValueError('variable_columns cannot be empty')
|
||||
|
||||
|
||||
def _validate_model_params(self) -> None:
|
||||
"""Validate model-related parameters."""
|
||||
if not self.model_metadata:
|
||||
raise ValueError('model_metadata is required')
|
||||
|
||||
schemas = self.model_metadata.get('schemas', {}).get("components", {}).get("schemas")
|
||||
|
||||
schemas = self.model_metadata.get('schemas', {}).get('components', {}).get('schemas')
|
||||
|
||||
if not schemas:
|
||||
return
|
||||
|
||||
data_model_schema = schemas.get("data_model")
|
||||
model_schema = schemas.get("model")
|
||||
opt_params_schema = schemas.get("opt_params")
|
||||
data_model_schema = schemas.get('data_model')
|
||||
model_schema = schemas.get('model')
|
||||
opt_params_schema = schemas.get('opt_params')
|
||||
|
||||
if data_model_schema:
|
||||
self._validate_model_param(data_model_schema, self.data_model_kwargs)
|
||||
@@ -283,8 +285,6 @@ class TrainModelParams:
|
||||
self._validate_model_param(model_schema, self.model_kwargs)
|
||||
if opt_params_schema:
|
||||
self._validate_model_param(opt_params_schema, self.opt_params)
|
||||
|
||||
|
||||
|
||||
def _validate_model_param(self, schema: dict[str, Any], value: Any) -> None:
|
||||
"""Validate model parameter against schema."""
|
||||
@@ -292,9 +292,7 @@ class TrainModelParams:
|
||||
validator = Draft202012Validator(schema)
|
||||
validator.validate(value)
|
||||
except ValidationError as e:
|
||||
raise ValueError(f'Model parameters validation failed: {e.message}')
|
||||
except Exception as e:
|
||||
raise ValueError(f'Unexpected error: {e}')
|
||||
raise ValueError(f'Model parameters validation failed: {e.message}') from e
|
||||
|
||||
def _validate_required_strings(self) -> None:
|
||||
"""Validate required string fields are not empty."""
|
||||
@@ -313,4 +311,4 @@ class TrainModelParams:
|
||||
def _validate_date_format(self) -> None:
|
||||
"""Validate date_format is one of the allowed frontend formats when set."""
|
||||
if self.date_format:
|
||||
validate_frontend_date_format(self.date_format)
|
||||
validate_frontend_date_format(self.date_format)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ integration. Models are trained elsewhere (e.g., via SientiaModel wrappers),
|
||||
and this repository focuses solely on preparing data structures for them.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
import json
|
||||
from os import makedirs, path
|
||||
from typing import Any
|
||||
from shutil import rmtree
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@@ -28,35 +28,42 @@ from sientia_do.temporal.constants import DATETIME_FORMAT_WITH_TZ
|
||||
from sientia_model.wrappers.sientia_model import SientiaModel
|
||||
|
||||
from model_manager.sientia.metrics import mae, mse, r2
|
||||
from model_manager.sientia.reports import Reports # type: ignore[import-untyped]
|
||||
from model_manager.utils.models.train_model_params import TrainModelParams
|
||||
from model_manager.utils.models.train_model_result import TrainModelResult
|
||||
from model_manager.sientia.reports import Reports # type: ignore[import-untyped]
|
||||
|
||||
def train_test_split(data: pd.DataFrame | pd.Series, train_size: float, random_state: int | None = None, shuffle: bool = True) -> tuple[pd.DataFrame, pd.DataFrame]:
|
||||
|
||||
def train_test_split(
|
||||
data: pd.DataFrame | pd.Series,
|
||||
train_size: float,
|
||||
random_state: int | None = None,
|
||||
shuffle: bool = True,
|
||||
) -> tuple[pd.DataFrame, pd.DataFrame]:
|
||||
# 1. Definir a semente (seed) para reprodutibilidade
|
||||
if random_state is not None:
|
||||
np.random.seed(random_state)
|
||||
|
||||
|
||||
# 2. Gerar índices e embaralhar se necessário
|
||||
indices = np.arange(len(data))
|
||||
|
||||
|
||||
if shuffle:
|
||||
np.random.shuffle(indices)
|
||||
|
||||
|
||||
# 3. Calcular o ponto de corte (split point)
|
||||
# Cálculo: N_treino = tamanho_total * proporcao_treino
|
||||
n_train = int(len(data) * train_size)
|
||||
|
||||
|
||||
# 4. Dividir os índices
|
||||
train_indices = indices[:n_train]
|
||||
test_indices = indices[n_train:]
|
||||
|
||||
|
||||
# 5. Retornar os dados fatiados (funciona para DataFrame ou Series)
|
||||
if isinstance(data, (pd.DataFrame, pd.Series)):
|
||||
return data.iloc[train_indices], data.iloc[test_indices]
|
||||
|
||||
|
||||
return data[train_indices], data[test_indices]
|
||||
|
||||
|
||||
def _ensure_date_column_parsed(data: pd.DataFrame, params: TrainModelParams) -> pd.DataFrame:
|
||||
"""
|
||||
If date_column is set, parse the column as timezone-aware
|
||||
@@ -178,7 +185,6 @@ class DataManagerRepository(SientiaMonitoring):
|
||||
if len(val_df) <= 0:
|
||||
raise ValueError('Validation data view is empty after transformation')
|
||||
|
||||
|
||||
val_data = pd.DataFrame(val_df[params.variable_columns + [params.target_variable]])
|
||||
else:
|
||||
# Fallback path: derive validation via train/test split from a single dataset.
|
||||
@@ -194,11 +200,7 @@ class DataManagerRepository(SientiaMonitoring):
|
||||
metadata,
|
||||
)
|
||||
|
||||
return TrainModelResult(
|
||||
params=params,
|
||||
train_data=train_data,
|
||||
val_data=val_data
|
||||
)
|
||||
return TrainModelResult(params=params, train_data=train_data, val_data=val_data)
|
||||
|
||||
def _as_series(self, pred: pd.DataFrame | pd.Series) -> pd.Series:
|
||||
if isinstance(pred, pd.Series):
|
||||
@@ -207,10 +209,8 @@ class DataManagerRepository(SientiaMonitoring):
|
||||
if pred.shape[1] == 1:
|
||||
return pred.iloc[:, 0]
|
||||
raise ValueError('y_pred/y_train_pred must be a Series or single-column DataFrame')
|
||||
|
||||
def _extract_model_equation(
|
||||
self, regr: Any, params: TrainModelParams
|
||||
) -> dict:
|
||||
|
||||
def _extract_model_equation(self, regr: Any, params: TrainModelParams) -> dict:
|
||||
"""
|
||||
Extract the linear regression equation coefficients and create equation metadata.
|
||||
|
||||
@@ -237,7 +237,7 @@ class DataManagerRepository(SientiaMonitoring):
|
||||
model_kwargs = params.model_kwargs or {}
|
||||
degree = model_kwargs.get('degree', 1)
|
||||
poly_feature_names = model_kwargs.get('poly_feature_names', None)
|
||||
|
||||
|
||||
if degree > 1 and poly_feature_names:
|
||||
feature_names = poly_feature_names
|
||||
else:
|
||||
@@ -271,7 +271,6 @@ class DataManagerRepository(SientiaMonitoring):
|
||||
'original_features': feature_names,
|
||||
}
|
||||
|
||||
|
||||
def compute_regression_metrics(
|
||||
self,
|
||||
tmr: TrainModelResult,
|
||||
@@ -301,7 +300,7 @@ class DataManagerRepository(SientiaMonitoring):
|
||||
|
||||
# True values are expected to come from val_data.
|
||||
y_true_val = tmr.val_data[target]
|
||||
|
||||
|
||||
y_pred_val = self._as_series(tmr.y_pred).sort_index()
|
||||
y_true_val = y_true_val.sort_index()
|
||||
|
||||
@@ -407,7 +406,9 @@ class DataManagerRepository(SientiaMonitoring):
|
||||
reports_dir = path.join(model_manager_dir, 'reports')
|
||||
return reports_dir
|
||||
|
||||
def _create_run_directory(self, base_path: str, run_name: str, metadata: dict[str, Any] | None = None) -> str:
|
||||
def _create_run_directory(
|
||||
self, base_path: str, run_name: str, metadata: dict[str, Any] | None = None
|
||||
) -> str:
|
||||
"""
|
||||
Creates a directory inside the 'reports' folder with the run name and a timestamp.
|
||||
|
||||
@@ -471,7 +472,6 @@ class DataManagerRepository(SientiaMonitoring):
|
||||
reference_data_float = reference_data.astype(np.float64)
|
||||
current_data_float = current_data.astype(np.float64)
|
||||
|
||||
|
||||
# Initialize report generator
|
||||
data.run_dir = self._create_run_directory(self._get_reports_directory(), data.run_name)
|
||||
report = Reports(
|
||||
|
||||
Reference in New Issue
Block a user