# 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.
