""" Excel method module. Handles Excel operations using the Excel service. """ import logging from typing import Dict, Any, List, Optional from datetime import datetime, UTC import json import base64 from modules.workflow.methodBase import MethodBase, ActionResult, action logger = logging.getLogger(__name__) class ExcelService: """Service for Microsoft Excel operations using Graph API""" def __init__(self, serviceContainer: Any): self.serviceContainer = serviceContainer def _getMicrosoftConnection(self, connectionReference: str) -> Optional[Dict[str, Any]]: """Get Microsoft connection from connection reference""" try: userConnection = self.serviceContainer.getUserConnectionFromConnectionReference(connectionReference) if not userConnection or userConnection.authority != "msft" or userConnection.status != "active": return None # Get the corresponding token for this user and authority token = self.serviceContainer.interfaceApp.getToken(userConnection.authority) if not token: logger.warning(f"No token found for user {userConnection.userId} and authority {userConnection.authority}") return None return { "id": userConnection.id, "accessToken": token.tokenAccess, "refreshToken": token.tokenRefresh, "scopes": ["Mail.ReadWrite", "User.Read"] # Default Microsoft scopes } except Exception as e: logger.error(f"Error getting Microsoft connection: {str(e)}") return None async def readFile(self, fileId: str, connectionReference: str, sheetName: str = "Sheet1", range: str = None) -> Dict[str, Any]: """Read data from Excel file using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "fileId": fileId, "connectionReference": connectionReference } # Get file data from service container file_data = self.serviceContainer.getFileData(fileId) file_info = self.serviceContainer.getFileInfo(fileId) if not file_data: return { "error": "File not found or empty", "fileId": fileId } # For now, simulate Excel reading with AI analysis # In a real implementation, you would use Microsoft Graph API excel_prompt = f""" Analyze this Excel file data and extract structured information. File: {file_info.get('name', 'Unknown')} Sheet: {sheetName} Range: {range or 'All data'} File content (first 5000 characters): {file_data.decode('utf-8', errors='ignore')[:5000] if isinstance(file_data, bytes) else str(file_data)[:5000]} Please extract: 1. All data from the specified sheet and range 2. Column headers and data types 3. Key metrics and calculations 4. Any charts or visualizations described 5. Summary statistics Return the data in a structured JSON format. """ # Use AI to analyze Excel content analysis_result = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(excel_prompt) return { "fileId": fileId, "sheetName": sheetName, "range": range, "data": analysis_result, "fileInfo": file_info, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error reading Excel file: {str(e)}") return { "error": str(e), "fileId": fileId } async def writeFile(self, fileId: str, connectionReference: str, sheetName: str, data: Any, range: str = None) -> Dict[str, Any]: """Write data to Excel file using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "fileId": fileId, "connectionReference": connectionReference } # For now, simulate Excel writing # In a real implementation, you would use Microsoft Graph API write_prompt = f""" Prepare data for writing to Excel file. File: {fileId} Sheet: {sheetName} Range: {range or 'Auto-detect'} Data to write: {json.dumps(data, indent=2)} Please format this data appropriately for Excel and provide: 1. Structured data ready for Excel 2. Column headers and formatting 3. Any formulas or calculations needed 4. Data validation rules if applicable """ # Use AI to prepare Excel data prepared_data = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(write_prompt) return { "fileId": fileId, "sheetName": sheetName, "range": range, "data": prepared_data, "status": "prepared", "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error writing to Excel file: {str(e)}") return { "error": str(e), "fileId": fileId } async def createFile(self, fileName: str, connectionReference: str, template: str = None) -> Dict[str, Any]: """Create new Excel file using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "connectionReference": connectionReference } # For now, simulate file creation # In a real implementation, you would use Microsoft Graph API create_prompt = f""" Create a new Excel file structure. File name: {fileName} Template: {template or 'Standard'} Please provide: 1. Initial sheet structure 2. Default column headers 3. Sample data if template specified 4. Formatting guidelines """ # Use AI to create Excel structure file_structure = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(create_prompt) # Create file using service container file_id = self.serviceContainer.createFile( fileName=fileName, mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", content=file_structure, base64encoded=False ) return { "fileId": file_id, "fileName": fileName, "template": template, "structure": file_structure, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error creating Excel file: {str(e)}") return { "error": str(e) } async def formatCells(self, fileId: str, connectionReference: str, sheetName: str, range: str, format: Dict[str, Any]) -> Dict[str, Any]: """Format Excel cells using Microsoft Graph API""" try: connection = self._getMicrosoftConnection(connectionReference) if not connection: return { "error": "No valid Microsoft connection found for the provided connection reference", "fileId": fileId, "connectionReference": connectionReference } # For now, simulate formatting # In a real implementation, you would use Microsoft Graph API format_prompt = f""" Apply formatting to Excel cells. File: {fileId} Sheet: {sheetName} Range: {range} Format: {json.dumps(format, indent=2)} Please provide: 1. Applied formatting details 2. Visual representation of the formatting 3. Any conditional formatting rules """ # Use AI to describe formatting formatting_result = await self.serviceContainer.interfaceAiCalls.callAiTextAdvanced(format_prompt) return { "fileId": fileId, "sheetName": sheetName, "range": range, "format": format, "result": formatting_result, "connection": { "id": connection["id"], "authority": "microsoft", "reference": connectionReference } } except Exception as e: logger.error(f"Error formatting Excel cells: {str(e)}") return { "error": str(e), "fileId": fileId } class MethodExcel(MethodBase): """Excel method implementation for spreadsheet operations""" def __init__(self, serviceContainer: Any): """Initialize the Excel method""" super().__init__(serviceContainer) self.name = "excel" self.description = "Handle Excel spreadsheet operations like reading and writing data" self.excelService = ExcelService(serviceContainer) @action async def read(self, parameters: Dict[str, Any]) -> ActionResult: """ Read data from Excel file Parameters: fileId (str): The ID of the Excel file to read connectionReference (str): Reference to the Microsoft connection sheetName (str, optional): Name of the sheet to read (default: "Sheet1") range (str, optional): Excel range to read (e.g., "A1:D10") includeHeaders (bool, optional): Whether to include column headers (default: True) """ try: fileId = parameters.get("fileId") connectionReference = parameters.get("connectionReference") sheetName = parameters.get("sheetName", "Sheet1") range = parameters.get("range") includeHeaders = parameters.get("includeHeaders", True) if not fileId or not connectionReference: return self._createResult( success=False, data={}, error="File ID and connection reference are required" ) # Read data from Excel data = await self.excelService.readFile( fileId=fileId, connectionReference=connectionReference, sheetName=sheetName, range=range ) return self._createResult( success=True, data=data ) except Exception as e: logger.error(f"Error reading Excel file: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def write(self, parameters: Dict[str, Any]) -> ActionResult: """ Write data to Excel file Parameters: fileId (str): The ID of the Excel file to write to connectionReference (str): Reference to the Microsoft connection sheetName (str, optional): Name of the sheet to write to (default: "Sheet1") data (Any): Data to write to the Excel file range (str, optional): Excel range to write to (e.g., "A1:D10") """ try: fileId = parameters.get("fileId") connectionReference = parameters.get("connectionReference") sheetName = parameters.get("sheetName", "Sheet1") data = parameters.get("data") range = parameters.get("range") if not fileId or not connectionReference or not data: return self._createResult( success=False, data={}, error="File ID, connection reference, and data are required" ) # Write data to Excel result = await self.excelService.writeFile( fileId=fileId, connectionReference=connectionReference, sheetName=sheetName, data=data, range=range ) return self._createResult( success=True, data=result ) except Exception as e: logger.error(f"Error writing to Excel file: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def create(self, parameters: Dict[str, Any]) -> ActionResult: """ Create new Excel file Parameters: fileName (str): Name of the new Excel file connectionReference (str): Reference to the Microsoft connection template (str, optional): Template to use for the new file """ try: fileName = parameters.get("fileName") connectionReference = parameters.get("connectionReference") template = parameters.get("template") if not fileName or not connectionReference: return self._createResult( success=False, data={}, error="File name and connection reference are required" ) # Create Excel file fileId = await self.excelService.createFile( fileName=fileName, connectionReference=connectionReference, template=template ) return self._createResult( success=True, data={"fileId": fileId} ) except Exception as e: logger.error(f"Error creating Excel file: {str(e)}") return self._createResult( success=False, data={}, error=str(e) ) @action async def format(self, parameters: Dict[str, Any]) -> ActionResult: """ Format Excel cells Parameters: fileId (str): The ID of the Excel file to format connectionReference (str): Reference to the Microsoft connection sheetName (str, optional): Name of the sheet to format (default: "Sheet1") range (str): Excel range to format (e.g., "A1:D10") format (Dict[str, Any]): Formatting options (e.g., {"font": {"bold": True}}) """ try: fileId = parameters.get("fileId") connectionReference = parameters.get("connectionReference") sheetName = parameters.get("sheetName", "Sheet1") range = parameters.get("range") format = parameters.get("format") if not fileId or not connectionReference or not range or not format: return self._createResult( success=False, data={}, error="File ID, connection reference, range, and format are required" ) # Apply formatting result = await self.excelService.formatCells( fileId=fileId, connectionReference=connectionReference, sheetName=sheetName, range=range, format=format ) return self._createResult( success=True, data=result ) except Exception as e: logger.error(f"Error formatting Excel cells: {str(e)}") return self._createResult( success=False, data={}, error=str(e) )