# Copyright (c) 2025 Patrick Motsch # All rights reserved. """Feature models: Feature definitions, instances, data sources, and shared feature types.""" import uuid from typing import Optional, Dict, Any, List from pydantic import BaseModel, Field from modules.datamodels.datamodelBase import PowerOnModel from modules.shared.i18nRegistry import i18nModel from modules.datamodels.datamodelUtils import TextMultilingual # --------------------------------------------------------------------------- # Feature & FeatureInstance # --------------------------------------------------------------------------- @i18nModel("Feature") class Feature(PowerOnModel): """Feature-Definition (global, z.B. 'trustee', 'commcoach'). Verfuegbare Funktionalitaeten der Plattform.""" code: str = Field( description="Unique feature code (Primary Key), z.B. 'trustee', 'commcoach'", json_schema_extra={"label": "Code", "frontend_type": "text", "frontend_readonly": False, "frontend_required": True} ) label: TextMultilingual = Field( description="Feature label in multiple languages (I18n)", json_schema_extra={"label": "Bezeichnung", "frontend_type": "multilingual", "frontend_readonly": False, "frontend_required": True} ) icon: str = Field( default="", description="Icon identifier for the feature", json_schema_extra={"label": "Symbol", "frontend_type": "text", "frontend_readonly": False, "frontend_required": False} ) @i18nModel("Feature-Instanz") class FeatureInstance(PowerOnModel): """Instanz eines Features in einem Mandanten. Ein Mandant kann mehrere Instanzen desselben Features haben.""" id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the feature instance", json_schema_extra={"label": "ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False} ) featureCode: str = Field( description="FK -> Feature.code", json_schema_extra={ "label": "Feature", "frontend_type": "select", "frontend_readonly": True, "frontend_required": True, "fk_target": {"db": "poweron_app", "table": "Feature", "column": "code", "labelField": "code"}, }, ) mandateId: str = Field( description="FK -> Mandate.id (CASCADE DELETE)", json_schema_extra={ "label": "Mandant", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"}, }, ) label: str = Field( default="", description="Instance label, z.B. 'Buchhaltung 2025'", json_schema_extra={"label": "Bezeichnung", "frontend_type": "text", "frontend_readonly": False, "frontend_required": True} ) enabled: bool = Field( default=True, description="Whether this feature instance is enabled", json_schema_extra={"label": "Aktiviert", "frontend_type": "checkbox", "frontend_readonly": False, "frontend_required": False} ) config: Optional[Dict[str, Any]] = Field( default=None, description="Instance-specific configuration (JSONB). Structure depends on featureCode.", json_schema_extra={"label": "Konfiguration", "frontend_type": "json", "frontend_readonly": False, "frontend_required": False} ) # --------------------------------------------------------------------------- # FeatureDataSource # --------------------------------------------------------------------------- @i18nModel("Feature-Datenquelle") class FeatureDataSource(PowerOnModel): """Feature-Instanz-Tabelle als Datenquelle im AI-Workspace.""" id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"label": "ID"}, ) featureInstanceId: str = Field( description="FK to FeatureInstance", json_schema_extra={"label": "Feature-Instanz", "fk_target": {"db": "poweron_app", "table": "FeatureInstance", "labelField": "label"}}, ) featureCode: str = Field( description="Feature code (e.g. trustee, commcoach)", json_schema_extra={"label": "Feature", "fk_target": {"db": "poweron_app", "table": "Feature", "column": "code", "labelField": "code"}}, ) tableName: str = Field( description="Table name from DATA_OBJECTS meta (e.g. TrusteePosition)", json_schema_extra={"label": "Tabelle"}, ) objectKey: str = Field( description="RBAC object key (e.g. data.feature.trustee.TrusteePosition)", json_schema_extra={"label": "Objekt-Schluessel"}, ) label: str = Field( description="User-visible label", json_schema_extra={"label": "Bezeichnung"}, ) mandateId: str = Field( default="", description="Mandate scope (set automatically from featureInstance.mandateId on create).", json_schema_extra={"label": "Mandant", "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"}}, ) neutralize: Optional[bool] = Field( default=None, description=( "Three-state neutralization flag with cascade-inherit semantics. " "None = inherit; True/False = explicit. Cascade-reset on parent toggle." ), json_schema_extra={"label": "Neutralisieren", "frontend_type": "checkbox", "frontend_readonly": False, "frontend_required": False}, ) ragIndexEnabled: Optional[bool] = Field( default=None, description=( "Three-state RAG-indexing flag with cascade-inherit semantics. " "None = inherit; True/False = explicit. Cascade-reset on parent toggle." ), json_schema_extra={"label": "RAG-Indexierung", "frontend_type": "checkbox", "frontend_readonly": False, "frontend_required": False}, ) neutralizeFields: Optional[List[str]] = Field( default=None, description="Column names whose values are replaced with placeholders before AI processing", json_schema_extra={"label": "Zu neutralisierende Felder", "frontend_type": "multiselect", "frontend_readonly": False, "frontend_required": False}, ) recordFilter: Optional[Dict[str, str]] = Field( default=None, description="Record-level filter applied when querying this table, e.g. {'sessionId': 'abc-123'}", json_schema_extra={"label": "Datensatzfilter"}, ) settings: Optional[Dict[str, Any]] = Field( default=None, description=( "FeatureDataSource-scoped settings (JSON). Currently used keys: " "ragLimits.{maxBytes,maxFileSize,maxItems,maxDepth}. " "Mirror of DataSource.settings so the UDB settings modal can target both." ), json_schema_extra={"label": "Einstellungen", "frontend_type": "json", "frontend_readonly": True, "frontend_required": False}, ) # --------------------------------------------------------------------------- # DataNeutralizerAttributes # --------------------------------------------------------------------------- @i18nModel("Neutralisiertes Datenattribut") class DataNeutralizerAttributes(PowerOnModel): """Zuordnung Originaltext zu Platzhalter fuer neutralisierte Daten.""" id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Unique ID of the attribute mapping (used as UID in neutralized files)", json_schema_extra={"label": "ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False}, ) mandateId: str = Field( description="ID of the mandate this attribute belongs to", json_schema_extra={ "label": "Mandanten-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"}, }, ) featureInstanceId: str = Field( description="ID of the feature instance this attribute belongs to", json_schema_extra={ "label": "Feature-Instanz-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "fk_target": {"db": "poweron_app", "table": "FeatureInstance", "labelField": "label"}, }, ) userId: str = Field( description="ID of the user who created this attribute", json_schema_extra={ "label": "Benutzer-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True, "fk_target": {"db": "poweron_app", "table": "UserInDB", "labelField": "username"}, }, ) originalText: str = Field( description="Original text that was neutralized", json_schema_extra={"label": "Originaltext", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True}, ) fileId: Optional[str] = Field( default=None, description="ID of the file this attribute belongs to", json_schema_extra={ "label": "Datei-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False, "fk_target": {"db": "poweron_management", "table": "FileItem", "labelField": "fileName"}, }, ) patternType: str = Field( description="Type of pattern that matched (email, phone, name, etc.)", json_schema_extra={"label": "Mustertyp", "frontend_type": "text", "frontend_readonly": True, "frontend_required": True}, ) # --------------------------------------------------------------------------- # AutoWorkflow — re-exported from canonical location (datamodelWorkflowAutomation) # --------------------------------------------------------------------------- from modules.datamodels.datamodelWorkflowAutomation import AutoWorkflow # noqa: F401