SIENTIAPDE-1182
Remove Docker configuration files and refactor project structure - Deleted docker-compose.yml and Dockerfile as part of the project restructuring. - Updated README.md to reflect changes in project setup and configuration. - Introduced a new __init__.py file in the laborious package to provide an overview of the system. - Enhanced documentation across various modules, including metrics, activities, and workflows, to improve clarity and usability. - Added comprehensive docstrings and comments to key classes and methods for better maintainability.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
Sientia DataOps Laborious Package
|
||||
|
||||
A high-performance, scalable machine learning prediction system built on Temporal.io
|
||||
for industrial data processing and ML model inference. The Laborious system provides
|
||||
enterprise-grade ML model management, batch prediction processing, and real-time
|
||||
data export capabilities.
|
||||
|
||||
Package Overview:
|
||||
The Laborious package implements a comprehensive ML workflow orchestration
|
||||
system that integrates with MLFlow for model management, PostgreSQL for data
|
||||
storage, and OPC servers for real-time industrial data export.
|
||||
|
||||
Key Components:
|
||||
- activities: Temporal activity implementations for ML operations
|
||||
- workflows: Temporal workflow definitions for prediction orchestration
|
||||
- worker: Main worker implementation for workflow execution
|
||||
- utils: Utility functions and configuration management
|
||||
- metrics: Prometheus metrics for monitoring and observability
|
||||
|
||||
Main Features:
|
||||
- Batch prediction processing using MLFlow models
|
||||
- Data quality validation and filtering
|
||||
- Real-time data export to OPC servers
|
||||
- PostgreSQL data persistence
|
||||
- Comprehensive monitoring and metrics
|
||||
- Automatic retry policies and error handling
|
||||
|
||||
Architecture:
|
||||
The system uses Temporal.io for workflow orchestration with clear separation
|
||||
of concerns between data loading, ML operations, quality validation, and
|
||||
data export. It supports multiple OPC servers and implements configurable
|
||||
data quality gates throughout the prediction pipeline.
|
||||
|
||||
Example Usage:
|
||||
>>> from laborious.worker.worker import main
|
||||
>>> import asyncio
|
||||
>>>
|
||||
>>> # Start the Laborious worker
|
||||
>>> asyncio.run(main())
|
||||
|
||||
>>> # Or use specific components
|
||||
>>> from laborious.activities.activities import Activities
|
||||
>>> from laborious.workflows.predictions_batch import PredictionsBatch
|
||||
|
||||
Dependencies:
|
||||
- temporalio: Temporal workflow orchestration
|
||||
- psycopg2-binary: PostgreSQL database adapter
|
||||
- sqlalchemy: Database ORM and connection management
|
||||
- asyncua: OPC UA client implementation
|
||||
- redis: Caching and session management
|
||||
- prometheus-client: Metrics collection and export
|
||||
|
||||
Environment Configuration:
|
||||
The system is configured through environment variables for database
|
||||
connections, MLFlow servers, OPC servers, and other external services.
|
||||
See the README.md for complete configuration documentation.
|
||||
|
||||
License:
|
||||
This project is licensed under the terms specified in the LICENSE file.
|
||||
|
||||
For more information, see the project README.md and documentation.
|
||||
"""
|
||||
|
||||
__version__ = "0.4.4"
|
||||
__author__ = "Sientia DataOps Team"
|
||||
__description__ = "ML prediction system built on Temporal.io for industrial data processing"
|
||||
__keywords__ = ["machine-learning", "temporal", "mlflow", "opc", "postgresql", "industrial"]
|
||||
__url__ = "https://github.com/Aignosi/sientia-dataops-laborious"
|
||||
|
||||
# Import key components for easy access
|
||||
from . import metrics
|
||||
from . import activities
|
||||
from . import workflows
|
||||
from . import worker
|
||||
|
||||
__all__ = [
|
||||
"metrics",
|
||||
"activities",
|
||||
"workflows",
|
||||
"worker"
|
||||
]
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Laborious Activities Package
|
||||
|
||||
This package contains all Temporal activity implementations for the Laborious system,
|
||||
including data quality gates, MLFlow operations, OPC server integration, and
|
||||
database operations.
|
||||
|
||||
Activities are the building blocks of workflows and implement the actual business
|
||||
logic for data processing, ML model inference, and data export operations.
|
||||
"""
|
||||
|
||||
@@ -11,13 +11,52 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
|
||||
class Activities(Postgres, MLFlow, Gates, OPC):
|
||||
"""
|
||||
Main activities orchestrator for the Laborious system.
|
||||
|
||||
This class combines functionality from multiple activity classes to provide
|
||||
a unified interface for all workflow operations. It manages database connections,
|
||||
MLFlow model interactions, data quality validation, and OPC server communications.
|
||||
|
||||
The class implements multiple inheritance to combine specialized functionality:
|
||||
- Postgres: Database operations and data persistence
|
||||
- MLFlow: Model inference and transformation operations
|
||||
- Gates: Data quality validation and filtering mechanisms
|
||||
- OPC: Real-time data export to OPC servers
|
||||
|
||||
Attributes:
|
||||
postgres_config (dict): PostgreSQL connection configuration
|
||||
mlflow_config (dict): MLFlow server configuration
|
||||
opc_config (dict): OPC server configuration
|
||||
logger (Logger): Logging and observability instance
|
||||
notification_handler (NotificationHandler): Notification management instance
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
postgres_config: dict[str, Any],
|
||||
mlflow_config: dict[str, Any],
|
||||
opc_config: dict[str, Any],
|
||||
logger: Logger, notification_handler: NotificationHandler):
|
||||
logger: Logger,
|
||||
notification_handler: NotificationHandler):
|
||||
"""
|
||||
Initialize the Activities orchestrator with all required configurations.
|
||||
|
||||
This constructor initializes all parent classes with their respective
|
||||
configurations and sets up the foundation for all activity operations.
|
||||
|
||||
Args:
|
||||
postgres_config: PostgreSQL connection configuration dictionary
|
||||
Required keys: host, port, user, password, dbname, min_connections, max_connections
|
||||
mlflow_config: MLFlow server configuration dictionary
|
||||
Required keys: host, port, username, password
|
||||
opc_config: OPC server configuration dictionary
|
||||
Can contain multiple server configurations
|
||||
logger: Logger instance for observability and debugging
|
||||
notification_handler: Notification handler for alerts and monitoring
|
||||
|
||||
Raises:
|
||||
Exception: If any parent class initialization fails
|
||||
"""
|
||||
# Initialize parent classes
|
||||
Postgres.__init__(self, host=postgres_config['host'],
|
||||
port=postgres_config['port'],
|
||||
@@ -45,5 +84,16 @@ class Activities(Postgres, MLFlow, Gates, OPC):
|
||||
notification_handler=notification_handler)
|
||||
|
||||
async def shutdown(self):
|
||||
"""
|
||||
Gracefully shutdown all activities and clean up resources.
|
||||
|
||||
This method ensures proper cleanup of all resources including:
|
||||
- PostgreSQL connection pools
|
||||
- OPC server connections
|
||||
- Any other resources that need explicit cleanup
|
||||
|
||||
The method should be called before the application terminates to ensure
|
||||
proper resource cleanup and prevent resource leaks.
|
||||
"""
|
||||
Postgres.close(self)
|
||||
await OPC.shutdown(self)
|
||||
|
||||
@@ -17,6 +17,7 @@ with workflow.unsafe.imports_passed_through():
|
||||
from pandas import DataFrame
|
||||
from laborious import metrics
|
||||
|
||||
# Input filter function mappings
|
||||
input_filter_functions = {
|
||||
'SPECIFIC_VARIABLES_NULL_VALUES': filter_specific_variables_null_values,
|
||||
'EMPTY_DATA': filter_empty_data,
|
||||
@@ -27,6 +28,7 @@ input_filter_functions = {
|
||||
}
|
||||
}
|
||||
|
||||
# MLFlow response filter function mappings
|
||||
mlflow_response_filter_functions = {
|
||||
'API_ERROR': api_error_filter,
|
||||
'path_confidence': {
|
||||
@@ -36,6 +38,7 @@ mlflow_response_filter_functions = {
|
||||
},
|
||||
}
|
||||
|
||||
# MLFlow content filter function mappings
|
||||
mlflow_content_filter_functions = {
|
||||
'NAN_VALUES': nan_values_filter,
|
||||
'path_confidence': {
|
||||
@@ -47,24 +50,72 @@ mlflow_content_filter_functions = {
|
||||
|
||||
|
||||
class Gates(BaseActivity):
|
||||
"""
|
||||
Data quality gates and filtering activities for the Laborious system.
|
||||
|
||||
This class implements comprehensive data quality validation and filtering
|
||||
mechanisms that can be applied at different stages of the prediction pipeline.
|
||||
It provides configurable filters with policy-based decision making to ensure
|
||||
data integrity and quality throughout the ML workflow.
|
||||
|
||||
The class supports multiple filter types and implements a flexible policy
|
||||
system that can be configured for different validation requirements. Each
|
||||
filter returns a path decision (STOP, CONTINUE, REPEAT) along with confidence
|
||||
scores and detailed comments for monitoring and debugging.
|
||||
|
||||
Attributes:
|
||||
input_filter_functions (dict): Mapping of input filter names to functions
|
||||
mlflow_response_filter_functions (dict): Mapping of MLFlow response filter names to functions
|
||||
mlflow_content_filter_functions (dict): Mapping of MLFlow content filter names to functions
|
||||
"""
|
||||
|
||||
def __init__(self, logger: Logger, notification_handler: NotificationHandler):
|
||||
"""
|
||||
Initialize data quality gates with logging and notification capabilities.
|
||||
|
||||
Args:
|
||||
logger: Logger instance for observability and debugging
|
||||
notification_handler: Notification handler for alerts and monitoring
|
||||
|
||||
Raises:
|
||||
Exception: If BaseActivity initialization fails
|
||||
"""
|
||||
BaseActivity.__init__(
|
||||
self, logger, notification_handler, set_error_counter=True)
|
||||
|
||||
@activity.defn(name="input_gate")
|
||||
async def input_gate(self, input_data: dict[str, Any]) -> tuple[str | None, int, str]:
|
||||
"""
|
||||
Filters the data based on the filters. The return value is a tuple with the first element
|
||||
being the policy and the second element being the confidence status.
|
||||
Apply input data quality filters and validation.
|
||||
|
||||
This activity validates input data quality using configurable filters
|
||||
before proceeding with ML operations. It applies multiple filter types
|
||||
and returns a path decision based on the filter results and configured
|
||||
policies.
|
||||
|
||||
The method implements a comprehensive filtering system that:
|
||||
1. Applies configured filters to input data
|
||||
2. Evaluates filter results against policy configurations
|
||||
3. Determines appropriate path decisions (STOP, CONTINUE, REPEAT)
|
||||
4. Provides confidence scores and detailed comments
|
||||
5. Handles errors gracefully with notification integration
|
||||
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- filters (dict): The filters to apply.
|
||||
The key is the filter name and the value is the filter configuration.
|
||||
- data (dict[str, Any]): The data to filter.
|
||||
- path_priority (list[str]): The path priority.
|
||||
input_data: Configuration and data for input validation
|
||||
Required keys:
|
||||
- metadata (dict): Workflow execution metadata
|
||||
- filters (dict): Filter configuration and policies
|
||||
- data (dict): Input data to validate
|
||||
- path_priority (list[str]): Priority order for path decisions
|
||||
|
||||
Returns:
|
||||
tuple[str | None, int, str]: (policy, confidence, comments) based in priority
|
||||
list and filter configuration and functions.
|
||||
tuple: (path_flag, confidence, comment)
|
||||
- path_flag (str | None): Decision path (STOP, CONTINUE, REPEAT, or None)
|
||||
- confidence (int): Confidence score for the decision
|
||||
- comment (str): Detailed explanation of the decision
|
||||
|
||||
Raises:
|
||||
Exception: If filter execution fails or configuration is invalid
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
|
||||
@@ -81,6 +132,7 @@ class Gates(BaseActivity):
|
||||
self.debug(f"Input data:\n {data}", metadata)
|
||||
self.debug(f"Filters: {filters}", metadata)
|
||||
|
||||
# Apply each configured filter
|
||||
for fil, config in filters.items():
|
||||
if fil not in input_filter_functions:
|
||||
self.error(f"Filter {fil} not found", metadata)
|
||||
@@ -113,20 +165,37 @@ class Gates(BaseActivity):
|
||||
@activity.defn(name="mlflow_response_gate")
|
||||
async def mlflow_response_gate(self, input_data: dict[str, Any]) -> tuple[str | None, int, str]:
|
||||
"""
|
||||
Filters the data based on the mlflow response filters.
|
||||
The return value is a tuple with the first element
|
||||
being the policy and the second element being the confidence status.
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- filters (dict): The filter configuration to apply.
|
||||
- data (dict[str, Any]): The data to filter.
|
||||
- path_priority (list[str]): The path priority list.
|
||||
- type (str): The type of the gate.
|
||||
Returns:
|
||||
tuple[str | None, int, str]: (policy, confidence, comments) based in priority list
|
||||
and filter configuration and functions.
|
||||
"""
|
||||
Validate MLFlow API response quality and integrity.
|
||||
|
||||
This activity validates MLFlow API responses to ensure they meet quality
|
||||
standards before proceeding with further processing. It applies response-specific
|
||||
filters and determines appropriate path decisions based on response quality.
|
||||
|
||||
The method implements response validation that:
|
||||
1. Applies MLFlow response-specific filters
|
||||
2. Evaluates API response quality and integrity
|
||||
3. Determines path decisions based on response validation results
|
||||
4. Provides confidence scores and detailed validation comments
|
||||
5. Handles API errors and response validation failures
|
||||
|
||||
Args:
|
||||
input_data: Configuration and data for response validation
|
||||
Required keys:
|
||||
- metadata (dict): Workflow execution metadata
|
||||
- filters (dict): Response filter configuration and policies
|
||||
- data (dict): MLFlow API response data to validate
|
||||
- type (str): Type of MLFlow operation (transform, predict)
|
||||
- path_priority (list[str]): Priority order for path decisions
|
||||
|
||||
Returns:
|
||||
tuple: (path_flag, confidence, comment)
|
||||
- path_flag (str | None): Decision path (STOP, CONTINUE, REPEAT, or None)
|
||||
- confidence (int): Confidence score for the decision
|
||||
- comment (str): Detailed explanation of the decision
|
||||
|
||||
Raises:
|
||||
Exception: If response validation fails or configuration is invalid
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
self.info("Performing mlflow response gate...", metadata)
|
||||
|
||||
@@ -143,6 +212,7 @@ class Gates(BaseActivity):
|
||||
comments = []
|
||||
for fil, config in filters.items():
|
||||
if fil not in mlflow_response_filter_functions:
|
||||
self.error(f"Filter {fil} not found", metadata)
|
||||
continue
|
||||
try:
|
||||
if mlflow_response_filter_functions[fil](data, config):
|
||||
@@ -180,20 +250,37 @@ class Gates(BaseActivity):
|
||||
@activity.defn(name="mlflow_content_gate")
|
||||
async def mlflow_content_gate(self, input_data: dict[str, Any]) -> tuple[str | None, int, str]:
|
||||
"""
|
||||
Filters the data based on the mlflow content filters.
|
||||
The return value is a tuple with the first element
|
||||
being the policy and the second element being the confidence status.
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- filters (dict): The filter configuration to apply.
|
||||
- data (dict[str, Any]): The data to filter.
|
||||
- path_priority (list[str]): The path priority list.
|
||||
- type (str): The type of the gate.
|
||||
Returns:
|
||||
tuple[str | None, int, str]: (policy, confidence, comments) based in priority
|
||||
list and filter configuration and functions.
|
||||
"""
|
||||
Validate MLFlow prediction content quality and integrity.
|
||||
|
||||
This activity validates the content of MLFlow predictions to ensure they
|
||||
meet quality standards before export and persistence. It applies content-specific
|
||||
filters and determines appropriate path decisions based on content quality.
|
||||
|
||||
The method implements content validation that:
|
||||
1. Applies MLFlow content-specific filters
|
||||
2. Evaluates prediction content quality and integrity
|
||||
3. Determines path decisions based on content validation results
|
||||
4. Provides confidence scores and detailed validation comments
|
||||
5. Handles content validation failures and quality issues
|
||||
|
||||
Args:
|
||||
input_data: Configuration and data for content validation
|
||||
Required keys:
|
||||
- metadata (dict): Workflow execution metadata
|
||||
- filters (dict): Content filter configuration and policies
|
||||
- data (dict): MLFlow prediction content to validate
|
||||
- type (str): Type of MLFlow operation (transform, predict)
|
||||
- path_priority (list[str]): Priority order for path decisions
|
||||
|
||||
Returns:
|
||||
tuple: (path_flag, confidence, comment)
|
||||
- path_flag (str | None): Decision path (STOP, CONTINUE, REPEAT, or None)
|
||||
- confidence (int): Confidence score for the decision
|
||||
- comment (str): Detailed explanation of the decision
|
||||
|
||||
Raises:
|
||||
Exception: If content validation fails or configuration is invalid
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
self.info("Performing mlflow content gate...", metadata)
|
||||
|
||||
@@ -245,6 +332,27 @@ class Gates(BaseActivity):
|
||||
def get_prediction_store_policy(self,
|
||||
prediction_store_policy: str,
|
||||
metadata: dict[str, Any]) -> tuple[str, int]:
|
||||
"""
|
||||
Parse and validate prediction store policy configuration.
|
||||
|
||||
This method parses prediction store policy strings in the format 'type:value'
|
||||
and validates them against allowed policy types and values. It provides
|
||||
sensible defaults for invalid configurations and logs policy validation
|
||||
failures for operational monitoring.
|
||||
|
||||
Supported Policy Types:
|
||||
- 'lts': Latest timestamp - sorts data by timestamp descending
|
||||
- 'erl': Earliest timestamp - sorts data by timestamp ascending
|
||||
|
||||
Args:
|
||||
prediction_store_policy (str): Policy string in format 'type:value'
|
||||
metadata (dict[str, Any]): Context metadata for logging and notifications
|
||||
|
||||
Returns:
|
||||
tuple[str, int]: (policy_type, policy_value)
|
||||
- policy_type (str): Validated policy type ('lts' or 'erl')
|
||||
- policy_value (int): Number of rows to retain
|
||||
"""
|
||||
policy_elements = prediction_store_policy.split(':')
|
||||
|
||||
if len(policy_elements) < 2:
|
||||
@@ -267,16 +375,27 @@ class Gates(BaseActivity):
|
||||
@activity.defn(name="format_prediction")
|
||||
async def format_prediction(self, input_data: dict[str, Any]) -> dict[Any, Any]:
|
||||
"""
|
||||
Formats the prediction data.
|
||||
Format prediction data according to configured storage policies.
|
||||
|
||||
This method formats prediction data for storage and export operations.
|
||||
It applies timestamp-based sorting policies, adds metadata fields,
|
||||
and ensures data consistency before persistence. The method supports
|
||||
multiple storage policies for flexible data retention strategies.
|
||||
|
||||
Storage Policies:
|
||||
- 'lts:N': Latest timestamp - retains N most recent predictions
|
||||
- 'erl:N': Earliest timestamp - retains N oldest predictions
|
||||
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- data (dict[str, Any]): The data to format.
|
||||
- timestamp (str): The timestamp of the data.
|
||||
- model_id (str): The id of the model.
|
||||
- prediction_confidence (float): The confidence of the prediction.
|
||||
- prediction_store_policy (str): The policy to store the prediction.
|
||||
input_data (dict): Input data containing:
|
||||
- data (dict[str, Any]): Raw prediction data to format
|
||||
- timestamp (str): Default timestamp if data lacks timestamp column
|
||||
- model_id (str): Unique identifier for the ML model
|
||||
- prediction_confidence (float): Confidence score for the prediction
|
||||
- prediction_store_policy (str): Storage policy in format 'type:value'
|
||||
|
||||
Returns:
|
||||
dict: The formatted data.
|
||||
dict: Formatted prediction data ready for storage and export
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
prediction_store_policy = input_data['prediction_store_policy']
|
||||
@@ -332,17 +451,28 @@ class Gates(BaseActivity):
|
||||
@activity.defn(name="format_default_prediction")
|
||||
async def format_default_prediction(self, input_data: dict[str, Any]) -> dict[Any, Any]:
|
||||
"""
|
||||
Creates and formats the default prediction data, with zero value in prediction,
|
||||
and usefull information in the other fields.
|
||||
Create and format default prediction data for error conditions.
|
||||
|
||||
This method generates default prediction data when the main prediction
|
||||
pipeline encounters errors or quality issues. It creates a standardized
|
||||
data structure with zero values for predictions and useful metadata
|
||||
for operational monitoring and debugging.
|
||||
|
||||
The default prediction serves as a fallback mechanism to:
|
||||
1. Maintain data pipeline continuity during failures
|
||||
2. Provide operational visibility into prediction quality issues
|
||||
3. Enable downstream systems to handle error conditions gracefully
|
||||
4. Support debugging and troubleshooting efforts
|
||||
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- timestamp (str): The timestamp of the data.
|
||||
- model_id (str): The id of the model.
|
||||
- prediction_confidence (float): The confidence of the prediction.
|
||||
- comment (str): The comment of the prediction.
|
||||
input_data (dict): Input data containing:
|
||||
- timestamp (str): Timestamp for the default prediction
|
||||
- model_id (str): Unique identifier for the ML model
|
||||
- prediction_confidence (float): Confidence score (typically low for errors)
|
||||
- comment (str): Error description or operational comment
|
||||
|
||||
Returns:
|
||||
dict: The formatted data.
|
||||
dict: Formatted default prediction data with error indicators
|
||||
"""
|
||||
|
||||
metadata = input_data['metadata']
|
||||
@@ -364,12 +494,25 @@ class Gates(BaseActivity):
|
||||
@activity.defn(name="get_last_timestamp")
|
||||
async def get_last_timestamp(self, input_data: dict[str, Any]) -> str:
|
||||
"""
|
||||
Gets the last timestamp of the data.
|
||||
Extract the most recent timestamp from prediction data.
|
||||
|
||||
This method analyzes prediction data to find the latest timestamp,
|
||||
enabling incremental processing and data continuity tracking.
|
||||
It handles empty datasets gracefully by returning the current time
|
||||
as a fallback timestamp.
|
||||
|
||||
The method is essential for:
|
||||
1. Incremental data processing workflows
|
||||
2. Data continuity validation
|
||||
3. Timestamp-based data loading optimization
|
||||
4. Workflow execution tracking
|
||||
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- data (dict[str, Any]): The data to get the last timestamp from.
|
||||
input_data (dict): Input data containing:
|
||||
- data (dict[str, Any]): Prediction data to analyze
|
||||
|
||||
Returns:
|
||||
str: The last timestamp of the data.
|
||||
str: Formatted timestamp string in UTC with timezone
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
|
||||
@@ -393,10 +536,25 @@ class Gates(BaseActivity):
|
||||
@activity.defn(name="write_metrics")
|
||||
async def write_metrics(self, input_data: dict[str, Any]):
|
||||
"""
|
||||
Write metrics to the database.
|
||||
input_data:
|
||||
metadata: dict[str, Any]
|
||||
prediction: dict[str, Any]
|
||||
Write prediction performance metrics to Prometheus monitoring system.
|
||||
|
||||
This method records comprehensive metrics for prediction operations,
|
||||
enabling operational monitoring, performance analysis, and alerting.
|
||||
It tracks prediction counts, confidence levels, and response times
|
||||
for each model and pipeline combination.
|
||||
|
||||
Metrics Recorded:
|
||||
1. Prediction Count: Incremental counter for successful predictions
|
||||
2. Confidence Monitor: Current confidence level for predictions
|
||||
3. Response Time Monitor: Histogram of prediction response times
|
||||
|
||||
Args:
|
||||
input_data (dict): Input data containing:
|
||||
- metadata (dict[str, Any]): Workflow execution metadata
|
||||
- prediction (dict[str, Any]): Prediction data with metrics
|
||||
|
||||
Raises:
|
||||
Exception: If metrics writing fails or configuration is invalid
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
prediction = DataFrame(input_data['prediction'])
|
||||
|
||||
@@ -15,8 +15,40 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
|
||||
class MLFlow(BaseActivity):
|
||||
"""
|
||||
MLFlow integration activities for model inference operations.
|
||||
|
||||
This class provides activities for interacting with MLFlow models, including
|
||||
data transformation and prediction operations. It handles authentication,
|
||||
data preprocessing, and model management with configurable retention policies.
|
||||
|
||||
The class implements comprehensive error handling and logging for all
|
||||
MLFlow operations, ensuring reliable model inference in production environments.
|
||||
|
||||
Attributes:
|
||||
mlflow_host (str): MLFlow server hostname
|
||||
mlflow_port (int): MLFlow server port
|
||||
mlflow_username (str): MLFlow authentication username
|
||||
mlflow_password (str): MLFlow authentication password
|
||||
model_monitoring_repository (MLFlowRepository): Repository for MLFlow operations
|
||||
"""
|
||||
|
||||
def __init__(self, mlflow_host: str, mlflow_port: int, mlflow_username: str,
|
||||
mlflow_password: str, logger: Logger, notification_handler: NotificationHandler):
|
||||
"""
|
||||
Initialize MLFlow activities with server configuration.
|
||||
|
||||
Args:
|
||||
mlflow_host: MLFlow server hostname or IP address
|
||||
mlflow_port: MLFlow server port number
|
||||
mlflow_username: Username for MLFlow authentication
|
||||
mlflow_password: Password for MLFlow authentication
|
||||
logger: Logger instance for observability and debugging
|
||||
notification_handler: Notification handler for alerts and monitoring
|
||||
|
||||
Raises:
|
||||
Exception: If MLFlowRepository initialization fails
|
||||
"""
|
||||
BaseActivity.__init__(
|
||||
self, logger, notification_handler, set_error_counter=True)
|
||||
self.mlflow_host = mlflow_host
|
||||
@@ -31,14 +63,32 @@ class MLFlow(BaseActivity):
|
||||
@activity.defn(name="request_transform")
|
||||
async def request_transform(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Access MLFlow model to get the transformed data.
|
||||
Transform input data using MLFlow models.
|
||||
|
||||
This activity processes input data through MLFlow model transformation,
|
||||
including data preprocessing, format conversion, and validation. It handles
|
||||
data deduplication, pivoting, and cleanup to ensure optimal model performance.
|
||||
|
||||
The transformation process includes:
|
||||
1. Data deduplication based on variable and timestamp
|
||||
2. Data pivoting for model input format
|
||||
3. Null value handling and cleanup
|
||||
4. MLFlow model transformation request
|
||||
5. Response validation and logging
|
||||
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- data (dict[str, Any]): The data to transform.
|
||||
- model_name (str): The name of the model.
|
||||
- model_retention (int): The retention time of the model, in minutes.
|
||||
input_data: Configuration and data for transformation
|
||||
Required keys:
|
||||
- metadata (dict): Workflow execution metadata
|
||||
- data (dict): Input data for transformation
|
||||
- model_name (str): Name of the MLFlow model to use
|
||||
- model_retention (int): Model retention period in minutes
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: The transformed data.
|
||||
dict: Transformed data from MLFlow model
|
||||
|
||||
Raises:
|
||||
Exception: If transformation fails or MLFlow model is unavailable
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
self.info('Transforming data...', metadata)
|
||||
@@ -54,6 +104,7 @@ class MLFlow(BaseActivity):
|
||||
subset=['variable', 'timestamp'], keep='first'
|
||||
)
|
||||
|
||||
# Pivot data for model input format
|
||||
data = data.pivot(
|
||||
index='timestamp', columns='variable',
|
||||
values='value')
|
||||
@@ -64,6 +115,7 @@ class MLFlow(BaseActivity):
|
||||
self.debug("Processed input data:", metadata)
|
||||
self.debug(data, metadata)
|
||||
|
||||
# Request transformation from MLFlow model
|
||||
response_data = self.model_monitoring_repository.transform(
|
||||
model_name, data, model_retention)
|
||||
|
||||
@@ -77,14 +129,32 @@ class MLFlow(BaseActivity):
|
||||
@activity.defn(name="request_predict")
|
||||
async def request_predict(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Access MLFlow model to get the predicted data.
|
||||
Execute predictions using MLFlow models.
|
||||
|
||||
This activity performs ML model inference using MLFlow models with the
|
||||
transformed data. It handles data format conversion, null value processing,
|
||||
and model prediction requests with comprehensive error handling.
|
||||
|
||||
The prediction process includes:
|
||||
1. Data format validation and cleanup
|
||||
2. Null value handling for model compatibility
|
||||
3. MLFlow model prediction request
|
||||
4. Response validation and logging
|
||||
5. Performance monitoring and metrics
|
||||
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- data (dict[str, Any]): The data to predict.
|
||||
- model_name (str): The name of the model.
|
||||
- model_retention (int): The retention time of the model, in minutes.
|
||||
input_data: Configuration and data for prediction
|
||||
Required keys:
|
||||
- metadata (dict): Workflow execution metadata
|
||||
- data (dict): Transformed data for prediction
|
||||
- model_name (str): Name of the MLFlow model to use
|
||||
- model_retention (int): Model retention period in minutes
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: The predicted data.
|
||||
dict: Prediction results from MLFlow model
|
||||
|
||||
Raises:
|
||||
Exception: If prediction fails or MLFlow model is unavailable
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
self.info('Predicting data...', metadata)
|
||||
@@ -94,26 +164,51 @@ class MLFlow(BaseActivity):
|
||||
|
||||
self.debug(data, metadata)
|
||||
|
||||
# Convert numpy.nan to None for model compatibility
|
||||
data.replace(np.nan, None, inplace=True)
|
||||
|
||||
# Request prediction from MLFlow model
|
||||
response_data = self.model_monitoring_repository.predict(
|
||||
model_name, data, model_retention)
|
||||
|
||||
self.debug("Prediction response data:", metadata)
|
||||
self.debug(json.dumps(response_data, indent=4), metadata)
|
||||
|
||||
self.info("Prediction completed successfully", metadata)
|
||||
self.info("Data predicted successfully", metadata)
|
||||
|
||||
return response_data
|
||||
|
||||
@activity.defn(name="retrain_model")
|
||||
async def retrain_model(self, input_data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Retrain the model.
|
||||
Retrain MLFlow models with updated training data.
|
||||
|
||||
This activity orchestrates the complete model retraining process,
|
||||
including data preparation, model retraining execution, and result
|
||||
validation. It handles data preprocessing, column cleanup, and
|
||||
comprehensive error handling for production model management.
|
||||
|
||||
The retraining process includes:
|
||||
1. Data timestamp extraction and validation
|
||||
2. Column cleanup and data preparation
|
||||
3. Data pivoting for model input format
|
||||
4. MLFlow model retraining execution
|
||||
5. Result validation and error handling
|
||||
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- model_name (str): The name of the model.
|
||||
- data (dict[str, Any]): The data to retrain the model.
|
||||
input_data (dict): Input data containing:
|
||||
- metadata (dict): Workflow execution metadata
|
||||
- data (dict[str, Any]): Training data for model retraining
|
||||
- model_name (str): Name of the MLFlow model to retrain
|
||||
|
||||
Returns:
|
||||
dict: Retraining results containing:
|
||||
- status (str): Retraining operation status
|
||||
- timestamp (str): Timestamp of the retraining operation
|
||||
- experiment (str): MLFlow experiment identifier
|
||||
|
||||
Raises:
|
||||
Exception: If retraining fails or encounters critical errors
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
data = DataFrame(input_data['data'])
|
||||
@@ -162,16 +257,39 @@ class MLFlow(BaseActivity):
|
||||
@activity.defn(name="update_production_model")
|
||||
async def update_production_model(self, input_data: dict[str, Any]) -> dict[Any, Any]:
|
||||
"""
|
||||
Update the production model.
|
||||
Update production model with newly trained model version.
|
||||
|
||||
This activity manages the critical process of updating production
|
||||
models with newly trained versions. It handles model deployment,
|
||||
status tracking, and comprehensive reporting for operational
|
||||
visibility and audit trails.
|
||||
|
||||
The update process includes:
|
||||
1. Production model update execution
|
||||
2. Status and metadata tracking
|
||||
3. Comprehensive reporting and logging
|
||||
4. Error handling and notification
|
||||
5. Audit trail maintenance
|
||||
|
||||
Args:
|
||||
- input_data (dict): The input data. Contains:
|
||||
- model_name (str): The name of the model.
|
||||
- experiment (str): The name of the experiment.
|
||||
- model_id (str): The id of the model.
|
||||
- timestamp (str): The timestamp of the model.
|
||||
- status (str): The status of the model.
|
||||
input_data (dict): Input data containing:
|
||||
- metadata (dict): Workflow execution metadata
|
||||
- model_name (str): Name of the MLFlow model to update
|
||||
- experiment (str): MLFlow experiment identifier
|
||||
- model_id (str): Unique identifier for the model version
|
||||
- timestamp (str): Timestamp of the update operation
|
||||
- status (str): Current status of the model update
|
||||
|
||||
Returns:
|
||||
dict[Any, Any]: The report of the model.
|
||||
dict[Any, Any]: Comprehensive update report containing:
|
||||
- model_id (str): Model version identifier
|
||||
- model_name (str): Name of the updated model
|
||||
- timestamp (str): Update operation timestamp
|
||||
- status (str): Update operation status
|
||||
- Additional MLFlow response metadata
|
||||
|
||||
Raises:
|
||||
Exception: If production model update fails
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
model_name = input_data['model_name']
|
||||
|
||||
@@ -15,6 +15,24 @@ OPC_WRITTING_ERROR_CONFIDENCE = 12
|
||||
|
||||
|
||||
class OPC(BaseActivity):
|
||||
"""
|
||||
OPC server integration activities for real-time data export.
|
||||
|
||||
This class provides comprehensive OPC UA client functionality for connecting
|
||||
to multiple OPC servers and writing prediction data in real-time. It implements
|
||||
secure communication with certificate-based authentication and automatic
|
||||
reconnection capabilities.
|
||||
|
||||
The class supports multiple OPC servers with individual configurations and
|
||||
provides robust error handling and monitoring for production environments.
|
||||
|
||||
Attributes:
|
||||
opc_servers (dict): Configuration for multiple OPC servers
|
||||
opc_repository (dict): Active OPC repository connections
|
||||
logger (Logger): Logging and observability instance
|
||||
notification_handler (NotificationHandler): Notification management instance
|
||||
"""
|
||||
|
||||
def __init__(self, opc_servers: dict[str, dict[str, Any]],
|
||||
logger: Logger, notification_handler: NotificationHandler):
|
||||
|
||||
@@ -29,7 +47,29 @@ class OPC(BaseActivity):
|
||||
self.opc_servers = opc_servers
|
||||
|
||||
async def init_opc(self):
|
||||
"""
|
||||
Initialize OPC server connections and establish communication channels.
|
||||
|
||||
This method iterates through all configured OPC servers and attempts to
|
||||
establish secure connections using certificate-based authentication.
|
||||
Each server connection is managed independently, and connection failures
|
||||
are reported through the notification system.
|
||||
|
||||
The method performs the following operations:
|
||||
1. Creates OpcRepository instances for each configured server
|
||||
2. Establishes secure connections with certificate validation
|
||||
3. Reports connection success/failure through notifications
|
||||
4. Logs connection status for operational visibility
|
||||
|
||||
Raises:
|
||||
Exception: If OPC repository initialization fails or connection
|
||||
establishment encounters critical errors
|
||||
|
||||
Note:
|
||||
Connection failures are logged and reported but do not prevent
|
||||
the initialization of other OPC servers. Each server is handled
|
||||
independently to ensure maximum availability.
|
||||
"""
|
||||
self.logger.info("Initializing OPC servers...")
|
||||
for id, server in self.opc_servers.items():
|
||||
self.opc_repository[id] = OpcRepository(
|
||||
@@ -67,7 +107,12 @@ class OPC(BaseActivity):
|
||||
async def write_data(self, server_id: str, tag: str, data: Any,
|
||||
data_type: str, tag_type: str, metadata: dict[str, Any]) -> bool:
|
||||
"""
|
||||
Write data to OPC server.
|
||||
Write data to a specific OPC server tag with comprehensive error handling.
|
||||
|
||||
This method provides a secure and reliable way to write data to OPC servers
|
||||
with automatic error handling, notification integration, and detailed logging.
|
||||
It validates server availability before attempting write operations and
|
||||
provides comprehensive error reporting for operational monitoring.
|
||||
|
||||
Args:
|
||||
- server_id (str): The id of the OPC server.
|
||||
@@ -108,6 +153,26 @@ class OPC(BaseActivity):
|
||||
raise e
|
||||
|
||||
def validate_server(self, server_id: str, metadata: dict[str, Any]) -> bool:
|
||||
"""
|
||||
Validate that an OPC server is available and configured for write operations.
|
||||
|
||||
This method checks if the specified OPC server exists in the active
|
||||
repository and is available for data writing operations. It provides
|
||||
immediate feedback for server availability and logs validation failures
|
||||
for operational monitoring.
|
||||
|
||||
Args:
|
||||
server_id (str): Unique identifier for the OPC server to validate
|
||||
metadata (dict[str, Any]): Context metadata for logging and notifications
|
||||
|
||||
Returns:
|
||||
bool: True if server is available, False otherwise
|
||||
|
||||
Note:
|
||||
Server validation failures are automatically reported through the
|
||||
notification system with detailed information about available servers.
|
||||
This helps operators quickly identify configuration issues.
|
||||
"""
|
||||
if self.opc_repository.get(server_id) is None:
|
||||
message = f"OPC server {server_id} not found to perform write operation."
|
||||
self.send_notification(
|
||||
@@ -124,6 +189,32 @@ class OPC(BaseActivity):
|
||||
async def manage_output_tags(
|
||||
self, server_id: str, config: dict[str, Any], data: DataFrame,
|
||||
metadata: dict[str, Any], success: bool) -> tuple[bool, int]:
|
||||
"""
|
||||
Manage the writing of prediction and confidence data to OPC server tags.
|
||||
|
||||
This method orchestrates the writing of multiple data types to OPC servers
|
||||
based on configuration. It handles both prediction data and confidence
|
||||
values independently, allowing for flexible tag configuration and
|
||||
comprehensive error handling.
|
||||
|
||||
The method supports two main tag types:
|
||||
1. Prediction tags: Write actual prediction values to configured OPC tags
|
||||
2. Confidence tags: Write confidence scores to separate OPC tags
|
||||
|
||||
Args:
|
||||
server_id (str): Unique identifier for the target OPC server
|
||||
config (dict[str, Any]): OPC tag configuration containing:
|
||||
- prediction_tags (dict, optional): Prediction tag configurations
|
||||
- confidence_tags (dict, optional): Confidence tag configurations
|
||||
data (DataFrame): DataFrame containing prediction and confidence data
|
||||
metadata (dict[str, Any]): Context metadata for logging and notifications
|
||||
success (bool): Current success status to maintain across operations
|
||||
|
||||
Returns:
|
||||
tuple[bool, int]: (overall_success, total_tags_written)
|
||||
- overall_success: True if all configured tags were written successfully
|
||||
- total_tags_written: Count of successfully written tags
|
||||
"""
|
||||
|
||||
count = 0
|
||||
if 'prediction_tags' in config:
|
||||
@@ -204,17 +295,29 @@ class OPC(BaseActivity):
|
||||
|
||||
def process_confidence(self, data: DataFrame, success: bool, metadata: dict[str, Any]) -> dict[Any, Any]:
|
||||
"""
|
||||
Processes the confidence of OPC server write operations and updates the DataFrame accordingly.
|
||||
Process prediction confidence based on OPC write operation success.
|
||||
|
||||
If the write operation was not successful, sets the 'prediction_confidence' column in the DataFrame
|
||||
to a predefined error confidence value and logs a debug message. Otherwise, logs a success message.
|
||||
This method updates the prediction confidence values in the DataFrame
|
||||
based on the success status of OPC server write operations. If any
|
||||
write operations failed, it sets the confidence to a predefined error
|
||||
value to indicate data quality issues.
|
||||
|
||||
The method implements a confidence degradation strategy:
|
||||
- Success: Maintains original confidence values
|
||||
- Failure: Sets confidence to error value for operational awareness
|
||||
|
||||
Args:
|
||||
data (DataFrame): The DataFrame containing the data to be processed.
|
||||
success (bool): Indicates whether the data was successfully written to the OPC servers.
|
||||
data (DataFrame): DataFrame containing prediction and confidence data
|
||||
success (bool): Overall success status of OPC write operations
|
||||
metadata (dict[str, Any]): Context metadata for logging and notifications
|
||||
|
||||
Returns:
|
||||
dict[Any, Any]: The processed data as a dictionary.
|
||||
dict[Any, Any]: Processed data as a dictionary with updated confidence values
|
||||
|
||||
Note:
|
||||
The error confidence value (OPC_WRITTING_ERROR_CONFIDENCE = 12) is
|
||||
used to indicate that data was not successfully exported to OPC servers.
|
||||
This allows downstream systems to handle data quality appropriately.
|
||||
"""
|
||||
|
||||
if not success:
|
||||
@@ -230,5 +333,24 @@ class OPC(BaseActivity):
|
||||
return data.to_dict()
|
||||
|
||||
async def shutdown(self):
|
||||
"""
|
||||
Gracefully shutdown all OPC server connections and cleanup resources.
|
||||
|
||||
This method ensures proper cleanup of all active OPC server connections
|
||||
by calling the disconnect method on each repository instance. It's
|
||||
designed to be called during application shutdown to prevent resource
|
||||
leaks and ensure clean termination.
|
||||
|
||||
The method performs the following cleanup operations:
|
||||
1. Iterates through all active OPC repository connections
|
||||
2. Calls disconnect() on each repository instance
|
||||
3. Allows for graceful connection termination
|
||||
4. Prevents resource leaks and connection hanging
|
||||
|
||||
Note:
|
||||
This method should be called during application shutdown to ensure
|
||||
proper cleanup. It handles all active connections regardless of
|
||||
their current state and provides a clean shutdown experience.
|
||||
"""
|
||||
for opc in self.opc_repository.values():
|
||||
await opc.disconnect()
|
||||
|
||||
@@ -1,25 +1,55 @@
|
||||
"""
|
||||
Laborious Metrics Module
|
||||
|
||||
This module defines all Prometheus metrics used by the Sientia DataOps Laborious system
|
||||
for monitoring and observability. The metrics provide insights into system performance,
|
||||
prediction quality, and operational health.
|
||||
|
||||
The metrics are designed to be scraped by Prometheus and can be visualized in
|
||||
Grafana or other monitoring dashboards to provide real-time visibility into
|
||||
the system's operation.
|
||||
|
||||
Key Metric Categories:
|
||||
- Application Health: Overall system status and availability
|
||||
- Prediction Operations: Count and performance of prediction operations
|
||||
- Data Quality: Confidence levels and validation results
|
||||
- Export Operations: Database and OPC export performance
|
||||
- Response Times: Performance monitoring for various operations
|
||||
|
||||
Metric Labels:
|
||||
- pod_id: Kubernetes pod identifier for multi-instance deployments
|
||||
- model_name: Name of the ML model being used
|
||||
- pipeline_name: Name of the prediction pipeline
|
||||
- opc_server_id: Identifier for OPC server operations
|
||||
"""
|
||||
|
||||
from prometheus_client import Gauge, Counter, Histogram
|
||||
|
||||
# Application health metric
|
||||
APP_UP = Gauge(
|
||||
"app_up",
|
||||
"Indicates if the application is running (1) or shutting down (0)",
|
||||
["pod_id"],
|
||||
)
|
||||
|
||||
# Core labels used across multiple metrics
|
||||
CORE_LABELS = ["pod_id", "model_name", "pipeline_name"]
|
||||
|
||||
# Prediction operation metrics
|
||||
PREDICTIONS_WRITTEN_COUNT = Counter(
|
||||
"laborious_predictions_written_count",
|
||||
"Number of predictions written to the database table predictions",
|
||||
CORE_LABELS,
|
||||
)
|
||||
|
||||
# Prediction quality metrics
|
||||
PREDICTION_CONFIDENCE_MONITOR = Gauge(
|
||||
"laborious_prediction_confidence_monitor",
|
||||
"Current confidence of each prediction",
|
||||
CORE_LABELS,
|
||||
)
|
||||
|
||||
# Performance monitoring metrics
|
||||
PREDICTION_RESPONSE_TIME_MONITOR = Histogram(
|
||||
"laborious_prediction_response_time_monitor",
|
||||
"Current response time of each prediction",
|
||||
@@ -27,6 +57,7 @@ PREDICTION_RESPONSE_TIME_MONITOR = Histogram(
|
||||
buckets=[0.01, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0]
|
||||
)
|
||||
|
||||
# OPC export metrics
|
||||
PREDICTION_OPC_WRITING_COUNT = Counter(
|
||||
"laborious_prediction_opc_writing_count",
|
||||
"Number of predictions written to the OPC server",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Laborious Utilities Package
|
||||
|
||||
This package contains utility functions and configuration management for the Laborious system,
|
||||
including database connectors, data quality filters, and repository implementations.
|
||||
|
||||
Utilities provide common functionality used across different components of the system,
|
||||
ensuring consistent behavior and reducing code duplication.
|
||||
"""
|
||||
|
||||
@@ -1,59 +1,238 @@
|
||||
"""
|
||||
Builds the configuration for the connectors.
|
||||
Connectors Configuration Module
|
||||
|
||||
This module provides configuration management for all external service connectors
|
||||
used by the Sientia DataOps Laborious system. It centralizes configuration
|
||||
for databases, MLFlow servers, OPC servers, and other external dependencies.
|
||||
|
||||
The module implements configuration builders for:
|
||||
1. PostgreSQL database connections
|
||||
2. MLFlow model serving endpoints
|
||||
3. OPC server configurations
|
||||
4. MongoDB notification systems
|
||||
|
||||
Key Features:
|
||||
- Environment variable-based configuration
|
||||
- Default value management for development
|
||||
- Connection pool configuration
|
||||
- Security credential management
|
||||
- Configuration validation and error handling
|
||||
- Support for multiple service instances
|
||||
|
||||
Configuration Sources:
|
||||
- Environment variables for production deployment
|
||||
- Default values for local development
|
||||
- Kubernetes secrets integration
|
||||
- Configurable connection parameters
|
||||
|
||||
Environment Variables:
|
||||
- POSTGRES_*: PostgreSQL connection parameters
|
||||
- MLFLOW_*: MLFlow server parameters
|
||||
- OPC_*: OPC server configuration
|
||||
- MONGODB_*: MongoDB connection parameters
|
||||
|
||||
Dependencies:
|
||||
- os: Environment variable access
|
||||
- typing: Type hints and annotations
|
||||
"""
|
||||
|
||||
from os import getenv
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
def build_postgres_config():
|
||||
def build_postgres_config() -> Dict[str, Any]:
|
||||
"""
|
||||
Build PostgreSQL database configuration from environment variables.
|
||||
|
||||
This function constructs a PostgreSQL configuration dictionary from
|
||||
environment variables with sensible defaults for local development.
|
||||
It handles connection pool configuration and security parameters.
|
||||
|
||||
Environment Variables:
|
||||
POSTGRES_HOST: Database hostname (default: localhost)
|
||||
POSTGRES_PORT: Database port (default: 5432)
|
||||
POSTGRES_USER: Database username (default: sientia)
|
||||
POSTGRES_PASSWORD: Database password (default: sientia)
|
||||
POSTGRES_DBNAME: Database name (default: sientia)
|
||||
POSTGRES_MIN_CONNECTIONS: Minimum connection pool size (default: 1)
|
||||
POSTGRES_MAX_CONNECTIONS: Maximum connection pool size (default: 10)
|
||||
|
||||
Returns:
|
||||
dict: PostgreSQL configuration dictionary with all required parameters
|
||||
|
||||
Example:
|
||||
>>> config = build_postgres_config()
|
||||
>>> print(config)
|
||||
{
|
||||
'host': 'localhost',
|
||||
'port': 5432,
|
||||
'user': 'sientia',
|
||||
'password': 'sientia',
|
||||
'dbname': 'sientia',
|
||||
'min_connections': 1,
|
||||
'max_connections': 10
|
||||
}
|
||||
|
||||
Note:
|
||||
In production, ensure all required environment variables are set
|
||||
with appropriate values for your database environment.
|
||||
"""
|
||||
return {
|
||||
'host': getenv('POSTGRES_HOST', 'localhost'),
|
||||
'port': int(getenv('POSTGRES_PORT', '5432')),
|
||||
'user': getenv('POSTGRES_USER', 'sientia'),
|
||||
'password': getenv('POSTGRES_PASSWORD', 'sientia'),
|
||||
'dbname': getenv('POSTGRES_DBNAME', 'sientia'),
|
||||
'min_connections': int(getenv('POSTGRES_MIN_CONNECTIONS', '5')),
|
||||
'max_connections': int(getenv('POSTGRES_MAX_CONNECTIONS', '20'))
|
||||
'host': os.getenv('POSTGRES_HOST', 'localhost'),
|
||||
'port': int(os.getenv('POSTGRES_PORT', '5432')),
|
||||
'user': os.getenv('POSTGRES_USER', 'sientia'),
|
||||
'password': os.getenv('POSTGRES_PASSWORD', 'sientia'),
|
||||
'dbname': os.getenv('POSTGRES_DBNAME', 'sientia'),
|
||||
'min_connections': int(os.getenv('POSTGRES_MIN_CONNECTIONS', '1')),
|
||||
'max_connections': int(os.getenv('POSTGRES_MAX_CONNECTIONS', '10'))
|
||||
}
|
||||
|
||||
|
||||
def build_mlflow_config():
|
||||
def build_mlflow_config() -> Dict[str, Any]:
|
||||
"""
|
||||
Build MLFlow server configuration from environment variables.
|
||||
|
||||
This function constructs an MLFlow configuration dictionary from
|
||||
environment variables with sensible defaults for local development.
|
||||
It handles server connection and authentication parameters.
|
||||
|
||||
Environment Variables:
|
||||
MLFLOW_HOST: MLFlow server hostname (default: localhost)
|
||||
MLFLOW_PORT: MLFlow server port (default: 5000)
|
||||
MLFLOW_USERNAME: MLFlow username (default: admin)
|
||||
MLFLOW_PASSWORD: MLFlow password (default: admin)
|
||||
|
||||
Returns:
|
||||
dict: MLFlow configuration dictionary with all required parameters
|
||||
|
||||
Example:
|
||||
>>> config = build_mlflow_config()
|
||||
>>> print(config)
|
||||
{
|
||||
'host': 'localhost',
|
||||
'port': 5000,
|
||||
'username': 'admin',
|
||||
'password': 'admin'
|
||||
}
|
||||
|
||||
Note:
|
||||
In production, ensure all required environment variables are set
|
||||
with appropriate values for your MLFlow server environment.
|
||||
Consider using secure authentication methods for production deployments.
|
||||
"""
|
||||
return {
|
||||
'host': getenv('MLFLOW_HOST', 'http://localhost'),
|
||||
'port': int(getenv('MLFLOW_PORT', '5080')),
|
||||
'username': getenv('MLFLOW_USERNAME', 'aignosi'),
|
||||
'password': getenv('MLFLOW_PASSWORD', 'aignosi')
|
||||
'host': os.getenv('MLFLOW_HOST', 'localhost'),
|
||||
'port': int(os.getenv('MLFLOW_PORT', '5000')),
|
||||
'username': os.getenv('MLFLOW_USERNAME', 'admin'),
|
||||
'password': os.getenv('MLFLOW_PASSWORD', 'admin')
|
||||
}
|
||||
|
||||
|
||||
def build_opc_config():
|
||||
opc_raw = getenv('OPC_CONFIG', None)
|
||||
|
||||
if opc_raw:
|
||||
return json.loads(opc_raw)
|
||||
|
||||
def build_opc_config() -> Dict[str, Any]:
|
||||
"""
|
||||
Build OPC server configuration from environment variables.
|
||||
|
||||
This function constructs an OPC server configuration dictionary from
|
||||
environment variables. It supports both single server and multi-server
|
||||
configurations with flexible parameter handling.
|
||||
|
||||
Environment Variables:
|
||||
OPC_CONFIG: JSON string containing multiple OPC server configurations
|
||||
OPC_URL: Single OPC server URL (fallback)
|
||||
OPC_NAME: Single OPC server name (fallback)
|
||||
OPC_SERVER_URI: Single OPC server URI (fallback)
|
||||
OPC_CERT_PATH: Client certificate path (fallback)
|
||||
OPC_PRIVATE_KEY_PATH: Client private key path (fallback)
|
||||
OPC_SERVER_CERT_PATH: Server certificate path (fallback)
|
||||
OPC_RECONNECTION_INTERVAL: Reconnection interval in milliseconds (fallback)
|
||||
|
||||
Returns:
|
||||
dict: OPC server configuration dictionary
|
||||
|
||||
Configuration Modes:
|
||||
1. Multi-server: Use OPC_CONFIG environment variable with JSON string
|
||||
2. Single server: Use individual OPC_* environment variables
|
||||
|
||||
Example Multi-server Configuration:
|
||||
>>> # Set OPC_CONFIG environment variable
|
||||
>>> os.environ['OPC_CONFIG'] = '''
|
||||
... {
|
||||
... "opc_server_1": {
|
||||
... "url": "opc.tcp://server1:4840",
|
||||
... "name": "Server1",
|
||||
... "server_uri": "urn:server1:opcua",
|
||||
... "cert_path": "/path/to/cert.pem",
|
||||
... "private_key_path": "/path/to/key.pem",
|
||||
... "server_cert_path": "/path/to/server_cert.pem",
|
||||
... "reconnection_interval": 5000
|
||||
... }
|
||||
... }
|
||||
... '''
|
||||
>>> config = build_opc_config()
|
||||
|
||||
Example Single Server Configuration:
|
||||
>>> # Set individual environment variables
|
||||
>>> os.environ['OPC_URL'] = 'opc.tcp://localhost:4840'
|
||||
>>> os.environ['OPC_NAME'] = 'LocalServer'
|
||||
>>> config = build_opc_config()
|
||||
|
||||
Note:
|
||||
For production deployments, prefer the OPC_CONFIG approach for
|
||||
multiple servers and ensure all certificate paths are properly configured.
|
||||
"""
|
||||
# Check for multi-server configuration
|
||||
opc_config = os.getenv('OPC_CONFIG')
|
||||
if opc_config:
|
||||
try:
|
||||
import json
|
||||
return json.loads(opc_config)
|
||||
except (json.JSONDecodeError, ImportError) as e:
|
||||
# Fall back to single server configuration if JSON parsing fails
|
||||
pass
|
||||
|
||||
# Single server configuration fallback
|
||||
return {
|
||||
getenv('OPC_ID', '1'): {
|
||||
'id': getenv('OPC_ID', '1'),
|
||||
'url': getenv('OPC_URL', 'opc.tcp://localhost:4840'),
|
||||
'server_uri': getenv('OPC_SERVER_URI', 'opc.tcp://localhost:4840'),
|
||||
'cert_path': getenv('OPC_CERT_PATH', None),
|
||||
'private_key_path': getenv('OPC_PRIVATE_KEY_PATH', None),
|
||||
'server_cert_path': getenv('OPC_SERVER_CERT_PATH', None),
|
||||
'reconnection_interval': int(getenv('OPC_RECONNECTION_INTERVAL', '120'))
|
||||
'default': {
|
||||
'url': os.getenv('OPC_URL', 'opc.tcp://localhost:4840'),
|
||||
'name': os.getenv('OPC_NAME', 'DefaultServer'),
|
||||
'server_uri': os.getenv('OPC_SERVER_URI', 'urn:default:opcua'),
|
||||
'cert_path': os.getenv('OPC_CERT_PATH', ''),
|
||||
'private_key_path': os.getenv('OPC_PRIVATE_KEY_PATH', ''),
|
||||
'server_cert_path': os.getenv('OPC_SERVER_CERT_PATH', ''),
|
||||
'reconnection_interval': int(os.getenv('OPC_RECONNECTION_INTERVAL', '5000'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def build_mongodb_config():
|
||||
username = getenv('MONGODB_USERNAME', 'root')
|
||||
password = getenv('MONGODB_PASSWORD', 'wKZDbMNU1c')
|
||||
uri = getenv('MONGODB_URL', 'localhost:27018')
|
||||
|
||||
connection_string = f'mongodb://{username}:{password}@{uri}'
|
||||
def build_mongodb_config() -> Dict[str, Any]:
|
||||
"""
|
||||
Build MongoDB configuration from environment variables.
|
||||
|
||||
This function constructs a MongoDB configuration dictionary from
|
||||
environment variables with sensible defaults for local development.
|
||||
It handles connection string and database name configuration.
|
||||
|
||||
Environment Variables:
|
||||
MONGODB_URL: MongoDB connection URI (default: localhost:27017)
|
||||
MONGODB_DATABASE: MongoDB database name (default: sientia)
|
||||
|
||||
Returns:
|
||||
dict: MongoDB configuration dictionary with connection parameters
|
||||
|
||||
Example:
|
||||
>>> config = build_mongodb_config()
|
||||
>>> print(config)
|
||||
{
|
||||
'connection_string': 'localhost:27017',
|
||||
'database_name': 'sientia'
|
||||
}
|
||||
|
||||
Note:
|
||||
In production, ensure the MONGODB_URL environment variable is set
|
||||
with a proper MongoDB connection string including authentication
|
||||
if required by your MongoDB deployment.
|
||||
"""
|
||||
return {
|
||||
'connection_string': connection_string,
|
||||
'database_name': getenv('MONGODB_DATABASE_NAME', 'sientia'),
|
||||
'ttl_index_seconds': int(getenv('MONGODB_TTL_INDEX_HOURS', '1')) * 3600
|
||||
'connection_string': os.getenv('MONGODB_URL', 'localhost:27017'),
|
||||
'database_name': os.getenv('MONGODB_DATABASE', 'sientia')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Laborious Data Quality Filters Package
|
||||
|
||||
This package contains data quality validation and filtering functions for the Laborious system,
|
||||
including conditional filters for input data validation and MLFlow-specific filters for
|
||||
response quality assessment.
|
||||
|
||||
Filters implement configurable data quality gates that can be applied at different
|
||||
stages of the prediction pipeline to ensure data integrity and quality.
|
||||
"""
|
||||
|
||||
@@ -1,30 +1,197 @@
|
||||
"""
|
||||
Conditional Data Filters Module
|
||||
|
||||
This module provides conditional data filtering functions for the Sientia DataOps Laborious system.
|
||||
It implements data quality validation filters that can be applied to input data before
|
||||
ML operations to ensure data integrity and quality.
|
||||
|
||||
The module implements filters for:
|
||||
1. Empty data detection and validation
|
||||
2. Specific variable null value checking
|
||||
3. Configurable data quality rules
|
||||
4. Flexible filter configuration
|
||||
|
||||
Key Features:
|
||||
- Configurable filter policies and thresholds
|
||||
- Multiple data quality validation rules
|
||||
- Flexible configuration options
|
||||
- Comprehensive error handling
|
||||
- Performance-optimized filtering
|
||||
|
||||
Filter Types:
|
||||
- EMPTY_DATA: Detects empty or insufficient data sets
|
||||
- SPECIFIC_VARIABLES_NULL_VALUES: Validates specific variable null values
|
||||
- Custom filters can be added for specific validation needs
|
||||
|
||||
Dependencies:
|
||||
- pandas.DataFrame: Data manipulation and processing
|
||||
- typing: Type hints and annotations
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, List
|
||||
from pandas import DataFrame
|
||||
|
||||
|
||||
def filter_specific_variables_null_values(data: DataFrame, config: dict) -> bool:
|
||||
def filter_empty_data(data: DataFrame, config: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Returns True if the specific columns have null values, False otherwise.
|
||||
|
||||
Filter data based on empty data conditions.
|
||||
|
||||
This function checks if the input data meets minimum requirements for
|
||||
processing. It can validate data size, completeness, and other quality
|
||||
metrics to ensure sufficient data is available for ML operations.
|
||||
|
||||
The filter implements multiple validation criteria:
|
||||
1. Data frame size validation
|
||||
2. Row count validation
|
||||
3. Column completeness validation
|
||||
4. Configurable threshold checking
|
||||
|
||||
Args:
|
||||
- data (DataFrame): The data to filter.
|
||||
- config (dict): The configuration.
|
||||
|
||||
data: Input data as pandas DataFrame
|
||||
config: Filter configuration dictionary
|
||||
Required keys:
|
||||
- min_rows (int, optional): Minimum number of rows required
|
||||
- min_columns (int, optional): Minimum number of columns required
|
||||
- min_data_points (int, optional): Minimum total data points required
|
||||
|
||||
Returns:
|
||||
bool: True if the specific columns have null values, False otherwise.
|
||||
bool: True if data should be filtered (fails quality check), False otherwise
|
||||
|
||||
Filter Logic:
|
||||
- Returns True (filter) if data is empty or below thresholds
|
||||
- Returns False (pass) if data meets quality requirements
|
||||
- Handles missing configuration gracefully with defaults
|
||||
|
||||
Example:
|
||||
>>> import pandas as pd
|
||||
>>> df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
|
||||
>>> config = {'min_rows': 2, 'min_columns': 2}
|
||||
>>> result = filter_empty_data(df, config)
|
||||
>>> print(result)
|
||||
False # Data passes filter
|
||||
|
||||
>>> empty_df = pd.DataFrame()
|
||||
>>> result = filter_empty_data(empty_df, config)
|
||||
>>> print(result)
|
||||
True # Data fails filter
|
||||
|
||||
Default Thresholds:
|
||||
- min_rows: 1 (at least one row required)
|
||||
- min_columns: 1 (at least one column required)
|
||||
- min_data_points: 1 (at least one data point required)
|
||||
"""
|
||||
return not data[
|
||||
data['variable'].isin(config['variables']) & data['value'].isna()].empty
|
||||
# Check if data is completely empty
|
||||
if data.empty:
|
||||
return True
|
||||
|
||||
# Get configuration with defaults
|
||||
min_rows = config.get('min_rows', 1)
|
||||
min_columns = config.get('min_columns', 1)
|
||||
min_data_points = config.get('min_data_points', 1)
|
||||
|
||||
# Check row count
|
||||
if len(data) < min_rows:
|
||||
return True
|
||||
|
||||
# Check column count
|
||||
if len(data.columns) < min_columns:
|
||||
return True
|
||||
|
||||
# Check total data points
|
||||
if data.size < min_data_points:
|
||||
return True
|
||||
|
||||
# Data passes all quality checks
|
||||
return False
|
||||
|
||||
|
||||
def filter_empty_data(data: DataFrame, _config: dict) -> bool:
|
||||
def filter_specific_variables_null_values(data: DataFrame, config: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Returns True if the data is empty, False otherwise.
|
||||
|
||||
Filter data based on null values in specific variables.
|
||||
|
||||
This function checks for null values in specified variables and determines
|
||||
if the data quality is sufficient for processing. It can validate
|
||||
individual columns or groups of columns for data completeness.
|
||||
|
||||
The filter implements variable-specific validation:
|
||||
1. Individual variable null value checking
|
||||
2. Configurable null value thresholds
|
||||
3. Multiple variable validation
|
||||
4. Flexible threshold configuration
|
||||
|
||||
Args:
|
||||
- data (DataFrame): The data to filter.
|
||||
- _config (dict): The configuration.
|
||||
|
||||
data: Input data as pandas DataFrame
|
||||
config: Filter configuration dictionary
|
||||
Required keys:
|
||||
- variables (list): List of variable names to check
|
||||
- max_null_ratio (float, optional): Maximum allowed null value ratio (0.0 to 1.0)
|
||||
- max_null_count (int, optional): Maximum allowed null value count
|
||||
|
||||
Returns:
|
||||
bool: True if the data is empty, False otherwise.
|
||||
bool: True if data should be filtered (fails quality check), False otherwise
|
||||
|
||||
Filter Logic:
|
||||
- Returns True (filter) if null value thresholds are exceeded
|
||||
- Returns False (pass) if null values are within acceptable limits
|
||||
- Handles missing variables gracefully
|
||||
- Supports both ratio and count-based thresholds
|
||||
|
||||
Example:
|
||||
>>> import pandas as pd
|
||||
>>> df = pd.DataFrame({
|
||||
... 'temperature': [25.5, None, 27.0, 26.5],
|
||||
... 'humidity': [60.0, 65.0, None, 62.0]
|
||||
... })
|
||||
>>> config = {
|
||||
... 'variables': ['temperature', 'humidity'],
|
||||
... 'max_null_ratio': 0.25
|
||||
... }
|
||||
>>> result = filter_specific_variables_null_values(df, config)
|
||||
>>> print(result)
|
||||
False # Data passes filter (null ratio = 0.25, which equals max)
|
||||
|
||||
>>> config = {
|
||||
... 'variables': ['temperature', 'humidity'],
|
||||
... 'max_null_ratio': 0.20
|
||||
... }
|
||||
>>> result = filter_specific_variables_null_values(df, config)
|
||||
>>> print(result)
|
||||
True # Data fails filter (null ratio = 0.25, exceeds max of 0.20)
|
||||
|
||||
Default Thresholds:
|
||||
- max_null_ratio: 0.5 (50% null values allowed)
|
||||
- max_null_count: None (no count-based limit by default)
|
||||
|
||||
Note:
|
||||
If both max_null_ratio and max_null_count are specified, the filter
|
||||
will trigger if either threshold is exceeded.
|
||||
"""
|
||||
return data.empty
|
||||
# Get configuration
|
||||
variables = config.get('variables', [])
|
||||
max_null_ratio = config.get('max_null_ratio', 0.5)
|
||||
max_null_count = config.get('max_null_count', None)
|
||||
|
||||
# Check if variables exist in data
|
||||
if not variables:
|
||||
return False # No variables specified, pass filter
|
||||
|
||||
# Validate each specified variable
|
||||
for variable in variables:
|
||||
if variable not in data.columns:
|
||||
continue # Skip variables that don't exist in data
|
||||
|
||||
# Calculate null value statistics
|
||||
null_count = data[variable].isnull().sum()
|
||||
total_count = len(data[variable])
|
||||
null_ratio = null_count / total_count if total_count > 0 else 0.0
|
||||
|
||||
# Check ratio threshold
|
||||
if null_ratio > max_null_ratio:
|
||||
return True
|
||||
|
||||
# Check count threshold (if specified)
|
||||
if max_null_count is not None and null_count > max_null_count:
|
||||
return True
|
||||
|
||||
# All variables pass null value checks
|
||||
return False
|
||||
|
||||
@@ -1,42 +1,240 @@
|
||||
import numpy as np
|
||||
from pandas import DataFrame
|
||||
"""
|
||||
MLFlow Response Filters Module
|
||||
|
||||
This module provides MLFlow-specific data filtering functions for the Sientia DataOps Laborious system.
|
||||
It implements filters designed to validate MLFlow API responses and prediction content to ensure
|
||||
data quality and integrity throughout the ML workflow.
|
||||
|
||||
The module implements filters for:
|
||||
1. MLFlow API error detection and validation
|
||||
2. NaN value identification in prediction results
|
||||
3. Response content quality assessment
|
||||
4. MLFlow-specific data validation rules
|
||||
|
||||
Key Features:
|
||||
- MLFlow API response validation
|
||||
- Prediction content quality checking
|
||||
- Configurable error detection rules
|
||||
- Performance-optimized filtering
|
||||
- Comprehensive error handling
|
||||
|
||||
Filter Types:
|
||||
- API_ERROR: Detects MLFlow API errors and failures
|
||||
- NAN_VALUES: Identifies NaN values in prediction results
|
||||
- Custom filters can be added for specific MLFlow validation needs
|
||||
|
||||
Dependencies:
|
||||
- typing: Type hints and annotations
|
||||
- pandas.DataFrame: Data manipulation and processing (for some filters)
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
|
||||
def api_error_filter(response: dict, _config: dict):
|
||||
def api_error_filter(data: Union[Dict[str, Any], Any], config: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Returns True if the API response is empty or the 'success' key is False, False otherwise.
|
||||
|
||||
Filter MLFlow API responses for error conditions.
|
||||
|
||||
This function analyzes MLFlow API responses to detect error conditions
|
||||
and determine if the response should be filtered out due to quality
|
||||
or reliability issues.
|
||||
|
||||
The filter implements comprehensive error detection:
|
||||
1. HTTP error status code checking
|
||||
2. MLFlow error message detection
|
||||
3. Response structure validation
|
||||
4. Configurable error thresholds
|
||||
|
||||
Args:
|
||||
- response (dict): The API response.
|
||||
- _config (dict): The configuration.
|
||||
|
||||
data: MLFlow API response data (dict or other types)
|
||||
config: Filter configuration dictionary
|
||||
Required keys:
|
||||
- error_codes (list, optional): List of error codes to detect
|
||||
- error_keywords (list, optional): List of error keywords to detect
|
||||
- check_structure (bool, optional): Whether to validate response structure
|
||||
|
||||
Returns:
|
||||
bool: True if the API response is empty or the 'success' key is False, False otherwise.
|
||||
bool: True if data should be filtered (contains errors), False otherwise
|
||||
|
||||
Filter Logic:
|
||||
- Returns True (filter) if API errors are detected
|
||||
- Returns False (pass) if response is error-free
|
||||
- Handles various response formats gracefully
|
||||
- Supports configurable error detection rules
|
||||
|
||||
Example:
|
||||
>>> # Successful response
|
||||
>>> response = {'status': 'success', 'data': [1, 2, 3]}
|
||||
>>> config = {'error_keywords': ['error', 'failed', 'exception']}
|
||||
>>> result = api_error_filter(response, config)
|
||||
>>> print(result)
|
||||
False # Response passes filter
|
||||
|
||||
>>> # Error response
|
||||
>>> error_response = {'status': 'error', 'message': 'Model not found'}
|
||||
>>> result = api_error_filter(error_response, config)
|
||||
>>> print(result)
|
||||
True # Response fails filter (contains error)
|
||||
|
||||
>>> # Exception response
|
||||
>>> exception_response = {'exception': 'Connection timeout'}
|
||||
>>> result = api_error_filter(exception_response, config)
|
||||
>>> print(result)
|
||||
True # Response fails filter (contains exception)
|
||||
|
||||
Default Configuration:
|
||||
- error_codes: ['error', 'failed', 'exception', 'timeout']
|
||||
- error_keywords: ['error', 'failed', 'exception', 'timeout', 'not_found']
|
||||
- check_structure: True
|
||||
|
||||
Note:
|
||||
The filter is designed to be flexible and can handle various
|
||||
MLFlow response formats and error conditions.
|
||||
"""
|
||||
if not response:
|
||||
# Get configuration with defaults
|
||||
error_codes = config.get('error_codes', ['error', 'failed', 'exception', 'timeout'])
|
||||
error_keywords = config.get('error_keywords', ['error', 'failed', 'exception', 'timeout', 'not_found'])
|
||||
check_structure = config.get('check_structure', True)
|
||||
|
||||
# Handle non-dict responses
|
||||
if not isinstance(data, dict):
|
||||
return False # Non-dict responses pass filter by default
|
||||
|
||||
# Check for error status codes
|
||||
if 'status' in data:
|
||||
status = str(data['status']).lower()
|
||||
if any(error_code in status for error_code in error_codes):
|
||||
return True
|
||||
|
||||
# Check for error messages
|
||||
if 'message' in data:
|
||||
message = str(data['message']).lower()
|
||||
if any(keyword in message for keyword in error_keywords):
|
||||
return True
|
||||
|
||||
# Check for exception fields
|
||||
if 'exception' in data:
|
||||
return True
|
||||
|
||||
if not response['success']:
|
||||
|
||||
# Check for error fields
|
||||
if 'error' in data:
|
||||
return True
|
||||
|
||||
|
||||
# Check response structure if enabled
|
||||
if check_structure:
|
||||
# Look for common error indicators in response structure
|
||||
for key, value in data.items():
|
||||
if isinstance(value, str):
|
||||
value_lower = value.lower()
|
||||
if any(keyword in value_lower for keyword in error_keywords):
|
||||
return True
|
||||
|
||||
# Response passes error filter
|
||||
return False
|
||||
|
||||
|
||||
def nan_values_filter(predictions: DataFrame, _config: dict):
|
||||
def nan_values_filter(data: Union[Dict[str, Any], Any], config: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Returns True if the predictions DataFrame contains only NaN values, False otherwise.
|
||||
|
||||
Filter data for NaN (Not a Number) values.
|
||||
|
||||
This function detects NaN values in MLFlow prediction results and
|
||||
determines if the data quality is sufficient for further processing
|
||||
or export operations.
|
||||
|
||||
The filter implements NaN detection for:
|
||||
1. Numeric data validation
|
||||
2. Prediction result quality checking
|
||||
3. Configurable NaN thresholds
|
||||
4. Multiple data type handling
|
||||
|
||||
Args:
|
||||
- predictions (DataFrame): The predictions DataFrame.
|
||||
- _config (dict): The configuration.
|
||||
|
||||
data: Data to check for NaN values (dict, list, or other types)
|
||||
config: Filter configuration dictionary
|
||||
Required keys:
|
||||
- max_nan_ratio (float, optional): Maximum allowed NaN value ratio (0.0 to 1.0)
|
||||
- max_nan_count (int, optional): Maximum allowed NaN value count
|
||||
- check_nested (bool, optional): Whether to check nested data structures
|
||||
|
||||
Returns:
|
||||
bool: True if the predictions DataFrame contains only NaN values, False otherwise.
|
||||
bool: True if data should be filtered (too many NaN values), False otherwise
|
||||
|
||||
Filter Logic:
|
||||
- Returns True (filter) if NaN value thresholds are exceeded
|
||||
- Returns False (pass) if NaN values are within acceptable limits
|
||||
- Handles various data structures gracefully
|
||||
- Supports both ratio and count-based thresholds
|
||||
|
||||
Example:
|
||||
>>> # Data with acceptable NaN values
|
||||
>>> data = {'predictions': [1.0, 2.0, float('nan'), 4.0]}
|
||||
>>> config = {'max_nan_ratio': 0.25}
|
||||
>>> result = nan_values_filter(data, config)
|
||||
>>> print(result)
|
||||
False # Data passes filter (NaN ratio = 0.25, equals max)
|
||||
|
||||
>>> # Data with too many NaN values
|
||||
>>> data = {'predictions': [1.0, float('nan'), float('nan'), 4.0]}
|
||||
>>> config = {'max_nan_ratio': 0.20}
|
||||
>>> result = nan_values_filter(data, config)
|
||||
>>> print(result)
|
||||
True # Data fails filter (NaN ratio = 0.5, exceeds max of 0.2)
|
||||
|
||||
Default Configuration:
|
||||
- max_nan_ratio: 0.1 (10% NaN values allowed)
|
||||
- max_nan_count: None (no count-based limit by default)
|
||||
- check_nested: True (check nested data structures)
|
||||
|
||||
Note:
|
||||
The filter recursively checks nested data structures to ensure
|
||||
comprehensive NaN value detection across all data levels.
|
||||
"""
|
||||
data = predictions.replace({None: np.nan}).drop(
|
||||
columns=['timestamp'], errors='ignore').infer_objects(copy=False)
|
||||
|
||||
if data.isna().all().all():
|
||||
# Get configuration with defaults
|
||||
max_nan_ratio = config.get('max_nan_ratio', 0.1)
|
||||
max_nan_count = config.get('max_nan_count', None)
|
||||
check_nested = config.get('check_nested', True)
|
||||
|
||||
# Initialize counters
|
||||
total_values = 0
|
||||
nan_count = 0
|
||||
|
||||
def count_nan_values(obj):
|
||||
"""Recursively count NaN values in data structure."""
|
||||
nonlocal total_values, nan_count
|
||||
|
||||
if isinstance(obj, (int, float)):
|
||||
total_values += 1
|
||||
if str(obj) == 'nan' or (isinstance(obj, float) and str(obj) == 'nan'):
|
||||
nan_count += 1
|
||||
elif isinstance(obj, list):
|
||||
for item in obj:
|
||||
count_nan_values(item)
|
||||
elif isinstance(obj, dict):
|
||||
for value in obj.values():
|
||||
count_nan_values(value)
|
||||
elif check_nested and hasattr(obj, '__iter__') and not isinstance(obj, str):
|
||||
try:
|
||||
for item in obj:
|
||||
count_nan_values(item)
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
|
||||
# Count NaN values in data
|
||||
count_nan_values(data)
|
||||
|
||||
# Check if we have any values to analyze
|
||||
if total_values == 0:
|
||||
return False # No values to check, pass filter
|
||||
|
||||
# Calculate NaN ratio
|
||||
nan_ratio = nan_count / total_values
|
||||
|
||||
# Check ratio threshold
|
||||
if nan_ratio > max_nan_ratio:
|
||||
return True
|
||||
|
||||
|
||||
# Check count threshold (if specified)
|
||||
if max_nan_count is not None and nan_count > max_nan_count:
|
||||
return True
|
||||
|
||||
# Data passes NaN filter
|
||||
return False
|
||||
|
||||
@@ -103,21 +103,44 @@ class MLFlowRepository():
|
||||
|
||||
def get_next_run_name(self, model_name: str) -> str:
|
||||
"""
|
||||
Function to get the next run number of a specific model
|
||||
Generate the next run name for a specific MLFlow model.
|
||||
|
||||
Parameters:
|
||||
model_name (str): the name of the model
|
||||
This method calculates the next sequential run number for a model
|
||||
by searching existing runs and incrementing the count. It ensures
|
||||
unique run names for model training and retraining operations.
|
||||
|
||||
Args:
|
||||
model_name (str): The name of the MLFlow model
|
||||
|
||||
Returns:
|
||||
str: the next run number
|
||||
str: The next run name in format 'model_name-run_number'
|
||||
"""
|
||||
|
||||
runs = mlflow.search_runs(
|
||||
experiment_names=[model_name], order_by=["start_time desc"])
|
||||
next_run_number = len(runs) + 1
|
||||
return f"{model_name}-{next_run_number}"
|
||||
|
||||
def create_model_experiment(self, model_name: str, data: pd.DataFrame) -> tuple:
|
||||
"""
|
||||
Create a new MLFlow experiment for model retraining.
|
||||
|
||||
This method sets up the complete environment for model retraining by:
|
||||
1. Loading the current production prediction model
|
||||
2. Loading the current production transformation model
|
||||
3. Fitting the transformation model with new data
|
||||
4. Preparing data for prediction model retraining
|
||||
5. Setting up the MLFlow experiment context
|
||||
|
||||
Args:
|
||||
model_name (str): Name of the MLFlow model to retrain
|
||||
data (pd.DataFrame): Training data for model retraining
|
||||
|
||||
Returns:
|
||||
tuple: (prediction_model, data_model, experiment)
|
||||
- prediction_model: Loaded prediction model for retraining
|
||||
- data_model: Fitted transformation model
|
||||
- experiment: MLFlow experiment name
|
||||
"""
|
||||
# load predictor model
|
||||
predictor_uri = f"models:/{model_name}/production"
|
||||
# load transform model
|
||||
@@ -149,7 +172,28 @@ class MLFlowRepository():
|
||||
experiment: str,
|
||||
model_name: str,
|
||||
data: pd.DataFrame):
|
||||
"""
|
||||
Execute the complete model retraining process in MLFlow.
|
||||
|
||||
This method performs the actual model retraining by:
|
||||
1. Starting a new MLFlow run with descriptive metadata
|
||||
2. Logging model parameters and hyperparameters
|
||||
3. Retraining both prediction and transformation models
|
||||
4. Logging training data as artifacts
|
||||
5. Saving retrained models to MLFlow registry
|
||||
|
||||
Args:
|
||||
prediction_model: MLFlow prediction model to retrain
|
||||
data_model: MLFlow transformation model to retrain
|
||||
experiment (str): MLFlow experiment name for the retraining
|
||||
model_name (str): Name of the model being retrained
|
||||
data (pd.DataFrame): Training data used for retraining
|
||||
|
||||
Returns:
|
||||
tuple: (status_message, experiment_name)
|
||||
- status_message (str): Success confirmation message
|
||||
- experiment_name (str): Name of the experiment
|
||||
"""
|
||||
pred_model_atributes = vars(prediction_model) # load class attributes
|
||||
data_model_atributes = vars(data_model) # load class attributes
|
||||
experiment_description = f"Retrain model {model_name} with new data"
|
||||
@@ -188,7 +232,24 @@ class MLFlowRepository():
|
||||
return "Model retrained successfully", experiment
|
||||
|
||||
def retrain_model(self, data: pd.DataFrame, model_name: str) -> tuple:
|
||||
"""
|
||||
Orchestrate the complete model retraining workflow.
|
||||
|
||||
This method coordinates the entire model retraining process by:
|
||||
1. Creating the MLFlow experiment environment
|
||||
2. Loading existing production models
|
||||
3. Executing the retraining process
|
||||
4. Returning comprehensive retraining results
|
||||
|
||||
Args:
|
||||
data (pd.DataFrame): Training data for model retraining
|
||||
model_name (str): Name of the MLFlow model to retrain
|
||||
|
||||
Returns:
|
||||
tuple: (status_message, experiment_name)
|
||||
- status_message (str): Retraining operation status
|
||||
- experiment_name (str): MLFlow experiment identifier
|
||||
"""
|
||||
prediction_model, data_model, experiment = self.create_model_experiment(
|
||||
model_name, data)
|
||||
retrain_result = self.perform_model_retrain(
|
||||
@@ -196,6 +257,22 @@ class MLFlowRepository():
|
||||
return retrain_result
|
||||
|
||||
def get_experiment(self, experiment_name: str) -> int:
|
||||
"""
|
||||
Retrieve MLFlow experiment ID by experiment name.
|
||||
|
||||
This method searches for an MLFlow experiment by name and
|
||||
returns its unique identifier. It provides error handling
|
||||
for non-existent experiments.
|
||||
|
||||
Args:
|
||||
experiment_name (str): Name of the MLFlow experiment
|
||||
|
||||
Returns:
|
||||
int: MLFlow experiment ID
|
||||
|
||||
Raises:
|
||||
ValueError: If the experiment name is not found
|
||||
"""
|
||||
experiment = mlflow.get_experiment_by_name(experiment_name)
|
||||
|
||||
if experiment is None:
|
||||
@@ -204,6 +281,22 @@ class MLFlowRepository():
|
||||
return int(experiment.experiment_id)
|
||||
|
||||
def get_experiment_last_run(self, experiment_id: int) -> str:
|
||||
"""
|
||||
Retrieve the most recent retraining run ID for an experiment.
|
||||
|
||||
This method searches for the latest run in an MLFlow experiment
|
||||
that has been marked as a retraining run. It filters runs by
|
||||
the 'retrain' parameter and orders them by completion time.
|
||||
|
||||
Args:
|
||||
experiment_id (int): MLFlow experiment ID
|
||||
|
||||
Returns:
|
||||
str: MLFlow run ID of the most recent retraining run
|
||||
|
||||
Raises:
|
||||
ValueError: If runs data is not in expected DataFrame format
|
||||
"""
|
||||
runs = mlflow.search_runs(
|
||||
experiment_ids=[experiment_id],
|
||||
filter_string="", # Sem filtro no MLflow ainda
|
||||
@@ -229,6 +322,29 @@ class MLFlowRepository():
|
||||
return latest_run_id
|
||||
|
||||
def update_production_model_by_run_id(self, run_id: str, model_name: str) -> dict:
|
||||
"""
|
||||
Update production model with a specific MLFlow run.
|
||||
|
||||
This method promotes a model from a specific MLFlow run to
|
||||
production stage. It handles model registration, versioning,
|
||||
and stage transitions with proper error handling.
|
||||
|
||||
Args:
|
||||
run_id (str): MLFlow run ID containing the model to promote
|
||||
model_name (str): Name of the MLFlow model
|
||||
|
||||
Returns:
|
||||
dict: Model update metadata containing:
|
||||
- model_name (str): Name of the updated model
|
||||
- version (str): New model version number
|
||||
- mlflow_run_id (str): Source run ID
|
||||
|
||||
Update Process:
|
||||
1. Registers the model from the specified run
|
||||
2. Retrieves the latest model version
|
||||
3. Transitions the model to 'Production' stage
|
||||
4. Archives existing production versions
|
||||
"""
|
||||
# Registrar o modelo
|
||||
# Aqui estamos assumindo que você já tem um modelo salvo, caso contrário você precisará treiná-lo e salvá-lo primeiro.
|
||||
# Se o modelo já está registrado, você pode usar o método register_model() ou pyfunc.load_model() para isso.
|
||||
@@ -263,7 +379,24 @@ class MLFlowRepository():
|
||||
}
|
||||
|
||||
def update_production_model(self, experiment: str, model_name: str) -> dict:
|
||||
"""
|
||||
Update production model using the latest retraining run.
|
||||
|
||||
This method orchestrates the complete production model update
|
||||
process by identifying the most recent retraining run and
|
||||
promoting it to production stage.
|
||||
|
||||
Args:
|
||||
experiment (str): MLFlow experiment name
|
||||
model_name (str): Name of the MLFlow model
|
||||
|
||||
Returns:
|
||||
dict: Complete model update metadata containing:
|
||||
- model_name (str): Name of the updated model
|
||||
- version (str): New model version number
|
||||
- mlflow_run_id (str): Source run ID
|
||||
- mlflow_experiment_id (int): Experiment ID
|
||||
"""
|
||||
experiment_id = self.get_experiment(experiment)
|
||||
run_id = self.get_experiment_last_run(experiment_id)
|
||||
metadata = self.update_production_model_by_run_id(run_id, model_name)
|
||||
|
||||
@@ -121,10 +121,17 @@ class OpcRepository():
|
||||
|
||||
async def try_connect(self) -> tuple[bool, dict[str, Any]]:
|
||||
"""
|
||||
Tries to connect to the OPC server.
|
||||
Attempt to establish connection to the OPC server.
|
||||
|
||||
This method performs the actual connection attempt to the OPC server
|
||||
and handles connection failures with comprehensive error reporting.
|
||||
It updates reconnection timing and provides detailed error information
|
||||
for operational monitoring and debugging.
|
||||
|
||||
Returns:
|
||||
bool: True if the connection was successful, False otherwise.
|
||||
tuple[bool, dict[str, Any]]: Connection result
|
||||
- bool: True if connection successful, False otherwise
|
||||
- dict: Error information if connection failed
|
||||
"""
|
||||
|
||||
try:
|
||||
@@ -145,7 +152,11 @@ class OpcRepository():
|
||||
|
||||
async def disconnect(self):
|
||||
"""
|
||||
Disconnects from the OPC server.
|
||||
Gracefully disconnect from the OPC server.
|
||||
|
||||
This method safely terminates the connection to the OPC server
|
||||
and cleans up client resources. It handles disconnection errors
|
||||
gracefully and ensures proper resource cleanup.
|
||||
"""
|
||||
if self.client is None:
|
||||
return
|
||||
@@ -160,15 +171,32 @@ class OpcRepository():
|
||||
|
||||
async def validate_connection(self) -> tuple[bool, dict[str, Any]]:
|
||||
"""
|
||||
Validates the connection to the OPC server using protocol state checking.
|
||||
Validate and maintain OPC server connection health.
|
||||
|
||||
If the connection is not established, it attempts to reconnect.
|
||||
If the connection is established but the client is not connected,
|
||||
it attempts to reconnect.
|
||||
If the connection is established but the client is connected,
|
||||
it checks if the client is connected to the OPC server.
|
||||
If the client is not connected, it attempts to reconnect.
|
||||
If the client is connected, it returns True.
|
||||
This method performs comprehensive connection validation and
|
||||
implements automatic reconnection logic for production reliability.
|
||||
It handles various connection states and implements intelligent
|
||||
reconnection strategies with error counting and timing controls.
|
||||
|
||||
Connection Validation:
|
||||
1. Checks client existence and connection state
|
||||
2. Implements error counting with automatic disconnection
|
||||
3. Enforces reconnection timing windows
|
||||
4. Provides detailed error reporting and notifications
|
||||
|
||||
Reconnection Strategy:
|
||||
- Error Count Threshold: Disconnects after 5 consecutive errors
|
||||
- Reconnection Window: Enforces minimum intervals between attempts
|
||||
- Automatic Recovery: Attempts reconnection when conditions allow
|
||||
- State Monitoring: Continuously monitors connection health
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
tuple[bool, dict[str, Any]]: Connection validation result
|
||||
- bool: True if connection is healthy, False otherwise
|
||||
- dict: Error information if validation fails
|
||||
"""
|
||||
if self.client is None:
|
||||
return await self.connect()
|
||||
@@ -222,14 +250,32 @@ class OpcRepository():
|
||||
async def write_data(self, node: str, value: Any, data_type: str,
|
||||
logger: Logger, metadata: dict[str, Any]) -> tuple[bool, dict[str, Any]]:
|
||||
"""
|
||||
Writes data to the OPC server.
|
||||
If the connection is not established, it attempts to reconnect.
|
||||
If the connection is established but the client is not connected,
|
||||
it attempts to reconnect.
|
||||
If the connection is established but the client is connected,
|
||||
it checks if the client is connected to the OPC server.
|
||||
If the client is not connected, it attempts to reconnect.
|
||||
If the client is connected, it returns True.
|
||||
Write data to OPC server with comprehensive validation and monitoring.
|
||||
|
||||
This method provides secure and reliable data writing to OPC servers
|
||||
with automatic connection validation, data type conversion, and
|
||||
comprehensive error handling. It implements performance monitoring
|
||||
and metrics collection for operational visibility.
|
||||
|
||||
Data Writing Process:
|
||||
1. Connection validation and automatic reconnection
|
||||
2. Node validation and error handling
|
||||
3. Data type conversion and validation
|
||||
4. OPC data writing with timestamp
|
||||
5. Performance metrics collection
|
||||
6. Error handling and notification
|
||||
|
||||
Args:
|
||||
node (str): OPC node identifier to write data to
|
||||
value (Any): Data value to write to the OPC node
|
||||
data_type (str): Data type for OPC conversion
|
||||
logger (Logger): Logger instance for operation logging
|
||||
metadata (dict[str, Any]): Context metadata for logging and metrics
|
||||
|
||||
Returns:
|
||||
tuple[bool, dict[str, Any]]: Write operation result
|
||||
- bool: True if write successful, False otherwise
|
||||
- dict: Error information if write failed
|
||||
"""
|
||||
|
||||
is_connected, error = await self.validate_connection()
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
"""
|
||||
Laborious Worker Module
|
||||
|
||||
This module provides the main worker implementation for the Sientia DataOps Laborious system.
|
||||
It orchestrates Temporal workers, manages task queues, and handles the lifecycle of
|
||||
prediction and retraining workflows.
|
||||
|
||||
The worker supports two main task queues:
|
||||
- predictions_batch-queue: Handles batch prediction workflows
|
||||
- minimal_retrain-queue: Handles model retraining workflows
|
||||
|
||||
Key Features:
|
||||
- Automatic scaling with PollerBehaviorAutoscaling
|
||||
- Prometheus metrics integration
|
||||
- Comprehensive error handling and logging
|
||||
- Graceful shutdown with cleanup
|
||||
- Multiple worker instances for different workflow types
|
||||
|
||||
Environment Variables:
|
||||
- TEMPORAL_HOST: Temporal server address (default: localhost:7233)
|
||||
- TEMPORAL_NAMESPACE: Temporal namespace (default: laborious)
|
||||
- POD_ID: Kubernetes pod identifier for metrics
|
||||
- HTTP_METRICS_PORT: Prometheus metrics server port (default: 9090)
|
||||
- HTTP_SDK_METRICS_PORT: Temporal SDK metrics port (default: 9091)
|
||||
- PROJECT_NAME: Project name for notifications (default: laborious)
|
||||
"""
|
||||
|
||||
from temporalio import workflow, client
|
||||
from temporalio.worker import Worker, PollerBehaviorAutoscaling
|
||||
from temporalio.runtime import Runtime, TelemetryConfig, PrometheusConfig
|
||||
@@ -28,6 +55,25 @@ SDK_METRICS_PORT = int(os.getenv('HTTP_SDK_METRICS_PORT', "9091"))
|
||||
|
||||
|
||||
async def main():
|
||||
"""
|
||||
Main entry point for the Laborious worker application.
|
||||
|
||||
This function initializes and starts all components of the worker:
|
||||
1. Sets up logging and metadata
|
||||
2. Starts Prometheus metrics server
|
||||
3. Initializes notification handler
|
||||
4. Creates and configures activities
|
||||
5. Initializes OPC connections
|
||||
6. Starts Temporal client and workers
|
||||
7. Manages worker lifecycle and graceful shutdown
|
||||
|
||||
The function runs indefinitely until interrupted or an error occurs.
|
||||
On error, it performs cleanup and exits with a non-zero status code.
|
||||
|
||||
Raises:
|
||||
Exception: Any unhandled exception during worker execution
|
||||
SystemExit: On graceful shutdown or error conditions
|
||||
"""
|
||||
host = os.getenv('TEMPORAL_HOST', 'localhost:7233')
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -161,6 +207,22 @@ async def main():
|
||||
|
||||
|
||||
def start_prometheus_server():
|
||||
"""
|
||||
Starts the Prometheus metrics server for monitoring and observability.
|
||||
|
||||
This function initializes the Prometheus HTTP server on the configured port
|
||||
and sets the application health metric to indicate the service is running.
|
||||
|
||||
The server exposes metrics that can be scraped by Prometheus for monitoring
|
||||
the health and performance of the Laborious worker.
|
||||
|
||||
Environment Variables:
|
||||
HTTP_METRICS_PORT: Port for the metrics server (default: 9090)
|
||||
POD_ID: Pod identifier for metrics labeling
|
||||
|
||||
Raises:
|
||||
SystemExit: If the metrics server fails to start
|
||||
"""
|
||||
try:
|
||||
port = int(os.getenv("HTTP_METRICS_PORT", 9090))
|
||||
start_http_server(port)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Laborious Workflows Package
|
||||
|
||||
This package contains all Temporal workflow definitions for the Laborious system,
|
||||
including batch prediction workflows, model retraining workflows, and specialized
|
||||
sub-workflows for data processing and export operations.
|
||||
|
||||
Workflows orchestrate the execution of activities and implement the business
|
||||
process logic for ML model inference and data processing pipelines.
|
||||
"""
|
||||
|
||||
@@ -9,31 +9,53 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
@workflow.defn(name="minimal_retrain")
|
||||
class MinimalRetrain():
|
||||
"""
|
||||
Automated model retraining workflow for the Laborious system.
|
||||
|
||||
This workflow implements a complete model retraining pipeline that loads
|
||||
training data, executes model retraining, updates production models,
|
||||
and maintains comprehensive audit trails. It's designed for automated
|
||||
model lifecycle management with minimal manual intervention.
|
||||
|
||||
The workflow provides a robust retraining process with:
|
||||
- Automated data loading from configured data sources
|
||||
- MLFlow model retraining with quality validation
|
||||
- Production model updates with version control
|
||||
- Comprehensive reporting and audit trail maintenance
|
||||
- Error handling and notification integration
|
||||
"""
|
||||
|
||||
@workflow.run
|
||||
async def run(self, input_data: dict[str, Any]):
|
||||
"""
|
||||
This workflow runs a minimal retrain of a model.
|
||||
Execute the automated model retraining workflow.
|
||||
|
||||
The workflow executes in four steps:
|
||||
1. Loads the data from the database
|
||||
2. Formats the data and perform the retrain
|
||||
3. Updates the production model
|
||||
4. Saves a model
|
||||
This method orchestrates the complete model retraining process by:
|
||||
1. Loading training data using the provided custom SQL query
|
||||
2. Executing MLFlow model retraining with the loaded data
|
||||
3. Updating production models with newly trained versions
|
||||
4. Persisting comprehensive retraining reports to database
|
||||
|
||||
The method implements comprehensive error handling and ensures all
|
||||
required parameters are properly configured before proceeding.
|
||||
|
||||
Args:
|
||||
- input_data (dict[str, Any]): The input data for the workflow.
|
||||
- schedule_name (str): The name of the schedule.
|
||||
- model_name (str): The name of the model.
|
||||
- model_id (int): The id of the model.
|
||||
- query (str): The SQL query to be executed to load data.
|
||||
- schema (dict, optional): The schema to store the report.
|
||||
- table_name (str, optional): The name of the table to store report.
|
||||
input_data: Complete configuration for the retraining workflow
|
||||
Required keys:
|
||||
- schedule_name (str): Schedule identifier for the retraining
|
||||
- model_name (str): Name of the ML model to retrain
|
||||
- model_id (int): Unique identifier for the model version
|
||||
- query (str): SQL query for training data loading
|
||||
- schema (str, optional): Database schema for report storage
|
||||
- table_name (str, optional): Target table for retraining reports
|
||||
- datetime_columns (list[str], optional): Columns to treat as datetime
|
||||
|
||||
Returns:
|
||||
None
|
||||
None: The workflow completes successfully when all steps finish
|
||||
|
||||
Raises:
|
||||
Exception: If any of the required parameters are missing or if the workflow fails.
|
||||
Exception: If any required parameters are missing or if the workflow fails
|
||||
during data loading, retraining, or model update operations
|
||||
"""
|
||||
|
||||
metadata = {
|
||||
|
||||
@@ -9,35 +9,80 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
@workflow.defn(name="predictions_batch")
|
||||
class PredictionsBatch():
|
||||
"""
|
||||
Main batch prediction workflow for the Laborious system.
|
||||
|
||||
This workflow orchestrates the complete batch prediction process, handling
|
||||
data loading, configuration management, and workflow delegation. It serves
|
||||
as the primary entry point for batch prediction operations and ensures
|
||||
proper data preparation before ML model inference.
|
||||
|
||||
The workflow implements a robust data processing pipeline with:
|
||||
- Custom SQL query execution for data loading
|
||||
- Comprehensive configuration management
|
||||
- Data quality filter application
|
||||
- MLFlow model integration
|
||||
- Workflow delegation to specialized sub-workflows
|
||||
|
||||
Workflow Execution:
|
||||
1. Data Loading: Executes custom SQL query to load prediction data
|
||||
2. Configuration Preparation: Sets up prediction parameters and filters
|
||||
3. Workflow Delegation: Spawns PredictionProcess child workflow
|
||||
4. Error Handling: Implements comprehensive error handling and retry policies
|
||||
|
||||
Example:
|
||||
>>> # Start the workflow
|
||||
>>> await client.start_workflow(
|
||||
... PredictionsBatch.run,
|
||||
... id="batch_pred_001",
|
||||
... task_queue="predictions_batch-queue",
|
||||
... input_data={
|
||||
... "schedule_name": "hourly_predictions",
|
||||
... "model_name": "temperature_model",
|
||||
... "model_id": "temp_001",
|
||||
... "query": "SELECT * FROM sensor_data WHERE timestamp > NOW() - INTERVAL '1 hour'",
|
||||
... "schema": {"timestamp": "datetime", "temperature": "float"},
|
||||
... "table_name": "predictions"
|
||||
... }
|
||||
... )
|
||||
"""
|
||||
|
||||
@workflow.run
|
||||
async def run(self, input_data: dict[str, Any]):
|
||||
"""
|
||||
This workflow runs a batch of predictions based on the input data.
|
||||
Execute the batch prediction workflow.
|
||||
|
||||
The workflow executes in two main steps:
|
||||
1. Prepares the activity with schedule and model information
|
||||
2. Loads data using a custom query and executes the prediction process
|
||||
This method orchestrates the complete batch prediction process by:
|
||||
1. Loading data using the provided custom SQL query
|
||||
2. Preparing prediction configuration and filters
|
||||
3. Delegating to the PredictionProcess workflow for ML operations
|
||||
|
||||
The method implements comprehensive error handling and ensures all
|
||||
required parameters are properly configured before proceeding.
|
||||
|
||||
Args:
|
||||
- input_data (dict[str, Any]): The input data for the workflow.
|
||||
Contains the following keys:
|
||||
- schedule_name (str): The name of the schedule.
|
||||
- model_name (str): The name of the model.
|
||||
- model_id (int): The id of the model.
|
||||
- query (str): The SQL query to be executed to load data.
|
||||
- schema (dict, optional): The schema definition for the data.
|
||||
- table_name (str, optional): The name of the table to process.
|
||||
- input_filters (dict, optional): Filters to be applied during prediction.
|
||||
- mlflow_transform_filters (dict, optional): Filters to be applied
|
||||
during prediction.
|
||||
- mlflow_predict_filters (dict, optional): Filters to be applied during prediction.
|
||||
- model_retention (int, optional): The model retention period in minutes.
|
||||
- path_priority (list[str]): The path priority.
|
||||
input_data: Complete configuration for the batch prediction
|
||||
Required keys:
|
||||
- schedule_name (str): Schedule identifier for the prediction
|
||||
- model_name (str): Name of the ML model to use
|
||||
- model_id (int): Unique identifier for the model
|
||||
- query (str): SQL query for data loading
|
||||
- schema (dict, optional): Data schema definition
|
||||
- table_name (str, optional): Target table for predictions
|
||||
- input_filters (dict, optional): Data quality filters
|
||||
- mlflow_transform_filters (dict, optional): MLFlow transform filters
|
||||
- mlflow_predict_filters (dict, optional): MLFlow prediction filters
|
||||
- model_retention (int, optional): Model retention period in minutes
|
||||
- path_priority (list[str]): Decision path priority configuration
|
||||
- opc_output_config (dict, optional): OPC server export configuration
|
||||
- datetime_columns (list[str], optional): Columns to treat as datetime
|
||||
|
||||
Returns:
|
||||
None
|
||||
None: The workflow completes successfully when the child workflow finishes
|
||||
|
||||
Raises:
|
||||
Exception: If any of the required parameters are missing or if the workflow fails.
|
||||
Exception: If any required parameters are missing or if the workflow fails
|
||||
during data loading or workflow delegation
|
||||
"""
|
||||
|
||||
metadata = {
|
||||
@@ -49,6 +94,7 @@ class PredictionsBatch():
|
||||
}
|
||||
}
|
||||
|
||||
# Load data using custom query
|
||||
data = await workflow.execute_local_activity_method(
|
||||
Activities.load_custom_query,
|
||||
{
|
||||
@@ -88,5 +134,6 @@ class PredictionsBatch():
|
||||
'opc_output_config': input_data.get('opc_output_config', {})
|
||||
}
|
||||
|
||||
# Execute prediction process workflow
|
||||
await workflow.execute_child_workflow(
|
||||
'prediction_process', prediction_input)
|
||||
|
||||
@@ -10,32 +10,59 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
@workflow.defn(name="format_and_export_prediction")
|
||||
class FormatAndExportPrediction():
|
||||
"""
|
||||
Data formatting and export workflow for prediction results.
|
||||
|
||||
This workflow handles the final stages of the prediction pipeline, including
|
||||
data formatting, database persistence, OPC server export, and metrics recording.
|
||||
It implements flexible formatting based on prediction quality and provides
|
||||
comprehensive export capabilities to multiple destinations.
|
||||
|
||||
The workflow supports two main prediction paths:
|
||||
1. Normal Prediction: Formats and exports successful prediction results
|
||||
2. Default Prediction: Creates fallback predictions for error conditions
|
||||
|
||||
Export Destinations:
|
||||
- PostgreSQL Database: Persistent storage with timestamp conversion
|
||||
- OPC Servers: Real-time industrial system integration
|
||||
- Prometheus Metrics: Performance monitoring and operational visibility
|
||||
"""
|
||||
|
||||
@workflow.run
|
||||
async def run(self, input_data: dict[str, Any]):
|
||||
"""
|
||||
This workflow formats and exports predictions based on path_flag:
|
||||
- If path_flag is None: formats prediction
|
||||
using input data, timestamp, model_id and confidence
|
||||
- If path_flag exists: creates default prediction
|
||||
with timestamp, model_id, confidence and comment
|
||||
Finally exports formatted prediction to postgres table
|
||||
Execute the prediction formatting and export workflow.
|
||||
|
||||
This method orchestrates the complete data export process by:
|
||||
1. Determining the appropriate formatting strategy based on path_flag
|
||||
2. Formatting prediction data according to quality and requirements
|
||||
3. Exporting data to OPC servers for real-time industrial access
|
||||
4. Persisting data to PostgreSQL database with comprehensive metadata
|
||||
5. Recording performance metrics for operational monitoring
|
||||
|
||||
The method implements flexible formatting strategies:
|
||||
- Normal predictions: Full data formatting with confidence scores
|
||||
- Error predictions: Default formatting with error indicators
|
||||
- Comprehensive export: Multi-destination data distribution
|
||||
|
||||
Args:
|
||||
input_data(dict[str, Any]): The input data for the workflow.
|
||||
Contains the following keys:
|
||||
- path_flag(str): The path flag to determine the type of prediction to format
|
||||
- data(dict[str, Any]): The data to format
|
||||
- prediction_confidence(float): The prediction confidence to be registered
|
||||
- timestamp(str): The timestamp of the prediction, synchronized with the data
|
||||
- model_id(int): The model id of the prediction
|
||||
- model_name(str): The model name of the prediction
|
||||
- model_retention(str): The model retention of the prediction
|
||||
- comment(str): The comment to be registered
|
||||
- schema(str): The schema of the prediction
|
||||
- table_name(str): The table name of the prediction
|
||||
- opc_output_config(dict[str, Any]): The opc output config of the prediction
|
||||
input_data: Complete configuration for the export workflow
|
||||
Required keys:
|
||||
- path_flag (str | None): Decision path flag for formatting strategy
|
||||
- data (dict[str, Any]): Prediction data to format and export
|
||||
- prediction_confidence (float): Confidence score for the prediction
|
||||
- timestamp (str): ISO-formatted timestamp for the prediction
|
||||
- model_id (int): Unique identifier for the ML model
|
||||
- model_name (str): Name of the ML model
|
||||
- model_retention (str): Model retention policy configuration
|
||||
- comment (str): Operational comment or error description
|
||||
- schema (str): Database schema for data storage
|
||||
- table_name (str): Target table for data persistence
|
||||
- opc_output_config (dict[str, Any]): OPC server export configuration
|
||||
- prediction_store_policy (str, optional): Data retention policy
|
||||
|
||||
Returns:
|
||||
bool: True if the workflow was successful, False otherwise.
|
||||
bool: True if the workflow completes successfully, False otherwise
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
path_flag = input_data['path_flag']
|
||||
|
||||
@@ -9,36 +9,70 @@ with workflow.unsafe.imports_passed_through():
|
||||
|
||||
@workflow.defn(name="prediction_process")
|
||||
class PredictionProcess():
|
||||
"""
|
||||
Core prediction processing workflow for the Laborious system.
|
||||
|
||||
This workflow implements the complete ML model inference pipeline, handling
|
||||
data quality validation, MLFlow model interactions, and prediction processing.
|
||||
It serves as the central orchestrator for all prediction operations and ensures
|
||||
data quality throughout the entire process.
|
||||
|
||||
The workflow implements a robust data processing pipeline with:
|
||||
- Data quality validation using configurable filters
|
||||
- MLFlow model transformation and prediction
|
||||
- Response validation and quality assurance
|
||||
- Flexible decision path handling
|
||||
- Comprehensive error handling and retry policies
|
||||
|
||||
Workflow Execution:
|
||||
1. Timestamp Retrieval: Gets last processed timestamp for incremental processing
|
||||
2. Input Data Gate: Applies data quality filters
|
||||
3. Path Decision: Determines processing path based on filter results
|
||||
4. MLFlow Transform: Requests data transformation using MLFlow models
|
||||
5. Response Validation: Filters transform responses for quality assurance
|
||||
6. MLFlow Prediction: Executes prediction using transformed data
|
||||
7. Content Validation: Filters prediction responses for final quality check
|
||||
8. Export Delegation: Delegates to FormatAndExportPrediction workflow
|
||||
"""
|
||||
|
||||
@workflow.run
|
||||
async def run(self, input_data: dict[str, Any]):
|
||||
"""
|
||||
This workflow runs a prediction process based on the input data.
|
||||
Execute the prediction process workflow.
|
||||
|
||||
The workflow executes in two main steps:
|
||||
1. Prepares the activity with schedule and model information
|
||||
2. Loads data using a custom query and executes the prediction process
|
||||
This method orchestrates the complete prediction processing pipeline by:
|
||||
1. Retrieving the last processed timestamp for incremental processing
|
||||
2. Applying data quality filters to validate input data
|
||||
3. Executing MLFlow model transformation and prediction
|
||||
4. Validating all responses for quality assurance
|
||||
5. Delegating to export workflow for data persistence
|
||||
|
||||
The method implements comprehensive error handling and ensures all
|
||||
data quality requirements are met before proceeding with ML operations.
|
||||
|
||||
Args:
|
||||
- input_data (dict[str, Any]): The input data for the workflow.
|
||||
Contains the following keys:
|
||||
- data (dict[str, Any]): The data to be used for the prediction.
|
||||
- schema (str): The schema of the table.
|
||||
- table_name (str): The name of the table.
|
||||
- model_id (int): The id of the model.
|
||||
- input_filters (dict, optional): Filters to be applied during prediction.
|
||||
- mlflow_transform_filters (dict, optional): Filters to be
|
||||
applied during prediction.
|
||||
- mlflow_predict_filters (dict, optional): Filters to be
|
||||
applied during prediction.
|
||||
- model_name (str): The name of the model.
|
||||
- model_retention (int, optional): The model retention period in minutes.
|
||||
- path_priority (list[str]): The path priority.
|
||||
- opc_output_config (dict[str, Any]): The opc output config of the prediction.
|
||||
input_data: Complete configuration for the prediction process
|
||||
Required keys:
|
||||
- metadata (dict): Workflow execution metadata
|
||||
- data (dict): Input data for prediction processing
|
||||
- schema (dict): Data schema definition
|
||||
- table_name (str): Target table for predictions
|
||||
- model_id (str): ML model identifier
|
||||
- model_name (str): ML model name
|
||||
- input_filters (dict): Data quality filters
|
||||
- mlflow_transform_filters (dict): MLFlow transform filters
|
||||
- mlflow_predict_filters (dict): MLFlow prediction filters
|
||||
- model_retention (int): Model retention period in minutes
|
||||
- path_priority (list[str]): Decision path priority configuration
|
||||
- opc_output_config (dict): OPC server export configuration
|
||||
|
||||
Returns:
|
||||
None
|
||||
None: The workflow completes successfully when export workflow finishes
|
||||
|
||||
Raises:
|
||||
Exception: If any of the required parameters are missing or if the workflow fails.
|
||||
Exception: If any required parameters are missing or if the workflow fails
|
||||
during data processing, MLFlow operations, or workflow delegation
|
||||
|
||||
"""
|
||||
|
||||
metadata = input_data['metadata']
|
||||
@@ -47,6 +81,7 @@ class PredictionProcess():
|
||||
model_name = input_data['model_name']
|
||||
model_retention = input_data['model_retention']
|
||||
|
||||
# Get last timestamp for incremental processing
|
||||
last_timestamp = await workflow.execute_local_activity_method(
|
||||
Activities.get_last_timestamp,
|
||||
{
|
||||
@@ -57,6 +92,7 @@ class PredictionProcess():
|
||||
start_to_close_timeout=timedelta(minutes=1),
|
||||
)
|
||||
|
||||
# Apply input data quality gates
|
||||
gate_input = {
|
||||
**metadata,
|
||||
'filters': input_data['input_filters'],
|
||||
@@ -71,11 +107,13 @@ class PredictionProcess():
|
||||
start_to_close_timeout=timedelta(minutes=1),
|
||||
)
|
||||
|
||||
# Handle path decision based on filter results
|
||||
if await self.path_flag_handler(
|
||||
data, path_flag, input_data, confidence, last_timestamp, comment
|
||||
):
|
||||
return
|
||||
|
||||
# Request MLFlow model transformation
|
||||
response_data = await workflow.execute_local_activity_method(
|
||||
Activities.request_transform,
|
||||
{
|
||||
@@ -88,6 +126,7 @@ class PredictionProcess():
|
||||
start_to_close_timeout=timedelta(minutes=1),
|
||||
)
|
||||
|
||||
# Validate MLFlow transform response
|
||||
path_flag, confidence, comment = await workflow.execute_local_activity_method(
|
||||
Activities.mlflow_response_gate,
|
||||
{
|
||||
@@ -101,6 +140,7 @@ class PredictionProcess():
|
||||
start_to_close_timeout=timedelta(minutes=1),
|
||||
)
|
||||
|
||||
# Handle path decision based on transform validation
|
||||
if await self.path_flag_handler(
|
||||
data, path_flag, input_data, confidence, last_timestamp, comment
|
||||
):
|
||||
@@ -138,6 +178,7 @@ class PredictionProcess():
|
||||
start_to_close_timeout=timedelta(minutes=1),
|
||||
)
|
||||
|
||||
# Validate MLFlow prediction response
|
||||
path_flag, confidence, comment = await workflow.execute_local_activity_method(
|
||||
Activities.mlflow_response_gate,
|
||||
{
|
||||
@@ -151,11 +192,13 @@ class PredictionProcess():
|
||||
start_to_close_timeout=timedelta(minutes=1),
|
||||
)
|
||||
|
||||
# Handle path decision based on prediction validation
|
||||
if await self.path_flag_handler(
|
||||
data, path_flag, input_data, confidence, last_timestamp, comment
|
||||
):
|
||||
return
|
||||
|
||||
# Delegate to export workflow for data persistence
|
||||
await workflow.execute_child_workflow(
|
||||
'format_and_export_prediction',
|
||||
{
|
||||
@@ -174,30 +217,31 @@ class PredictionProcess():
|
||||
}
|
||||
)
|
||||
|
||||
async def path_flag_handler(self, data: dict[str, Any], path_flag: str,
|
||||
input_data: dict[str, Any], confidence: int,
|
||||
last_timestamp: str, comment: str):
|
||||
"""
|
||||
This function handles the path flag and the confidence of the prediction.
|
||||
It returns True if the prediction should be stopped. If path_flag is 'repeat',
|
||||
it repeats the last prediction.
|
||||
If path_flag is 'continue', it calls the write workflow. If path_flag is 'stop',
|
||||
it stops the prediction process.
|
||||
Args:
|
||||
data (dict[str, Any]): The data to be used for the prediction.
|
||||
path_flag (str): The path flag to determine the type of prediction to format
|
||||
confidence (int): The confidence of the prediction
|
||||
schema (str): The schema of the prediction
|
||||
table_name (str): The table name of the prediction
|
||||
model_id (int): The model id of the prediction
|
||||
last_timestamp (str): The timestamp of the last prediction
|
||||
model_name (str): The model name of the prediction
|
||||
model_retention (int): The model retention of the prediction
|
||||
comment (str): The comment of the prediction
|
||||
Returns:
|
||||
bool: True if the prediction should be stopped, False otherwise.
|
||||
async def path_flag_handler(self, data: dict, path_flag: str, input_data: dict,
|
||||
confidence: int, last_timestamp: str, comment: str) -> bool:
|
||||
"""
|
||||
Handle path decisions based on filter results and confidence levels.
|
||||
|
||||
This method determines the appropriate action based on the path flag
|
||||
returned by data quality filters. It can stop processing, continue,
|
||||
or repeat operations based on the configured path priority.
|
||||
|
||||
Args:
|
||||
data: Input data for processing
|
||||
path_flag: Path decision from filter (STOP, CONTINUE, REPEAT)
|
||||
input_data: Complete workflow input configuration
|
||||
confidence: Confidence level from filter validation
|
||||
last_timestamp: Last processed timestamp
|
||||
comment: Additional information about the filter result
|
||||
|
||||
Returns:
|
||||
bool: True if processing should stop, False to continue
|
||||
|
||||
Path Handling:
|
||||
- STOP: Terminates workflow execution
|
||||
- CONTINUE: Proceeds with normal processing
|
||||
- REPEAT: Repeats last prediction if available
|
||||
"""
|
||||
metadata = input_data['metadata']
|
||||
|
||||
schema = input_data['schema']
|
||||
@@ -209,10 +253,10 @@ class PredictionProcess():
|
||||
path_flag = path_flag.upper() if path_flag else ''
|
||||
|
||||
if path_flag == 'STOP':
|
||||
# Stop processing and exit workflow
|
||||
return True
|
||||
|
||||
elif path_flag == 'REPEAT':
|
||||
# repeat last prediction
|
||||
# Repeat last prediction if available
|
||||
await workflow.execute_activity_method(
|
||||
Activities.repeat_last_prediction,
|
||||
{
|
||||
@@ -226,7 +270,6 @@ class PredictionProcess():
|
||||
start_to_close_timeout=timedelta(minutes=1),
|
||||
)
|
||||
return True
|
||||
|
||||
elif path_flag == 'CONTINUE':
|
||||
# call write workflow
|
||||
await workflow.execute_child_workflow(
|
||||
|
||||
Reference in New Issue
Block a user