Admin-Plattform für die Tenants von Onyscribe: Editor mit Live-Vorschau (echter Core in Sandbox), Versionen, KI-Agent (Cortecs), OIDC-Abgleich über die Login-Developer-API, Deploy-Engine (Mittwald, gesperrt ohne DEPLOY_LIVE, Freigabe pro Tenant), Core-Vertrag (Schema/Manifest), Core-Releases mit Kompatibilitätsprüfung, Canary-Rollout und Rollback, Zustandsabgleich. Runbook: docs/RUNBOOK_PARALLELTEST.md Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
65 lines
2.3 KiB
Python
65 lines
2.3 KiB
Python
"""Per-tenant code hooks.
|
|
|
|
Most customers differ only in *data* (colours, logo, prompt, PDF template) and
|
|
need no code here. For the few that need genuine behaviour changes, drop an
|
|
`overrides.py` into their tenant folder defining a `Hooks(TenantHooks)` subclass
|
|
that overrides only the methods it needs. Core stays untouched, so core updates
|
|
keep flowing.
|
|
|
|
Example tenants/acme/overrides.py:
|
|
|
|
from core.hooks import TenantHooks
|
|
|
|
class Hooks(TenantHooks):
|
|
def build_prompt(self, base, ctx):
|
|
return base + "\\nBevorzuge Fachterminologie aus der Orthopädie."
|
|
|
|
def extra_routes(self, app):
|
|
@app.get("/health/acme")
|
|
def _h():
|
|
return {"ok": True}
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
from core.contract import HOOK_API
|
|
|
|
|
|
class TenantHooks:
|
|
"""Default (no-op) behaviour. Override selectively per tenant."""
|
|
|
|
def build_prompt(self, base_prompt: str, ctx: dict) -> str:
|
|
"""Adjust the system prompt sent to the LLM. ctx carries the form fields."""
|
|
return base_prompt
|
|
|
|
def transform_transcript(self, transcript: str, ctx: dict) -> str:
|
|
"""Post-process the transcript before summarisation/delivery."""
|
|
return transcript
|
|
|
|
def pdf_context(self, context: dict) -> dict:
|
|
"""Last chance to enrich/alter the data passed to the PDF template."""
|
|
return context
|
|
|
|
def extra_routes(self, app) -> None:
|
|
"""Register tenant-specific FastAPI routes."""
|
|
return None
|
|
|
|
|
|
def load_hooks(tenant_dir: Path) -> TenantHooks:
|
|
override = tenant_dir / "overrides.py"
|
|
if not override.exists():
|
|
return TenantHooks()
|
|
spec = importlib.util.spec_from_file_location(f"tenant_overrides_{tenant_dir.name}", override)
|
|
module = importlib.util.module_from_spec(spec)
|
|
assert spec and spec.loader
|
|
spec.loader.exec_module(module)
|
|
declared = getattr(module, "HOOK_API", HOOK_API) # fehlt die Deklaration, gilt die aktuelle Version
|
|
if declared != HOOK_API:
|
|
raise RuntimeError(f"{override}: HOOK_API={declared} wird von diesem Core nicht unterstützt (Core: {HOOK_API})")
|
|
hooks_cls = getattr(module, "Hooks", None)
|
|
if hooks_cls is None:
|
|
return TenantHooks()
|
|
return hooks_cls()
|