# Ressursforbruk — Måling og synliggjøring

## Konsept

Alle ressurskrevende operasjoner logges med naturlige enheter.
Forbruket akkumuleres på tre akser: noden som ble behandlet,
brukeren som utløste det, og samlingen det skjedde i.

Formålet er synliggjøring og innsikt, ikke fakturering.

## Ressurstyper

| Ressurstype | Enhet | Hva måles |
|---|---|---|
| `ai` | tokens inn / tokens ut | LLM-kall via AI Gateway |
| `whisper` | sekunder prosessert lyd | Transkripsjons-pipeline |
| `tts` | tegn | Tekst-til-tale-generering |
| `cas` | bytes | Lagring i CAS (store/delete) |
| `bandwidth` | bytes ut | Servering av mediefiler og publisert innhold |
| `livekit` | deltaker-minutter | WebRTC-sesjoner (møter, opptak) |
| `graph` | noder / edges | Opprettelse av noder og edges i grafen |

## Logg-skjema

```sql
CREATE TABLE resource_usage_log (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    target_node_id UUID NOT NULL REFERENCES nodes(id),
    triggered_by UUID REFERENCES nodes(id),  -- null for system-jobber
    collection_id UUID REFERENCES nodes(id),
    resource_type TEXT NOT NULL,  -- 'ai', 'whisper', 'tts', 'cas', 'bandwidth', 'livekit'
    detail JSONB NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_resource_usage_target ON resource_usage_log(target_node_id);
CREATE INDEX idx_resource_usage_triggered ON resource_usage_log(triggered_by);
CREATE INDEX idx_resource_usage_collection ON resource_usage_log(collection_id);
CREATE INDEX idx_resource_usage_type_time ON resource_usage_log(resource_type, created_at);
```

## Detail-struktur per type

### AI (LLM-kall)

```jsonc
{
    "model_level": "fast",       // "fast" | "smart" | "deep"
    "model_id": "gemini-2.0-flash",
    "tokens_in": 1240,
    "tokens_out": 380,
    "job_type": "auto_tag"       // hva jobben var
}
```

Modellnivåer:

| Nivå | Semantikk | Typiske modeller |
|---|---|---|
| `fast` | Billig, lav latens | Gemini Flash, Haiku |
| `smart` | Balansert | Sonnet, Gemini Pro |
| `deep` | Grundig, dyr | Opus, GPT-4 |

### Whisper (transkripsjon)

```jsonc
{
    "model": "medium",           // "small" | "medium" | "large-v3"
    "duration_seconds": 2520,    // lengde på prosessert lyd
    "language": "no",
    "mode": "batch"              // "live" | "batch"
}
```

### TTS (tekst-til-tale)

```jsonc
{
    "provider": "elevenlabs",    // "elevenlabs" | "local"
    "characters": 8200,
    "voice_id": "norwegian_male_1"
}
```

### CAS (lagring)

```jsonc
{
    "hash": "sha256-abc123...",
    "size_bytes": 84000000,
    "mime": "audio/mp3",
    "operation": "store"         // "store" | "delete"
}
```

### Bandwidth (servering)

```jsonc
{
    "size_bytes": 84000000,
    "path": "/media/podcast/ep47.mp3",
    "client": "Apple Podcasts"   // parsert fra User-Agent
}
```

### LiveKit (sanntid)

```jsonc
{
    "room_id": "meeting-abc123",
    "participant_minutes": 180,
    "tracks": 4                  // antall aktive lyd/video-spor
}
```

### Graph (noder og edges)

Trenger ikke logges i `resource_usage_log` — kan telles direkte fra
`nodes` og `edges`-tabellene med `COUNT` + `GROUP BY created_by` eller
`GROUP BY collection`. Billig spørring, ingen ekstra lagring.

Vises i bruker- og samlingsvisning som kontekst:

```
Vegard:
  423 noder opprettet
  1 204 edges

Sidelinja:
  2 891 noder
  8 340 edges
```

## Aggregering

Tre naturlige visninger, alle er GROUP BY-spørringer mot samme tabell:

### Per node

Synlig i node-detaljer for eieren. Gir innsikt i hva en spesifikk
node har kostet i ressurser.

```
Episode 47:
  AI (smart)     12k tokens inn, 3k ut — 4 jobber
  Whisper        42 min prosessert (medium)
  TTS            8 200 tegn
  CAS            84 MB lagret
  Båndbredde     2.3 GB servert
  LiveKit        180 deltaker-minutter
  12 noder, 34 edges
```

### Per bruker

Synlig for brukeren selv i sin profil/innstillinger. Sum av alle
noder brukeren har utløst arbeid på.

```
Vegard denne måneden:
  AI         fast: 42k / smart: 18k / deep: 3k tokens
  Whisper    3.2 timer prosessert
  TTS        24k tegn
  423 noder opprettet, 1 204 edges
```

### Per samling

Synlig for samlingens eiere/admins. Sum av alt forbruk i samlingen.
Nyttig for å forstå hvilke samlinger som bruker mest ressurser.

```
Sidelinja (mars 2026):
  AI         148k tokens totalt
  Whisper    12.4 timer prosessert
  CAS        2.1 GB lagret
  Båndbredde 48 GB servert
  2 891 noder, 8 340 edges
```

## Triggered-by-regler

| Scenario | triggered_by |
|---|---|
| Bruker klikker "oppsummer" | Brukeren |
| Bruker sender melding som trigger auto-tag | Brukeren |
| Nattlig samlings-digest | null (system) |
| Podcast-nedlasting av ekstern lytter | null (system) |

Når `triggered_by` er null, tilhører forbruket kun samlingen —
det belaster ingen spesifikk bruker.

## Logging-ansvar

Maskinrommet logger all ressursbruk. Hver handler (AI, Whisper, TTS,
CAS, LiveKit) skriver til `resource_usage_log` som siste steg etter
vellykket operasjon. Feilede jobber logges ikke — ingen ressurs ble
forbrukt.

Båndbredde-logging skjer via Caddy-logg-parsing i nattlig batch-jobb
(samme mønster som `docs/features/podcast_statistikk.md`).

## Implementeringsstatus

Følgende ressurstyper logges til `resource_usage_log`:

| Ressurstype | Handler | Status |
|---|---|---|
| `ai` | `summarize.rs`, `ai_edges.rs`, `agent.rs` | Implementert. Token-telling fra LiteLLM `usage`-feltet. Agent bruker `claude` CLI og logger 0 tokens (CLI gir ikke token-info). |
| `whisper` | `transcribe.rs` | Implementert. Logger `duration_seconds`, `model`, `language`, `mode`. |
| `tts` | `tts.rs` | Implementert. Logger `provider`, `characters`, `voice_id`. |
| `cas` | `intentions.rs` (upload_media) | Implementert. Logger kun nye filer (ikke dedup). `hash`, `size_bytes`, `mime`, `operation`. |
| `livekit` | `intentions.rs` (join_communication) | Implementert. Logger `join`-hendelser. Faktisk `participant_minutes` krever LiveKit webhook-integrasjon (fremtidig). |
| `bandwidth` | `bandwidth.rs` | Implementert. Nattlig jobb (kl 03:00) parser Caddy JSON-access-logger. |

### Sentralisert hjelpemodul

`resource_usage.rs` tilbyr `log()` og `find_collection_for_node()`.
Alle handlers bruker denne for konsistent logging.

### Admin-dashboard (oppgave 15.8)

`/admin/usage` viser aggregert forbruksoversikt:

- **Totalkort** per ressurstype med naturlige enheter (tokens, timer, GB, tegn, minutter)
- **Per samling**-tabell: filtrerbar på ressurstype og tidsperiode (7/30/90/365 dager)
- **AI drill-down**: per jobbtype og modellnivå (fast/smart/deep), tokens inn/ut
- **Daglig tidsserie**: aktivitet per dag og ressurstype
- Samlings- og ressurstype-filtre med live-oppdatering

Backend: `maskinrommet/src/usage_overview.rs` → `GET /admin/usage?days=30&collection_id=<uuid>`
Frontend: `frontend/src/routes/admin/usage/+page.svelte`

### Brukersynlig forbruk (oppgave 15.9)

To nye endepunkter i maskinrommet:

- `GET /my/usage?days=30` — brukerens eget forbruk (filtrert på `triggered_by`)
- `GET /query/node_usage?node_id=<uuid>&days=30` — forbruk for én node (kun eier)

Begge returnerer `by_type` (aggregert per ressurstype) og `daily` (tidsserie).
`/my/usage` inkluderer også `graph` (noder/edges opprettet av brukeren).

Tilgangssjekk for nodeforbruk: brukeren må ha opprettet noden (`created_by`)
eller ha `owner`/`admin`-edge til den.

Frontend:

- `/profile` — profilside med grafstatistikk, totalkort per ressurstype,
  daglig tidsserie. Lenket fra brukernavnet i headeren.
- `NodeUsage.svelte` — kollapserbart panel som viser nodeforbruk.
  Integrert i samlings-detaljsiden (`/collection/[id]`).

Backend: `maskinrommet/src/user_usage.rs`
Frontend: `frontend/src/routes/profile/+page.svelte`,
`frontend/src/lib/components/NodeUsage.svelte`

### Caddy-oppsett

JSON access logging er konfigurert i Caddyfile for `sidelinja.org`
og `synops.no`. Logger skrives til `/var/log/caddy/access-*.log`
med 100 MiB rotasjon og 7 filer beholdt.
