docu for validation and automation
This commit is contained in:
parent
70e1f15668
commit
f637b83f39
2 changed files with 939 additions and 0 deletions
|
|
@ -0,0 +1,285 @@
|
||||||
|
# Pydantic Class Enhancement Proposal
|
||||||
|
## Format Tracking & Validation Alignment
|
||||||
|
|
||||||
|
**Date:** 2025-11-02
|
||||||
|
**Purpose:** Align validation logic with prompt requirements, enable workflow-level validation, and track expected file formats
|
||||||
|
|
||||||
|
**Simplified Approach:** Use existing document metadata (name, size, format, mimeType) - no summary fields needed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This proposal addresses:
|
||||||
|
1. **Validation alignment**: What prompts ask for matches what validators check
|
||||||
|
2. **Workflow-level validation**: Check ALL deliverables from ALL tasks against original user request
|
||||||
|
3. **Format tracking**: Track expected formats (list) at workflow and task levels
|
||||||
|
4. **Adaptive task planning**: Next task uses ALL workflow data (messages, document metadata) to refine objective
|
||||||
|
|
||||||
|
**Key Simplification:** Actions deliver documents with metadata (as today). No summary fields needed - use existing document metadata.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. ActionResult Class Changes
|
||||||
|
|
||||||
|
**File:** `gateway/modules/datamodels/datamodelChat.py` (lines 483-521)
|
||||||
|
|
||||||
|
### NO CHANGES NEEDED
|
||||||
|
|
||||||
|
**Current Structure (KEEP ALL - ALL USED):**
|
||||||
|
- ✅ `success: bool` - Used by validation
|
||||||
|
- ✅ `error: Optional[str]` - Used for error handling
|
||||||
|
- ✅ `documents: List[ActionDocument]` - Contains document metadata (name, data, mimeType)
|
||||||
|
- ✅ `resultLabel: Optional[str]` - Used for document routing
|
||||||
|
|
||||||
|
**Documents already provide all needed metadata:**
|
||||||
|
- `documentName` - File name
|
||||||
|
- `documentData` - Content
|
||||||
|
- `mimeType` - MIME type (can derive format from this)
|
||||||
|
|
||||||
|
**No summary field needed** - document metadata is sufficient.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. TaskResult Class Changes
|
||||||
|
|
||||||
|
**File:** `gateway/modules/datamodels/datamodelChat.py` (lines 718-736)
|
||||||
|
|
||||||
|
### NO CHANGES NEEDED
|
||||||
|
|
||||||
|
**Current Structure (KEEP ALL - ALL USED):**
|
||||||
|
- ✅ `taskId: str` - Task identification
|
||||||
|
- ✅ `status: TaskStatus` - Task status tracking
|
||||||
|
- ✅ `success: bool` - Success flag
|
||||||
|
- ✅ `feedback: Optional[str]` - Task feedback
|
||||||
|
- ✅ `error: Optional[str]` - Error message
|
||||||
|
|
||||||
|
**Document metadata available from workflow:**
|
||||||
|
- Can extract delivered formats from documents in workflow messages
|
||||||
|
- No need to store separately - use existing document metadata
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. TaskStep Class Changes
|
||||||
|
|
||||||
|
**File:** `gateway/modules/datamodels/datamodelChat.py` (lines 790-825)
|
||||||
|
|
||||||
|
### Modify
|
||||||
|
- Change `expectedFormat: Optional[str]` → `expectedFormats: Optional[List[str]]`
|
||||||
|
- Keep `dataType` and `qualityRequirements` as-is
|
||||||
|
|
||||||
|
### Modified Class:
|
||||||
|
```python
|
||||||
|
class TaskStep(BaseModel):
|
||||||
|
id: str
|
||||||
|
objective: str
|
||||||
|
dependencies: Optional[list[str]] = Field(default_factory=list)
|
||||||
|
successCriteria: Optional[list[str]] = Field(default_factory=list)
|
||||||
|
estimatedComplexity: Optional[str] = None
|
||||||
|
userMessage: Optional[str] = Field(
|
||||||
|
None, description="User-friendly message in user's language"
|
||||||
|
)
|
||||||
|
# Format details extracted from intent analysis
|
||||||
|
dataType: Optional[str] = Field(
|
||||||
|
None, description="Expected data type (text, numbers, documents, etc.)"
|
||||||
|
)
|
||||||
|
expectedFormats: Optional[List[str]] = Field(
|
||||||
|
None, description="Expected output file format extensions (e.g., ['docx', 'pdf', 'xlsx']). Use actual file extensions, not conceptual terms."
|
||||||
|
)
|
||||||
|
qualityRequirements: Optional[Dict[str, Any]] = Field(
|
||||||
|
None, description="Quality requirements and constraints"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Register Labels
|
||||||
|
Update:
|
||||||
|
```python
|
||||||
|
"expectedFormats": {"en": "Expected Formats", "fr": "Formats attendus"}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. ChatWorkflow Class Changes (for Workflow-Level Tracking)
|
||||||
|
|
||||||
|
**File:** `gateway/modules/datamodels/datamodelChat.py` (find ChatWorkflow class)
|
||||||
|
|
||||||
|
### Add (if not exists)
|
||||||
|
```python
|
||||||
|
expectedFormats: Optional[List[str]] = Field(
|
||||||
|
None,
|
||||||
|
description="List of expected file format extensions from user request (e.g., ['xlsx', 'pdf']). Extracted during intent analysis."
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `_workflowIntent` is already stored as a dict (not a model field), so `expectedFormats` can be extracted from there, but having it as an explicit field makes it easier to query.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. ActionItem Class Review
|
||||||
|
|
||||||
|
**File:** `gateway/modules/datamodels/datamodelChat.py` (lines 652-715)
|
||||||
|
|
||||||
|
### Current Structure (ALL USED - KEEP):
|
||||||
|
- ✅ `id: str` - Used for action identification
|
||||||
|
- ✅ `execMethod: str` - Used for action execution
|
||||||
|
- ✅ `execAction: str` - Used for action execution
|
||||||
|
- ✅ `execParameters: Dict[str, Any]` - Used for action execution
|
||||||
|
- ✅ `execResultLabel: Optional[str]` - Used for document routing
|
||||||
|
- ✅ `expectedDocumentFormats: Optional[List[Dict[str, str]]]` - Used by action planning
|
||||||
|
- ✅ `userMessage: Optional[str]` - Used for user communication
|
||||||
|
- ✅ `status: TaskStatus` - Used for tracking
|
||||||
|
- ✅ `error: Optional[str]` - Used for error handling
|
||||||
|
- ✅ `retryCount: int` - Used for retry logic
|
||||||
|
- ✅ `retryMax: int` - Used for retry logic
|
||||||
|
- ✅ `processingTime: Optional[float]` - Used for performance tracking
|
||||||
|
- ✅ `timestamp: float` - Used for ordering/auditing
|
||||||
|
- ✅ `result: Optional[str]` - Used to store action result text
|
||||||
|
|
||||||
|
**NO CHANGES NEEDED** - All attributes are used
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Summary of Changes
|
||||||
|
|
||||||
|
### Classes to Modify:
|
||||||
|
1. ✅ **TaskStep** - Change `expectedFormat` (str) → `expectedFormats` (List[str])
|
||||||
|
2. ✅ **ChatWorkflow** - Add `expectedFormats` (optional, for explicit tracking)
|
||||||
|
|
||||||
|
### Classes to Review (NO CHANGES):
|
||||||
|
- ✅ **ActionResult** - Keep as-is, documents already have metadata
|
||||||
|
- ✅ **TaskResult** - Keep as-is, no summary needed
|
||||||
|
- ✅ **ActionDocument** - Already correct (documentName, documentData, mimeType)
|
||||||
|
- ✅ **ActionItem** - All attributes used
|
||||||
|
- ✅ **Observation** - Already has contentValidation field
|
||||||
|
- ✅ **TaskItem** - Used for database storage, separate from TaskStep
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Implementation Impact
|
||||||
|
|
||||||
|
### Files That Will Need Updates:
|
||||||
|
|
||||||
|
1. **datamodelChat.py** - Class definitions (this proposal)
|
||||||
|
- Change `expectedFormat` → `expectedFormats` in TaskStep
|
||||||
|
- Add `expectedFormats` to ChatWorkflow (optional)
|
||||||
|
|
||||||
|
2. **taskPlanner.py** - Populate `expectedFormats` list instead of single `expectedFormat`
|
||||||
|
- **Adaptive planning:** Use ALL workflow data (messages, document metadata) to refine next task objective
|
||||||
|
- Extract delivered formats from workflow documents
|
||||||
|
- Compare what was delivered vs. what was planned
|
||||||
|
|
||||||
|
3. **contentValidator.py** - Use `expectedFormats` list for validation
|
||||||
|
- **Action-level validation:** Check action results against task objective (already exists)
|
||||||
|
- **Task-level validation:** Validate THIS task's deliverables against THIS task's expectations
|
||||||
|
- Uses document metadata (name, size, format, mimeType) - no summaries needed
|
||||||
|
|
||||||
|
4. **intentAnalyzer.py** - Fix prompt to ask for actual file format extensions
|
||||||
|
- Change from conceptual terms ("raw_data", "formatted") to actual extensions ("pdf", "docx", "xlsx")
|
||||||
|
|
||||||
|
5. **promptGenerationTaskplan.py** - Ask for `expectedFormats` in task planning
|
||||||
|
- **Adaptive planning:** Include ALL workflow data (messages, document names/sizes/formats/metadata) when planning next task
|
||||||
|
- Show what was actually delivered to help refine objective
|
||||||
|
|
||||||
|
6. **workflowManager.py** - Pass ALL workflow data to next task planning
|
||||||
|
- Messages (text content)
|
||||||
|
- Document metadata (names, sizes, formats, mimeTypes)
|
||||||
|
- Validation results
|
||||||
|
|
||||||
|
### Key Implementation Points:
|
||||||
|
|
||||||
|
- **No summary fields:** Use existing document metadata (name, size, format, mimeType)
|
||||||
|
- **Adaptive task planning:** Next task receives ALL workflow data (messages + document metadata) to refine objective
|
||||||
|
- **Validation scope:** Task validation checks ONLY that task's actions, not all workflow actions
|
||||||
|
- **After each action:** Validate against task objective → decide if complete or next action needed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Validation Logic Alignment
|
||||||
|
|
||||||
|
### Action-Level Validation (Within Task):
|
||||||
|
- **When:** After each action execution within a task
|
||||||
|
- **Checks:** Action results against task objective
|
||||||
|
- **Against:** Action documents (name, size, format, mimeType metadata)
|
||||||
|
- **Purpose:** Decide if task is complete or next action needed
|
||||||
|
- **Triggers:** Continue to next action if incomplete, complete task if done
|
||||||
|
|
||||||
|
### Task Planning (Adaptive - Uses ALL Workflow Data):
|
||||||
|
- **Input:** ALL workflow data available:
|
||||||
|
- All messages (text content)
|
||||||
|
- All document metadata (names, sizes, formats/extensions, mimeTypes)
|
||||||
|
- Previous task validation results
|
||||||
|
- **Process:**
|
||||||
|
- Extract delivered formats from all workflow documents
|
||||||
|
- Compare what was ACTUALLY delivered vs. what was PLANNED
|
||||||
|
- Refine next task objective:
|
||||||
|
- Deliver MORE if previous tasks delivered less than expected
|
||||||
|
- Deliver LESS if previous tasks already delivered more
|
||||||
|
- Adapt to actual workflow progress
|
||||||
|
|
||||||
|
### Task-Level Validation (Task Completion):
|
||||||
|
- **When:** After ALL actions in a task complete
|
||||||
|
- **Checks:** Task objective, task `expectedFormats`, task `successCriteria`
|
||||||
|
- **Against:** Documents from THIS task only (extract formats from document metadata)
|
||||||
|
- **Purpose:** Verify THIS task delivered what was expected for THIS task scope
|
||||||
|
- **Output:** Validation result (used in workflow data for next task planning)
|
||||||
|
|
||||||
|
### Workflow-Level Validation (Final):
|
||||||
|
- **When:** After ALL tasks complete
|
||||||
|
- **Checks:** Original user request, workflow `expectedFormats`, workflow success criteria
|
||||||
|
- **Against:** ALL documents from ALL tasks (extract formats from document metadata)
|
||||||
|
- **Purpose:** Final verification that complete workflow delivered what user requested
|
||||||
|
- **Triggers:** New compensatory task if validation fails (missing deliverables)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Next Steps
|
||||||
|
|
||||||
|
1. **Review and approve this proposal**
|
||||||
|
2. **Implement class changes** in datamodelChat.py
|
||||||
|
3. **Update intent analyzer prompt** to request actual file format extensions
|
||||||
|
4. **Update task planning prompt** to request `expectedFormats` list
|
||||||
|
5. **Update AI generation prompts** to include summary instruction
|
||||||
|
6. **Implement aggregation logic** for summaries at task/workflow levels
|
||||||
|
7. **Implement workflow-level validation** method
|
||||||
|
8. **Update all references** from `expectedFormat` to `expectedFormats`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions Answered
|
||||||
|
|
||||||
|
✅ **Document metadata:** Use existing document fields (name, size, format from mimeType/extensions) - no summaries needed
|
||||||
|
✅ **Format extraction:** Extract formats from document metadata (mimeType or file extensions)
|
||||||
|
✅ **Task validation scope:** Task validation checks ONLY actions in that task, not all workflow actions
|
||||||
|
✅ **Adaptive planning:** Next task uses ALL workflow data (messages + document metadata) to refine objective
|
||||||
|
✅ **After each action:** Validate against task objective → decide complete or next action needed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Validation Flow Clarification
|
||||||
|
|
||||||
|
### Simplified Flow:
|
||||||
|
|
||||||
|
1. **Within Task (Action-by-Action):**
|
||||||
|
- Action executes → delivers documents with metadata
|
||||||
|
- Validate action results against task objective
|
||||||
|
- If incomplete → next action needed
|
||||||
|
- If complete → task done
|
||||||
|
|
||||||
|
2. **Task Planning (Adaptive):**
|
||||||
|
- Receives: ALL workflow data (messages, document metadata from all previous tasks)
|
||||||
|
- Extracts: Delivered formats from document metadata (file extensions/mimeTypes)
|
||||||
|
- Compares: What was actually delivered vs. what was planned
|
||||||
|
- Refines: Next task objective (may need more/less based on actual progress)
|
||||||
|
|
||||||
|
3. **Task Completion:**
|
||||||
|
- Validate: THIS task's documents (extract formats from metadata) against THIS task's expectations
|
||||||
|
- Result: Used in workflow data for next task planning
|
||||||
|
|
||||||
|
4. **Workflow Completion:**
|
||||||
|
- Final validation: All documents (extract formats from metadata) meet original user request
|
||||||
|
- If missing: Create compensatory task
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status:** Ready for implementation after approval
|
||||||
|
|
||||||
|
|
@ -0,0 +1,654 @@
|
||||||
|
# Workflow Automation Feature - Analysis & Implementation Proposal
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document analyzes the requirements for the "Workflow Automation" feature, identifies gaps and unclear requirements, asks clarifying questions, and proposes an implementation solution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ What I Understand
|
||||||
|
|
||||||
|
### 1. **Core Concept**
|
||||||
|
- Users can define static workflows with placeholders
|
||||||
|
- Workflows can be tested manually
|
||||||
|
- Workflows can be launched as scheduled background events
|
||||||
|
- Each user manages their own automation definitions
|
||||||
|
|
||||||
|
### 2. **UI Components Required**
|
||||||
|
- **AutomationDefinition**: FormGeneric view for CRUD operations + execute + status display
|
||||||
|
- **AutomationItemEdit**: FormGeneric editor to edit placeholders of a record (simple form for placeholder values)
|
||||||
|
- **Admin Event Control Module**: Sysadmin module in admin section to view and control all running events (for debugging and control)
|
||||||
|
|
||||||
|
### 3. **Backend Components Required**
|
||||||
|
- **Pydantic Model**: `AutomationDefinition` in `datamodelChat.py`
|
||||||
|
- **Interface Methods**: CRUD operations + execute + automation event handler (syncs with scheduler)
|
||||||
|
- **Route Endpoints**: Standard REST endpoints following existing patterns
|
||||||
|
- **Automation Event Handler**: Called on CUD operations and app start to sync scheduler with active automations
|
||||||
|
|
||||||
|
### 4. **Model Fields Required**
|
||||||
|
- `label`: User-friendly name
|
||||||
|
- `schedule`: List of schedule options (user selects from predefined patterns)
|
||||||
|
- `template`: JSON string with placeholders (format: `{{KEY:PLACEHOLDER_NAME}}`)
|
||||||
|
- `plan`: Auto-generated from template every time (NOT stored, derived on-demand)
|
||||||
|
- `placeholders`: Dictionary of placeholder key/value pairs (e.g., `{'connectionName': 'MyConnection', 'webResearchUrl': 'https://...'}`)
|
||||||
|
- `active`: Boolean flag indicating if automation should be launched in event handler
|
||||||
|
- `eventId`: Event ID from event management system (None if not registered, readonly)
|
||||||
|
- `status`: Computed field (readonly, for UI) - indicates if event is registered and running
|
||||||
|
|
||||||
|
### 4.1 **Initial Template**
|
||||||
|
The initial template JSON is stored in `gateway/modules/workflows/processing/shared/automationTemplateInitial.json` and provides a default workflow structure with three tasks:
|
||||||
|
1. Web research (action 1) → `web_research_response`
|
||||||
|
2. SharePoint data extraction (action 2) → `sharepoint_data`
|
||||||
|
3. Document generation (action 3) using both previous results → `result_data`
|
||||||
|
|
||||||
|
This template includes placeholders that should be defined in the `placeholders` dictionary:
|
||||||
|
- `connectionName` → `{{KEY:connectionName}}` - SharePoint connection name
|
||||||
|
- `sharepointFolderNameSource` → `{{KEY:sharepointFolderNameSource}}` - SharePoint folder path
|
||||||
|
- `webResearchUrl` → `{{KEY:webResearchUrl}}` - URL for web research
|
||||||
|
- `webResearchPrompt` → `{{KEY:webResearchPrompt}}` - Prompt for web research
|
||||||
|
- `documentPrompt` → `{{KEY:documentPrompt}}` - Prompt for document generation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Clarified Requirements
|
||||||
|
|
||||||
|
### 1. **Schedule Field Structure**
|
||||||
|
**Answer**: B - A list of schedule options that the user can choose from.
|
||||||
|
|
||||||
|
**Implementation**: Select dropdown with predefined patterns:
|
||||||
|
```python
|
||||||
|
schedule: str = Field(
|
||||||
|
description="Schedule pattern",
|
||||||
|
frontend_type="select",
|
||||||
|
frontend_options=[
|
||||||
|
{"value": "0 */4 * * *", "label": {"en": "Every 4 hours", "fr": "Toutes les 4 heures"}},
|
||||||
|
{"value": "0 22 * * *", "label": {"en": "Daily at 22:00", "fr": "Quotidien à 22:00"}},
|
||||||
|
{"value": "0 10 * * 1", "label": {"en": "Weekly Monday 10:00", "fr": "Hebdomadaire lundi 10:00"}}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Template and Plan JSON Structure**
|
||||||
|
**Answer**: Yes, use the structure of `TaskPlan` from `datamodelChat.py`.
|
||||||
|
|
||||||
|
**Placeholder Format**: `{{KEY:PLACEHOLDER_NAME}}`
|
||||||
|
|
||||||
|
**Example structure**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"overview": "Brief description",
|
||||||
|
"userMessage": "User-friendly message",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"id": "task_1",
|
||||||
|
"objective": "Clear objective",
|
||||||
|
"actionList": [
|
||||||
|
{
|
||||||
|
"execMethod": "web",
|
||||||
|
"execAction": "research",
|
||||||
|
"execParameters": {
|
||||||
|
"url": "{{KEY:webResearchUrl}}",
|
||||||
|
"prompt": "{{KEY:PromptWebResearch}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. **Placeholder Replacement Mechanism**
|
||||||
|
**Answer**: Placeholder values are set in the automation definition. Replacement happens when plan is created from template.
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- Placeholder key/value pairs are stored in the `placeholders` dictionary
|
||||||
|
- Replacement happens automatically when generating plan from template (every time plan is needed)
|
||||||
|
- Plan is static after generation - regenerated from template each execution/event trigger
|
||||||
|
|
||||||
|
### 4. **Active Flag vs EventStart/EventStop**
|
||||||
|
**Answer**: Replace eventStart/eventStop with an `active` boolean field.
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- **`active` (bool)**: Indicates if automation should be launched in event handler
|
||||||
|
- **Automation Event Handler**:
|
||||||
|
- Triggered on any CUD operation (Create/Update/Delete) of AutomationDefinition
|
||||||
|
- Triggered on app start
|
||||||
|
- Checks all automations and syncs scheduler:
|
||||||
|
- If `active=True` and `eventId=None`: Register new event
|
||||||
|
- If `active=True` and `eventId` exists: Remove old event, register new (in case schedule changed)
|
||||||
|
- If `active=False` and `eventId` exists: Remove event from scheduler, set `eventId=None`
|
||||||
|
- **Execute**: Run workflow immediately (one-time execution, test mode) - does NOT affect `active` flag or scheduler
|
||||||
|
|
||||||
|
### 5. **AutomationItemEdit Module**
|
||||||
|
**Answer**: Simple formGeneric editor to edit placeholders of a record.
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- FormGeneric-based editor for placeholder values
|
||||||
|
- Allows editing the placeholder values stored in the automation definition
|
||||||
|
- Shows available placeholders and their current values
|
||||||
|
|
||||||
|
### 6. **Event Execution Context**
|
||||||
|
**Answer**: Use the user who created the automation (`_createdBy`), stored in the automation definition.
|
||||||
|
|
||||||
|
**Implementation**: When event fires, load automation, get `_createdBy` userId, create user context, and execute workflow.
|
||||||
|
|
||||||
|
### 7. **Database Storage**
|
||||||
|
**Answer**: Yes, store in database. Implement in existing `interfaceDbChatObjects`.
|
||||||
|
|
||||||
|
**Implementation**: Follow existing patterns in `interfaceDbChatObjects.py` for CRUD operations.
|
||||||
|
|
||||||
|
### 8. **Plan vs Template Relationship**
|
||||||
|
**Answer**: Plan to be auto-generated from `template` every time when executing/starting.
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
- `plan` is NOT stored in database
|
||||||
|
- Generated on-demand from `template` + placeholder values
|
||||||
|
- Regenerated each time workflow is executed (manual or scheduled)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Proposed Implementation Solution
|
||||||
|
|
||||||
|
### Phase 1: Data Model & Access Control
|
||||||
|
|
||||||
|
#### 1.1 Pydantic Model (`datamodelChat.py`)
|
||||||
|
```python
|
||||||
|
class AutomationDefinition(BaseModel):
|
||||||
|
id: str = Field(
|
||||||
|
default_factory=lambda: str(uuid.uuid4()),
|
||||||
|
description="Primary key",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=False
|
||||||
|
)
|
||||||
|
mandateId: str = Field(
|
||||||
|
description="Mandate ID",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=False
|
||||||
|
)
|
||||||
|
label: str = Field(
|
||||||
|
description="User-friendly name",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_required=True
|
||||||
|
)
|
||||||
|
schedule: str = Field(
|
||||||
|
description="Cron schedule pattern",
|
||||||
|
frontend_type="select",
|
||||||
|
frontend_options=[
|
||||||
|
{"value": "0 */4 * * *", "label": {"en": "Every 4 hours", "fr": "Toutes les 4 heures"}},
|
||||||
|
{"value": "0 22 * * *", "label": {"en": "Daily at 22:00", "fr": "Quotidien à 22:00"}},
|
||||||
|
{"value": "0 10 * * 1", "label": {"en": "Weekly Monday 10:00", "fr": "Hebdomadaire lundi 10:00"}}
|
||||||
|
],
|
||||||
|
frontend_required=True
|
||||||
|
)
|
||||||
|
template: str = Field(
|
||||||
|
description="JSON template with placeholders (format: {{KEY:PLACEHOLDER_NAME}})",
|
||||||
|
frontend_type="textarea",
|
||||||
|
frontend_required=True
|
||||||
|
)
|
||||||
|
placeholders: Dict[str, str] = Field(
|
||||||
|
default_factory=dict,
|
||||||
|
description="Dictionary of placeholder key/value pairs (e.g., {'connectionName': 'MyConnection', 'sharepointFolderNameSource': '/folder/path', 'webResearchUrl': 'https://...', 'webResearchPrompt': '...', 'documentPrompt': '...'})",
|
||||||
|
frontend_type="text"
|
||||||
|
)
|
||||||
|
active: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="Whether automation should be launched in event handler",
|
||||||
|
frontend_type="checkbox",
|
||||||
|
frontend_required=False
|
||||||
|
)
|
||||||
|
eventId: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description="Event ID from event management (None if not registered)",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=False
|
||||||
|
)
|
||||||
|
status: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description="Status: 'active' if event is registered, 'inactive' if not (computed, readonly)",
|
||||||
|
frontend_type="text",
|
||||||
|
frontend_readonly=True,
|
||||||
|
frontend_required=False
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**:
|
||||||
|
- `plan` is NOT stored - it's generated on-demand from `template` + `placeholders` dictionary
|
||||||
|
- `status` is computed for UI display (based on `eventId` presence)
|
||||||
|
- `placeholders` dictionary stores all placeholder key/value pairs (e.g., `{'connectionName': 'MyConnection', 'webResearchUrl': 'https://...'}`)
|
||||||
|
|
||||||
|
#### 1.2 Access Control
|
||||||
|
Add to `interfaceDbChatAccess.py`:
|
||||||
|
```python
|
||||||
|
elif table_name == "AutomationDefinition":
|
||||||
|
# Users see only their own automation definitions
|
||||||
|
filtered_records = [
|
||||||
|
r for r in recordset
|
||||||
|
if r.get("mandateId","-") == self.mandateId and r.get("_createdBy") == self.userId
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3 Status Field Computation
|
||||||
|
The `status` field is computed when retrieving automation definitions:
|
||||||
|
- If `eventId` is not None and not empty: status = "active" (event registered)
|
||||||
|
- If `eventId` is None or empty: status = "inactive" (no event registered)
|
||||||
|
|
||||||
|
### Phase 2: Interface Methods
|
||||||
|
|
||||||
|
#### 2.1 Add to `interfaceDbChatObjects.py`
|
||||||
|
```python
|
||||||
|
class ChatObjects:
|
||||||
|
# CRUD methods
|
||||||
|
def getAllAutomationDefinitions(self, pagination: Optional[PaginationParams] = None) -> Union[List[Dict], PaginatedResult]:
|
||||||
|
"""Get all automation definitions for current user, with computed status field"""
|
||||||
|
|
||||||
|
def getAutomationDefinition(self, automationId: str) -> Optional[Dict]:
|
||||||
|
"""Get single automation definition with computed status"""
|
||||||
|
|
||||||
|
def createAutomationDefinition(self, automation: Dict) -> Dict:
|
||||||
|
"""Create new automation definition, then trigger automation event handler"""
|
||||||
|
|
||||||
|
def updateAutomationDefinition(self, automationId: str, automationData: Dict) -> Dict:
|
||||||
|
"""Update automation definition, then trigger automation event handler"""
|
||||||
|
|
||||||
|
def deleteAutomationDefinition(self, automationId: str) -> bool:
|
||||||
|
"""Delete automation definition, then trigger automation event handler"""
|
||||||
|
|
||||||
|
# Action methods
|
||||||
|
async def executeAutomation(self, automationId: str) -> ChatWorkflow:
|
||||||
|
"""Execute automation immediately (test mode) - generates plan from template"""
|
||||||
|
|
||||||
|
# Automation Event Handler (called on CUD operations and app start)
|
||||||
|
async def syncAutomationEvents(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Automation event handler - syncs scheduler with all active automations.
|
||||||
|
Called:
|
||||||
|
- After any CUD operation on AutomationDefinition
|
||||||
|
- On app start
|
||||||
|
Logic:
|
||||||
|
- For each automation with active=True:
|
||||||
|
- If eventId is None: register new event
|
||||||
|
- If eventId exists: remove old event, register new (handles schedule changes)
|
||||||
|
- For each automation with active=False:
|
||||||
|
- If eventId exists: remove event, set eventId=None
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 Automation Event Handler Implementation
|
||||||
|
```python
|
||||||
|
async def syncAutomationEvents(self) -> Dict[str, Any]:
|
||||||
|
"""Sync event scheduler with automation definitions"""
|
||||||
|
from modules.shared.eventManagement import eventManager
|
||||||
|
|
||||||
|
# Get all automation definitions (for current mandate)
|
||||||
|
automations = self.db.getRecordset(AutomationDefinition)
|
||||||
|
filtered = self._uam(AutomationDefinition, automations)
|
||||||
|
|
||||||
|
registered_events = {}
|
||||||
|
|
||||||
|
for automation in filtered:
|
||||||
|
automation_id = automation.get("id")
|
||||||
|
is_active = automation.get("active", False)
|
||||||
|
current_event_id = automation.get("eventId")
|
||||||
|
schedule = automation.get("schedule")
|
||||||
|
|
||||||
|
# Parse schedule to cron kwargs
|
||||||
|
cron_kwargs = self._parseScheduleToCron(schedule)
|
||||||
|
|
||||||
|
if is_active:
|
||||||
|
# Remove existing event if present (handles schedule changes)
|
||||||
|
if current_event_id:
|
||||||
|
eventManager.remove(current_event_id)
|
||||||
|
|
||||||
|
# Register new event
|
||||||
|
new_event_id = f"automation.{automation_id}"
|
||||||
|
eventManager.registerCron(
|
||||||
|
jobId=new_event_id,
|
||||||
|
func=self._createAutomationEventHandler(automation_id),
|
||||||
|
cronKwargs=cron_kwargs,
|
||||||
|
replaceExisting=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update automation with new eventId
|
||||||
|
self.db.updateRecord(AutomationDefinition, automation_id, {"eventId": new_event_id})
|
||||||
|
registered_events[automation_id] = new_event_id
|
||||||
|
else:
|
||||||
|
# Remove event if exists
|
||||||
|
if current_event_id:
|
||||||
|
eventManager.remove(current_event_id)
|
||||||
|
self.db.updateRecord(AutomationDefinition, automation_id, {"eventId": None})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"synced": len(registered_events),
|
||||||
|
"events": registered_events
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.3 Event Handler Function Factory
|
||||||
|
```python
|
||||||
|
def _createAutomationEventHandler(self, automationId: str):
|
||||||
|
"""Create event handler function for a specific automation"""
|
||||||
|
async def handler():
|
||||||
|
# Load automation
|
||||||
|
automation = self.getAutomationDefinition(automationId)
|
||||||
|
if not automation or not automation.get("active"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get user who created automation
|
||||||
|
creator_user_id = automation.get("_createdBy")
|
||||||
|
creator_user = self._getUserById(creator_user_id)
|
||||||
|
|
||||||
|
# Execute workflow
|
||||||
|
await self.executeAutomation(automationId)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Route Endpoints
|
||||||
|
|
||||||
|
#### 3.1 Create `routeDataAutomation.py`
|
||||||
|
```python
|
||||||
|
router = APIRouter(prefix="/api/automations", tags=["Automation"])
|
||||||
|
|
||||||
|
@router.get("", response_model=PaginatedResponse[AutomationDefinition])
|
||||||
|
async def get_automations(...): # List with pagination, includes computed status
|
||||||
|
|
||||||
|
@router.post("", response_model=AutomationDefinition)
|
||||||
|
async def create_automation(...): # Create, then triggers syncAutomationEvents
|
||||||
|
|
||||||
|
@router.get("/{automationId}", response_model=AutomationDefinition)
|
||||||
|
async def get_automation(...): # Get one with computed status
|
||||||
|
|
||||||
|
@router.put("/{automationId}", response_model=AutomationDefinition)
|
||||||
|
async def update_automation(...): # Update, then triggers syncAutomationEvents
|
||||||
|
|
||||||
|
@router.delete("/{automationId}")
|
||||||
|
async def delete_automation(...): # Delete, then triggers syncAutomationEvents
|
||||||
|
|
||||||
|
@router.post("/{automationId}/execute")
|
||||||
|
async def execute_automation(...): # Execute immediately (test mode)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 Create `routeAdminAutomationEvents.py` (Sysadmin Module)
|
||||||
|
```python
|
||||||
|
router = APIRouter(prefix="/api/admin/automation-events", tags=["Admin Automation Events"])
|
||||||
|
|
||||||
|
@router.get("", response_model=List[Dict])
|
||||||
|
async def get_all_automation_events(...): # Get all events across all mandates (sysadmin only)
|
||||||
|
|
||||||
|
@router.post("/sync")
|
||||||
|
async def sync_all_automation_events(...): # Manually trigger sync for all automations (sysadmin only)
|
||||||
|
|
||||||
|
@router.post("/{eventId}/remove")
|
||||||
|
async def remove_event(...): # Manually remove event from scheduler (sysadmin only)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Placeholder Replacement & Execution
|
||||||
|
|
||||||
|
#### 4.1 Placeholder Replacement Logic
|
||||||
|
```python
|
||||||
|
def replacePlaceholders(template: str, automation: Dict) -> str:
|
||||||
|
"""
|
||||||
|
Replace placeholders in template with actual values from automation.
|
||||||
|
|
||||||
|
Placeholder format: {{KEY:PLACEHOLDER_NAME}}
|
||||||
|
Values come from automation.placeholders dictionary
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
result = template
|
||||||
|
placeholders = automation.get("placeholders", {})
|
||||||
|
|
||||||
|
# Replace all {{KEY:PLACEHOLDER_NAME}} patterns
|
||||||
|
for placeholderName, value in placeholders.items():
|
||||||
|
pattern = f"{{{{KEY:{placeholderName}}}}}"
|
||||||
|
result = result.replace(pattern, str(value))
|
||||||
|
|
||||||
|
return result
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 Execution Function
|
||||||
|
```python
|
||||||
|
async def executeAutomation(self, automationId: str) -> ChatWorkflow:
|
||||||
|
"""Execute automation workflow with placeholder replacement"""
|
||||||
|
# 1. Load automation definition
|
||||||
|
automation = self.getAutomationDefinition(automationId)
|
||||||
|
if not automation:
|
||||||
|
raise ValueError(f"Automation {automationId} not found")
|
||||||
|
|
||||||
|
# 2. Replace placeholders in template to generate plan
|
||||||
|
planJson = replacePlaceholders(automation["template"], automation)
|
||||||
|
plan = json.loads(planJson)
|
||||||
|
|
||||||
|
# 3. Get user who created automation
|
||||||
|
creator_user_id = automation.get("_createdBy")
|
||||||
|
creator_user = self.access.currentUser # Need to get user by ID
|
||||||
|
|
||||||
|
# 4. Create UserInputRequest from plan
|
||||||
|
# Convert plan to UserInputRequest format
|
||||||
|
userInput = UserInputRequest(
|
||||||
|
prompt=self._planToPrompt(plan),
|
||||||
|
listFileId=[],
|
||||||
|
userLanguage=creator_user.language
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5. Start workflow using WorkflowManager
|
||||||
|
from modules.features.chatPlayground.mainChatPlayground import chatStart
|
||||||
|
from modules.datamodels.datamodelChat import WorkflowModeEnum
|
||||||
|
|
||||||
|
workflow = await chatStart(
|
||||||
|
currentUser=creator_user,
|
||||||
|
userInput=userInput,
|
||||||
|
workflowMode=WorkflowModeEnum.WORKFLOW_TEMPLATE,
|
||||||
|
workflowId=None
|
||||||
|
)
|
||||||
|
|
||||||
|
return workflow
|
||||||
|
|
||||||
|
def _planToPrompt(self, plan: Dict) -> str:
|
||||||
|
"""Convert plan structure to prompt string for workflow execution"""
|
||||||
|
# Extract user message or generate from tasks
|
||||||
|
return plan.get("userMessage", plan.get("overview", "Execute automation workflow"))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.3 Schedule Parsing
|
||||||
|
```python
|
||||||
|
def _parseScheduleToCron(self, schedule: str) -> Dict[str, Any]:
|
||||||
|
"""Parse schedule string to cron kwargs for APScheduler"""
|
||||||
|
# schedule format: "0 */4 * * *" (cron string)
|
||||||
|
# Parse to: {"minute": "0,20,40", "hour": "*", "day": "*", "month": "*", "day_of_week": "*"}
|
||||||
|
parts = schedule.split()
|
||||||
|
if len(parts) != 5:
|
||||||
|
raise ValueError(f"Invalid schedule format: {schedule}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"minute": parts[0],
|
||||||
|
"hour": parts[1],
|
||||||
|
"day": parts[2],
|
||||||
|
"month": parts[3],
|
||||||
|
"day_of_week": parts[4]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.4 App Start Integration
|
||||||
|
In app startup (e.g., `app.py` or main startup file):
|
||||||
|
```python
|
||||||
|
async def on_app_start():
|
||||||
|
"""Called when app starts"""
|
||||||
|
from modules.interfaces.interfaceDbChatObjects import getInterface
|
||||||
|
from modules.datamodels.datamodelUam import User
|
||||||
|
|
||||||
|
# Get event user or system user
|
||||||
|
eventUser = getEventUser() # Get system/event user
|
||||||
|
interface = getInterface(eventUser)
|
||||||
|
|
||||||
|
# Sync all automation events
|
||||||
|
await interface.syncAutomationEvents()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: UI Integration
|
||||||
|
|
||||||
|
#### 5.1 FormGeneric Module Configuration
|
||||||
|
```javascript
|
||||||
|
// frontend_agents/public/js/modules/formAutomationDefinition.js
|
||||||
|
const automationConfig = {
|
||||||
|
entityType: "automation",
|
||||||
|
apiEndpoint: {
|
||||||
|
get: () => api.get("/api/automations"),
|
||||||
|
create: (data) => api.post("/api/automations", data),
|
||||||
|
update: (id, data) => api.put(`/api/automations/${id}`, data),
|
||||||
|
delete: (id) => api.delete(`/api/automations/${id}`)
|
||||||
|
},
|
||||||
|
customActions: [
|
||||||
|
{
|
||||||
|
label: "Execute",
|
||||||
|
action: async (item) => {
|
||||||
|
await api.post(`/api/automations/${item.id}/execute`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Status field is displayed in table (readonly, computed)
|
||||||
|
// active field is shown as checkbox in form
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2 AutomationItemEdit Module
|
||||||
|
```javascript
|
||||||
|
// frontend_agents/public/js/modules/formAutomationItemEdit.js
|
||||||
|
// Simple formGeneric editor for placeholder values
|
||||||
|
const automationItemEditConfig = {
|
||||||
|
entityType: "automation",
|
||||||
|
entityId: null, // Set dynamically when editing
|
||||||
|
fields: [
|
||||||
|
// Dynamically generated from automation.placeholders dictionary keys
|
||||||
|
// Each placeholder key becomes a text input field
|
||||||
|
// Values stored directly in automation.placeholders[placeholderName]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
- FormGeneric-based editor for editing placeholder values
|
||||||
|
- Dynamically generates form fields from `automation.placeholders` dictionary keys
|
||||||
|
- Shows placeholder names and allows editing their values
|
||||||
|
- Values stored directly in `automation.placeholders` dictionary (key/value pairs)
|
||||||
|
|
||||||
|
#### 5.3 Admin Event Control Module
|
||||||
|
```javascript
|
||||||
|
// frontend_agents/public/js/modules/adminAutomationEvents.js
|
||||||
|
// Sysadmin module in admin section of navigator
|
||||||
|
const adminEventsConfig = {
|
||||||
|
title: "Automation Events",
|
||||||
|
apiEndpoint: {
|
||||||
|
get: () => api.get("/api/admin/automation-events"),
|
||||||
|
sync: () => api.post("/api/admin/automation-events/sync"),
|
||||||
|
remove: (eventId) => api.post(`/api/admin/automation-events/${eventId}/remove`)
|
||||||
|
},
|
||||||
|
// Shows all registered events across all mandates
|
||||||
|
// Allows manual sync and event removal for debugging
|
||||||
|
};
|
||||||
|
```
|
||||||
|
- Shows all running automation events (sysadmin only)
|
||||||
|
- Displays event ID, automation ID, schedule, status
|
||||||
|
- Allows manual sync of all events
|
||||||
|
- Allows manual removal of specific events (for debugging)
|
||||||
|
- Located in admin section of navigator
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Additional Considerations
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
- What happens if placeholder values are missing?
|
||||||
|
- What if template JSON is invalid?
|
||||||
|
- What if workflow execution fails?
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
- Validate cron schedule format
|
||||||
|
- Validate template JSON structure
|
||||||
|
- Validate placeholder names match defined list
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Ensure users can only execute their own automations
|
||||||
|
- Validate placeholder values (sanitize inputs)
|
||||||
|
- Prevent injection attacks in placeholder replacement
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
- Log automation executions
|
||||||
|
- Track success/failure rates
|
||||||
|
- Monitor event scheduler status
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Implementation Checklist
|
||||||
|
|
||||||
|
### Phase 1: Data Model ✅
|
||||||
|
- [x] Create `AutomationDefinition` Pydantic model in `datamodelChat.py`
|
||||||
|
- [x] Add `active` boolean field (replaces eventStart/eventStop)
|
||||||
|
- [x] Add `status` computed field for UI
|
||||||
|
- [x] Add `placeholders` dict field (key/value pairs)
|
||||||
|
- [x] Add `registerModelLabels` for translations
|
||||||
|
- [x] Update access control in `interfaceDbChatAccess.py`
|
||||||
|
|
||||||
|
### Phase 2: Interface Methods ✅
|
||||||
|
- [ ] Add CRUD methods to `interfaceDbChatObjects.py`
|
||||||
|
- [ ] Add `executeAutomation` method
|
||||||
|
- [ ] Add `syncAutomationEvents` method (automation event handler)
|
||||||
|
- [ ] Add `replacePlaceholders` helper function
|
||||||
|
- [ ] Add `_parseScheduleToCron` helper function
|
||||||
|
- [ ] Add status computation in get methods
|
||||||
|
|
||||||
|
### Phase 3: Route Endpoints ✅
|
||||||
|
- [ ] Create `routeDataAutomation.py` with CRUD + execute endpoints
|
||||||
|
- [ ] Add sync trigger after CUD operations
|
||||||
|
- [ ] Create `routeAdminAutomationEvents.py` for sysadmin module
|
||||||
|
- [ ] Register routes in `app.py`
|
||||||
|
|
||||||
|
### Phase 4: Event Integration ✅
|
||||||
|
- [ ] Implement `syncAutomationEvents` logic
|
||||||
|
- [ ] Create event handler factory function
|
||||||
|
- [ ] Add app startup integration
|
||||||
|
- [ ] Test event registration/removal
|
||||||
|
|
||||||
|
### Phase 5: UI Components ✅
|
||||||
|
- [ ] Create `formAutomationDefinition.js` module
|
||||||
|
- [ ] Create `formAutomationItemEdit.js` module
|
||||||
|
- [ ] Create `adminAutomationEvents.js` module (sysadmin)
|
||||||
|
- [ ] Add to navigator admin section
|
||||||
|
|
||||||
|
### Phase 6: Testing ✅
|
||||||
|
- [ ] Test CRUD operations
|
||||||
|
- [ ] Test placeholder replacement
|
||||||
|
- [ ] Test execute endpoint
|
||||||
|
- [ ] Test active flag changes trigger sync
|
||||||
|
- [ ] Test event registration/removal
|
||||||
|
- [ ] Test app startup sync
|
||||||
|
- [ ] Test sysadmin module
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ All Questions Answered
|
||||||
|
|
||||||
|
1. **Schedule Format**: ✅ B - List of schedule options (select dropdown)
|
||||||
|
2. **Template Structure**: ✅ Use TaskPlan structure with `{{KEY:PLACEHOLDER_NAME}}` format
|
||||||
|
3. **Placeholder Source**: ✅ Values stored in `placeholders` dictionary (key/value pairs) in automation definition
|
||||||
|
4. **Plan Storage**: ✅ Auto-generated from template every time (not stored)
|
||||||
|
5. **Execute Behavior**: ✅ Execute runs workflow immediately (test mode)
|
||||||
|
6. **Active Flag**: ✅ Boolean `active` field, sync triggered on CUD operations and app start
|
||||||
|
7. **User Context**: ✅ Use `_createdBy` user for scheduled executions
|
||||||
|
8. **Database Storage**: ✅ Implement in `interfaceDbChatObjects`
|
||||||
|
9. **Placeholder Format**: ✅ `{{KEY:PLACEHOLDER_NAME}}`
|
||||||
|
10. **Status Field**: ✅ Computed readonly field showing if event is registered
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Ready to implement:
|
||||||
|
1. ✅ Pydantic model with all clarified fields
|
||||||
|
2. ✅ Interface methods with event sync logic
|
||||||
|
3. ✅ Route endpoints with CUD + execute
|
||||||
|
4. ✅ Placeholder replacement with `{{KEY:NAME}}` format
|
||||||
|
5. ✅ Event management integration with `active` flag
|
||||||
|
6. ✅ UI modules for automation management
|
||||||
|
7. ✅ Admin module for event control
|
||||||
|
|
||||||
Loading…
Reference in a new issue