138 lines
5.5 KiB
Python
138 lines
5.5 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
|
|
import logging
|
|
import json
|
|
import base64
|
|
import pandas as pd
|
|
import csv as csv_module
|
|
from io import BytesIO
|
|
from datetime import datetime, UTC
|
|
from typing import Dict, Any
|
|
from modules.aichat.datamodelFeatureAiChat import ActionResult, ActionDocument
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
async def createExcelContent(self, parameters: Dict[str, Any]) -> ActionResult:
|
|
try:
|
|
dataParam = parameters.get("data")
|
|
if not dataParam:
|
|
return ActionResult.isFailure(error="data parameter is required")
|
|
|
|
headersParam = parameters.get("headers")
|
|
columnsParam = parameters.get("columns")
|
|
taskSyncDefinitionParam = parameters.get("taskSyncDefinition")
|
|
|
|
# Get data from document
|
|
dataJson = self.documentParsing.parseJsonFromDocument(dataParam)
|
|
if dataJson is None:
|
|
return ActionResult.isFailure(error="Could not parse data from document reference")
|
|
|
|
# Extract data array if wrapped in object
|
|
if isinstance(dataJson, dict) and "data" in dataJson:
|
|
dataList = dataJson["data"]
|
|
elif isinstance(dataJson, list):
|
|
dataList = dataJson
|
|
else:
|
|
return ActionResult.isFailure(error="Data must be a JSON array or object with 'data' field")
|
|
|
|
# Get headers
|
|
headers = {"header1": "Header 1", "header2": "Header 2"}
|
|
if headersParam:
|
|
headersJson = self.documentParsing.parseJsonFromDocument(headersParam)
|
|
if headersJson and isinstance(headersJson, dict) and "headers" in headersJson:
|
|
headers = headersJson["headers"]
|
|
elif headersJson and isinstance(headersJson, dict):
|
|
headers = headersJson
|
|
|
|
# Get columns
|
|
if columnsParam:
|
|
if isinstance(columnsParam, str):
|
|
try:
|
|
columns = json.loads(columnsParam) if columnsParam.startswith('[') or columnsParam.startswith('{') else columnsParam.split(',')
|
|
except:
|
|
columns = columnsParam.split(',')
|
|
elif isinstance(columnsParam, list):
|
|
columns = columnsParam
|
|
else:
|
|
columns = None
|
|
elif taskSyncDefinitionParam:
|
|
# Extract columns from taskSyncDefinition
|
|
if isinstance(taskSyncDefinitionParam, str):
|
|
taskSyncDefinition = json.loads(taskSyncDefinitionParam)
|
|
else:
|
|
taskSyncDefinition = taskSyncDefinitionParam
|
|
columns = list(taskSyncDefinition.keys())
|
|
elif dataList and len(dataList) > 0:
|
|
columns = list(dataList[0].keys())
|
|
else:
|
|
columns = []
|
|
|
|
# Create DataFrame
|
|
if not dataList:
|
|
df = pd.DataFrame(columns=columns)
|
|
else:
|
|
df = pd.DataFrame(dataList)
|
|
# Ensure all columns exist
|
|
for col in columns:
|
|
if col not in df.columns:
|
|
df[col] = ""
|
|
# Reorder columns
|
|
df = df[columns]
|
|
|
|
# Clean data
|
|
for column in df.columns:
|
|
df[column] = df[column].astype("object").fillna("")
|
|
df[column] = df[column].astype(str).str.replace('\n', '\\n', regex=False).str.replace('"', '""', regex=False)
|
|
|
|
# Create headers with timestamp
|
|
timestamp = datetime.fromtimestamp(self.services.utils.timestampGetUtc(), UTC).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
header1Row = next(csv_module.reader([headers.get("header1", "Header 1")]), [])
|
|
header2Row = next(csv_module.reader([headers.get("header2", "Header 2")]), [])
|
|
if len(header2Row) > 1:
|
|
header2Row[1] = timestamp
|
|
|
|
headerRow1 = pd.DataFrame([header1Row + [""] * (len(df.columns) - len(header1Row))], columns=df.columns)
|
|
headerRow2 = pd.DataFrame([header2Row + [""] * (len(df.columns) - len(header2Row))], columns=df.columns)
|
|
tableHeaders = pd.DataFrame([df.columns.tolist()], columns=df.columns)
|
|
finalDf = pd.concat([headerRow1, headerRow2, tableHeaders, df], ignore_index=True)
|
|
|
|
# Convert to Excel bytes
|
|
buf = BytesIO()
|
|
finalDf.to_excel(buf, index=False, header=False, engine='openpyxl')
|
|
excelBytes = buf.getvalue()
|
|
|
|
logger.info(f"Created Excel content: {len(dataList)} rows, {len(columns)} columns")
|
|
|
|
# Generate filename
|
|
workflowContext = self.services.chat.getWorkflowContext() if hasattr(self.services, 'chat') else None
|
|
filename = self._generateMeaningfulFileName(
|
|
"ticket_sync",
|
|
"xlsx",
|
|
workflowContext,
|
|
"createExcelContent"
|
|
)
|
|
|
|
validationMetadata = self._createValidationMetadata(
|
|
"createExcelContent",
|
|
rowCount=len(dataList),
|
|
columnCount=len(columns)
|
|
)
|
|
|
|
# Store as base64 for document
|
|
excelBase64 = base64.b64encode(excelBytes).decode('utf-8')
|
|
|
|
document = ActionDocument(
|
|
documentName=filename,
|
|
documentData=excelBase64,
|
|
mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
validationMetadata=validationMetadata
|
|
)
|
|
|
|
return ActionResult.isSuccess(documents=[document])
|
|
|
|
except Exception as e:
|
|
errorMsg = f"Error creating Excel content: {str(e)}"
|
|
logger.error(errorMsg)
|
|
return ActionResult.isFailure(error=errorMsg)
|
|
|