diff --git a/CLAUDE.md b/CLAUDE.md
index 827cf69..44c514a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -46,7 +46,7 @@ src/:
health_run.py: "In-Memory-Status des manuellen Health-Runs (pure Funktionen, unit-testbar). Der Knopf Jetzt prüfen ruft die geteilten Dienste source_health + source_suggester auf (POST /api/sources/health/run, Polling über GET /health/run-status). Zusätzlich zum Monitor-Nachtjob um 04:00. Status ist nach Neustart idle, partieller Stand heilt beim nächsten Lauf"
routers/:
- organizations.py: "CRUD Mandanten (organizations + Org-Settings + Token-Budget)"
+ organizations.py: "CRUD Mandanten (organizations + Org-Settings inkl. output_language und ai_backend + Token-Budget)"
licenses.py: "CRUD Lizenzen (Org-Lizenzen, Ablauf, Nutzer-Limit, Module, Credits-Stellwerte inkl. Periode; Helper insert_license_row wird auch vom Org-Anlegen genutzt). Kein Credits-Übertrag mehr (Produktentscheidung 07/2026, ungenutzte Credits verfallen)"
users.py: "CRUD User pro Org, Magic-Link-Einladung an info@aegis-sight.de"
sources.py: "Vereinte Quellenliste (GET /api/sources?scope=all|global|tenant inkl. org_name + health_status), Stats/Languages, Einzelquellen-Health (GET /{id}/health), Aufgaben-Badge (GET /tasks/summary), Promote, Discovery, PDF-Upload, KI-Vorschläge, Klassifikation"
@@ -219,6 +219,7 @@ tests:
test_models.py: Pydantic-Validierung (MagicLink, Org, License, User)
test_source_meta.py: Single Source of Truth Konsistenz
test_imports.py: alle Backend-Module importierbar (Syntax-Catchnet)
+ test_org_ki_vorgabe.py: organisationsweiter KI-Weg (ai_backend), Modelle, Router und Oberflaeche. Der Leerstring bedeutet Servervorgabe und muss von einer fehlenden Angabe unterscheidbar bleiben
philosophie:
- reine Unit-Tests, kein DB-Zugriff, kein HTTP-Server
diff --git a/src/models.py b/src/models.py
index abf9728..1fb4df4 100644
--- a/src/models.py
+++ b/src/models.py
@@ -49,6 +49,8 @@ class OrgCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
slug: str = Field(min_length=1, max_length=100, pattern="^[a-z0-9-]+$")
output_language: str = Field(default="de", pattern="^(de|en)$")
+ # KI-Weg der Organisation. Leer heisst, es gilt die Servervorgabe.
+ ai_backend: str = Field(default="", pattern="^(cli|bedrock|)$")
# Optional direkt eine Lizenz mit Credits-Kontingent anlegen
license: Optional[LicenseParams] = None
@@ -57,6 +59,7 @@ class OrgUpdate(BaseModel):
name: Optional[str] = Field(default=None, max_length=200)
is_active: Optional[bool] = None
output_language: Optional[str] = Field(default=None, pattern="^(de|en)$")
+ ai_backend: Optional[str] = Field(default=None, pattern="^(cli|bedrock|)$")
class OrgResponse(BaseModel):
@@ -71,6 +74,7 @@ class OrgResponse(BaseModel):
globe_access: bool = False
network_access: bool = False
output_language: str = "de"
+ ai_backend: str = ""
class LicenseCreate(LicenseParams):
diff --git a/src/routers/organizations.py b/src/routers/organizations.py
index 28f38e7..3292478 100644
--- a/src/routers/organizations.py
+++ b/src/routers/organizations.py
@@ -34,6 +34,14 @@ async def _enrich_org(db: aiosqlite.Connection, row: aiosqlite.Row) -> dict:
lang_row = await cursor.fetchone()
org["output_language"] = lang_row["value"] if lang_row else "de"
+ # ai_backend aus organization_settings (leer = Servervorgabe)
+ cursor = await db.execute(
+ "SELECT value FROM organization_settings WHERE organization_id = ? AND key = 'ai_backend'",
+ (org["id"],),
+ )
+ ai_row = await cursor.fetchone()
+ org["ai_backend"] = (ai_row["value"] if ai_row else "") or ""
+
return org
@@ -79,6 +87,7 @@ async def create_organization(
# output_language als organization_settings-Eintrag persistieren
from shared.services.org_settings import set_org_setting
await set_org_setting(db, org_id, "output_language", data.output_language)
+ await set_org_setting(db, org_id, "ai_backend", data.ai_backend or "")
cursor = await db.execute("SELECT * FROM organizations WHERE id = ?", (org_id,))
new_row_obj = await cursor.fetchone()
@@ -138,6 +147,12 @@ async def update_organization(
from shared.services.org_settings import set_org_setting
await set_org_setting(db, org_id, "output_language", data.output_language)
+ # KI-Weg. Der Leerstring ist ein gueltiger Wert und bedeutet
+ # "Servervorgabe", deshalb hier die Pruefung auf None statt auf Wahrheit.
+ if data.ai_backend is not None:
+ from shared.services.org_settings import set_org_setting
+ await set_org_setting(db, org_id, "ai_backend", data.ai_backend)
+
after = await row_to_dict(db, "organizations", org_id)
await log_action(
db, admin, get_client_ip(request),
diff --git a/src/static/dashboard.html b/src/static/dashboard.html
index e43b314..03d0a0c 100644
--- a/src/static/dashboard.html
+++ b/src/static/dashboard.html
@@ -170,6 +170,7 @@
Gültig bis
Status
Pipeline-Sprache
+
KI-Verarbeitung
Aktionen
@@ -431,6 +432,15 @@
Steuert die Ausgabesprache der KI-Pipeline (Lagebild, Faktencheck, Recherche) und die sichtbarsten UI-Strings im Monitor.
+
+
+
+ Gilt für alle Lagen dieser Organisation, solange eine Lage nichts Eigenes einstellt. EU verarbeitet über AWS Bedrock in Frankfurt und recherchiert über den europäischen Suchindex staan.
+
-
+
diff --git a/src/static/js/app.js b/src/static/js/app.js
index 7771a20..cd69039 100644
--- a/src/static/js/app.js
+++ b/src/static/js/app.js
@@ -359,6 +359,14 @@ function setOrgLanguage(wert) {
_saveOrgSetting({ output_language: wert }, "Pipeline-Sprache gespeichert");
}
+// Der Leerstring ist hier ein gueltiger Wert (Servervorgabe), er darf
+// also nicht wegoptimiert werden.
+function setOrgAiBackend(wert) {
+ const namen = { "": "Servervorgabe", cli: "Anthropic", bedrock: "EU (Frankfurt)" };
+ _saveOrgSetting({ ai_backend: wert },
+ "KI-Verarbeitung gespeichert, " + (namen[wert] || wert));
+}
+
// Verbrauchszähler direkt an der Kachel korrigieren (Kulanz, Fehlbereinigung)
async function korrigiereVerbrauch() {
const input = document.getElementById("korrCreditsUsed");
@@ -542,7 +550,7 @@ async function loadOrgLicenses(orgId) {
const licenses = await API.get(`/api/licenses?org_id=${orgId}`);
const tbody = document.getElementById("licenseTable");
if (licenses.length === 0) {
- tbody.innerHTML = '
Keine Lizenzen
';
+ tbody.innerHTML = '
Keine Lizenzen
';
return;
}
// Pipeline-Sprache ist eine Org-Einstellung und steht nur in der aktiven
@@ -553,6 +561,15 @@ async function loadOrgLicenses(orgId) {
`;
+ // KI-Weg der Organisation. Greift fuer jede Lage, die nichts Eigenes
+ // eingestellt hat. Leer heisst, es gilt die Servervorgabe.
+ const ki = org.ai_backend || "";
+ const kiZelle = `
+
`;
tbody.innerHTML = licenses.map(l => `
${l.license_type}
@@ -564,6 +581,7 @@ async function loadOrgLicenses(orgId) {