# Session 2026-05-06: Refaktor och user-scoped AI

Denna session har genomfört:
- **User-scoped AI-fallback:** AI-förslag för ingrediens- och kategorimatchning är nu individuellt aktiverbara per användare (premium).
- **Admin-toggles:** Backend och UI har stöd för att admin kan slå på/av AI per användare.
- **Premium-scope:** Flutter och backend respekterar premium-flagga och AI-tillgång i alla flöden.
- **Rematch och manuell produkt:** Flutter har stöd för ommatchning och manuell produkt vid import.
- **Lessons learned:**
  - Nullable propagation i Prisma och DTO:er kräver noggrannhet.
  - Fallback-first AI och tydlig separation av analyskontrakt ger robustare flöden.
  - User-scoped features kräver ownerId/userId-filter i all logik.
  - Manuella migrationer kan krävas vid DB-problem (se migrering-MSI.md).

Se även:
- [TEKNISK_BESKRIVNING.md](TEKNISK_BESKRIVNING.md) för teknisk genomgång.
- [AI-FUNKTIONER.md](_archive/microservice-ai/AI-FUNKTIONER.md) för AI-översikt.

# Session 2026-05-09: Simplified Matching Logic (Consolidation Phase 3)

Denna session har genomfört den tredje omarbetningsfasen som konsoliderar receipt-import matching-flödet:

## Genomförda förbättringar

### 1. Unified Matcher (`matchAndEnrichReceiptItem`)
- **Tidigare:** Matching var splittrad mellan `matchProducts()` (alias + word-match) och `enrichWithAiCategories()` (~850 rader).
- **Nu:** En central metod som gör allt i explicit ordning:
  1. **Alias lookup** — certifierad match från ReceiptAlias
  2. **Word-match** — fuzzy produktmatchning med scoring
  3. **Categorization** — regel-baserad → AI (fallback) → guards → hard overrides

### 2. Improved Context Management (`prepareMatchingContext`)
- Alla data (aliases, produkter, unit mappings, categories) hämtas **en gång** per receipt
- Parallell loading med `Promise.all()` — högre performance än tidigare sekventiell loading
- Context passeras till alla items för effektiv återanvändning

### 3. Better Decision Logging (`enrichCategoryForItem`)
- Structured trace med steg-för-steg-loggning:
  - ✓ Rule-based hits with path name
  - ✓ AI suggestions
  - ⚠️ Guard remaps (contradiction resolution)
  - ⚠️ Hard overrides (special cases)
  - ✅ Final decision with confidence level
- Debug-träd med alla decision points
- Kan aktiveras per receipt via `RECEIPT_TRACE_DECISIONS` env-var

### 4. Simplified parseReceipt Flow
```
Before:
  parseReceipt() → matchProducts() → enrichWithAiCategories()

After:
  parseReceipt() → prepareContext() → matchAndEnrichReceiptItem() (per item)
```

## Technical Details

**Nya metoder:**
- `matchAndEnrichReceiptItem()` — unified matching pipeline per item
- `prepareMatchingContext()` — one-time context preparation
- `enrichCategoryForItem()` — consolidated categorization logic
- `findWordMatchWithScore()` — refactored word matching with explicit score return

**Datatyper:**
- `MatchDecision` interface för strukturerad result (i `dto/match-decision.ts`)

**Build Status:**
- ✅ Full TypeScript compilation successful
- ✅ No breaking changes to existing API contracts

## Cleanup Pending

De gamla metoderna är fortfarande i koden men kan nu ta bort:
- `private async matchProducts()` — deprecated by unified matcher
- `private async enrichWithAiCategories()` — deprecated by unified matcher
- `private findWordMatch()` — replaced by findWordMatchWithScore()

**Cleanup tasks:**
1. Ta bort `matchProducts()`
2. Ta bort `enrichWithAiCategories()`
3. Ta bort `findWordMatch()` (gammal version)
4. Uppdatera kommentarer/docstrings
5. Kör full test suite för regression detection

Cleanup bör göras i nästa session för att ge tid för monitoring/QA.

# Plan för omarbetning av receptimport

# 2026-05-07: Säkerhets- och deployförbättringar

- **Inventory är nu user-scopad:** Alla inventory-operationer kräver och filtrerar på userId i backend (schema, migration, service, controller, tester).
- **IDOR-skydd för inventory:** Det är nu omöjligt för användare att läsa eller ändra andras inventarieposter. Tester verifierar att åtkomst nekas vid försök till IDOR.
- **.gitignore och deploy-hygien:** backend/dist och backend/tsconfig.tsbuildinfo ignoreras och är ej längre spårade i git. .env och .env.* ignoreras, men .env.example finns och är uppdaterad.
- **CI/CD-härdning:** npm audit och prisma validate körs i pipeline. Alla tester och byggen måste passera.

## Bakgrund

Nuvarande importflöde blandar ihop två olika ansvar:

1. Att importera och spara receptet så troget källan som möjligt.
2. Att matcha receptets ingredienser mot interna produkter.

Det som fungerar bra i dag är:
- webskrapning
- import med text och bild
- presentation av receptbild och text när receptet väl är sparat

Det som fungerar sämre är:
- ingrediensimporten
- tidig produktmatchning i importsteget
- att receptets innehåll förvrängs eller tappar ingredienser om matchning saknas

## Målbild

Importsteget ska bara göra detta:
- hämta receptets titel, bild, beskrivning, instruktioner och ingrediensrader
- strukturera ingrediensraderna så gott det går
- spara receptet även när ingen produktmatchning finns
- låta användaren granska receptet utan att behöva välja produkter

Ett senare analyssteg ska göra detta:
- jämföra receptets ingredienser med inventory och pantry
- avgöra exakt träff, trolig ersättningsvara eller saknad vara
- ge underlag för shoppinglista
- ge underlag för AI-förslag och substitutionsförslag

## Arkitekturprincip

Receptet ska vara källtroget.
Produktmatchning ska vara ett separat lager ovanpå receptet.

Det innebär att vi behöver separera:
- receptets råa ingredienser
- användarens lager och skafferi
- matchnings- och analyslogik

## Föreslagen datamodell

### Nuvarande problem

I dag kräver `RecipeIngredient` i Prisma:
- `productId`
- `quantity`
- `unit`

Det gör att en ingrediens inte kan sparas om vi inte redan vet vilken intern produkt den motsvarar.

### Ny målmodell

Inför två nivåer för ingredienser:

1. Receptets egen ingrediensrad
2. Matchning/analys mot interna produkter

### Förslag A: utöka befintlig `RecipeIngredient`

Det enklaste spåret är att behålla `RecipeIngredient`, men göra den receptcentrisk i stället för produktcentrisk.

Nya eller ändrade fält:
- `rawLine String?`
- `rawName String`
- `productId Int?` i stället för required
- `quantity Decimal?`
- `unit String?`
- `note String?`
- `matchConfidence Float?`
- `matchSource String?` (`heuristic`, `ai`, `manual`)
- `analysisStatus String?` (`unmatched`, `exact`, `substitutable`, `missing`)

Behåll:
- `alternativeProductIds Json?`
- `recipeId`

### Förslag B: dela upp i två tabeller

Mer robust på sikt men större ombyggnad:
- `RecipeIngredient` blir rå ingrediensrad
- `RecipeIngredientMatch` blir separat matchningslager

Första implementationen bör använda Förslag A för lägre risk.

## Rekommenderad genomförandeordning

1. Gör datamodellen tolerant för omatchade ingredienser.
2. Ändra backend så att import och sparning fungerar utan produktmatchning.
3. Förenkla frontendens importgranskning.
4. Lägg till separat analyssteg mot inventory och pantry.
5. Lägg till AI-stöd först när grundflödet är stabilt.

---

# Fil-för-fil-plan

## 1. Databas och Prisma

### Fil: `backend/prisma/schema.prisma`

### Ändringar
- Gör `RecipeIngredient.productId` optional.
- Gör `RecipeIngredient.product` optional relation.
- Gör `quantity` optional.
- Gör `unit` optional.
- Lägg till `rawName`.
- Lägg till `rawLine`.
- Lägg till `matchConfidence`.
- Lägg till `matchSource`.
- Lägg till `analysisStatus`.

### Exempel på målmodell

```prisma
model RecipeIngredient {
  id                   Int      @id @default(autoincrement())
  recipe               Recipe   @relation(fields: [recipeId], references: [id])
  recipeId             Int
  product              Product? @relation(fields: [productId], references: [id])
  productId            Int?
  rawName              String
  rawLine              String?  @db.Text
  quantity             Decimal? @db.Decimal(10, 2)
  unit                 String?
  note                 String?
  alternativeProductIds Json?
  matchConfidence      Float?
  matchSource          String?
  analysisStatus       String?
  createdAt            DateTime @default(now())
  updatedAt            DateTime @updatedAt
}
```

### Leverabler
- Prisma-schema uppdaterat
- ny migration skapad
- Prisma Client regenererad

### Risk
- All kod som i dag antar att `productId`, `quantity` och `unit` alltid finns måste uppdateras

---

## 2. DTO:er för receptskapande

### Fil: `backend/src/recipes/dto/create-recipe.dto.ts`

### Nuvarande problem
DTO:n kräver att varje ingrediens har:
- `productId`
- `quantity`
- `unit`

Det blockerar trogen import.

### Ändringar
- Gör `productId` optional.
- Gör `quantity` optional.
- Gör `unit` optional.
- Lägg till `rawName` som required.
- Lägg till `rawLine` som optional.
- Lägg till `matchConfidence` och `matchSource` som optional om vi vill bevara preliminär heuristik.

### Mål
Backend ska kunna acceptera ett recept där ingrediensen bara är:
- `rawName`
- eventuellt `rawLine`
- eventuellt `quantity`, `unit`, `note`

### Konsekvens
`ArrayMinSize(1)` kan sannolikt vara kvar, men valideringen måste vara råingrediens-baserad i stället för produkt-baserad.

---

## 3. DTO för parse-resultat

### Fil: `backend/src/recipes/dto/parse-markdown.dto.ts`

### Ändringar
Ingen stor strukturändring behövs här om endpointen fortsatt bara tar emot markdown.

### Eventuell komplettering
Om importen senare ska stödja flera källor mer explicit kan ett `sourceType` läggas till:
- `markdown`
- `web`
- `ocr`

Detta är inte nödvändigt i första omgången.

---

## 4. Receptservice: parse ska sluta göra för mycket

### Fil: `backend/src/recipes/recipes.service.ts`

### Nuvarande problem
`parseMarkdown()` gör i dag flera saker:
- anropar importer-api eller lokal parser
- hämtar alla produkter
- kör heuristisk matchning med normalisering och Levenshtein
- returnerar suggestions

Det gör att importsteget implicit blir ett matchningssteg.

### Rekommenderad ändring i etapp 1
Behåll parse men ändra dess ansvar:
- parse ska alltid returnera råingredienser
- suggestions får vara optional och sekundära
- frontend ska inte vara beroende av suggestions för att kunna spara

### Konkret refaktorering
Dela upp i privata metoder:
- `parseRecipeContent(markdown)`
- `buildIngredientSuggestions(parsedIngredients)`
- `createRecipeFromImport(dto, userId)`

### Ändringar i `create()`
- sluta kräva att alla ingredienser har `productId`
- kör `assertProductsActive()` bara för ingredienser som faktiskt har `productId`
- spara `rawName` även när produktmatchning finns
- spara `rawLine` när det finns

### Ny metod att lägga till
- `analyzeRecipeIngredients(recipeId, userId)`

Ansvar för `analyzeRecipeIngredients`:
- ladda receptets råingredienser
- jämför mot inventory
- jämför mot pantry
- returnera analysstatus per ingrediens
- returnera förslag på ersättningsvaror och inköp

### Ny privat logik i service
- `findExactInventoryMatches(...)`
- `findPantryMatches(...)`
- `findSubstituteCandidates(...)`
- `buildShoppingNeeds(...)`

### Viktig designregel
`parseMarkdown()` får gärna returnera suggestions, men `create()` får inte vara beroende av att de finns.

---

## 5. Receptcontroller: separera import från analys

### Fil: `backend/src/recipes/recipes.controller.ts`

### Ändringar
Behåll:
- `POST /recipes/parse-markdown`
- `POST /recipes`

Lägg till:
- `GET /recipes/:id/analysis`
- eventuellt `POST /recipes/:id/rematch`

### Förslag på ansvar
`GET /recipes/:id/analysis`
- returnerar en användarspecifik analys av receptet mot inventory och pantry

`POST /recipes/:id/rematch`
- kör ny matchning om användaren har ändrat inventory/pantry eller om heuristiken förbättrats

### Mål
Importen blir en egen sak.
Analysen blir en egen endpoint.

---


---

## 7. Alias-strategi och matchedVia (2026-05-07)

- Alias-strategin är nu fullt implementerad:
  - Backend och Flutter stödjer user-alias och globala alias.
  - matchedVia-badge visas i UI (Alias/Ordmatch/AI).
  - Användare kan spara egna alias, admins kan spara globala.
  - Profilsidan har alias-lista för användare, admin-panelen visar produkt-ID och radstruktur.
  - Backend har 3 nya tester för matchedVia, totalt 66 tester gröna.

### Fil: `backend/src/recipes/recipes.module.ts`

### Ändringar
Lägg till eventuella nya providers när analyslogik bryts ut:
- `RecipeAnalysisService`
- eventuellt `RecipeMatchingService`

### Rekommendation
Om analysen växer, bryt ut den från `RecipesService` för att undvika att samma service blir för stor.

---

## 7. Ny analysservice

### Ny fil: `backend/src/recipes/recipe-analysis.service.ts`

### Ansvar
- ta ett sparat recept som indata
- analysera varje ingrediens mot inventory och pantry
- ta fram:
  - exakt träff
  - täcks av pantry
  - möjlig ersättning
  - saknas

### Förslag på output

```ts
{
  recipeId: 123,
  ingredients: [
    {
      ingredientId: 1,
      rawName: "gul lök",
      quantity: 1,
      unit: "st",
      status: "exact_match",
      matchedProductId: 456,
      matchedProductName: "Gul lök",
      source: "inventory"
    }
  ],
  summary: {
    exactCount: 3,
    pantryCount: 2,
    substituteCount: 1,
    missingCount: 4
  },
  shoppingListCandidates: []
}
```

### Nytta
Detta blir grunden för:
- "kan jag laga detta?"
- shoppinglista
- AI-förslag

---

## 8. Ny matchningstjänst

### Ny fil: `backend/src/recipes/recipe-matching.service.ts`

### Ansvar
- matcha rå ingrediens mot produktkatalog
- inte spara något själv
- kunna användas av både import och analys

### Matchningsnivåer
1. Exakt match på normaliserat namn
2. Synonym/alias-match
3. Kategori- eller taggmatchning
4. AI-fallback

### Varför separat tjänst
Det gör att heuristik, aliaslogik och AI-stöd inte behöver ligga inbyggt i `parseMarkdown()`.

---

## 9. AI-integration för ingredienstolkning

### Fil: `backend/src/ai/ai.service.ts`

### Nuvarande läge
AI används redan för kategorisering.

### Nytt användningsområde
Lägg till en separat metod, till exempel:
- `suggestIngredientMatches(rawIngredient, candidates)`
- `suggestSubstitutions(rawIngredient, availableProducts)`

### Viktig regel
AI ska inte styra om receptet går att spara.
AI ska bara förbättra analys och förslag efteråt.

### Mål
AI används där osäkerhet är acceptabel:
- substitutionsförslag
- kompletterande matchning
- shoppingförslag

---

## 10. Importskärm i Flutter

### Fil: `flutter/lib/features/recipes/presentation/create_recipe_screen.dart`

### Nuvarande problem
Skärmen gör i dag detta i review-steget:
- checkbox per ingrediens
- produktval via suggestions
- manuellt tillagda ingredienser med produktdropdown
- vid sparning ignoreras ingredienser utan `productId`

Det gör att användaren i praktiken förväntas produktmatcha importen.

### Målbild för skärmen
Importgranskningen ska fokusera på receptet, inte lagerkoppling.

### Ändringar
Ta bort eller dölja i importläget:
- produktdropdownar
- krav på produktmatchning
- manuell ingrediensskapning via produktlista

Behåll eller lägg till:
- justera titel
- justera beskrivning vid behov
- justera servings
- visa importerade ingrediensrader som text
- tillåt enklare rättningar av mängd, enhet, notering och namn
- inkludera/uteslut ingrediensrad

### Ny save-logik
Vid `_save()` ska frontend skicka:
- `rawName`
- `rawLine`
- `quantity`
- `unit`
- `note`
- `productId` bara om en automatisk eller manuell matchning redan finns

### Rekommenderad UI-förändring
Dela upp skärmen i två modes:
- `import review`
- `manual recipe editing`

Import review ska vara avsevärt enklare än manuell receptredigering.

---

## 11. Parsed recipe-domain i Flutter

### Fil: `flutter/lib/features/recipes/domain/parsed_recipe.dart`

### Ändringar
Lägg till fält som tydligare representerar rå importdata:
- `rawLine`
- `matchState`
- `isParsedSafely`

### Rekommendation
`ParsedIngredient` ska behandlas som importdata, inte som nästan färdig `RecipeIngredient`.

### Ny riktning
`suggestions` ska vara optional hjälpdata i UI, inte en förutsättning för att spara.

---

## 12. Flutter-repository för recept

### Fil: `flutter/lib/features/recipes/data/recipe_repository.dart`

### Ändringar
- uppdatera `createRecipe()` så att request body tillåter råingredienser utan `productId`
- lägg till metod:
  - `fetchRecipeAnalysis(int id)`
- lägg till eventuell metod:
  - `rematchRecipeIngredients(int id)`

### Mål
Frontend ska kunna:
- spara troget importerat recept
- senare hämta analys mot inventory/pantry

---

## 13. API-paths i Flutter

### Fil: `flutter/lib/core/api/api_paths.dart`

### Ändringar
Lägg till:
- `static String analysis(int id) => '/recipes/$id/analysis';`
- eventuellt `static String rematch(int id) => '/recipes/$id/rematch';`

---

## 14. Receptdomän i Flutter

### Fil: `flutter/lib/features/recipes/domain/recipe.dart`

### Nuvarande problem
Nuvarande domän ser sannolikt ingredienser som produktkopplade objekt.

### Ändringar
Gör att `RecipeIngredient` klarar:
- `productId == null`
- `productName == null`
- `rawName` finns alltid
- `quantity` och `unit` kan vara null eller tomma

### Viktigt
UI för receptdetalj ska kunna visa rå ingredienstext även när ingen produktmatchning finns.

---

## 15. Receptdetaljskärm i Flutter

### Fil: `flutter/lib/features/recipes/presentation/recipe_detail_screen.dart`

### Ändringar
Ingredienslistan ska rendera enligt följande prioritet:
1. `rawName` och råa fält
2. matchat produktnamn om det finns

### Exempel
Om produktmatchning saknas ska användaren ändå se:
- `2 msk olivolja`
- `1 gul lök`
- `400 g krossade tomater`

### Lägg till ny sektion
- "Har du hemma?"

Den sektionen ska bygga på `GET /recipes/:id/analysis` i stället för att anta att receptingredienser redan är perfekt produktmatchade.

---

## 16. Ny analysdomän i Flutter

### Ny fil: `flutter/lib/features/recipes/domain/recipe_analysis.dart`

### Ansvar
Representera analysresultat från backend.

### Förslag på typer
- `RecipeAnalysis`
- `RecipeIngredientAnalysis`
- `RecipeIngredientAvailabilityStatus`

### Statusvärden
- `exactMatch`
- `coveredByPantry`
- `substitutable`
- `missing`

---

## 17. Ny provider för analys

### Fil: `flutter/lib/features/recipes/data/recipe_providers.dart`

### Ändringar
Lägg till provider:
- `recipeAnalysisProvider`

### Ansvar
- ladda analys för ett recept
- kunna invalidateras när inventory eller pantry ändras

---

## 18. Manual recipe editing ska vara separat

### Fil: `flutter/lib/features/recipes/presentation/recipe_edit_screen.dart`

### Rekommendation
Behåll denna skärm för riktig redigering av receptets struktur.

### Viktig skillnad mot import
- import review = bevara originalet
- recipe edit = medveten redigering av receptet

### Praktisk åtgärd
Undvik att återanvända fullständig edit-UI direkt i importflödet.

---

## 19. Alias- och produktmatchning

### Relevanta filer
- `backend/src/receipt-import/...`
- `backend/src/products/...`
- `backend/src/recipes/recipes.service.ts`

### Åtgärd
Flytta all generell ingrediensmatchning till en central tjänst i stället för att ha den dold i `parseMarkdown()`.

### Mål
Samma logik ska kunna användas av:
- receptimport
- analys mot inventory/pantry
- AI-förslag
- eventuellt framtida shoppinglista

---

## 20. Migration av befintliga data

### Vad behöver hanteras
Befintliga recept har ingredienser som redan är produktkopplade.

### Strategi
- behåll gamla värden
- fyll `rawName` med bästa tillgängliga namn från produkt eller befintlig text
- sätt `matchSource = 'legacy'` för migrerade rader
- sätt `analysisStatus = null` initialt

### Mål
Gamla recept fortsätter fungera utan att behöva byggas om manuellt.

---

# Implementationsfaser

## Fas 1: Gör receptingredienser omatchningsbara

### Filer
- `backend/prisma/schema.prisma`
- `backend/src/recipes/dto/create-recipe.dto.ts`
- `backend/src/recipes/recipes.service.ts`
- `flutter/lib/features/recipes/data/recipe_repository.dart`
- `flutter/lib/features/recipes/domain/recipe.dart`

### Resultat
Ett recept kan sparas även när inga ingredienser är produktmatchade.

---

## Fas 2: Förenkla importgranskningen

### Filer
- `flutter/lib/features/recipes/presentation/create_recipe_screen.dart`
- `flutter/lib/features/recipes/domain/parsed_recipe.dart`

### Resultat
Användaren granskar receptet som recept, inte som lagerobjekt.

---

## Fas 3: Lägg till analys mot inventory/pantry

### Filer
- `backend/src/recipes/recipe-analysis.service.ts`
- `backend/src/recipes/recipes.controller.ts`
- `backend/src/recipes/recipes.module.ts`
- `flutter/lib/core/api/api_paths.dart`
- `flutter/lib/features/recipes/domain/recipe_analysis.dart`
- `flutter/lib/features/recipes/data/recipe_repository.dart`
- `flutter/lib/features/recipes/data/recipe_providers.dart`
- `flutter/lib/features/recipes/presentation/recipe_detail_screen.dart`

### Resultat
Appen kan svara på:
- vad har jag exakt
- vad täcks av pantry
- vad kan ersättas
- vad saknas

---

## Fas 4: Förbättra matchning och AI

### Filer
- `backend/src/recipes/recipe-matching.service.ts`
- `backend/src/ai/ai.service.ts`
- eventuellt fler UI-filer för ersättningsförslag och shoppinglista

### Resultat
AI blir en förbättrare, inte ett krav för importen.

---

# Beslut som bör tas innan implementation

## 1. Ska `RecipeIngredient` byggas om eller delas upp?

Rekommendation:
- börja med att bygga om `RecipeIngredient`
- dela upp i fler tabeller först om behovet blir tydligt senare

## 2. Ska suggestions visas under import?

Rekommendation:
- ja, men bara som passiv hjälp
- inte som något användaren måste välja för att kunna spara

## 3. Ska användaren kunna redigera ingrediensrader under import?

Rekommendation:
- ja, men bara lätt redigering
- ingen tung produktkoppling i detta steg

## 4. När ska AI användas?

Rekommendation:
- efter att receptet är sparat
- för matchning, substitutioner och shoppingstöd
- inte för att avgöra om receptet får sparas

---

# Definition av klart

Denna omarbetning är klar när följande gäller:

1. Ett importerat recept kan sparas utan att någon ingrediens är kopplad till en intern produkt.
2. Receptets ingredienslista visas troget även utan produktmatchning.
3. Importskärmen kräver inte produktval.
4. Receptdetaljen kan analysera receptet mot inventory och pantry i ett separat steg.
5. Analysen kan särskilja exakt träff, pantry-träff, ersättningsbar och saknad ingrediens.
6. AI används bara som hjälplager ovanpå detta.

---

# Rekommenderat första arbetsblock

Om implementationen ska påbörjas direkt är detta bästa första block:

1. Uppdatera `schema.prisma` för råingrediensstöd.
2. Skapa migration.
3. Uppdatera `create-recipe.dto.ts`.
4. Uppdatera `recipes.service.ts#create()` så att recept kan sparas utan `productId`.
5. Uppdatera `create_recipe_screen.dart` så att `_save()` skickar råingredienser i stället för att kasta bort omatchade rader.

Det blocket ger störst effekt med lägst risk, eftersom det löser kärnproblemet: att importen inte längre förstör receptets innehåll.

## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.

## 2026-05-10: Admin-inventarie (CRUD, merge, filter, sortering, preview, säkerhet), user-scope, IDOR-skydd, säkerhetshärdning, optimeringar och utökad testtäckning är nu genomförda och dokumenterade i README, TEKNISK_BESKRIVNING, SÄKERHETSHÄRDNINGSPLAN och SESSIONLOGGAR.
