# Copyright (c) 2025 Patrick Motsch # All rights reserved. """GraphicalEditor models with Auto-prefix: AutoWorkflow, AutoVersion, AutoRun, AutoStepLog, AutoTask.""" from enum import Enum from typing import Dict, Any, List, Optional from pydantic import BaseModel, Field from modules.datamodels.datamodelBase import PowerOnModel from modules.shared.i18nRegistry import i18nModel import uuid # --------------------------------------------------------------------------- # Enums # --------------------------------------------------------------------------- class AutoWorkflowStatus(str, Enum): DRAFT = "draft" PUBLISHED = "published" ARCHIVED = "archived" class AutoRunStatus(str, Enum): RUNNING = "running" PAUSED = "paused" COMPLETED = "completed" FAILED = "failed" CANCELLED = "cancelled" class AutoStepStatus(str, Enum): PENDING = "pending" RUNNING = "running" COMPLETED = "completed" FAILED = "failed" SKIPPED = "skipped" class AutoTaskStatus(str, Enum): PENDING = "pending" COMPLETED = "completed" CANCELLED = "cancelled" EXPIRED = "expired" class AutoTemplateScope(str, Enum): USER = "user" INSTANCE = "instance" MANDATE = "mandate" SYSTEM = "system" # --------------------------------------------------------------------------- # AutoWorkflow # --------------------------------------------------------------------------- @i18nModel("Workflow") class AutoWorkflow(PowerOnModel): id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "ID"}, ) mandateId: str = Field( description="Mandate ID", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Mandanten-ID", "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"}, }, ) featureInstanceId: str = Field( description="Feature instance ID (GE owner instance / RBAC scope)", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Feature-Instanz-ID", "fk_target": {"db": "poweron_app", "table": "FeatureInstance", "labelField": "label"}, }, ) targetFeatureInstanceId: Optional[str] = Field( default=None, description="Target feature instance for execution data scope. NULL for templates, mandatory for non-templates.", json_schema_extra={ "frontend_type": "select", "frontend_readonly": False, "frontend_required": False, "label": "Ziel-Instanz", "fk_target": {"db": "poweron_app", "table": "FeatureInstance", "labelField": "label"}, }, ) label: str = Field( description="User-friendly workflow name", json_schema_extra={"frontend_type": "text", "frontend_required": True, "label": "Bezeichnung"}, ) description: Optional[str] = Field( default=None, description="Workflow description", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Beschreibung"}, ) tags: List[str] = Field( default_factory=list, description="Tags for categorization", json_schema_extra={"frontend_type": "tags", "frontend_required": False, "label": "Tags"}, ) isTemplate: bool = Field( default=False, description="Whether this workflow is a template", json_schema_extra={ "frontend_type": "checkbox", "frontend_required": False, "label": "Ist Vorlage", "frontend_format_labels": ["Ja", "-", "Nein"], }, ) templateSourceId: Optional[str] = Field( default=None, description="ID of the template this workflow was created from", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Vorlagen-Quelle", # Soft FK: holds either a real AutoWorkflow.id (UUID, when copied # from a stored template) OR an in-code sentinel like # "trustee-receipt-import" (when bootstrapped from # featureModule.getTemplateWorkflows()). Sentinel values do not # exist as DB rows by design — orphan cleanup MUST skip this column. "fk_target": { "db": "poweron_graphicaleditor", "table": "AutoWorkflow", "labelField": "label", "softFk": True, }, }, ) templateScope: Optional[str] = Field( default=None, description="Template scope: user, instance, mandate, system (AutoTemplateScope)", json_schema_extra={ "frontend_type": "select", "frontend_required": False, "label": "Vorlagen-Bereich", "frontend_options": [ {"value": "user", "label": "Meine"}, {"value": "instance", "label": "Instanz"}, {"value": "mandate", "label": "Mandant"}, {"value": "system", "label": "System"}, ], }, ) sharedReadOnly: bool = Field( default=False, description="If true, shared template is read-only for non-owners", json_schema_extra={ "frontend_type": "checkbox", "frontend_required": False, "label": "Freigabe nur-lesen", "frontend_format_labels": ["Ja", "-", "Nein"], }, ) currentVersionId: Optional[str] = Field( default=None, description="ID of the currently published AutoVersion", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Aktuelle Version", "fk_target": {"db": "poweron_graphicaleditor", "table": "AutoVersion", "labelField": "versionNumber"}, }, ) active: bool = Field( default=True, description="Whether workflow is active", json_schema_extra={ "frontend_type": "checkbox", "frontend_required": False, "label": "Aktiv", "frontend_format_labels": ["Ja", "-", "Nein"], }, ) eventId: Optional[str] = Field( default=None, description="Scheduler event ID for incremental sync", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Event-ID"}, ) notifyOnFailure: bool = Field( default=True, description="Send notification (in-app + email) when a run fails", json_schema_extra={ "frontend_type": "checkbox", "frontend_required": False, "label": "Bei Fehler benachrichtigen", "frontend_format_labels": ["Ja", "-", "Nein"], }, ) # Legacy fields kept for backward compatibility during transition graph: Dict[str, Any] = Field( default_factory=dict, description="Graph with nodes and connections (legacy; prefer AutoVersion.graph)", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Graph"}, ) invocations: List[Dict[str, Any]] = Field( default_factory=list, description="Entry points / starts (manual, form, schedule, webhook, ...)", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Starts / Einstiegspunkte"}, ) # --------------------------------------------------------------------------- # AutoVersion # --------------------------------------------------------------------------- @i18nModel("Workflow-Version") class AutoVersion(PowerOnModel): id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "ID"}, ) workflowId: str = Field( description="FK -> AutoWorkflow", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Workflow-ID", "fk_target": {"db": "poweron_graphicaleditor", "table": "AutoWorkflow", "labelField": "label"}, }, ) versionNumber: int = Field( default=1, description="Incrementing version number", json_schema_extra={"frontend_type": "number", "frontend_readonly": True, "frontend_required": False, "label": "Version"}, ) status: str = Field( default=AutoWorkflowStatus.DRAFT.value, description="Version status: draft, published, archived", json_schema_extra={ "frontend_type": "select", "frontend_required": False, "label": "Status", "frontend_options": [ {"value": "draft", "label": "Entwurf"}, {"value": "published", "label": "Veröffentlicht"}, {"value": "archived", "label": "Archiviert"}, ], }, ) graph: Dict[str, Any] = Field( default_factory=dict, description="Graph with nodes and connections (incl. node parameters)", json_schema_extra={"frontend_type": "textarea", "frontend_required": True, "label": "Graph"}, ) invocations: List[Dict[str, Any]] = Field( default_factory=list, description="Entry points / starts for this version", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Einstiegspunkte"}, ) publishedAt: Optional[float] = Field( default=None, description="Timestamp when version was published", json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False, "label": "Veröffentlicht am"}, ) publishedBy: Optional[str] = Field( default=None, description="User ID who published this version", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Veröffentlicht von", "fk_target": {"db": "poweron_app", "table": "UserInDB", "labelField": "username"}, }, ) # --------------------------------------------------------------------------- # AutoRun # --------------------------------------------------------------------------- @i18nModel("Workflow-Ausführung") class AutoRun(PowerOnModel): id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "ID"}, ) workflowId: str = Field( description="Workflow ID", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Workflow-ID", "fk_target": {"db": "poweron_graphicaleditor", "table": "AutoWorkflow", "labelField": "label"}, }, ) label: Optional[str] = Field( default=None, description="Human-readable run label, set at creation from workflow name or caller", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Bezeichnung"}, ) mandateId: Optional[str] = Field( default=None, description="Mandate ID for cross-feature querying", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Mandanten-ID", "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"}, }, ) ownerId: Optional[str] = Field( default=None, description="User ID who triggered this run", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Auslöser", "fk_target": {"db": "poweron_app", "table": "UserInDB", "labelField": "username"}, }, ) versionId: Optional[str] = Field( default=None, description="AutoVersion ID used for this run", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Versions-ID", "fk_target": {"db": "poweron_graphicaleditor", "table": "AutoVersion", "labelField": "versionNumber"}, }, ) status: str = Field( default=AutoRunStatus.RUNNING.value, description="Status: running, paused, completed, failed, cancelled", json_schema_extra={ "frontend_type": "select", "frontend_required": False, "label": "Status", "frontend_options": [ {"value": "running", "label": "Läuft"}, {"value": "paused", "label": "Pausiert"}, {"value": "completed", "label": "Abgeschlossen"}, {"value": "failed", "label": "Fehlgeschlagen"}, {"value": "cancelled", "label": "Abgebrochen"}, ], }, ) trigger: Dict[str, Any] = Field( default_factory=dict, description="Trigger info (type, entryPointId, payload, etc.)", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Auslöser"}, ) startedAt: Optional[float] = Field( default=None, description="Run start timestamp", json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False, "label": "Gestartet am"}, ) completedAt: Optional[float] = Field( default=None, description="Run completion timestamp", json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False, "label": "Abgeschlossen am"}, ) nodeOutputs: Dict[str, Any] = Field( default_factory=dict, description="Outputs from executed nodes", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Node-Ausgaben"}, ) currentNodeId: Optional[str] = Field( default=None, description="Node ID when paused (human task / email wait)", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "Aktueller Knoten"}, ) resumeContext: Dict[str, Any] = Field( default_factory=dict, description="Context for resume (connectionMap, inputSources, etc.)", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Wiederaufnahme-Kontext"}, ) error: Optional[str] = Field( default=None, description="Error message if failed", json_schema_extra={"frontend_type": "textarea", "frontend_readonly": True, "frontend_required": False, "label": "Fehler"}, ) costTokens: int = Field( default=0, description="Total tokens consumed by AI nodes", json_schema_extra={"frontend_type": "number", "frontend_readonly": True, "frontend_required": False, "label": "Verbrauchte Tokens"}, ) costCredits: float = Field( default=0.0, description="Total credits consumed", json_schema_extra={"frontend_type": "number", "frontend_readonly": True, "frontend_required": False, "label": "Verbrauchte Credits"}, ) # --------------------------------------------------------------------------- # AutoStepLog # --------------------------------------------------------------------------- @i18nModel("Schritt-Protokoll") class AutoStepLog(PowerOnModel): id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "ID"}, ) runId: str = Field( description="FK -> AutoRun", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Lauf-ID", "fk_target": {"db": "poweron_graphicaleditor", "table": "AutoRun", "labelField": "label"}, }, ) nodeId: str = Field( description="Node ID in the graph", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Knoten-ID"}, ) nodeType: str = Field( description="Node type (e.g. ai.chat, email.send)", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Knotentyp"}, ) status: str = Field( default=AutoStepStatus.PENDING.value, description="Step status: pending, running, completed, failed, skipped", json_schema_extra={ "frontend_type": "select", "frontend_required": False, "label": "Status", "frontend_options": [ {"value": "pending", "label": "Wartend"}, {"value": "running", "label": "Läuft"}, {"value": "completed", "label": "Abgeschlossen"}, {"value": "failed", "label": "Fehlgeschlagen"}, {"value": "skipped", "label": "Übersprungen"}, ], }, ) inputSnapshot: Dict[str, Any] = Field( default_factory=dict, description="Snapshot of inputs at execution time", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Eingabe-Snapshot"}, ) output: Dict[str, Any] = Field( default_factory=dict, description="Node output", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Ausgabe"}, ) error: Optional[str] = Field( default=None, description="Error message if step failed", json_schema_extra={"frontend_type": "textarea", "frontend_readonly": True, "frontend_required": False, "label": "Fehler"}, ) startedAt: Optional[float] = Field( default=None, description="Step start timestamp", json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False, "label": "Gestartet am"}, ) completedAt: Optional[float] = Field( default=None, description="Step completion timestamp", json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False, "label": "Abgeschlossen am"}, ) durationMs: Optional[int] = Field( default=None, description="Execution duration in milliseconds", json_schema_extra={"frontend_type": "number", "frontend_readonly": True, "frontend_required": False, "label": "Dauer (ms)"}, ) tokensUsed: int = Field( default=0, description="Tokens consumed by this step", json_schema_extra={"frontend_type": "number", "frontend_readonly": True, "frontend_required": False, "label": "Verbrauchte Tokens"}, ) retryCount: int = Field( default=0, description="Number of retries executed", json_schema_extra={"frontend_type": "number", "frontend_readonly": True, "frontend_required": False, "label": "Wiederholungen"}, ) # --------------------------------------------------------------------------- # AutoTask # --------------------------------------------------------------------------- @i18nModel("Aufgabe") class AutoTask(PowerOnModel): id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "label": "ID"}, ) runId: str = Field( description="FK -> AutoRun", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Lauf-ID", "fk_target": {"db": "poweron_graphicaleditor", "table": "AutoRun", "labelField": "label"}, }, ) workflowId: str = Field( description="Workflow ID", json_schema_extra={ "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Workflow-ID", "fk_target": {"db": "poweron_graphicaleditor", "table": "AutoWorkflow", "labelField": "label"}, }, ) nodeId: str = Field( description="Node ID in the graph", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Knoten-ID"}, ) nodeType: str = Field( description="Node type: form, approval, upload, comment, review, selection, confirmation", json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "label": "Knotentyp"}, ) config: Dict[str, Any] = Field( default_factory=dict, description="Node config (form schema, approval text, etc.)", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Konfiguration"}, ) assigneeId: Optional[str] = Field( default=None, description="User ID assigned to complete the task", json_schema_extra={ "frontend_type": "text", "frontend_readonly": False, "frontend_required": False, "label": "Zugewiesen an", "fk_target": {"db": "poweron_app", "table": "UserInDB", "labelField": "username"}, }, ) status: str = Field( default=AutoTaskStatus.PENDING.value, description="Status: pending, completed, cancelled, expired", json_schema_extra={ "frontend_type": "select", "frontend_required": False, "label": "Status", "frontend_options": [ {"value": "pending", "label": "Wartend"}, {"value": "completed", "label": "Abgeschlossen"}, {"value": "cancelled", "label": "Abgebrochen"}, {"value": "expired", "label": "Abgelaufen"}, ], }, ) result: Optional[Dict[str, Any]] = Field( default=None, description="Task result (form data, approval decision, etc.)", json_schema_extra={"frontend_type": "textarea", "frontend_required": False, "label": "Ergebnis"}, ) expiresAt: Optional[float] = Field( default=None, description="Expiration timestamp for the task", json_schema_extra={"frontend_type": "timestamp", "frontend_required": False, "label": "Läuft ab am"}, ) # --------------------------------------------------------------------------- # Backward-compatible aliases for transition period # --------------------------------------------------------------------------- Automation2Workflow = AutoWorkflow Automation2WorkflowRun = AutoRun Automation2HumanTask = AutoTask