employee_service.py
1 """ 2 Defines the abstract base class and factory for creating Employee Service providers. 3 """ 4 5 import logging 6 import importlib 7 from abc import ABC, abstractmethod 8 from typing import Any, Dict, List, Optional 9 import importlib.metadata as metadata 10 11 12 from ..utils.in_memory_cache import InMemoryCache 13 import pandas as pd 14 15 log = logging.getLogger(__name__) 16 17 class BaseEmployeeService(ABC): 18 """ 19 Abstract base class for Employee Service providers. 20 21 This class defines a "thin provider" contract. Implementations should focus 22 solely on fetching data from a source system (like an HR platform) and 23 mapping it to the canonical format. All complex business logic, such as 24 building organizational charts or calculating availability schedules, should 25 be handled by the tools that consume this service. 26 27 Canonical Employee Schema 28 This schema defines the standard fields that all EmployeeService providers should aim to return. 29 - id (string): A unique, stable, and lowercase identifier for the employee (e.g., email, GUID). 30 - displayName (string): The employee's full name for display purposes. 31 - workEmail (string): The employee's primary work email address. 32 - jobTitle (string): The employee's official job title. 33 - department (string): The department the employee belongs to. 34 - location (string): The physical or regional location of the employee. 35 - supervisorId (string): The unique id of the employee's direct manager. 36 - hireDate (string): The date the employee was hired (ISO 8601: YYYY-MM-DD). 37 - mobilePhone (string): The employee's mobile phone number (optional). 38 39 """ 40 41 def __init__(self, config: Dict[str, Any]): 42 """ 43 Initializes the service with its specific configuration block. 44 45 Args: 46 config: The dictionary of configuration parameters for this provider. 47 """ 48 self.config = config 49 self.log_identifier = f"[{self.__class__.__name__}]" 50 self.cache_ttl = config.get("cache_ttl_seconds", 3600) 51 self.cache = InMemoryCache() if self.cache_ttl > 0 else None 52 log.info( 53 "%s Initialized. Cache TTL: %d seconds.", 54 self.log_identifier, 55 self.cache_ttl, 56 ) 57 58 @abstractmethod 59 async def get_employee_dataframe(self) -> pd.DataFrame: 60 """Returns the entire employee directory as a pandas DataFrame.""" 61 pass 62 63 @abstractmethod 64 async def get_employee_profile(self, employee_id: str) -> Optional[Dict[str, Any]]: 65 """Fetches the profile for a single employee.""" 66 pass 67 68 @abstractmethod 69 async def get_time_off_data(self, employee_id: str, start_date: Optional[str] = None, end_date: Optional[str] = None) -> List[Dict[str, Any]]: 70 """ 71 Retrieves a list of raw time-off entries for an employee. 72 73 The tool consuming this data is responsible for interpreting it. 74 Each dictionary in the list MUST contain the following keys: 75 - 'start' (str): The start date of the leave (YYYY-MM-DD). 76 - 'end' (str): The end date of the leave (YYYY-MM-DD). 77 - 'type' (str): The category of leave (e.g., 'Vacation', 'Sick', 'Holiday'). 78 - 'amount' (str): The amount of time taken, must be one of 'full_day' or 'half_day'. 79 80 Example: 81 [ 82 {'start': '2025-07-04', 'end': '2025-07-04', 'type': 'Holiday', 'amount': 'full_day'}, 83 {'start': '2025-08-15', 'end': '2025-08-15', 'type': 'Vacation', 'amount': 'full_day'} 84 ] 85 86 Args: 87 employee_id: The unique identifier for the employee. 88 89 Returns: 90 A list of dictionaries, each representing a time-off entry. 91 """ 92 pass 93 94 @abstractmethod 95 async def get_employee_profile_picture(self, employee_id: str) -> Optional[str]: 96 """ 97 Fetches an employee's profile picture and returns it as a data URI. 98 99 Args: 100 employee_id: The unique identifier for the employee. 101 102 Returns: 103 A string containing the data URI (e.g., 'data:image/jpeg;base64,...') 104 or None if not available. 105 """ 106 pass 107 108 109 def create_employee_service( 110 config: Optional[Dict[str, Any]], 111 ) -> Optional[BaseEmployeeService]: 112 """ 113 Factory function to create an instance of an Employee Service provider 114 based on the provided configuration. 115 """ 116 if not config: 117 log.info( 118 "[EmployeeFactory] No 'employee_service' configuration found. Skipping creation." 119 ) 120 return None 121 122 provider_type = config.get("type") 123 if not provider_type: 124 raise ValueError("Employee service config must contain a 'type' key.") 125 126 log.info( 127 f"[EmployeeFactory] Attempting to create employee service of type: {provider_type}" 128 ) 129 130 try: 131 entry_points = metadata.entry_points(group="solace_agent_mesh.plugins") 132 provider_info_entry = next( 133 (ep for ep in entry_points if ep.name == provider_type), None 134 ) 135 136 if not provider_info_entry: 137 raise ValueError( 138 f"No plugin provider found for type '{provider_type}' under the 'solace_agent_mesh.plugins' entry point." 139 ) 140 141 provider_info = provider_info_entry.load() 142 class_path = provider_info.get("class_path") 143 if not class_path: 144 raise ValueError( 145 f"Plugin '{provider_type}' is missing 'class_path' in its info dictionary." 146 ) 147 148 module_path, class_name = class_path.rsplit(".", 1) 149 module = importlib.import_module(module_path) 150 provider_class = getattr(module, class_name) 151 152 if not issubclass(provider_class, BaseEmployeeService): 153 raise TypeError( 154 f"Provider class '{class_path}' does not inherit from BaseEmployeeService." 155 ) 156 157 log.info(f"Successfully loaded employee provider plugin: {provider_type}") 158 return provider_class(config) 159 except (ImportError, AttributeError, TypeError, ValueError) as e: 160 log.exception( 161 f"[EmployeeFactory] Failed to load employee provider plugin '{provider_type}'. " 162 "Ensure the plugin is installed and the entry point is correct." 163 ) 164 raise ValueError(f"Could not load employee provider plugin: {e}") from e