# Del 28 – Lärande-loop (ai_corrections → ai_training_bank)

> Ingen AI är facit. Varje skanning där användaren granskar förslag blir ett
> träningsexempel — om hon samtycker. Detta dokument beskriver datakontraktet,
> samtyckesgätning och GDPR-radering.

## Översikt

```
App → skanna → worker → AI-förslag → app (awaiting_confirmation)
                         ↓
              användaren granskar varje item
                         ↓
              POST /v1/scans/:id/confirm
                         ↓
           ai_corrections (ett rad per item)
                         ↓
         BUILD_TRAINING_SAMPLE (scheduler, 1×/vecka)
                         ↓
              ai_training_bank (cibello-ägt dataset)
```

`ai_training_bank` är den consenteda platsen för rikare data
(bildreferens, förslag, korrigering). Analytics får aldrig innehålla PII
eller råa bilder (se §56/§58).

## Datakontrakt per skanning

Varje rad i `ai_corrections` representerar **ett item** från en skanning:

| Fält                             | Innehåll                                                                                                                                                                           |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scanJobId`                      | Källskanningen (`scan_jobs.id`).                                                                                                                                                   |
| `taskType`                       | T.ex. `ANALYZE_FRIDGE_IMAGE`, `READ_RECEIPT`.                                                                                                                                      |
| `aiOutput`                       | Hela AI-raw-resultatet från `scan_jobs.result`.                                                                                                                                    |
| `proposal`                       | Det specifika AI-förslag item:et kom från (`detectedName`, `canonicalIngredientId`, `brand`, `estimatedQuantity`, `unit`, `bestBeforeDate`, `confidence`, `requiresConfirmation`). |
| `userCorrection`                 | `{ action: "accept" \| "edit" \| "reject" \| "add", corrected?: {...} }`                                                                                                           |
| `corrected` (inbäddad)           | Användarens slutgiltiga värden vid accept/edit/add: `displayName`, `canonicalIngredientId`, `brand`, `quantity`, `unit`, `bestBeforeDate`, `useByDate`.                            |
| `imageS3Key`                     | Första lagrade bildnyckeln från skanningen, **endast om** `image_training`-samtycke fanns vid bekräftelsen. Annars `null`.                                                         |
| `modelVersion` / `promptVersion` | Vilken modell och prompt som producerade förslaget.                                                                                                                                |
| `consentSnapshot`                | `{ anonymized_improvement: "granted"\|"denied", image_training: "granted"\|"denied", ... }` som JSON vid bekräftelsetillfället.                                                    |
| `createdAt`                      | Tidsstämpel för bekräftelsen.                                                                                                                                                      |

### Åtgärder som sparas

- **`accept`** — positivt exempel. AI-förslaget var korrekt nog att användaren accepterade det oförändrat.
- **`edit`** — användaren ändrade något (namn, kvantitet, enhet, datum …).
- **`add`** — AI missade item:et helt; användaren lade till det manuellt.
- **`reject`** — AI hittade något som inte finns; användaren kastade det.

Alla fyra åtgärder sparas. Bara accept/edit/add leder till att ett
`inventory_items`-rad skapas; reject gör det inte.

## ai_training_bank

När `BUILD_TRAINING_SAMPLE` kör (veckoschema i worker) bankas rader med
`anonymized_improvement = granted` till `ai_training_bank`:

| Fält                             | Innehåll                                                       |
| -------------------------------- | -------------------------------------------------------------- |
| `correctionId`                   | Referens till `ai_corrections.id` (cascade delete).            |
| `scanJobId`                      | Källskanningen.                                                |
| `version`                        | Dataset-version, t.ex. `v1`. Bumpar när formatet ändras.       |
| `taskType`                       | Samma som källan.                                              |
| `imageS3Key`                     | Kopierad från `ai_corrections.image_s3_key` (kan vara `null`). |
| `proposal`                       | AI-förslaget för just det item:et.                             |
| `action`                         | Användarens åtgärd.                                            |
| `corrected`                      | Slutgiltiga värden, eller `null` vid reject.                   |
| `modelVersion` / `promptVersion` | Spårbarhet till modell/prompt.                                 |
| `consentSnapshot`                | Kopia av samtyckesläget.                                       |
| `exportedAt`                     | När raden bankades.                                            |

Banken ägs av cibello och är versionerad. Ingen extern leverantör
anropas under exporten — jobbet får aldrig kasta på grund av att
`AAMOS_MODE=gemini` saknar `EXPORT_TRAINING_SAMPLE`-stöd.

## Samtycke

Två separata samtycken styr vad som sparas och var:

1. **`anonymized_improvement`** — krävs för att överhuvudtaget banka till
   `ai_training_bank`. Utan detta lämnas `ai_corrections` kvar men raderna
   exporteras inte.
2. **`image_training`** — krävs för att `imageS3Key` ska sparas. Utan
   samtycke sparas endast textparet (`proposal`, `corrected`) och
   `imageS3Key` är `null`.

Samtyckessnapshoten sparas per rad så att framtida ändringar av
användarens samtycke inte påverkar redan bankade data.

## Retention

- `ai_corrections`: behålls så länge användarkontot finns. Underlättar
  support och debugging.
- `ai_training_bank`: behålls så länge användarkontot finns, om inte
  användaren återkallar samtycke — då raderas endast rader där
  `consentSnapshot.anonymized_improvement = "denied"` (i praktiken
  exporteras de aldrig).
- **Träningsbilder (`image_s3_key`)**: raderas senast **90 dagar efter
  att de bankats** till `ai_training_bank`, om användaren inte aktivt
  valt att spara sina bidrag längre. För användare som samtycker till
  långtidslagring (t.ex. för att förbättra modellen över tid) kan
  bilderna behållas så länge kontot finns, men aldrig längre än vad
  användaren samtyckt till. Vid återkallat `image_training`-samtycke
  raderas bilderna inom 30 dagar.
- Bilder som endast ingår i råa `scan_jobs` (inte bankade som
  träningsdata): raderas enligt samma 90-dagarspolicy eller när skanningen
  tas bort, beroende på vilket som inträffar först.

## GDPR / kontoradering

`DELETE /v1/me` soft-deletar (anonymiserar) `users`-raden för att
bevara referensintegritet i aggregat. Därför fyras **inte** FK-cascade
från `users` till `ai_corrections` eller `scan_jobs`. Istället sker
raderingen explicit:

1. Saml alla distinkta bildnycklar för användaren:
   - `ai_corrections.image_s3_key`
   - `ai_training_bank.image_s3_key` (via join mot `ai_corrections`)
   - `scan_jobs.s3_keys`
2. Radera varje nyckel i objektlagring med `storage.deleteObject`.
   S3-fel loggas och påverkar inte fortsatt radering.
3. Radera `ai_corrections` för `userId`. `ai_training_bank` försvinner
   automatiskt via `correction_id ON DELETE CASCADE`.
4. Radera `scan_jobs` för `userId` (även denna cascade fyrar inte vid
   soft-delete).
5. Fortsätt med hård radering av övrig persondata och anonymisera
   användarposten.

Verifiera alltid att kontoraderingstestet kontrollerar både
`ai_corrections`, `ai_training_bank`, `scan_jobs` och att inga
överblivna bildnycklar finns kvar i S3/mock-lagringen.

## Inget PII i analytics

Träningsdatan (rikare bild+förslag+korrigering) finns endast i
`ai_corrections`/`ai_training_bank` under samtycke. Analytics-events som
`AI_CORRECTED` innehåller endast `scanJobId`, `taskType` och `field`
(åtgärd), aldrig bilder, namn eller detaljerade värden.
