feat: extend skills with dynamic resolution and RBAC, add API docs
Some checks failed
Deploy to VPS / deploy (push) Failing after 49s
Some checks failed
Deploy to VPS / deploy (push) Failing after 49s
- Add resolveAnalysisMode() to modes/index.ts for dynamic skill lookup - Extend structured-analysis.ts to use resolveAnalysisMode (supports custom skill prompts and skillId persistence) - Update structured analyses route to use resolveAnalysisMode instead of hardcoded VALID_MODES set - Add skills:read/create/edit/delete RBAC permissions to rbac.ts - Add docs/API_GUIDE.md with full endpoint reference Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
633
docs/API_GUIDE.md
Normal file
633
docs/API_GUIDE.md
Normal file
@@ -0,0 +1,633 @@
|
||||
# StageAI API Usage Guide
|
||||
|
||||
This guide covers all API endpoints with practical examples for text and office document sources.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:3000/api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Authentication
|
||||
|
||||
### Register a New Tenant
|
||||
|
||||
Creates a new tenant (law firm) and its first admin user.
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Dr. Müller",
|
||||
"email": "mueller@kanzlei.de",
|
||||
"password": "securepassword123",
|
||||
"tenantName": "Kanzlei Müller"
|
||||
}'
|
||||
```
|
||||
|
||||
**Response (201):**
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"id": "uuid",
|
||||
"email": "mueller@kanzlei.de",
|
||||
"name": "Dr. Müller",
|
||||
"role": "admin"
|
||||
},
|
||||
"tenant": {
|
||||
"id": "uuid",
|
||||
"name": "Kanzlei Müller",
|
||||
"slug": "kanzlei-mueller"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Sign In (Get Session Cookie)
|
||||
|
||||
Authentication uses NextAuth.js with the Credentials provider. Sign in to get a session cookie:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/auth/callback/credentials \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email": "mueller@kanzlei.de", "password": "securepassword123"}' \
|
||||
-c cookies.txt
|
||||
```
|
||||
|
||||
Use `-b cookies.txt` on subsequent requests to send the session cookie.
|
||||
|
||||
### Session Info
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/api/auth/session -b cookies.txt
|
||||
```
|
||||
|
||||
Returns the current user session including `tenantId`, `userId`, `role`, `email`, and `name`.
|
||||
|
||||
> **Note:** Sessions expire after 8 hours. All protected endpoints require a valid session cookie. Tenant isolation is enforced at the database level via Row-Level Security.
|
||||
|
||||
---
|
||||
|
||||
## 2. Cases (Fälle)
|
||||
|
||||
### Create a Case
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/cases \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{
|
||||
"caseNumber": "2024-BSchG-001",
|
||||
"title": "Nichtverlängerung Solist",
|
||||
"clientName": "Max Mustermann",
|
||||
"opposingParty": "Staatstheater Berlin",
|
||||
"venue": "Bühnenschiedsgericht Berlin",
|
||||
"status": "active",
|
||||
"domains": ["nv_buehne", "arbeitsrecht"]
|
||||
}'
|
||||
```
|
||||
|
||||
**Response (201):**
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"caseNumber": "2024-BSchG-001",
|
||||
"title": "Nichtverlängerung Solist",
|
||||
"clientName": "Max Mustermann",
|
||||
"status": "active",
|
||||
"createdAt": "2024-01-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### List Cases
|
||||
|
||||
```bash
|
||||
# All cases
|
||||
curl http://localhost:3000/api/cases -b cookies.txt
|
||||
|
||||
# Search + filter
|
||||
curl "http://localhost:3000/api/cases?q=Mustermann&status=active&limit=10&offset=0" \
|
||||
-b cookies.txt
|
||||
```
|
||||
|
||||
### Get Case Details
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/api/cases/{caseId} -b cookies.txt
|
||||
```
|
||||
|
||||
Returns the case along with related analyses and proceedings.
|
||||
|
||||
### Update / Delete a Case
|
||||
|
||||
```bash
|
||||
# Update
|
||||
curl -X PATCH http://localhost:3000/api/cases/{caseId} \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{"status": "closed"}'
|
||||
|
||||
# Delete
|
||||
curl -X DELETE http://localhost:3000/api/cases/{caseId} -b cookies.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Document Upload (Text & Office Sources)
|
||||
|
||||
StageAI supports **PDF** and **DOCX** files (max 10 MB). Text is extracted automatically after upload.
|
||||
|
||||
### Upload a Generic Document
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/documents \
|
||||
-b cookies.txt \
|
||||
-F "file=@/path/to/urteil.pdf" \
|
||||
-F "category=entscheidung" \
|
||||
-F "sourceScope=global"
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Field | Required | Values |
|
||||
|-------|----------|--------|
|
||||
| `file` | Yes | PDF or DOCX file |
|
||||
| `category` | Yes | `entscheidung`, `norm`, `falldokument`, `sonstiges` |
|
||||
| `sourceScope` | No | `case` (private to a case) or `global` |
|
||||
| `caseId` | No | Link to a specific case |
|
||||
| `decisionId` | No | Link to a specific decision |
|
||||
| `normInstrumentId` | No | Link to a norm instrument |
|
||||
|
||||
**Response (201):**
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"filename": "urteil.pdf",
|
||||
"mimeType": "application/pdf",
|
||||
"category": "entscheidung",
|
||||
"status": "uploaded",
|
||||
"createdAt": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
After upload, text extraction runs asynchronously. Status progresses: `uploaded` -> `extracting` -> `extracted` (or `failed`).
|
||||
|
||||
### List Documents
|
||||
|
||||
```bash
|
||||
# All documents
|
||||
curl "http://localhost:3000/api/documents" -b cookies.txt
|
||||
|
||||
# Filter by category and scope
|
||||
curl "http://localhost:3000/api/documents?category=entscheidung&sourceScope=global&limit=20&offset=0" \
|
||||
-b cookies.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Contract Analysis (Vertragsanalyse)
|
||||
|
||||
Upload employment contracts (NV Bühne) for AI-powered clause analysis.
|
||||
|
||||
### Upload a Contract
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/contracts \
|
||||
-b cookies.txt \
|
||||
-F "file=@/path/to/arbeitsvertrag.pdf" \
|
||||
-F "caseId=uuid-of-case"
|
||||
```
|
||||
|
||||
**Response (201):**
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"filename": "arbeitsvertrag.pdf",
|
||||
"mimeType": "application/pdf",
|
||||
"status": "uploaded",
|
||||
"caseId": "uuid-of-case"
|
||||
}
|
||||
```
|
||||
|
||||
### Trigger Clause Analysis
|
||||
|
||||
After uploading, trigger the AI analysis:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/contracts/{contractId}/analyze \
|
||||
-b cookies.txt
|
||||
```
|
||||
|
||||
This extracts text from the document, identifies contract clauses, and compares them against NV Bühne standard clauses. The status progresses: `uploaded` -> `extracting` -> `extracted` -> `analyzing` -> `completed`.
|
||||
|
||||
### Get Contract with Analysis Results
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/api/contracts/{contractId} \
|
||||
-H "x-tenant-id: your-tenant-id" \
|
||||
-b cookies.txt
|
||||
```
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"document": {
|
||||
"id": "uuid",
|
||||
"filename": "arbeitsvertrag.pdf",
|
||||
"status": "completed"
|
||||
},
|
||||
"clauses": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"category": "Vergütung",
|
||||
"extractedText": "Die monatliche Gage beträgt...",
|
||||
"rating": "standard",
|
||||
"analysis": "Entspricht der Gagenklasse III NV Bühne.",
|
||||
"riskScore": 5,
|
||||
"deviations": []
|
||||
},
|
||||
{
|
||||
"id": "uuid",
|
||||
"category": "Nichtverlängerung",
|
||||
"extractedText": "Der Vertrag kann mit einer Frist von...",
|
||||
"rating": "kritisch",
|
||||
"analysis": "Die Frist weicht von § 69 NV Bühne ab.",
|
||||
"riskScore": 85,
|
||||
"deviations": ["Frist kürzer als tariflich vorgesehen"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Clause Categories:** Vertragsparteien, Vertragsdauer, Nichtverlängerung, Vergütung, Arbeitszeit, Proben, Gastspiele, Urlaub, Krankheit, Kündigung, Nebentätigkeit, Geheimhaltung, Sonstiges
|
||||
|
||||
**Ratings:** `standard` (conforms to NV Bühne), `abweichend` (deviates), `kritisch` (critical deviation), `unbekannt` (unclassifiable)
|
||||
|
||||
### List Contracts
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3000/api/contracts?limit=20&offset=0" -b cookies.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Legal Norms (Normen)
|
||||
|
||||
### Create a Norm Instrument
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/norms \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{
|
||||
"type": "tarifvertrag",
|
||||
"sourceRank": "tarifvertrag",
|
||||
"abbreviation": "NV Bühne",
|
||||
"fullTitle": "Normalvertrag Bühne",
|
||||
"enactedAt": "2023-01-01",
|
||||
"issuingBody": "GDBA / VdO / DBV"
|
||||
}'
|
||||
```
|
||||
|
||||
### Import Norm Provisions (Bulk)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/norms/import \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{
|
||||
"instrumentId": "uuid-of-instrument",
|
||||
"provisions": [
|
||||
{
|
||||
"paragraph": "§ 1",
|
||||
"title": "Geltungsbereich",
|
||||
"body": "Dieser Normalvertrag gilt für alle Bühnenmitglieder...",
|
||||
"validFrom": "2023-01-01",
|
||||
"domains": ["nv_buehne"]
|
||||
},
|
||||
{
|
||||
"paragraph": "§ 69",
|
||||
"title": "Nichtverlängerungsmitteilung",
|
||||
"body": "Die Nichtverlängerungsmitteilung muss bis zum 31. Oktober...",
|
||||
"validFrom": "2023-01-01",
|
||||
"domains": ["nv_buehne", "nichtverlängerung"]
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### Query Norms (with Temporal Versioning)
|
||||
|
||||
Retrieve all paragraphs of an instrument valid on a specific date:
|
||||
|
||||
```bash
|
||||
# Norms valid today (default)
|
||||
curl http://localhost:3000/api/norms/{instrumentId} -b cookies.txt
|
||||
|
||||
# Norms valid on a specific date (Stichtag)
|
||||
curl "http://localhost:3000/api/norms/{instrumentId}?date=2024-06-15" -b cookies.txt
|
||||
```
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"instrument": {
|
||||
"id": "uuid",
|
||||
"abbreviation": "NV Bühne",
|
||||
"fullTitle": "Normalvertrag Bühne",
|
||||
"type": "tarifvertrag"
|
||||
},
|
||||
"provisions": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"paragraph": "§ 1",
|
||||
"title": "Geltungsbereich",
|
||||
"body": "Dieser Normalvertrag gilt für alle Bühnenmitglieder...",
|
||||
"validFrom": "2023-01-01",
|
||||
"validTo": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Decisions (Entscheidungen)
|
||||
|
||||
### Create a Decision
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/decisions \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{
|
||||
"type": "schiedsspruch",
|
||||
"caseReference": "BSchG Berlin 3/2024",
|
||||
"decisionDate": "2024-03-15",
|
||||
"court": "Bühnenschiedsgericht Berlin",
|
||||
"headnote": "Zur Wirksamkeit einer Nichtverlängerungsmitteilung...",
|
||||
"tenor": "Der Schiedsspruch wird aufgehoben...",
|
||||
"facts": "Der Kläger ist seit 2018 als Solist...",
|
||||
"reasoning": "Die Nichtverlängerungsmitteilung ist unwirksam, weil...",
|
||||
"domains": ["nv_buehne", "nichtverlängerung"],
|
||||
"keywords": ["Nichtverlängerung", "Solist", "Fristversäumnis"]
|
||||
}'
|
||||
```
|
||||
|
||||
### Full-Text Search for Decisions
|
||||
|
||||
Uses PostgreSQL full-text search with German language support:
|
||||
|
||||
```bash
|
||||
# Search by keyword
|
||||
curl "http://localhost:3000/api/decisions?q=Vergütung" -b cookies.txt
|
||||
|
||||
# Combined filters
|
||||
curl "http://localhost:3000/api/decisions?q=Nichtverlängerung&court=Bühnenschiedsgericht&type=schiedsspruch&dateFrom=2020-01-01&dateTo=2024-12-31" \
|
||||
-b cookies.txt
|
||||
```
|
||||
|
||||
### Link Norms to a Decision
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/decisions/{decisionId}/norms \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{
|
||||
"normId": "uuid-of-norm-paragraph",
|
||||
"applicationType": "angewendet",
|
||||
"passage": "Rn. 15-18"
|
||||
}'
|
||||
```
|
||||
|
||||
**Application types:** `angewendet` (applied), `zitiert` (cited), `ausgelegt` (interpreted), `verworfen` (rejected)
|
||||
|
||||
---
|
||||
|
||||
## 7. AI-Powered Legal Analysis (Analysen)
|
||||
|
||||
### Create a Streaming Analysis
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/analyses \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{
|
||||
"mode": "gutachten",
|
||||
"title": "Wirksamkeit der Nichtverlängerung",
|
||||
"query": "Ist die Nichtverlängerungsmitteilung vom 15.11.2024 wirksam, wenn der Solist seit 15 Jahren am Haus beschäftigt ist?",
|
||||
"normIds": ["uuid-of-§69-nv-buehne"],
|
||||
"decisionIds": ["uuid-of-relevant-decision"],
|
||||
"documentIds": ["uuid-of-uploaded-document"],
|
||||
"stichtag": "2024-11-15",
|
||||
"caseId": "uuid-of-case"
|
||||
}'
|
||||
```
|
||||
|
||||
The response streams text (the AI-generated analysis). The `X-Analysis-Id` response header contains the analysis ID for later retrieval.
|
||||
|
||||
**Analysis Modes:**
|
||||
|
||||
| Mode | Purpose | Requires |
|
||||
|------|---------|----------|
|
||||
| `gutachten` | Expert legal opinion | Norms |
|
||||
| `entscheidung` | Decision proposal based on precedent | Decisions |
|
||||
| `vergleich` | Comparative analysis | Norms or decisions |
|
||||
| `risiko` | Risk assessment with probability ratings | Any sources |
|
||||
|
||||
### Create a Structured Analysis (JSON Output)
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/analyses/structured \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{
|
||||
"mode": "risiko",
|
||||
"query": "Risikobewertung für die Kündigung eines Chormitglieds nach § 626 BGB",
|
||||
"normIds": ["uuid1"],
|
||||
"decisionIds": ["uuid2"]
|
||||
}'
|
||||
```
|
||||
|
||||
Returns typed JSON with structured fields depending on the mode.
|
||||
|
||||
### List & Retrieve Analyses
|
||||
|
||||
```bash
|
||||
# List (metadata only, DSGVO-compliant)
|
||||
curl "http://localhost:3000/api/analyses?limit=20&offset=0" -b cookies.txt
|
||||
|
||||
# Get single analysis with full result and sources
|
||||
curl http://localhost:3000/api/analyses/{analysisId} \
|
||||
-H "x-tenant-id: your-tenant-id" \
|
||||
-b cookies.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Proceedings (Verfahren)
|
||||
|
||||
### Create a Proceeding
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/proceedings \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"tenantId": "your-tenant-id",
|
||||
"type": "bschgo_bezirk",
|
||||
"caseId": "uuid-of-case",
|
||||
"applicant": "Max Mustermann",
|
||||
"respondent": "Staatstheater Berlin",
|
||||
"subject": "Nichtverlängerung Spielzeit 2024/25"
|
||||
}'
|
||||
```
|
||||
|
||||
**Proceeding types:** `bschgo_bezirk`, `bschgo_bund`, `arbgg_erste_instanz`, `arbgg_berufung`, `arbgg_revision`
|
||||
|
||||
Workflow steps and initial deadlines are automatically created from templates.
|
||||
|
||||
### Check Deadlines
|
||||
|
||||
```bash
|
||||
# All deadlines
|
||||
curl http://localhost:3000/api/proceedings/{proceedingId}/deadlines
|
||||
|
||||
# Only overdue deadlines
|
||||
curl "http://localhost:3000/api/proceedings/{proceedingId}/deadlines?overdue=true"
|
||||
|
||||
# Upcoming in next 14 days
|
||||
curl "http://localhost:3000/api/proceedings/{proceedingId}/deadlines?upcoming=14"
|
||||
```
|
||||
|
||||
### Advance Proceeding
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/proceedings/{proceedingId}/advance \
|
||||
-b cookies.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. NV Bühne Utilities
|
||||
|
||||
These are public endpoints (no auth required) for quick calculations.
|
||||
|
||||
### Calculate Compensation (Gage)
|
||||
|
||||
```bash
|
||||
# GET (simple query)
|
||||
curl "http://localhost:3000/api/nv-buehne/compensation?gagenklasse=III&yearsOfService=5&spielzeit=2024/25&fachgruppe=Solo"
|
||||
|
||||
# POST (with tenant-specific rules)
|
||||
curl -X POST http://localhost:3000/api/nv-buehne/compensation \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"gagenklasse": "III",
|
||||
"yearsOfService": 5,
|
||||
"spielzeit": "2024/25",
|
||||
"fachgruppe": "Solo",
|
||||
"tenantId": "uuid",
|
||||
"fachgruppeId": "uuid"
|
||||
}'
|
||||
```
|
||||
|
||||
### Check Non-Renewal Deadline (Nichtverlängerungsfrist)
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3000/api/nv-buehne/deadline-check?yearsOfService=15&isOver55=false&spielzeit=2024/25&fachgruppe=Solo&referenceDate=2024-10-31"
|
||||
```
|
||||
|
||||
### Get Current Spielzeit (Season)
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3000/api/nv-buehne/spielzeit"
|
||||
# or for a specific date:
|
||||
curl "http://localhost:3000/api/nv-buehne/spielzeit?date=2024-09-01"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Settings
|
||||
|
||||
### Manage AI Provider API Keys
|
||||
|
||||
```bash
|
||||
# List keys (returns hints only, never full keys)
|
||||
curl http://localhost:3000/api/settings/api-keys -b cookies.txt
|
||||
|
||||
# Add an API key
|
||||
curl -X POST http://localhost:3000/api/settings/api-keys \
|
||||
-H "Content-Type: application/json" \
|
||||
-b cookies.txt \
|
||||
-d '{
|
||||
"provider": "anthropic",
|
||||
"apiKey": "sk-ant-...",
|
||||
"label": "Production Key"
|
||||
}'
|
||||
```
|
||||
|
||||
Keys are encrypted at rest with AES-256-GCM.
|
||||
|
||||
---
|
||||
|
||||
## Pagination
|
||||
|
||||
All list endpoints support pagination:
|
||||
|
||||
```
|
||||
?limit=20&offset=0
|
||||
```
|
||||
|
||||
**Response format:**
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"pagination": {
|
||||
"total": 100,
|
||||
"limit": 20,
|
||||
"offset": 0,
|
||||
"hasMore": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All errors return JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Human-readable error message"
|
||||
}
|
||||
```
|
||||
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| 400 | Validation error (missing/invalid fields) |
|
||||
| 401 | Not authenticated (missing or expired session) |
|
||||
| 403 | Permission denied (role lacks required permission) |
|
||||
| 404 | Resource not found |
|
||||
| 409 | Conflict (duplicate entry) |
|
||||
| 413 | File too large (max 10 MB) |
|
||||
|
||||
## User Roles & Permissions
|
||||
|
||||
| Role | Permissions |
|
||||
|------|------------|
|
||||
| `admin` | Full access, manage settings & users |
|
||||
| `attorney` | Create/edit cases, analyses, norms, decisions |
|
||||
| `paralegal` | Read access + limited write |
|
||||
| `viewer` | Read-only |
|
||||
|
||||
## Typical Workflow
|
||||
|
||||
1. **Register** a tenant and admin user
|
||||
2. **Configure** AI provider API key in Settings
|
||||
3. **Create norms** (import NV Bühne provisions)
|
||||
4. **Upload decisions** (create + link applicable norms)
|
||||
5. **Create a case** for a client
|
||||
6. **Upload documents** (court rulings, contracts as PDF/DOCX)
|
||||
7. **Upload & analyze a contract** to identify clause deviations
|
||||
8. **Run AI analyses** referencing norms, decisions, and documents
|
||||
9. **Create proceedings** to track deadlines and workflow steps
|
||||
@@ -3,11 +3,9 @@
|
||||
|
||||
import { type NextRequest } from 'next/server';
|
||||
import { runStructuredAnalysis } from '@/lib/ai/structured-analysis';
|
||||
import { AnalyseMode } from '@/types';
|
||||
import { resolveAnalysisMode } from '@/lib/ai/modes';
|
||||
import { requirePermission } from '@/lib/auth/rbac';
|
||||
|
||||
const VALID_MODES = new Set(Object.values(AnalyseMode));
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await requirePermission('analyses:create');
|
||||
if ('response' in auth) return auth.response;
|
||||
@@ -25,9 +23,17 @@ export async function POST(request: NextRequest) {
|
||||
additionalContext,
|
||||
} = body;
|
||||
|
||||
if (!mode || !VALID_MODES.has(mode)) {
|
||||
if (!mode || typeof mode !== 'string') {
|
||||
return Response.json(
|
||||
{ error: `Invalid mode. Must be one of: ${[...VALID_MODES].join(', ')}` },
|
||||
{ error: 'mode is required' },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const modeConfig = await resolveAnalysisMode(mode, ctx.tenantId);
|
||||
if (!modeConfig) {
|
||||
return Response.json(
|
||||
{ error: `Unbekannter Analysemodus: ${mode}` },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
// Each mode defines a specific legal analysis workflow
|
||||
|
||||
import { AnalyseMode } from '@/types';
|
||||
import { withTenantDb } from '@/lib/db';
|
||||
import { skills } from '@/lib/db/schema';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
|
||||
export interface AnalysisModeConfig {
|
||||
mode: AnalyseMode;
|
||||
mode: string;
|
||||
systemPromptKey: string;
|
||||
requiresNorms: boolean;
|
||||
requiresDecisions: boolean;
|
||||
@@ -12,6 +15,10 @@ export interface AnalysisModeConfig {
|
||||
label: string;
|
||||
/** Short description */
|
||||
description: string;
|
||||
/** Custom system prompt (for skill-based modes) */
|
||||
customSystemPrompt?: string;
|
||||
/** Skill ID if resolved from skills table */
|
||||
skillId?: string;
|
||||
}
|
||||
|
||||
export const ANALYSIS_MODES: Record<AnalyseMode, AnalysisModeConfig> = {
|
||||
@@ -48,3 +55,43 @@ export const ANALYSIS_MODES: Record<AnalyseMode, AnalysisModeConfig> = {
|
||||
description: 'Umfassende Risikoanalyse mit Eintrittswahrscheinlichkeiten und Minderungsstrategien',
|
||||
},
|
||||
};
|
||||
|
||||
const STANDARD_MODES = new Set<string>(Object.values(AnalyseMode));
|
||||
|
||||
/**
|
||||
* Resolve an analysis mode config — checks hardcoded standard modes first,
|
||||
* then falls back to the skills table for custom tenant-defined modes.
|
||||
* Returns null if the mode/slug does not exist.
|
||||
*/
|
||||
export async function resolveAnalysisMode(
|
||||
mode: string,
|
||||
tenantId: string,
|
||||
): Promise<AnalysisModeConfig | null> {
|
||||
// Standard hardcoded mode
|
||||
if (STANDARD_MODES.has(mode)) {
|
||||
return ANALYSIS_MODES[mode as AnalyseMode];
|
||||
}
|
||||
|
||||
// Look up custom skill by slug
|
||||
const skill = await withTenantDb(tenantId, async (tdb) => {
|
||||
const [row] = await tdb
|
||||
.select()
|
||||
.from(skills)
|
||||
.where(and(eq(skills.slug, mode), eq(skills.isActive, true)))
|
||||
.limit(1);
|
||||
return row ?? null;
|
||||
});
|
||||
|
||||
if (!skill) return null;
|
||||
|
||||
return {
|
||||
mode: skill.slug,
|
||||
systemPromptKey: skill.slug,
|
||||
requiresNorms: skill.requiresNorms ?? false,
|
||||
requiresDecisions: skill.requiresDecisions ?? false,
|
||||
label: skill.name,
|
||||
description: skill.description ?? '',
|
||||
customSystemPrompt: skill.systemPrompt,
|
||||
skillId: skill.id,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
import { generateText } from 'ai';
|
||||
import { getModelForTenant } from './providers';
|
||||
import { SYSTEM_PROMPTS, buildContextBlock, type AnalysisModeKey } from './prompts';
|
||||
import { ANALYSIS_MODES } from './modes';
|
||||
import { ANALYSIS_MODES, resolveAnalysisMode } from './modes';
|
||||
import { STRUCTURED_OUTPUT_INSTRUCTION, type StructuredAnalysisOutput } from './structured-output';
|
||||
import { AnalyseMode } from '@/types';
|
||||
import { db } from '@/lib/db';
|
||||
import { norms, normInstruments, decisions, analyses } from '@/lib/db/schema';
|
||||
import { eq, and, lte, or, isNull, gte, inArray } from 'drizzle-orm';
|
||||
@@ -15,7 +14,7 @@ interface StructuredAnalysisInput {
|
||||
tenantId: string;
|
||||
userId: string;
|
||||
caseId?: string;
|
||||
mode: AnalyseMode;
|
||||
mode: string;
|
||||
title: string;
|
||||
query: string;
|
||||
normIds?: string[];
|
||||
@@ -108,8 +107,10 @@ export async function runStructuredAnalysis(
|
||||
result: StructuredAnalysisOutput;
|
||||
sources: { normIds: string[]; decisionIds: string[] };
|
||||
}> {
|
||||
const modeConfig = ANALYSIS_MODES[input.mode];
|
||||
const systemPromptKey = modeConfig.systemPromptKey as AnalysisModeKey;
|
||||
const modeConfig = await resolveAnalysisMode(input.mode, input.tenantId);
|
||||
if (!modeConfig) {
|
||||
throw new Error(`Unbekannter Analysemodus: ${input.mode}`);
|
||||
}
|
||||
|
||||
const [normContext, decisionContext] = await Promise.all([
|
||||
modeConfig.requiresNorms
|
||||
@@ -132,8 +133,10 @@ export async function runStructuredAnalysis(
|
||||
|
||||
const userMessage = messageParts.join('\n\n---\n\n');
|
||||
|
||||
// Add structured output instruction to system prompt
|
||||
const systemPrompt = SYSTEM_PROMPTS[systemPromptKey] + STRUCTURED_OUTPUT_INSTRUCTION;
|
||||
// Use custom system prompt for skill-based modes, standard prompts for built-in modes
|
||||
const basePrompt = modeConfig.customSystemPrompt
|
||||
?? SYSTEM_PROMPTS[modeConfig.systemPromptKey as AnalysisModeKey];
|
||||
const systemPrompt = basePrompt + STRUCTURED_OUTPUT_INSTRUCTION;
|
||||
|
||||
const { model, provider, modelId } = await getModelForTenant(input.tenantId);
|
||||
|
||||
@@ -145,6 +148,7 @@ export async function runStructuredAnalysis(
|
||||
userId: input.userId,
|
||||
caseId: input.caseId ?? null,
|
||||
mode: input.mode,
|
||||
skillId: modeConfig.skillId ?? null,
|
||||
status: 'in_progress',
|
||||
title: input.title,
|
||||
query: input.query,
|
||||
@@ -172,7 +176,7 @@ export async function runStructuredAnalysis(
|
||||
const jsonMatch = result.text.match(/\{[\s\S]*\}/);
|
||||
if (!jsonMatch) throw new Error('No JSON object found');
|
||||
structured = JSON.parse(jsonMatch[0]);
|
||||
structured.mode = input.mode;
|
||||
(structured as any).mode = input.mode;
|
||||
} catch {
|
||||
// Fallback: store raw text, mark as non-structured
|
||||
await db
|
||||
|
||||
@@ -31,6 +31,12 @@ const PERMISSIONS: Record<string, readonly Role[]> = {
|
||||
'decisions:write': ['admin', 'attorney'],
|
||||
'decisions:read': ['admin', 'attorney', 'paralegal', 'viewer'],
|
||||
|
||||
// Skills
|
||||
'skills:read': ['admin', 'attorney', 'paralegal', 'viewer'],
|
||||
'skills:create': ['admin'],
|
||||
'skills:edit': ['admin'],
|
||||
'skills:delete': ['admin'],
|
||||
|
||||
// Settings
|
||||
'settings:manage': ['admin'],
|
||||
|
||||
|
||||
Reference in New Issue
Block a user