import random from datetime import datetime, timezone from typing import Any import json from kafka import KafkaProducer from temporalio import activity from sientia_do.notifications.handlers import CoreNotificationHandler as NotificationHandler from sientia_do.temporal.activities.base import BaseActivity from sientia_do.observability.logger import Logger class Faker(BaseActivity): """ Synthetic data generation for testing and development. This class generates realistic industrial sensor data for testing purposes. It provides: - Configurable sensor tag simulation - Realistic data value generation - Kafka integration for data publishing - Comprehensive error handling and logging The class is designed for development, testing, and demonstration of data processing pipelines without requiring real industrial data sources. """ def __init__(self, bootstrap_servers: str, logger: Logger, notification_handler: NotificationHandler): """ Initialize the Faker class with Kafka producer and sensor configuration. Args: bootstrap_servers (str): Kafka bootstrap servers configuration logger (Logger): Logger instance for operation logging notification_handler (NotificationHandler): Handler for system notifications """ self.producer = KafkaProducer( bootstrap_servers=bootstrap_servers, value_serializer=lambda v: json.dumps(v).encode('utf-8') ) # Predefined lists for tag and name self.tags = { 'ns=1;i=1001': 'Temperature Sensor', 'ns=1;i=1002': 'Vibration Meter', 'ns=1;i=1003': 'Pressure Gauge', 'ns=1;i=1004': 'Flow Meter', 'ns=1;i=1005': 'Voltage Sensor', 'ns=1;i=1006': 'Current Sensor' } BaseActivity.__init__(self, logger, notification_handler) @activity.defn(name="generate_and_send_data") async def generate_and_send_data(self, input_data: dict[str, Any]) -> None: """ Generate synthetic sensor data and publish to Kafka topic. This activity creates realistic industrial sensor readings and publishes them to the specified Kafka topic. The data includes sensor tags, names, timestamps, and values with configurable message counts. Args: input_data (dict[str, Any]): Activity input parameters. Required fields: - topic (str): Kafka topic name for data publication - metadata (dict[str, Any], optional): Workflow execution metadata - num_messages (int, optional): Number of messages to generate. Defaults to random count between 1 and available sensor tags Returns: None: This activity publishes data but doesn't return results Raises: ValueError: If topic is not specified Exception: If data generation or Kafka publishing fails """ metadata = input_data['metadata'] topic = input_data.get('topic') num_messages = input_data.get( 'num_messages', random.randint(1, len(self.tags))) # NOSONAR if not topic: raise ValueError("Topic must be specified in input_data") self.info( f"Generating {num_messages} messages for topic {topic}", metadata=metadata ) for _ in range(num_messages): # Select random tag and name tag = random.choice(list(self.tags.keys())) # NOSONAR name = self.tags[tag] # Generate random value between 0 and 100 if random.random() < 0.1: # NOSONAR value = None else: value = round(random.uniform(0, 100), 2) # Create data dictionary data = { 'tag': tag, 'name': name, 'timestamp': datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S'), 'value': value } # Send to Kafka self.producer.send(topic, value=data) # Ensure all messages are sent self.producer.flush() self.info("Success", metadata=metadata)