# Forbedringsløkke — design & datamodell

**Status:** migrering `2026-06-02-scenario-feedback-pipeline.sql` + `2026-06-03-scenario-signal-view.sql` + UI/admin Quality-tab + transparens-notice i app. Gullsett gjenstår.
**Eier:** DPO + eng. **Sist oppdatert:** 2026-05-31.

## Formål

Hente brukerverdi ut av tjenesten for å forbedre den, **uten** å bryte personvern eller
arbeidsrettslig grunnlag. Tre mål, prioritert:

1. **Opplevd verdi** — opplever brukerne at treningen er nyttig? (viktigst)
2. **Bedre scenariogenerering** — hvilke genererte scenarioer er flate/urealistiske?
3. **Rubrikk-kalibrering** — scorer AI-en slik en fagperson ville?

## Kjerneprinsipp — skill artefakt fra menneske

Hele modellen hviler på ett skille:

| Type | Eksempel | Personopplysning? | Bruk |
|---|---|---|---|
| **AI-generert artefakt** | case-tekst, persona, scenario | **Nei** (AI-generert per invariant) | Fritt inspiserbart for å diagnostisere genereringskvalitet |
| **Menneskets bidrag** | det traineen skrev, scoren de fikk | **Ja** — i HR-tier potensielt prestasjonsdata | Kun aggregert, med minimums-cellestørrelse |

> Aggreger mennesket. Inspiser artefaktet. Datamodellen bygges rundt dette skillet —
> artefaktet logges *frakoblet* trainee-responsen.

## Datamodell

Følger arkitektur-invarianten: skriv til norske kildetabeller i `app`-schema, les via
engelske views. Alt scopet til `org_hash` / `kommune_hash`.

### 1. `app.generert_case` (view: `app.generated_case`) — artefakt-logg

Persisterer det AI-genererte artefaktet, frakoblet trainee-respons. Ikke-personlig innhold.

| Kolonne | Type | Merknad |
|---|---|---|
| `case_id` | uuid PK | stabil id for artefaktet |
| `org_hash` | text | kun tenancy-scoping — innholdet er ikke-personlig |
| `domene` | text | children / adult_care / health / youth_justice / hr |
| `seleksjoner` | jsonb | de 10 kortvalgene som produserte casen |
| `case_tekst` | jsonb | generert background / persona / opening |
| `prompt_versjon` | text | for å spore hvilken prompt-revisjon som genererte |
| `modell` | text | LLM-modell brukt |
| `opprettet` | timestamptz | |

Inneholder **ingen** trainee-respons. Dette er det fagpersoner (du) ser på for å fikse
genereringen — uten personvernproblem.

### 2. `app.scenario_tilbakemelding` (view: `app.scenario_feedback`) — opt-in ekspert-feedback

Frivillig, samtykket. «Kjentes dette realistisk ut?»

| Kolonne | Type | Merknad |
|---|---|---|
| `feedback_id` | uuid PK | |
| `case_id` | uuid FK → generert_case | |
| `org_hash` | text | |
| `bruker_hash` | text | pseudonym; aldri eksponert til klient |
| `realistisk` | smallint | tommel opp/ned eller 1–5 |
| `nyttig` | smallint | |
| `fritekst` | text | valgfritt |
| `opprettet` | timestamptz | |

### 3. `app.scenario_signal` (view: `app.scenario_signal_v`) — aggregert signal

**View, ikke råtabell.** Aggregerer eksisterende `app.sesjon`-data per scenariotype/rolle/
dimensjon. **Minimums-cellestørrelse håndheves** (`HAVING count(*) >= k`, foreslått k=5)
— ellers re-identifiserbart i små kommuner.

Eksponerer: antall startet, fullført, avbrutt, snitt-rating. Aldri individnivå.

### 4. `app.rubrikk_gullsett` (view: `app.rubric_goldset`) — kalibreringssett

Lite, **samtykket** ekspert-dobbeltscoret utvalg. **Ikke** produksjonshøsting.

| Kolonne | Type | Merknad |
|---|---|---|
| `gullsett_id` | uuid PK | |
| `respons_tekst` | text | syntetisk eller eksplisitt samtykket |
| `kriterium` | text | rubrikk-kriterium |
| `ai_score` | smallint | |
| `ekspert_score` | smallint | |
| `scorer_hash` | text | pseudonym |
| `opprettet` | timestamptz | |

## Hvordan målene dekkes

| Mål | Kilde | Tilstrekkelig? |
|---|---|---|
| 3 — opplevd verdi | `scenario_signal` (fullføring/gjenbruk) + `scenario_tilbakemelding` | ✅ fullt |
| 2 — generering | `scenario_signal` triagerer → fagperson inspiserer `generert_case` | ✅ (artefakt er ikke-personlig) |
| 1 — kalibrering | `scenario_signal` fanger *drift* (symptom); `rubrikk_gullsett` gir *sann* kalibrering | ⚠️ drift via aggregat, kalibrering via gullsett — ikke produksjonshøsting |

## Sikkerhet & scoping

- **Alt scopet til `org_hash`** — ingen kryss-kommune-læring som lekker. Gjelder også
  signal-viewet.
- **`resolveAuthUid(req)`** eneste identitetskilde i forbedrings-API-et.
- **`res.setHeader("Cache-Control", "no-store")`** på alle routes som returnerer data.
- **Aldri eksponer** `bruker_hash` / `auth_uid` til klient.
- **`opslog.admin_action`** logger all admin-tilgang/eksport av aggregater (immutabelt).
- **Parametriserte queries** alltid.

## DPIA-sjekkliste

- [ ] **Rettsgrunnlag** dekker tjenesteforbedring — dokumentert i privacy.html (Art. 6(1)(f)); org DPO signerer i Org Admin → Settings.
- [ ] **Formålsbegrensning** dokumentert; ingen stille gjenbruk.
- [x] **Minimums-cellestørrelse** (k≥5) håndhevet i alle aggregater — `app.scenario_signal`, `app.scenario_feedback_signal`.
- [ ] **Lagringsbegrensning** — definert oppbevaringstid for feedback/gullsett.
- [ ] **DPO-gjennomgang** før produksjonsskala utover pilot — org-spesifikk sign-off i Org Admin (versjon 1.1-2026-06).
- [x] **Transparens** — brukervendt tekst (se [feedback-user-help.md](feedback-user-help.md)) — live i app + handbook.

## Det vi IKKE gjør (linjene)

- ❌ Individuell prestasjonsdata forlater org/kommune-grensen.
- ❌ Treningsdata mater tilbake i noe som berører ansettelsesforholdet (HR-tier sandkasses).
- ❌ Fine-tuning på ekte trainee-transkripsjoner.
- ❌ Aggregater uten minimums-cellestørrelse.
- ❌ Gjenbruk utover varslet formål.

## Neste steg

1. DPO-gjennomgang av dette designet før pilot skaleres.
2. ~~Skriv migrering~~ — `2026-06-02-scenario-feedback-pipeline.sql` + `2026-06-03-scenario-signal-view.sql` (kjørt på prod).
3. ~~Bygg signal-viewet med k-anonymitet~~ — `app.scenario_signal`, `app.scenario_feedback_signal`.
4. ~~Legg opt-in feedback-knapp i scenario-UI.~~ — `ScenarioFeedback` i `src/case-output.jsx`.
5. Seed et lite gullsett for kalibrering (`app.rubrikk_gullsett`).
6. Editorial view: inspiser `generert_case`-artefakter direkte fra admin (ikke bare feedback-tabell).
