SIENTIAPDE-1253: Implement business rule validation for training parameters. Adds a validate_business_rules method to the TrainModelParams class to enforce constraints on training parameters, improving data integrity and preventing errors. Also updates tests to include target variable in variable columns and adds tests for business rule validations.
This commit is contained in:
@@ -88,10 +88,12 @@ class Training(BaseActivity):
|
||||
try:
|
||||
self.info('Validating training parameters', metadata)
|
||||
|
||||
# Convert input_data directly to TrainModelParams (this validates all fields)
|
||||
# The from_dict method will extract only the fields it needs
|
||||
# Step 1: Convert input_data to TrainModelParams (validates types and required fields)
|
||||
train_params = TrainModelParams.from_dict(input_data)
|
||||
|
||||
# Step 2: Validate business rules (ranges, consistency, etc.)
|
||||
train_params.validate_business_rules()
|
||||
|
||||
self.info(
|
||||
f'Training parameters validated successfully - '
|
||||
f'Target: {train_params.target_variable}, '
|
||||
|
||||
@@ -167,3 +167,71 @@ class TrainModelParams:
|
||||
raise TypeError(error)
|
||||
|
||||
return value
|
||||
|
||||
def validate_business_rules(self) -> None:
|
||||
"""
|
||||
Validate business rules and constraints for training parameters.
|
||||
|
||||
This method performs additional validation beyond type checking to ensure
|
||||
that parameter values are within acceptable ranges and logically consistent.
|
||||
It implements defense-in-depth validation to catch configuration errors
|
||||
early in the workflow.
|
||||
|
||||
Raises:
|
||||
ValueError: If any business rule is violated
|
||||
|
||||
Example:
|
||||
>>> params = TrainModelParams.from_dict(data)
|
||||
>>> params.validate_business_rules() # Raises ValueError if invalid
|
||||
"""
|
||||
# Validate train_size range (1-99%)
|
||||
if not 1 <= self.train_size <= 99:
|
||||
raise ValueError(f'train_size must be between 1 and 99, got {self.train_size}')
|
||||
|
||||
# Validate variable_columns is not empty
|
||||
if not self.variable_columns:
|
||||
raise ValueError('variable_columns cannot be empty')
|
||||
|
||||
# Validate positive integers
|
||||
if self.lag_train <= 0:
|
||||
raise ValueError(f'lag_train must be positive, got {self.lag_train}')
|
||||
|
||||
if self.lag_val <= 0:
|
||||
raise ValueError(f'lag_val must be positive, got {self.lag_val}')
|
||||
|
||||
if self.window <= 0:
|
||||
raise ValueError(f'window must be positive, got {self.window}')
|
||||
|
||||
# Validate low_lim and upp_lim consistency
|
||||
if set(self.low_lim.keys()) != set(self.upp_lim.keys()):
|
||||
raise ValueError(
|
||||
f'low_lim and upp_lim must have the same keys. '
|
||||
f'low_lim keys: {set(self.low_lim.keys())}, '
|
||||
f'upp_lim keys: {set(self.upp_lim.keys())}'
|
||||
)
|
||||
|
||||
# Validate that low_lim < upp_lim for each variable
|
||||
for var in self.low_lim:
|
||||
if self.low_lim[var] >= self.upp_lim[var]:
|
||||
raise ValueError(
|
||||
f'low_lim must be less than upp_lim for variable "{var}". '
|
||||
f'Got low_lim={self.low_lim[var]}, upp_lim={self.upp_lim[var]}'
|
||||
)
|
||||
|
||||
# Validate target_variable is in variable_columns
|
||||
if self.target_variable not in self.variable_columns:
|
||||
raise ValueError(
|
||||
f'target_variable "{self.target_variable}" must be in variable_columns: '
|
||||
f'{self.variable_columns}'
|
||||
)
|
||||
|
||||
# Validate bucket_name and file_name are not empty
|
||||
if not self.bucket_name.strip():
|
||||
raise ValueError('bucket_name cannot be empty or whitespace')
|
||||
|
||||
if not self.file_name.strip():
|
||||
raise ValueError('file_name cannot be empty or whitespace')
|
||||
|
||||
# Validate experiment_name is not empty
|
||||
if not self.experiment_name.strip():
|
||||
raise ValueError('experiment_name cannot be empty or whitespace')
|
||||
|
||||
Reference in New Issue
Block a user