SIENTIAPDE-1579: Added date format and convert methods
This commit is contained in:
@@ -34,6 +34,8 @@ class TrainModelParams:
|
|||||||
file_name (str): Name of the file in the MinIO bucket.
|
file_name (str): Name of the file in the MinIO bucket.
|
||||||
line_separator (str): Line separator used in the CSV file.
|
line_separator (str): Line separator used in the CSV file.
|
||||||
decimal_separator (str): Decimal separator used in the CSV file.
|
decimal_separator (str): Decimal separator used in the CSV file.
|
||||||
|
date_column (str | None): Name of the date/time column. If set with date_format, the column is parsed as datetime.
|
||||||
|
date_format (str | None): Format of the date column (e.g. dd/MM/yyyy HH:mm:ss). Used when date_column is set.
|
||||||
train_size (int): Percentage of data to use for training (0-100).
|
train_size (int): Percentage of data to use for training (0-100).
|
||||||
shuffle (bool): Whether to shuffle the data during train/test split.
|
shuffle (bool): Whether to shuffle the data during train/test split.
|
||||||
experiment_run_id (int): Unique identifier for the experiment run.
|
experiment_run_id (int): Unique identifier for the experiment run.
|
||||||
@@ -64,6 +66,8 @@ class TrainModelParams:
|
|||||||
file_name: str
|
file_name: str
|
||||||
line_separator: str
|
line_separator: str
|
||||||
decimal_separator: str
|
decimal_separator: str
|
||||||
|
date_column: str | None
|
||||||
|
date_format: str | None
|
||||||
train_size: int
|
train_size: int
|
||||||
shuffle: bool
|
shuffle: bool
|
||||||
experiment_run_id: int
|
experiment_run_id: int
|
||||||
@@ -120,6 +124,8 @@ class TrainModelParams:
|
|||||||
decimal_separator=cls._check_none(
|
decimal_separator=cls._check_none(
|
||||||
data.get('decimal_separator'), str, 'decimal_separator'
|
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'),
|
train_size=cls._check_none(data.get('train_size'), int, 'train_size'),
|
||||||
shuffle=cls._check_none(data.get('shuffle'), bool, 'shuffle'),
|
shuffle=cls._check_none(data.get('shuffle'), bool, 'shuffle'),
|
||||||
experiment_run_id=cls._check_none(
|
experiment_run_id=cls._check_none(
|
||||||
|
|||||||
@@ -21,6 +21,32 @@ from model_manager.utils.models.train_model_params import TrainModelParams
|
|||||||
from model_manager.utils.models.train_model_result import TrainModelResult
|
from model_manager.utils.models.train_model_result import TrainModelResult
|
||||||
|
|
||||||
|
|
||||||
|
def _frontend_format_to_strftime(fmt: str) -> str:
|
||||||
|
"""Convert front-end date format (dd/MM/yyyy HH:mm:ss) to Python strftime (%d/%m/%Y %H:%M:%S)."""
|
||||||
|
if not fmt:
|
||||||
|
return fmt
|
||||||
|
out = fmt.replace('yyyy', '%Y').replace('MM', '%m').replace('dd', '%d')
|
||||||
|
out = out.replace('HH', '%H').replace('mm', '%M').replace('ss', '%S')
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_date_column_parsed(data: pd.DataFrame, params: TrainModelParams) -> pd.DataFrame:
|
||||||
|
"""If date_column and date_format are set, parse the column as datetime to avoid comparison errors downstream."""
|
||||||
|
if not params.date_column or not params.date_format or params.date_column not in data.columns:
|
||||||
|
return data
|
||||||
|
try:
|
||||||
|
python_fmt = _frontend_format_to_strftime(params.date_format)
|
||||||
|
data = data.copy()
|
||||||
|
data[params.date_column] = pd.to_datetime(
|
||||||
|
data[params.date_column], format=python_fmt, errors='coerce'
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(
|
||||||
|
f'Failed to parse date column "{params.date_column}" with format "{params.date_format}": {e}'
|
||||||
|
) from e
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class TrainingRepository:
|
class TrainingRepository:
|
||||||
"""
|
"""
|
||||||
Repository for machine learning model training operations.
|
Repository for machine learning model training operations.
|
||||||
@@ -67,10 +93,13 @@ class TrainingRepository:
|
|||||||
Exception: If data loading, preprocessing, or training fails
|
Exception: If data loading, preprocessing, or training fails
|
||||||
"""
|
"""
|
||||||
data = load_data(uploaded_file, params.line_separator, params.decimal_separator)
|
data = load_data(uploaded_file, params.line_separator, params.decimal_separator)
|
||||||
|
if data is None:
|
||||||
# Configure datetime index if timestamp column exists
|
raise ValueError(
|
||||||
# Required for TimeSeriesDiscontinuityAnalyzer (static window removal)
|
'Failed to load CSV data: load_data returned None. '
|
||||||
data = self._configure_datetime_index(data)
|
'Check file encoding, line separator and decimal separator.'
|
||||||
|
)
|
||||||
|
data = _ensure_date_column_parsed(data, params)
|
||||||
|
data = self._configure_datetime_index(data, params)
|
||||||
|
|
||||||
process_data = self._init_data_preprocessor(params)
|
process_data = self._init_data_preprocessor(params)
|
||||||
process_data.fit(data)
|
process_data.fit(data)
|
||||||
@@ -350,40 +379,41 @@ class TrainingRepository:
|
|||||||
'original_features': params.variable_columns,
|
'original_features': params.variable_columns,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _configure_datetime_index(self, data: pd.DataFrame) -> pd.DataFrame:
|
def _configure_datetime_index(
|
||||||
|
self, data: pd.DataFrame | None, params: TrainModelParams
|
||||||
|
) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Configure datetime index for the DataFrame.
|
Configure datetime index for the DataFrame.
|
||||||
|
|
||||||
This method attempts to identify a timestamp column and set it as the
|
Guards against None to avoid 'NoneType' object has no attribute 'index' downstream.
|
||||||
DataFrame index with DatetimeIndex type. This is required for
|
Prefers params.date_column when set; otherwise looks for common timestamp column names.
|
||||||
TimeSeriesDiscontinuityAnalyzer (used in static window removal).
|
|
||||||
|
|
||||||
The method looks for common timestamp column names and converts the
|
|
||||||
first matching column to datetime, then sets it as the index.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: Input DataFrame
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
pd.DataFrame: DataFrame with DatetimeIndex if timestamp column found,
|
|
||||||
otherwise returns original DataFrame unchanged
|
|
||||||
"""
|
"""
|
||||||
# If index is already DatetimeIndex, just ensure it's sorted
|
if data is None:
|
||||||
|
raise ValueError(
|
||||||
|
'Data is None after load_data. '
|
||||||
|
'Check file format, line separator and decimal separator.'
|
||||||
|
)
|
||||||
|
if not isinstance(data, pd.DataFrame):
|
||||||
|
raise TypeError(f'Expected DataFrame, got {type(data).__name__}')
|
||||||
|
|
||||||
if isinstance(data.index, pd.DatetimeIndex):
|
if isinstance(data.index, pd.DatetimeIndex):
|
||||||
self.logger.info('DataFrame already has DatetimeIndex')
|
self.logger.info('DataFrame already has DatetimeIndex')
|
||||||
return data.sort_index()
|
return data.sort_index()
|
||||||
|
|
||||||
# Common timestamp column names
|
common_timestamp_columns = [
|
||||||
timestamp_columns = [
|
|
||||||
'timestamp',
|
'timestamp',
|
||||||
'Timestamp',
|
'Timestamp',
|
||||||
'TIMESTAMP',
|
'TIMESTAMP',
|
||||||
'date',
|
'date',
|
||||||
'Date',
|
'Date',
|
||||||
'DATE',
|
'DATE',
|
||||||
|
'DATA',
|
||||||
'datetime',
|
'datetime',
|
||||||
'DateTime',
|
'DateTime',
|
||||||
]
|
]
|
||||||
|
timestamp_columns = (
|
||||||
|
[params.date_column] if params.date_column else []
|
||||||
|
) + [c for c in common_timestamp_columns if c != params.date_column]
|
||||||
|
|
||||||
for col in timestamp_columns:
|
for col in timestamp_columns:
|
||||||
if col in data.columns:
|
if col in data.columns:
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ def build_workflow_payload(
|
|||||||
'file_name': file_name,
|
'file_name': file_name,
|
||||||
'line_separator': request_data['lineSeparator'],
|
'line_separator': request_data['lineSeparator'],
|
||||||
'decimal_separator': request_data['decimalSeparator'],
|
'decimal_separator': request_data['decimalSeparator'],
|
||||||
|
'date_column': request_data.get('dateColumn'),
|
||||||
|
'date_format': request_data.get('dateFormat'),
|
||||||
'removed_intervals': request_data['removedIntervals'],
|
'removed_intervals': request_data['removedIntervals'],
|
||||||
# New parameters
|
# New parameters
|
||||||
'model_name': request_data.get('modelName', 'Linear Regression'),
|
'model_name': request_data.get('modelName', 'Linear Regression'),
|
||||||
|
|||||||
Reference in New Issue
Block a user