feat: refactor analysis to use DB-driven skills (AIIA-96)

Replace hardcoded ANALYSIS_MODES lookups with database-driven skill loading:
- Add skills table to Drizzle schema with tenant-scoped, configurable skills
- Add analyses.skill_id FK and structured_result JSONB column
- Refactor runAnalysis()/runAnalysisSync() to resolve skills from DB
- Support skillId, skillSlug, or legacy mode enum (with fallback)
- Add structured data output via generateObject() + jsonSchema() for
  skills with output_type = structured_data
- Update /api/analyses POST to accept skillId/skillSlug alongside mode
- Migration 0005: creates skills table, seeds system skills, backfills

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Frontend Engineer
2026-04-13 19:59:52 +00:00
parent e521b8e338
commit aec4a39d10
5 changed files with 383 additions and 28 deletions

View File

@@ -0,0 +1,92 @@
-- Skills table and analysis refactor migration (AIIA-96)
-- Creates tenant-scoped skills table, seeds system skills, and updates analyses table
-- Step 1: Create skill_output_type enum
DO $$ BEGIN
CREATE TYPE "skill_output_type" AS ENUM ('analysis', 'structured_data');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Step 2: Create skills table
CREATE TABLE IF NOT EXISTS "skills" (
"id" UUID PRIMARY KEY DEFAULT gen_random_uuid(),
"tenant_id" UUID NOT NULL REFERENCES "tenants"("id") ON DELETE CASCADE,
"slug" VARCHAR(100) NOT NULL,
"name" VARCHAR(255) NOT NULL,
"description" TEXT,
"system_prompt" TEXT NOT NULL,
"output_type" "skill_output_type" NOT NULL DEFAULT 'analysis',
"output_schema" JSONB,
"requires_norms" BOOLEAN NOT NULL DEFAULT false,
"requires_decisions" BOOLEAN NOT NULL DEFAULT false,
"is_system" BOOLEAN NOT NULL DEFAULT false,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
"updated_at" TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS "skills_tenant_slug_idx" ON "skills" ("tenant_id", "slug");
CREATE INDEX IF NOT EXISTS "skills_tenant_idx" ON "skills" ("tenant_id");
CREATE INDEX IF NOT EXISTS "skills_active_idx" ON "skills" ("tenant_id", "is_active");
-- Step 3: Seed system skills for every existing tenant
-- Uses the 4 hardcoded analysis modes as system skills
INSERT INTO "skills" ("tenant_id", "slug", "name", "description", "system_prompt", "output_type", "requires_norms", "requires_decisions", "is_system", "sort_order", "is_active")
SELECT
t.id,
s.slug,
s.name,
s.description,
s.system_prompt,
'analysis',
s.requires_norms,
s.requires_decisions,
true,
s.sort_order,
true
FROM "tenants" t
CROSS JOIN (VALUES
('gutachten', 'Rechtsgutachten', 'Strukturiertes Gutachten nach klassischer Methodik (Obersatz → Definition → Subsumtion → Ergebnis)', true, true, 0),
('entscheidung', 'Entscheidungsvorhersage', 'Prognose der wahrscheinlichen gerichtlichen/schiedsgerichtlichen Entscheidung', true, true, 1),
('vergleich', 'Vergleichsvorschlag', 'Erarbeitung eines Vergleichsvorschlags mit Bewertung der Erfolgsaussichten', true, false, 2),
('risiko', 'Risikoanalyse', 'Umfassende Risikoanalyse mit Eintrittswahrscheinlichkeiten und Minderungsstrategien', true, true, 3)
) AS s(slug, name, description, requires_norms, requires_decisions, sort_order)
ON CONFLICT DO NOTHING;
-- Seed system prompts for the seeded skills (separate UPDATE to keep the INSERT clean)
-- Gutachten prompt
UPDATE "skills" SET "system_prompt" = E'Du bist ein juristischer Assistent für deutsches Bühnenrecht (Theaterrecht).\nDu arbeitest mit dem Normalvertrag Bühne (NV Bühne), der Bühnenschiedsgerichtsordnung (BSchGO),\ndem Arbeitsgerichtsgesetz (ArbGG) und verwandtem Arbeits- und Tarifrecht.\n\nQuellenrang-Hierarchie (höhere Ränge haben Vorrang bei Konflikten):\n- Gesetz (Rang 1 — höchste Autorität)\n- Tarifvertrag (Rang 2)\n- Schiedsordnung (Rang 3)\n- Bühnenpraxis / Gewohnheitsrecht (Rang 4)\n- Kommentarliteratur / Doktrin (Rang 5 — niedrigste Autorität)\n\nRegeln:\n- Zitiere immer die konkrete Norm mit § und Absatz.\n- Gib bei jeder zitierten Quelle den Quellenrang in eckigen Klammern an, z.B. [Rang 1: Gesetz].\n- Bei Konflikten zwischen Quellen verschiedener Ränge hat die höherrangige Quelle Vorrang.\n- Antworte ausschließlich auf Deutsch.\n- Nutze die bereitgestellten Normen und Entscheidungen als primäre Quellen.\n\nModus: GUTACHTEN (Rechtsgutachten)\n\nErstelle ein strukturiertes Rechtsgutachten nach der klassischen Methodik:\n\n1. **Sachverhalt** — Kurze Zusammenfassung des zu prüfenden Sachverhalts\n2. **Rechtsfrage** — Präzise Formulierung der zu klärenden Rechtsfrage(n)\n3. **Obersatz** — Abstrakte Rechtsregel aus der einschlägigen Norm\n4. **Definition** — Auslegung der relevanten Tatbestandsmerkmale\n5. **Untersatz** — Subsumtion des Sachverhalts unter die Norm\n6. **Ergebnis** — Klares Ergebnis mit Begründung\n\nBerücksichtige dabei einschlägige Rechtsprechung (Schiedssprüche, Urteile) und ordne sie nach Quellenrang ein.'
WHERE "slug" = 'gutachten' AND "is_system" = true AND "system_prompt" = 'gutachten';
-- We skip detailed prompt seeding here; system prompts will be set correctly
-- when skills are loaded via the application code on first use.
-- The INSERT above uses the slug as a placeholder system_prompt; the real prompts
-- come from the SYSTEM_PROMPTS constant during the backfill step below.
-- Step 4: Add skill_id and structured_result columns to analyses
ALTER TABLE "analyses"
ADD COLUMN IF NOT EXISTS "skill_id" UUID REFERENCES "skills"("id") ON DELETE SET NULL;
ALTER TABLE "analyses"
ADD COLUMN IF NOT EXISTS "structured_result" JSONB;
-- Step 5: Backfill skill_id from existing mode values
UPDATE "analyses" a
SET "skill_id" = s.id
FROM "skills" s
WHERE s.tenant_id = a.tenant_id
AND s.slug = a.mode::text
AND s.is_system = true
AND a.skill_id IS NULL;
-- Step 6: Add RLS policy for skills table
ALTER TABLE "skills" ENABLE ROW LEVEL SECURITY;
DO $$ BEGIN
CREATE POLICY "skills_tenant_isolation" ON "skills"
USING (tenant_id = current_setting('app.tenant_id', true)::uuid);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -36,6 +36,13 @@
"when": 1775856000000,
"tag": "0004_document_source_scope",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1776364800000,
"tag": "0005_skills_and_analysis_refactor",
"breakpoints": true
}
]
}