Spec: brukerklasser og AI-budsjettering

Konfigurerbare brukerklasser (Basis, Proff(ish), Superduper ultra
premium, Admin) med token-budsjett per dag, modellnivå-tilgang og
feature-gates. Budsjettsjekk før hvert LLM-kall. Admin-forbruk
vises med kostnadsestimat. Automatiske triggere teller mot
brukerens budsjett. Klasser og brukere som noder i grafen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-20 01:21:44 +00:00
parent 9bc8624592
commit 68d8fff2dd

300
docs/infra/brukerklasser.md Normal file
View file

@ -0,0 +1,300 @@
# Brukerklasser og AI-budsjettering
## Konsept
Brukerklasser styrer tilgang til AI-ressurser. Hver klasse
definerer token-budsjett, tilgjengelige modellnivåer og
hvilke AI-features brukeren kan trigge. Admin konfigurerer
klasser og tildeler brukere.
## Brukerklasser som noder
```
node_kind: 'user_class'
title: "Proff(ish)"
metadata: {
budget_tokens_per_day: 500000,
model_access: ["synops/low", "synops/medium"],
features: ["chat_bot", "summarize", "suggest_edges", "ai_process"],
priority: 2, // høyere = finere (for sortering)
description: "For de som mener alvor. Noen ganger."
}
```
## Default-klasser
| Klasse | Budsjett/dag | Modeller | Features |
|--------|-------------|----------|----------|
| **Basis** | 50k tokens | low | chat-bot |
| **Proff(ish)** | 500k tokens | low, medium | chat-bot, oppsummering, suggest-edges, ai-prosessering |
| **Superduper ultra premium** | 5M tokens | low, medium, high | alt unntatt modellvalg og konfig |
| **Admin** | ubegrenset | alle inkl. extreme | alt + modellvalg (/claude, /grok) + konfig |
Klassenavnene er konfigurerbare. Default-navnene er selvironiske
i tråd med plattformens tone. Admin kan opprette, endre, slette
og omdøpe klasser fritt.
## Tildeling
Bruker → klasse via edge:
```
person_node --[has_class]--> user_class_node
```
Én klasse per bruker. Default for nye brukere: "Basis" (eller
konfigurerbar i admin). Admin kan endre klasse per bruker.
## Budsjettsjekk
Maskinrommet sjekker budsjett **før** hvert LLM-kall:
```rust
async fn check_budget(user_node_id: Uuid, pool: &PgPool) -> Result<bool> {
// Hent brukerens klasse
let class = get_user_class(user_node_id, pool).await?;
// Ubegrenset? Alltid OK.
if class.budget_tokens_per_day == 0 { return Ok(true); }
// Hent forbruk siste 24 timer
let used = sqlx::query_scalar!(
"SELECT COALESCE(SUM(input_tokens + output_tokens), 0)
FROM ai_usage_log
WHERE user_node_id = $1
AND created_at > now() - interval '1 day'",
user_node_id
).fetch_one(pool).await?;
Ok(used < class.budget_tokens_per_day)
}
```
Ved overskridelse:
- LLM-kall avvises
- Bruker får melding: "Daglig budsjett brukt opp. Tilbakestilles kl. 00:00."
- Admin varsles hvis ønskelig
- Jobber som trigger for brukeren køes til neste dag (eller droppes)
## Modellnivå-gate
I tillegg til token-budsjett: brukerens klasse bestemmer hvilke
modellnivåer (synops/low, medium, high, extreme) de kan bruke.
```rust
async fn check_model_access(user_node_id: Uuid, model_alias: &str, pool: &PgPool) -> bool {
let class = get_user_class(user_node_id, pool).await.unwrap();
class.model_access.contains(model_alias)
}
```
"Basis"-brukere får alltid `synops/low`. Prøver de en feature
som krever `synops/high`, downgrades modellen automatisk til
det høyeste nivået de har tilgang til — med varsel om at
resultatet kan være dårligere.
## Automatiske triggere og budsjett
Når en orkestrering (som suggest-edges) trigger på en brukers
handling, teller kostnadene mot **brukerens** budsjett:
```
Trond oppretter en innholds-node
→ orkestrering: suggest-edges triggers
→ budsjettsjekk: har Trond (Proff(ish)) tokens igjen?
→ ja: kjør suggest-edges, logg mot Trond
→ nei: dropp, logg "skipped:budget"
```
Systemjobber (RSS, rendering) som ikke er bruker-initiert
kjører under et "system"-budsjett som admin styrer separat.
## Feature-gate
Hver AI-feature har et navn brukt i feature-listen:
| Feature-nøkkel | Beskrivelse |
|-----------------|------------|
| `chat_bot` | @bot i samtaler |
| `summarize` | AI-oppsummering |
| `suggest_edges` | Automatisk foreslåtte relasjoner |
| `ai_process` | AI-prosessering (verktøy-panel) |
| `transcribe` | Whisper-transkribering |
| `tts` | Tekst-til-tale |
| `clip` | Web clipper med AI-oppsummering |
| `orchestration` | AI-steg i orkestreringer |
| `model_select` | Eksplisitt modellvalg (/claude, /grok) |
| `admin_config` | Konfigurasjon av klasser og ruting |
Klassen lister hvilke features den har tilgang til. Maskinrommet
sjekker feature-tilgang før det tillater operasjonen.
## Admin-UI: Brukerpanel
### Brukeroversikt (/admin/users)
```
┌─ Brukere ──────────────────────────────────────────┐
│ │
│ Navn Klasse Forbruk Status │
│ Vegard Admin 1.2M i dag ✅ │
│ Trond Superduper ultra.. 2.1M/5M ✅ │
│ Arne Proff(ish) 430k/500k ⚠️ │
│ Lise Basis 48k/50k ⚠️ │
│ Gjest Basis 0/50k ✅ │
│ │
│ Klikk bruker → detaljer med klasse-endring │
└─────────────────────────────────────────────────────┘
```
### Brukerdetaljer
```
┌─ Arne ─────────────────────────────────────────────┐
│ │
│ Klasse: [Proff(ish) ▼] │
│ │
│ Forbruk i dag: 430,000 / 500,000 tokens (86%) │
│ Forbruk denne uken: 1.8M tokens │
│ ████████████████████░░░ │
│ │
│ Modellfordeling i dag: │
│ synops/low: 380k tokens (88%) │
│ synops/medium: 50k tokens (12%) │
│ │
│ Siste AI-kall: │
│ 14:30 chat_bot (synops/low) 1.2k tokens │
│ 14:15 suggest_edges (synops/low) 800 tokens │
│ 13:50 summarize (synops/medium) 3.5k tokens │
│ │
│ [Overstyr budsjett] [Deaktiver bruker] │
└─────────────────────────────────────────────────────┘
```
### Admin-forbruk
Admin har ubegrenset budsjett, men forbruket vises like fullt:
```
┌─ Vegard (Admin) ───────────────────────────────────┐
│ │
│ Forbruk i dag: 1,200,000 tokens │
│ Forbruk denne uken: 8.3M tokens │
│ Forbruk denne måneden: 31M tokens │
│ │
│ Estimert kostnad (basert på modellpriser): │
│ synops/low: 800k × $0.10/M = $0.08 │
│ synops/medium: 300k × $0.50/M = $0.15 │
│ synops/high: 100k × $3.00/M = $0.30 │
│ ───────────────────────────────── │
│ Total i dag: ~$0.53 │
│ Total denne uken: ~$3.70 │
│ Total denne måneden: ~$14.20 │
│ │
│ Claude Code (abonnement, ikke tokens): │
│ Sesjoner i dag: 3 │
│ Total tid: 4t 20min │
└─────────────────────────────────────────────────────┘
```
### Klasse-konfigurasjon (/admin/classes)
```
┌─ Brukerklasser ────────────────────────────────────┐
│ │
│ Navn Budsjett Modeller │
│ Basis 50k/dag low │
│ Proff(ish) 500k/dag low+medium │
│ Superduper ultra premium 5M/dag low+med+high │
│ Admin ∞ alle │
│ │
│ [+ Ny klasse] │
└─────────────────────────────────────────────────────┘
Klikk en klasse → rediger:
┌─ Rediger: Proff(ish) ─────────────────────────────┐
│ │
│ Navn: [Proff(ish) ] │
│ Beskrivelse: [For de som mener alvor. ] │
│ │
│ Budsjett: [500000] tokens per [dag ▼] │
│ │
│ Modellnivåer: │
│ ☑ synops/low │
│ ☑ synops/medium │
│ ☐ synops/high │
│ ☐ synops/extreme │
│ │
│ Features: │
│ ☑ chat_bot ☑ summarize │
│ ☑ suggest_edges ☑ ai_process │
│ ☐ transcribe ☐ tts │
│ ☐ model_select ☐ admin_config │
│ │
│ Brukere i denne klassen: 2 (Arne, Lise) │
│ │
│ [Lagre] [Avbryt] │
└─────────────────────────────────────────────────────┘
```
## Systembudsjett
Jobber som ikke tilhører en spesifikk bruker (RSS-generering,
planlagt publisering, system-orkestreringer) kjører under et
eget systembudsjett:
```
metadata på system-config-node: {
system_budget_tokens_per_day: 1000000,
system_model_level: "synops/low"
}
```
Vises i admin-panelet under "Systemforbruk".
## Implementering
### Database
Ingen ny tabell — brukerklasser er noder:
```sql
-- Opprett default-klasser (seed)
INSERT INTO nodes (node_kind, title, visibility, metadata) VALUES
('user_class', 'Basis', 'readable', '{"budget_tokens_per_day":50000,"model_access":["synops/low"],"features":["chat_bot"],"priority":1,"description":"Grunnpakken. Du får en bot. Vær takknemlig."}'),
('user_class', 'Proff(ish)', 'readable', '{"budget_tokens_per_day":500000,"model_access":["synops/low","synops/medium"],"features":["chat_bot","summarize","suggest_edges","ai_process"],"priority":2,"description":"For de som mener alvor. Noen ganger."}'),
('user_class', 'Superduper ultra premium', 'readable', '{"budget_tokens_per_day":5000000,"model_access":["synops/low","synops/medium","synops/high"],"features":["chat_bot","summarize","suggest_edges","ai_process","transcribe","tts","clip","orchestration"],"priority":3,"description":"Alt du kan drømme om. Nesten."}'),
('user_class', 'Admin', 'readable', '{"budget_tokens_per_day":0,"model_access":["synops/low","synops/medium","synops/high","synops/extreme"],"features":["chat_bot","summarize","suggest_edges","ai_process","transcribe","tts","clip","orchestration","model_select","admin_config"],"priority":99,"description":"Hersker over alt. Betaler regningen."}');
```
(`budget_tokens_per_day: 0` = ubegrenset for Admin)
### Maskinrommet
- `check_ai_budget(user_node_id, estimated_tokens)` — kall før LLM
- `check_model_access(user_node_id, model_alias)` — kall før LLM
- `check_feature_access(user_node_id, feature_key)` — kall før feature
- Alle bruker `get_user_class()` som cacher i minne (invalideres ved endring)
### Frontend
- `/admin/users` — utvidet med klasse, forbruk, statusindikator
- `/admin/classes` — ny side for klasse-konfigurasjon
- Brukerens egen profil viser forbruk og gjenstående budsjett
### Modellpris-tabell
For å estimere kostnad i admin-UI:
```sql
CREATE TABLE model_pricing (
model_alias TEXT PRIMARY KEY,
input_price_per_m NUMERIC, -- pris per million input-tokens
output_price_per_m NUMERIC, -- pris per million output-tokens
updated_at TIMESTAMPTZ DEFAULT now()
);
```
Admin oppdaterer priser manuelt (de endres sjelden).
Brukes kun for visning — ikke for faktisk fakturering.