150 lines
5.4 KiB
Python
150 lines
5.4 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""AI Audit Log data model for Compliance & AI-Datenfluss tracking.
|
|
|
|
Records metadata (and optionally content) of every AI provider call
|
|
for compliance, audit, and data-protection reporting.
|
|
"""
|
|
|
|
import uuid
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from modules.shared.i18nRegistry import i18nModel
|
|
from modules.shared.timeUtils import getUtcTimestamp
|
|
|
|
|
|
@i18nModel("AI-Audit-Eintrag")
|
|
class AiAuditLogEntry(BaseModel):
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Primary key",
|
|
json_schema_extra={"label": "ID"},
|
|
)
|
|
timestamp: float = Field(
|
|
default_factory=getUtcTimestamp,
|
|
description="Event timestamp (UTC epoch seconds)",
|
|
json_schema_extra={
|
|
"label": "Zeitpunkt",
|
|
"frontend_type": "timestamp",
|
|
"frontend_readonly": True,
|
|
},
|
|
)
|
|
|
|
userId: str = Field(
|
|
description="ID of the user who triggered the AI call",
|
|
json_schema_extra={"label": "Benutzer-ID", "fk_target": {"db": "poweron_app", "table": "UserInDB", "labelField": "username"}},
|
|
)
|
|
username: Optional[str] = Field(
|
|
default=None,
|
|
description="Username at time of call (denormalized for display)",
|
|
json_schema_extra={"label": "Benutzername"},
|
|
)
|
|
mandateId: str = Field(
|
|
description="Mandate context of the call",
|
|
json_schema_extra={"label": "Mandanten-ID", "fk_target": {"db": "poweron_app", "table": "Mandate", "labelField": "label"}},
|
|
)
|
|
featureInstanceId: Optional[str] = Field(
|
|
default=None,
|
|
description="Feature instance context",
|
|
json_schema_extra={"label": "Feature-Instanz-ID", "fk_target": {"db": "poweron_app", "table": "FeatureInstance", "labelField": "label"}},
|
|
)
|
|
featureCode: Optional[str] = Field(
|
|
default=None,
|
|
description="Feature code (e.g. workspace, trustee)",
|
|
json_schema_extra={"label": "Feature", "fk_target": {"db": "poweron_app", "table": "Feature", "column": "code", "labelField": "code"}},
|
|
)
|
|
instanceLabel: Optional[str] = Field(
|
|
default=None,
|
|
description="Human-readable instance label at time of call",
|
|
json_schema_extra={"label": "Instanz"},
|
|
)
|
|
|
|
aiProvider: str = Field(
|
|
description="AI provider key (e.g. azure-openai, anthropic)",
|
|
json_schema_extra={"label": "AI-Provider"},
|
|
)
|
|
aiModel: str = Field(
|
|
description="Model name used (e.g. gpt-4o, claude-3.5-sonnet)",
|
|
json_schema_extra={"label": "AI-Modell"},
|
|
)
|
|
operationType: Optional[str] = Field(
|
|
default=None,
|
|
description="Operation type (chat, embedding, image, tts, …)",
|
|
json_schema_extra={"label": "Typ"},
|
|
)
|
|
|
|
tokensInput: Optional[int] = Field(
|
|
default=None,
|
|
description="Input tokens consumed",
|
|
json_schema_extra={"label": "Tokens (Input)"},
|
|
)
|
|
tokensOutput: Optional[int] = Field(
|
|
default=None,
|
|
description="Output tokens consumed",
|
|
json_schema_extra={"label": "Tokens (Output)"},
|
|
)
|
|
processingTimeMs: Optional[int] = Field(
|
|
default=None,
|
|
description="Processing time in milliseconds",
|
|
json_schema_extra={"label": "Verarbeitungszeit (ms)"},
|
|
)
|
|
priceCHF: Optional[float] = Field(
|
|
default=None,
|
|
description="Cost in CHF (base price, before markup)",
|
|
json_schema_extra={"label": "Kosten (CHF)"},
|
|
)
|
|
|
|
neutralizationActive: bool = Field(
|
|
default=False,
|
|
description="Whether neutralization was active for this call",
|
|
json_schema_extra={"label": "Neutralisierung"},
|
|
)
|
|
neutralizationMappingsCount: Optional[int] = Field(
|
|
default=None,
|
|
description="Number of neutralization mappings applied",
|
|
json_schema_extra={"label": "Neutralisierungs-Mappings"},
|
|
)
|
|
|
|
contentStored: bool = Field(
|
|
default=False,
|
|
description="Whether full content was persisted (mandate opt-in)",
|
|
json_schema_extra={"label": "Inhalt gespeichert"},
|
|
)
|
|
contentInputHash: Optional[str] = Field(
|
|
default=None,
|
|
description="SHA-256 hash of the input content",
|
|
json_schema_extra={"label": "Input-Hash", "frontend_visible": False},
|
|
)
|
|
contentInputPreview: Optional[str] = Field(
|
|
default=None,
|
|
description="First ~200 chars of input (always stored)",
|
|
json_schema_extra={"label": "Input-Vorschau"},
|
|
)
|
|
contentOutputPreview: Optional[str] = Field(
|
|
default=None,
|
|
description="First ~200 chars of output (always stored)",
|
|
json_schema_extra={"label": "Output-Vorschau"},
|
|
)
|
|
contentInputFull: Optional[str] = Field(
|
|
default=None,
|
|
description="Full input content (only if mandate opted in)",
|
|
json_schema_extra={"label": "Vollständiger Input", "frontend_visible": False},
|
|
)
|
|
contentOutputFull: Optional[str] = Field(
|
|
default=None,
|
|
description="Full output content (only if mandate opted in)",
|
|
json_schema_extra={"label": "Vollständiger Output", "frontend_visible": False},
|
|
)
|
|
|
|
success: bool = Field(
|
|
default=True,
|
|
description="Whether the AI call succeeded",
|
|
json_schema_extra={"label": "Erfolgreich"},
|
|
)
|
|
errorMessage: Optional[str] = Field(
|
|
default=None,
|
|
description="Error message if the call failed",
|
|
json_schema_extra={"label": "Fehlermeldung"},
|
|
)
|