Initial commit (unpacked platform)

This commit is contained in:
Sven (AAMOS AI)
2026-08-05 19:21:11 +07:00
commit ac5340195a
314 changed files with 57584 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
# Del 1 Executive summary
## Kärnprodukt
Appen (arbetsnamn: se `brand.config.json`) är ett AI-baserat operativsystem för hushållets mat inte ännu en receptapp.
Skillnaden är **Food Twin**: en transaktionsbaserad digital tvilling av allt ätbart i
hushållet (kyl, frys, skafferi, matlådor), byggd ur foton, kvitton, streckkoder och
manuell inmatning, som hålls levande av varje lagad måltid.
Ovanpå tvillingen ligger appens centrala löfte, knappen **"Vad ska vi äta?"**:
personliga, förklarade och direkt användbara middagsförslag som väger samman vad som
finns hemma, vad som går ut, hushållets smak, tid, budget, näringsmål, säsong och högtid.
Varje förslag motiveras öppet: _"Ni har 92 % av ingredienserna. Kycklingen bör användas
senast i morgon. Rätten ger 58 gram protein per portion."_
Fyra flikar bär hela upplevelsen: **Vad ska vi äta?** (rekommendationer),
**Skanna** (kyl/frys/skafferi/tallrik/kvitto/streckkod), **Min dag** (nutrition mot
personliga mål) och **Hemma** (lager, matlådor, inköpslista, budget, hushåll).
## Målgrupper
1. **Familjelogistikern (primär)** 2845 år, planerar mat för 36 personer, sliten av
"vad ska vi äta?"-frågan, känslig för matsvinn och matkostnad. Betalar för tid och ro.
2. **Hälso-/träningsmedvetna** vill ha protein- och kalorikontroll utan MyFitnessPal-pyssel;
enkelt läge + tallriksfoto sänker friktionen radikalt.
3. **Budget- och svinnjägaren** studenten/pensionären/klimatmedvetna som vill använda
det som finns innan nytt köps. Gratisnivån är deras ingång, "använd först" deras krok.
4. **Matkreatörer (sekundär)** bygger publik via recept, cred och ranking; skapar
innehållsvolym och organisk tillväxt.
## Differentiering
| Konkurrentklass | Deras fokus | Vårt övertag |
| --------------------------------- | -------------------- | ----------------------------------------------------------------- |
| Receptappar (t.ex. köksklassiker) | Innehåll | Vet vad du HAR hemma recepten filtreras genom ditt lager |
| Kaloriräknare (MFP m.fl.) | Loggning | Loggningen är en biprodukt av matlagningen, inte ett andra jobb |
| Svinnappar | Rädda enskilda varor | Hela kedjan: lager → plan → inköp → rest → matlåda |
| Inköpslisteappar | Listan | Listan genereras ur plan minus lager, och fyller lagret efter köp |
Den tekniska vallgraven är kombinationen **Food Twin + deterministiska motorer + AAMOS-minne**:
ju längre ett hushåll använder appen, desto bättre blir förslagen (smakprofil, portioner,
högtidsfavoriter, svinnmönster) ett datadrivet byte-hinder som receptappar saknar.
Kritiska beräkningar (kalorier, allergener, saldo) är deterministisk kod; AI:n föreslår
och tolkar men hittar aldrig på vilket gör produkten trovärdig nog för allergifamiljer.
## Affärsvärde
Prenumeration via App Store/Google Play: **Household 79 kr**, **Family 129 kr**,
**Large Household 169 kr** per månad, 7 dagars fri provperiod utan kort, användbar
gratisnivå (10 AI-skanningar/mån) som behåller användare i tratten.
Ett genomsnittligt svenskt barnhushåll slänger mat för tusenlappar per år och lägger
10 000+ kr/månad på mat en app som synligt sänker svinnet och svarar på vardagens
mest tjatiga fråga motiverar 79129 kr/mån. AI-kostnaden per betalande hushåll är
enstaka kronor per månad med fair use-tak (se Del 10), vilket ger sund bruttomarginal
från första betalande kund. Hushållsdelning ger inbyggd viral loop (inbjudningskod)
och höjer bytebarriären ytterligare: den som lämnar tar hela familjens matminne med sig.
+63
View File
@@ -0,0 +1,63 @@
# Del 2 Kritisk produktbedömning
Ärlig bedömning enligt spec §1: "Du ska vara kritisk."
## Styrkor
Visionen löser ett verkligt, dagligt och känslomässigt laddat problem ("vad ska vi äta?")
i stället för ett nischat. Food Twin-idén ger en datafördel som växer med användningen och
som konkurrenter inte kan kopiera utan samma historik. Fyrfliksnavigationen håller en
enorm funktionsyta begriplig. Beslutet att hålla nutrition/allergier deterministiska gör
produkten säljbar till just de familjer som behöver den mest. Prissättningen per hushåll
(inte per person) matchar hur familjer faktiskt betalar.
## Svagheter
**Kallstartsproblemet är produktens största svaghet.** "Vad ska vi äta?" är magiskt först
när lagret är någorlunda ifyllt men att fylla lagret är arbete. Motmedel som nu är byggda:
skanningen kräver max ett foto per plats, kvittoskanning fyller lagret som biprodukt av
ett köp, inköpslistan fyller lagret automatiskt vid avslutad runda, och motorn fungerar
även med tomt lager (då rankas på smak/säsong/tid i stället). Detta måste mätas besatt i
beta: _time-to-first-magic-recommendation_ är nyckeltalet.
**Lagerdrift.** Verkligheten äter digitala tvillingar till frukost: gäster äter, barn
småplockar, saker glöms. Utan ödmjuk design ("stämmer detta?"-avstämningar, enkla
korrigeringar, förlåtande FEFO) blir tvillingen en lögn och förtroendet dör. Transaktions-
modellen med correction-typ och synliga confidence-värden är byggd för detta, men UX-arbetet
här är aldrig färdigt.
**Bildanalysens verkliga träffsäkerhet.** En stökig svensk kyl med Arla-förpackningar i
motljus är svår. Därför: confidence-trösklar, obligatorisk bekräftelse, "osäker/okänd" som
förstklassigt svar, och eval-bibliotek innan modellbyten (spec §34). Lova aldrig magi i
marknadsföringen som modellen inte klarar.
**Omfånget.** Specen rymmer 56 produkter. Utan feature flags och stenhård Launch
Core-disciplin (Del 3) blir detta en byggplats i stället för en app. Community/creator-
systemet är medvetet flaggat AV vid lansering det är rätt beslut och ska försvaras.
## Risker (topp 5 fullständigt register i Del 18)
1. Kallstart/onboarding-avhopp innan första wow-ögonblicket.
2. Lagerdrift → felaktiga förslag → förtroendetapp.
3. AI-kostnad skenar utan fair use-tak och Haiku-first-routing (mitigerat i design).
4. Juridik kring receptdata frestar till genvägar source registry är obligatoriskt.
5. App Store-granskning: hälsopåståenden och "AI-diagnos"-tolkning kräver försiktig copy.
## Betalningsvilja
79129 kr/mån konkurrerar med en enda hämtpizza. Betalningsviljan bärs av tre synliga
värden: tid (planering + "vad ska vi äta?" löst), pengar (svinnvärde i kronor visas i
appen "du har räddat mat för 214 kr i månaden") och hälsa (mål utan loggningsjobb).
Kritisk sanning: gratisnivån måste vara bra nog att bygga vanan men inte så bra att
uppgraderingen känns onödig 10 AI-skanningar/mån + manuellt lager + full receptbank är
den balansen. Priset testas i beta med prisexperiment innan låsning.
## Retention
Retention avgörs av tre loopar: **dagliga** (Min dag + middagsförslag), **veckovisa**
(plan + inköpslista + matlådor) och **säsongsloopen** (midsommar-/julförslag som känns
personliga via Food Memory). Hushållsdelning är den starkaste enskilda retention-mekanismen:
när lagret, listan och planen delas av fyra personer lämnar ingen ensam. Notiser ska vara
få och träffsäkra (bäst före + matlåda) notisspam är den snabbaste vägen till avinstallation.
Churn-risk: hushåll som slutar skanna. Motmedel: kvitto som lägsta-friktionsväg och
"snabbavstämning" av lagret på 30 sekunder.
+66
View File
@@ -0,0 +1,66 @@
# Del 3 Funktionskatalog
Fullständig katalog ur spec §3–§45, organiserad efter beroenden och lanseringsordning.
Inget är bortförenklat allt är placerat. ✅ = byggt i denna kodbas, 🔧 = delvis/stub, ⬜ = ej påbörjat.
## Launch Core (måste finnas dag 1)
| Funktion | Spec | Status |
| ------------------------------------------------------------------------------------------ | ---------- | -------------------------------------------------------- |
| Konto, auth, onboarding med mål/kost/allergier/hushåll | §6 | ✅ |
| Hushåll: skapa/gå med via kod, roller, portionsfaktorer, delat vs privat | §7 | ✅ |
| Food Twin: transaktionsbaserat lager, platser, saldo+historik | §8 | ✅ |
| Registreringskällor: kyl/frys/skafferi/ingrediensbild, streckkod, kvitto, manuell, fritext | §912 | ✅ (bild/kvitto via AAMOS; röst ⬜ flagga) |
| Granska-och-bekräfta-flöde med confidence och korrigeringar | §10, §61.5 | ✅ |
| Bäst före-motor: use_by/best_before/öppnad/fryst, deterministisk | §13 | ✅ |
| Receptbibliotek med struktur, varianter, Recipe DNA | §14, §16 | ✅ (22 seedrecept; volymplan i Del 11) |
| Receptmotor: matchning, filtrering, allergisäkerhet, skalning, substitution | §17, §20 | ✅ |
| "Vad ska vi äta?" med förklaringar + "Jag är sugen på" | §1819 | ✅ |
| Nutrition: deterministisk beräkning, BMR/TDEE-mål, Min dag | §21 | ✅ |
| Måltidsloggning: recept/tidigare/manuell/tallriksfoto-intervall | §2223 | ✅ |
| Matlådor & rester, rekommenderas före ny lagning | §24 | ✅ |
| Inköpslista: aggregera, dra av lager, avdelningssortera, → lager | §27 | ✅ |
| Budget: vecka/månad, svinnvärde, kostnad/portion | §26 | ✅ |
| Säsong & högtid (datadriven eventmotor) | §28 | ✅ |
| Cooking Mode: steg, timers, skärm vaken, skalning | §41 | ✅ (röststyrning ⬜) |
| Prenumerationer: trial 7d, free tier, entitlements, signerad offline-token | §4447 | ✅ (butiksverifiering 🔧 sandbox; riktiga nycklar fas 7) |
| AAMOS-integration med typade kontrakt + mock | §31 | ✅ |
| Minne: "Vad appen vet om mig" rätta/pausa/radera/exportera | §32 | ✅ |
| Separata samtycken (personalisering/förbättring/bildträning) | §33 | ✅ |
| GDPR: export + kontoradering | §56 | ✅ |
| Adminpanel: users, moderering, flaggor, subscriptions, hälsa, audit | §57 | ✅ |
| Observability-grund: correlation-id, healthchecks, audit | §58 | ✅ |
## Launch Advanced (bakom feature flags, aktiveras under beta)
| Funktion | Flagga | Status |
| --------------------------------------------- | ---------------------- | --------------------------------- |
| AI-genererad veckoplan + dynamisk omplanering | `week_plan_ai` | ✅ |
| Tallriksfoto-analys (kalorii­ntervall) | `plate_photo_analysis` | ✅ |
| Kvittoskanning | `receipt_scanning` | ✅ |
| Smakprofil-inlärning ur betyg/feedback | (alltid på, ofarlig) | ✅ signaler; viktning 🔧 |
| Pantry Forecast-notiser | `pantry_forecast` | ✅ motor, 🔧 notisjobb |
| Väderbaserade förslag | `weather_context` | 🔧 motorstöd klart, väderkälla ⬜ |
| Röstinmatning ("jag är sugen på", lager) | `voice_input` | ⬜ |
| AAMOS-omrankning av topplistan | `ai_rerank` | ✅ |
| Hälsointegration (HealthKit/Health Connect) | `health_integration` | 🔧 connector-stubs |
## Post-launch
| Funktion | Spec | Kommentar |
| --------------------------------------------------------- | ---- | ----------------------------------------------------------------------------------- |
| Community: publicera recept, forks, cred | §35 | ✅ backend-flöde byggt, flagga `community_publishing` av tills moderering bemannats |
| Creator-nivåer, badges, verifierade recept | §37 | 🔧 datamodell klar, logik enkel |
| Rankinglistor (aldrig vikt/kalorier) | §38 | ✅ bakom `creator_rankings` |
| Food Memories ("förra midsommaren …") | §39 | 🔧 tabell klar, generering ⬜ |
| Dubblettdetektering recept (AI-förfinad) | §36 | ✅ Jaccard-bas, AI-nivå ⬜ |
| Specialistmodeller (nordiska förpackningar, kvitto-OCR …) | §33 | ⬜ AAMOS-sida, kontrakt redo |
| AI-evals-pipeline med testbibliotek | §34 | 🔧 tabeller + adminvy, corpus byggs under beta |
| Digital kvitto-import | §9 | ⬜ kräver lagliga avtal |
| Fler språk/marknader | §0 | 🔧 i18n-arkitektur klar (sv + en-skelett) |
## Future Partnerships (byggs EJ före laglig åtkomst + behov, spec §60)
Smarta kylskåp, smarta ugnar, köksvågar, butikskedjor/erbjudanden, direktköp,
kylkamera, creator payments. Arkitekturstöd finns: connector-interfacet (§43) och
eventmodellen är förberedda; inga av dessa får bli beroenden för kärnan.
+70
View File
@@ -0,0 +1,70 @@
# Del 4 UX
Implementerad i `apps/mobile` (Expo/expo-router). Detta dokument beskriver flödena;
skärmarna finns som kod under `src/app/`.
## Designprinciper
Enkelhet trots djup (spec §4): fyra flikar bär allt, avancerat göms bakom naturliga
ingångar. Osäkerhet visas alltid (⚠️-flagga, ~-prefix, intervall). Korrigering är ett
tryck bort. Svenska först. Stora tryckytor (min 48 pt). Grön/varm palett (`src/lib/theme.ts`).
## Onboarding (§6) `onboarding.tsx`
5 steg, allt frivilligt, progressindikator: (1) primärt mål → (2) kosthållning +
allergier (med löftet "rätter med dina allergener visas aldrig") → (3) kroppsdata för
kalorimål, med icke-medicinsk-disclaimer → (4) hushåll: skapa/gå med via kod/hoppa över →
(5) enkelt vs exakt läge (§5), kombinerbart. Registrering startar 7-dagars trial utan kort.
## Flikarna
**Vad ska vi äta?** (`(tabs)/index.tsx`): "Jag är sugen på"-fält överst, aktiva
högtidstaggar, matlådor-först-sektion (§24), därefter rankade kort med täckningsprocent,
⏳-taggar för varor som räddas, och 💡-rutan "varför detta förslag?". Tryck → receptdetalj.
**Skanna** (`(tabs)/scan.tsx`): rutnät med alla tio skanningstyper (§4.2), fototips
(§10: översikt, hylla för hylla, ljus, flytta skymmande), kvarvarande AI-kvot synlig.
Flöde: foto → uppladdning → analys-spinner → **Granska** (`scan-review/[jobId].tsx`) där
varje fynd kan godkännas/ändras/tas bort och rader kan läggas till; osäkra rader flaggas.
Streckkod öppnar kamerabaserad läsare (`barcode.tsx`) med okänd-produkt-fallback till
förpackningsfoto.
**Min dag** (`(tabs)/my-day.tsx`): kalorier + fyra makron + salt som progressbarer mot
personliga mål, dagens måltider (uppskattningar markerade med ~ och intervall),
logga-knapp → modal med snabb återloggning/manuellt (`log-meal.tsx`).
**Hemma** (`(tabs)/home.tsx`): snabblänkar (inköpslista, matlådor, hushåll, profil),
budgetkort med veckoinköp + svinnvärde, "Använd snart"-kort, lagret grupperat per
förvaringsplats med statustaggar ("2 dagar kvar", "Passerat bäst före lukta och smaka").
## Nyckelflöden
**Laga-flödet** (kärnan): receptdetalj (`recipe/[id].tsx`) visar säkerhet mot din profil,
portionsskalning, varianter, betyg → "Laga nu" → **Cooking Mode** (`cooking/[id].tsx`,
§41): ett steg i taget i stor text, inbyggda timers med haptik, skärmen hålls vaken →
"Klart" → efterflöde (§2324): portioner, matlådor, lagerdragning enligt FEFO → allt
loggas och lagret uppdateras.
**Inköpsrundan** (§27): lista sorterad per butiksavdelning, delad i hushållet, summa-
uppskattning, bocka av → "Avsluta köprundan" lägger bockade varor i lagret.
**Minnet** (§32, `memory.tsx`): sektioner per minnestyp med ursprungsmärkning
("Du har berättat" / "Observerat mönster" / "AI-antagande"), knappar Stämmer/Pausa/Radera
per post samt Radera allt.
## Paywall (§4546, `paywall.tsx`)
Visas vid premiumfunktion utan entitlement och från profilen. Tre planer med
Family markerad som populärast, trial-nedräkning, fair use-texten, gratisnivåns innehåll
ärligt beskrivet, Återställ köp. Copy undviker "obegränsat".
## Community/creator (post-launch)
Creatorprofil med recept/följare/nivå finns som API + enkel vy; publicerings-UX byggs
när `community_publishing` slås på: fritext → AI-strukturering → förhandsgranskning →
inskickat-läge → notis vid publicering med creator-cred ("Skapat av …", forks: "Baserat på …").
## Offline (§44)
React Query-cache persisteras i AsyncStorage: recept, lager, lista och favoriter läses
offline; bockningar synkas när nätet är tillbaka; AI kräver alltid server och sägs så.
+90
View File
@@ -0,0 +1,90 @@
# Del 56 Arkitektur & repostruktur
## Systemöversikt
```text
┌──────────────┐ HTTPS/JSON ┌─────────────────────────────┐
│ iOS/Android │ ──────────────▶ │ Food API │
│ (Expo RN) │ ◀────────────── │ (Fastify) │
└──────────────┘ │ auth · inventory · recipes │
│ presigned PUT │ meals · plan · shopping │
▼ │ recs · memory · subs · adm │
┌──────────────┐ └──────┬──────────┬───────────┘
│ S3 (bilder) │ ◀── presigned ──────────┘ │ enqueue
└──────┬───────┘ ▼
│ läs-URL ┌──────────────────┐
│ │ Redis / BullMQ │
▼ └────────┬─────────┘
┌──────────────┐ typade kontrakt │ konsumerar
│ AAMOS │ ◀───────────────────────┌─────────▼─────────┐
│ (befintlig │ ─────────────────────▶ │ Worker │
│ AI-plattform)│ validerade svar │ bildanalys·kvitto │
└──────────────┘ │ plan·minne·notiser│
└─────────┬─────────┘
┌────────────┐ │
│ PostgreSQL │ ◀───────────────┘
│ (app) │ ◀── Adminpanel (Vite/React) via /admin/v1
└────────────┘
```
Regler som bär arkitekturen (spec §31, §61):
mobilen pratar **endast** med Food API; AAMOS nås **endast** från API/worker via
`@app/ai-contracts` (Zod-validerad input och output brutet kontrakt är ett fel,
aldrig data); alla säkerhetskritiska beräkningar (nutrition, allergener, saldo,
entitlements) är deterministiska paket; AI-resultat blir aldrig lagerdata utan
användarbekräftelse.
## Centrala dataflöden
**Skanning (spec §50):** App → `POST /v1/scans` (kvot + presign) → PUT bild →
`POST /scans/:id/start` → kö → worker → AAMOS → validerat resultat →
`awaiting_confirmation` → app granskar → `POST /scans/:id/confirm` → inventory-
transaktioner + events + ai_corrections (samtyckessnapshot).
**"Vad ska vi äta?":** API läser lager+medlemsbegränsningar+dagsläge+säsong →
deterministisk säkerhetsfiltrering → täckning (recipe-engine) → poäng+förklaring
(recommendation-engine) → ev. AAMOS-omrankning (flagga) → matlådor först.
**Events (spec §55):** skrivs i outbox-tabellen i samma transaktion som affärsdata;
worker publicerar var 30:e sekund; minnesjobbet konsumerar veckans events per användare
med samtycke → AAMOS UPDATE_USER_MEMORY → förslag till `memory_items`.
## Repostruktur och ansvar per paket
```text
apps/
mobile/ Expo-app. Får ALDRIG innehålla AI-nycklar eller premiumlogik (§61.1314).
api/ Enda ingången. Routes per domän, plugins (auth/core/storage), tunna handlers.
worker/ BullMQ-processorer för spec §54-jobben + schemalagda jobb.
admin/ Vite/React-panel mot /admin/v1 (§57).
packages/
shared-types/ Enums, entiteter, konstanter. Noll beroenden. Allas sanning.
validation/ Zod-scheman för API-kontrakt (in-DTO:er).
database/ Drizzle-schema (54 tabeller), klient, migrationer, seed.
nutrition-engine/ §21: enheter, per-100-beräkning, BMR/TDEE, dagsmål. Rent.
inventory-engine/ §8/§13: saldo, FEFO, bäst före-klassning, dubbletter, forecast.
recipe-engine/ §17/§20: säkerhet (allergi/diet/religion), täckning, skalning, substitution, kostnad.
recommendation-engine/ §1819/§28: poängvikter, förklaringar (sv), craving-parser, säsongsalgoritmer.
ai-contracts/ §31: AAMOS task-typer, Zod-kuvert, HttpAamosClient + MockAamosClient.
memory-client/ §32: minnesförslag via AAMOS, "Vad appen vet"-vy, pseudonymisering.
subscriptions/ §4447: entitlements, signerad offline-token, StoreVerifier.
connectors/ §43: interface + Livsmedelsverket, Open Food Facts, hälso-stubs.
events/ §55: typade payloads + makeEvent.
feature-flags/ DB+env-flaggor med rollout-procent.
infrastructure/ Docker, compose (dev+prod), migrations (genererad SQL),
deployment (DB-bootstrap, deploy.sh), monitoring, security.
docs/ Denna dokumentation (Del 120).
```
Beroenderiktning: `apps → packages`, `packages → shared-types`, aldrig tvärtom och
aldrig paket ↔ paket-cykler. Motorerna är rena funktioner (inga DB-anrop) → triviala
att testa och att flytta till egen tjänst vid skalning (§59 steg 23 kräver inga
kodändringar i motorerna, bara i apps/).
## Service-to-service (§59)
API↔AAMOS: HTTPS, Bearer-nyckel, timeout+exponentiell retry, correlation-id-header,
versionerat kontrakt (`x-contract-version`). Circuit breaker läggs i HttpAamosClient
när trafik finns att kalibrera mot. Samma API-domän behålls genom alla skalningssteg.
+58
View File
@@ -0,0 +1,58 @@
# Del 7 Datamodell
Källa: `packages/database/src/schema/` (Drizzle). Genererad SQL:
`infrastructure/migrations/0000_*.sql` **54 tabeller**, 38 enums, FK:er och index.
Kör: `pnpm db:generate` (ny migration ur schemat), `pnpm db:migrate`, `pnpm db:seed`.
## Domänöversikt (tabeller per fil)
| Fil | Tabeller | Nyckelidéer |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| users.ts | users, user_credentials, refresh_tokens, user_health_profiles, user_preferences, user_consents | Hälsodata i EGEN tabell, exponeras aldrig via hushålls-API (§7/§56). Refresh-tokens hashade med familje-id → stöldskydd. Samtycken som (user, kind)-PK med tidsstämplar (§33). |
| households.ts | households, household_members, storage_locations | invite_code unik. portion_factor per medlem (§7). Platser = kyl/frys/skafferi/…/custom med sublocations[] (§8). |
| ingredients.ts | canonical_ingredients, substitutions | Navet för normalisering (§9). Näring per 100 med provenance-JSON (aldrig AI-påhitt). Diet-/allergiflaggor för deterministisk filtrering. shelf_life_guidance per platstyp (§13). |
| products.ts | products | Versionshantering: (gtin, version) unik, aktuell rad = valid_to IS NULL (§11, §61.12). |
| inventory.ts | inventory_items, inventory_transactions | Transaktioner är sanningen (§8): typade (+purchase/cook_use/discard/…), value_minor på discard → svinnvärde (§26). Saldo cachas på posten. Index på (household, best_before). |
| recipes.ts | recipe_source_registry, recipes, recipe_ingredients, recipe_steps, recipe_ratings, recipe_favorites, recipe_cooks, recipe_similarities | Source registry för juridik (§15). DNA som jsonb (§16). variant_of/forked_from (§16/§35). Status-flöde §35. Similarity-klassning §36. Steg med timer/temperatur för Cooking Mode. |
| meals.ts | meals, meal_boxes | Näring som snapshot-jsonb (historik ändras inte när recept ändras). Estimatintervall för tallriksfoto (§22). Matlådor med portions_remaining + recommended_use_by (§24). |
| planning.ts | week_plans, week_plan_entries, shopping_lists, shopping_list_items | Planpost kan peka på recept ELLER matlåda. reschedule_reason_sv för dynamisk omplanering (§25). Lista: store_section, origin (plan/manual/…) (§27). |
| receipts.ts | receipts, receipt_lines, price_observations | Kvittorader med confidence + verified (§12). Prishistorik → budget/prognoser. |
| scans.ts | scan_jobs, ai_corrections | Jobbstatus inkl. awaiting_confirmation. Korrigeringar med consent_snapshot → träningsdata endast med samtycke (§33). model/prompt_version på allt (§9). |
| memory.ts | memory_items, taste_signals, food_memories | Minnestyper §32, origin user_stated/observed/ai_inferred (§30), paused/verified per post. |
| seasons.ts | season_events | Datadrivna datumregler (fixed/range/computed: midsommar/påsk/advent) (§28). |
| subscriptions.ts | subscriptions, subscription_events, store_transactions, store_notifications, trials, ai_usage_counters | Backend source of truth (§47). (provider, original_transaction_id) unik → idempotent verifiering. Månadskvot för fair use (§4546). |
| platform.ts | domain_events, feature_flags, audit_logs, idempotency_keys, notifications, push_tokens, creator_stats, creator_follows, ai_eval_cases, ai_eval_runs, waste_summaries | Outbox-mönster (§55). Flaggor med rollout-procent. Audit (§56). Evals (§34). |
## Viktiga constraints & index (urval)
Unika: users.email; households.invite_code; recipes.slug; (recipe,user) i ratings;
(provider, original_transaction_id) i subscriptions (partial, NOT NULL); (gtin, version)
i products (partial); (user, month) i ai_usage_counters; refresh_tokens.token_hash.
Prestanda: inventory(household, best_before) för utgångslistan; meals(user, date) för
Min dag; domain_events(published_at, occurred_at) för outbox; scan_jobs(status);
recipes(status), (cuisine), (total_time).
## Regler för ändringar (spec §63)
1. Ändra schema i `packages/database/src/schema/``pnpm db:generate` → granska SQL:en.
2. Ingen destruktiv ändring utan: backup (deploy.sh tar dump), migration, dokumenterad
rollback, kontroll efteråt.
3. Logga varje migration i tabellen nedan.
## Migrationslogg
| # | Fil | Datum | Innehåll | Rollback |
| ---- | ------------------------------- | ---------- | --------------------------------------------------------- | --------------------------- |
| 0000 | 0000_dusty_lady_deathstrike.sql | 2026-08-02 | Initialt schema: 54 tabeller, 38 enums, samtliga index/FK | Drop databasen (pre-launch) |
## i18n-tillägg (M1M6)
- `households.currency_code` char(3) NOT NULL DEFAULT 'SEK' pengagränsen går vid hushållet.
- Alla pengakolumner är `integer` i minor units (`*_minor`).
- `ingredient_translations` (ingredient_id, language_tag, name, aliases[], source, status) unik per (ingrediens, språk).
- `unit_translations` (unit_code, language_tag, abbreviation, name) PK (kod, språk).
- `recipe_translations` (recipe_id, language_tag, title, description, storage_guidance, status draft_ai|in_review|published, source, verification jsonb).
- `recipe_step_translations` (recipe_id, language_tag, step_number, instruction, tip) PK (recept, språk, steg).
- `nutrition_display_profiles` (region_code PK, energy_display kcal|kj|both, salt_display salt|sodium, energy_label_key).
- `allergen_market_rules` (region_code, allergen, must_highlight) PK (region, allergen).
- Trigramindex (pg_trgm) på canonical_ingredients.name_sv, ingredient_translations.name, recipes.title_sv; unaccent för accentokänslig sök.
+144
View File
@@ -0,0 +1,144 @@
# Del 8 API-referens (v1)
Bas: `https://<apiDomain>` (ur `brand.config.json`) (dev: `http://localhost:4000`). All data JSON.
Auth: `Authorization: Bearer <accessToken>` (JWT, 15 min) + roterande refresh-token.
Fel: `{ "error": { code, message, details?, correlationId } }`. Rate limit 300/min
(auth-endpoints 10/min). Live-lista över endpoints: `GET /docs`.
## Auth
| Metod | Path | Beskrivning |
| ----- | ------------------------ | --------------------------------------------------------------- |
| POST | /v1/auth/register | Skapa konto; startar 7-dagars trial; returnerar tokens |
| POST | /v1/auth/login | Logga in |
| POST | /v1/auth/refresh | Rotera refresh-token (återanvändning → hela familjen revokeras) |
| POST | /v1/auth/logout | Revokera alla sessioner |
| POST | /v1/auth/change-password | Byt lösenord (loggar ut övriga enheter) |
## Användare & profil (§6, §21, §3233, §56)
| GET/PATCH | /v1/me | Konto, precisionMode, aktivt hushåll |
| GET/PATCH | /v1/me/health-profile | Hälsodata (separat domän) |
| GET/PATCH | /v1/me/preferences | Mål, kost, allergier, favoritkök, budget, utrustning |
| POST | /v1/me/onboarding | Allt-i-ett: profil + preferenser + hushållsval |
| GET/PUT | /v1/me/consents | Separata samtycken |
| GET | /v1/me/daily-targets | Deterministiska dagsmål + beräkningsgrund |
| GET | /v1/me/entitlements | Plan, kvoter, signerad offline-token (§44/§47) |
| GET | /v1/me/export | GDPR-export |
| DELETE | /v1/me | GDPR-radering |
| GET | /v1/me/memory | "Vad appen vet om mig" (sektioner per minnestyp) |
| PATCH/DELETE | /v1/me/memory/:id | Rätta (→ user_stated, verifierad) / radera post |
| DELETE | /v1/me/memory | Radera allt minne |
| POST | /v1/me/memory/pause-all | Pausa/återuppta allt |
## Hushåll (§78)
| GET/POST | /v1/households | Mina hushåll / skapa (auto: kyl, frys, skafferi) |
| GET/PATCH | /v1/households/:id | Detalj med medlemmar+platser (ALDRIG medlemmars hälsodata) |
| POST | /v1/households/join | Gå med via inviteCode (plangräns kontrolleras) |
| PATCH/DELETE | /v1/households/:id/members/:userId | Roll/portionsfaktor / ta bort |
| POST/PATCH | /v1/households/:id/storage-locations[/:locationId] | Platser |
## Lager Food Twin (§813)
| GET | /v1/inventory | Lager med expiry-klassning; filter: plats/status/sök |
| GET | /v1/inventory/expiring | "Använd snart", sorterat mest bråttom först |
| POST | /v1/inventory/items | Lägg till (+purchase-transaktion, dubblettkandidater i svaret) |
| PATCH/DELETE | /v1/inventory/items/:id | Ändra (mängd → adjust-transaktion) / arkivera |
| POST | /v1/inventory/items/:id/transactions | consume/discard/adjust… (discard sätter value_minor) |
| GET | /v1/inventory/items/:id/transactions | Historik |
| GET | /v1/ingredients?search= | Kanonisk ingrediens-sök (alias-medveten) |
## Skanning (§1012, §50)
| POST | /v1/scans | Skapa jobb; AI-typer drar kvot + ger presignade upload-URL:er; barcode svarar direkt |
| POST | /v1/scans/:id/start | Lägg på kön (worker → AAMOS) |
| GET | /v1/scans/:id | Status + kontraktvaliderat resultat |
| POST | /v1/scans/:id/confirm | Godkänn/ändra/avvisa/lägg till → lager + ai_corrections |
| GET | /v1/scans | Mina senaste skanningar |
## Recept (§1420)
| GET | /v1/recipes | Sök/filter: kök, måltid, taggar, tid, kcal, protein, kostnad, svårighet, exkl. allergener |
| GET | /v1/recipes/:id | Detalj + personlig säkerhetsanalys + varianter + mitt betyg |
| GET | /v1/recipes/:id/scaled?portions= | Skalade mängder |
| POST | /v1/recipes/:id/cook | "Jag lagade": FEFO-lagerdrag, måltider per ätare, matlådor, events |
| POST | /v1/recipes/:id/rate | Betyg + feedbacktaggar (→ smaksignaler) |
| POST/DELETE | /v1/recipes/:id/favorite | Favorit |
| GET | /v1/recipes/favorites/mine | Mina favoriter |
| POST | /v1/recipes | Användarrecept: strukturerat eller fritext (AAMOS-strukturering) → moderering (§35) |
| GET | /v1/substitutions?fromIngredientId=&context= | Substitutionsförslag (§20) |
## Rekommendationer (§1819)
| GET | /v1/recommendations/what-to-eat | Kärn-endpointen: craving, persons, maxMinutes, maxCost; svarar med matlådor-först + rankade förslag med whySv, saknade varor, utgående varor |
## Måltider & matlådor (§2224)
| POST | /v1/meals | Logga (recept/produkt/foto-intervall/manuell aldrig AI-påhitt) |
| GET | /v1/meals/day?date= | Min dag: måltider + summering mot mål |
| GET | /v1/meals/recent | Snabb återloggning |
| DELETE | /v1/meals/:id | Ta bort |
| GET/POST | /v1/meal-boxes | Matlådor |
| POST | /v1/meal-boxes/:id/consume | Ät (drar portioner, loggar måltid) |
| POST | /v1/meal-boxes/:id/discard | Släng |
## Planering & inköp (§2527)
| GET | /v1/week-plans | Planer med poster |
| POST | /v1/week-plans/generate | Generera (premium; asynkront via worker) |
| PATCH | /v1/week-plans/:id/entries/:entryId | Ändra/skippa (auto-omplanering med förklaring) |
| POST | /v1/week-plans/:id/activate | Aktivera |
| GET/POST | /v1/shopping-lists | Listor / skapa (generateFromPlan drar av lagret) |
| GET | /v1/shopping-lists/:id | Lista sorterad per avdelning + prissumma |
| POST/PATCH/DELETE | /v1/shopping-lists/:id/items[/:itemId] | Rader (auto-merge per ingrediens) |
| POST | /v1/shopping-lists/:id/complete | Bockade varor → lagret |
## Budget (§26)
| GET | /v1/budget/summary | Vecka/månad: inköp, svinnvärde, kostnad/portion |
## Prenumerationer (§4547)
| POST | /v1/subscriptions/verify | Verifiera Apple/Google-köp (backend = sanningen) |
| POST | /v1/subscriptions/restore | Återställ-köp-hänvisning |
| POST | /v1/subscriptions/webhooks/apple | App Store Server Notifications (rå → kö) |
| POST | /v1/subscriptions/webhooks/google | Play RTDN (rå → kö) |
## Community (§3538)
| GET | /v1/creators/:id | Creatorprofil (respekterar visibility) |
| POST/DELETE | /v1/creators/:id/follow | Följ |
| GET | /v1/rankings/:kind | most-cooked/top-rated/budget/protein (flagga; aldrig vikt/kalorier) |
## Admin (§57) kräver admin-roll, auditloggas
users · moderation/recipes (kö + approve/reject/request_changes) · flags (PUT med rollout) ·
subscriptions (+grant) · jobs/overview · system/health · audit-logs · ai/corrections · ai/eval-runs
## Infra
GET /healthz · GET /readyz · GET /docs (endpointkatalog)
## i18n-endpoints (M2M6)
| Metod | Path | Beskrivning |
| ----- | ----------------------------------------------- | -------------------------------------------------------------- |
| GET | /v1/me/locale-preferences | Användarens språk/region/mått/valuta (SE-defaults) |
| PATCH | /v1/me/locale-preferences | Uppdatera locale-preferenser |
| GET | /v1/i18n/units?languageTag= | Enhetsetiketter per språk ur unit_translations |
| GET | /v1/i18n/nutrition-profile?region= | Näringsvisning + allergenframhävning per marknad (EU-fallback) |
| POST | /admin/v1/recipes/:id/translate | Beställ AI-utkast för språk (TRANSLATE_RECIPE-jobb) |
| GET | /admin/v1/recipes/:id/translations | Lista översättningar med verifieringsresultat |
| POST | /admin/v1/recipes/:id/translations/:languageTag | publish / in_review / back_to_draft (+ redaktionell rättning) |
Recept- och ingrediens-svar bär `language` + upplösta fält (`title`, `name`,
`instruction` …) på användarens språk med svensk källa som fallback.
Alla belopp i svar är minor units; budget/summeringar bär `currency`.
## Lösenordsåterställning
| Metod | Path | Beskrivning |
| ----- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| POST | /v1/auth/forgot-password | Begär återställningsmejl. Svarar ALLTID {ok:true} (anti-enumeration). Token: 32 slumpbytes, sha256-hashad i DB, 30 min TTL, engångs |
| POST | /v1/auth/reset-password | Token + nytt lösenord. Loggar ut ALLA sessioner. Auditloggas |
+86
View File
@@ -0,0 +1,86 @@
# Del 9 AAMOS-integration
AAMOS är er befintliga AI-plattform och nås **uteslutande via API** från appens
backend (beslut 2026-08-02). Mobilappen har aldrig direktkontakt och innehåller inga
AI-hemligheter (spec §31, §61.13).
## Kontraktet (`packages/ai-contracts`)
Appen definierar ett versionerat, Zod-validerat kontrakt per uppgiftstyp. Input
valideras FÖRE nätverksanropet, output valideras EFTER ett svar som bryter kontraktet
behandlas som fel och når aldrig användardata. AAMOS kan därmed byta modeller,
prompts och leverantörer fritt bakom kontraktet ("Food API ska få stabila kontrakt
oavsett modell").
Antaget transport-API (justeras mot er AAMOS-dokumentation endast
`HttpAamosClient` berörs):
```text
POST {AAMOS_API_URL}/v1/tasks Authorization: Bearer {AAMOS_API_KEY}
→ AamosRequestEnvelope { taskId, taskType, contractVersion, input, metadata }
← AamosResponseEnvelope { taskId, status: ok|uncertain|failed, output,
modelVersion, promptVersion, latencyMs, costUsd }
GET {AAMOS_API_URL}/v1/health
```
Metadata per anrop: `correlationId` (spårbarhet §58), `subjectRef`
(**pseudonymiserat** id aldrig e-post/namn, §56), `priority`, samt
`consentFlags {personalization, anonymizedImprovement, imageTraining}` så att AAMOS
kan upprätthålla samtyckesreglerna på sin sida också.
**Behövs från er:** AAMOS bas-URL + API-nyckel per miljö, och er endpoint-/schema-
dokumentation. Avviker kuvertformatet mappas det i `HttpAamosClient` kontrakten
utåt ändras inte.
## Uppgiftstyper (15)
Bild/OCR: `ANALYZE_FRIDGE_IMAGE`, `ANALYZE_PANTRY_IMAGE`, `ANALYZE_MEAL_IMAGE`
(kcal-INTERVALL, aldrig exakt påstående), `READ_RECEIPT`, `READ_NUTRITION_LABEL`
(värden från etiketten inte modellens gissning), `READ_EXPIRY_DATE`.
Text/struktur: `NORMALIZE_PRODUCTS`, `DEDUPLICATE_INVENTORY`, `STRUCTURE_RECIPE_TEXT`,
`PARSE_CRAVING`, `MODERATE_RECIPE`.
Rådgivande: `GENERATE_RECIPE_OPTIONS` (granskas redaktionellt, §15), `RANK_RECIPES`
(får ordna om aldrig lägga till), `GENERATE_WEEK_PLAN`, `UPDATE_USER_MEMORY`.
Alla outputs bär `confidence`, och "osäker/okänd" är förstklassiga svar
(`canonicalIngredientId: null`, `requiresConfirmation: true`) spec §10/§61.4.
## Routing & specialister (spec §33)
Rekommenderad routing inne i AAMOS: specialistmodell först (vanliga livsmedel,
nordiska förpackningar, kvitto-OCR, datum-OCR, portionssegmentering) → hög confidence:
svara; låg: extern generalist (Haiku 4.5-klass för volym, Sonnet-klass för svåra fall);
fortsatt osäkert: `status: "uncertain"` → appen frågar användaren. Appen skickar
`modelVersion`/`promptVersion` vidare in i `scan_jobs` och `ai_corrections` så att
varje datapunkt är spårbar till modellversion (§9).
## Feedback → träning (spec §33)
Varje användarkorrigering i granska-flödet sparas som `ai_corrections`
(AI-utdata + korrigering + modell/promptversion + **samtyckessnapshot**).
Veckojobbet `BUILD_TRAINING_SAMPLE` exporterar endast rader där
`anonymized_improvement = granted` vid korrigeringstillfället; bilder kräver
separat `image_training`-samtycke. Personligt minne är aldrig träningsdata (§32).
## Memory (spec §32)
Appens DB är den användarsynliga sanningen (`memory_items`). Nattjobbet skickar
veckans domänhändelser + befintliga minnesnycklar till `UPDATE_USER_MEMORY`; AAMOS
returnerar förslag (kind/key/sammanfattning/confidence/expires) som skrivs in men
poster som användaren verifierat eller pausat skrivs **aldrig** över. Användarens
rättelser blir `user_stated` med confidence 1 och vinner alltid (§30).
## Evals (spec §34)
`ai_eval_cases` (fast testbibliotek: kyl/frys/skafferi/tallrik/kvitton/etiketter/
allergifall/mörka bilder/överlapp/orimliga recept) + `ai_eval_runs` (precision, recall,
missade produkter, hallucinationer, allergifel, latens, kostnad, korrigeringsgrad).
Regel: **ingen modell- eller promptändring i AAMOS tas i produktion för appen utan
grön eval-körning**, synlig i adminpanelen (`/admin/v1/ai/eval-runs`). Corpus byggs
under beta ur anonymiserade, samtyckta exempel.
## Kostnadskontroll
`costUsd` per anrop loggas på scan_jobs → daglig kostnad per uppgiftstyp i admin;
larm vid spik (§58). Fair use-kvoterna (Del 10/13) begränsar exponeringen per användare.
Mock-läget (`AAMOS_MODE=mock`) finns för dev/test; produktion vägrar starta i mock.
+70
View File
@@ -0,0 +1,70 @@
# Del 10 AI-modeller och kostnad
Priser verifierade 2026-08-02 (BenchLM, uppdaterad 2026-08-01, samt korsläst mot
publika prislistor). **Kontrollera mot Anthropics officiella prissida innan avtal.**
## Aktuella modeller och listpriser (USD per miljon tokens, in/ut)
| Modell | Input | Output | Roll i appen |
| ---------------- | ----- | ----------------------- | ----------------------------------------------------------- |
| Claude Haiku 4.5 | $1 | $5 | Arbetshäst: bildanalys, OCR, normalisering, craving-parsing |
| Claude Sonnet 5 | $2 | $10 (t.o.m. 2026-08-31) | Fallback vid låg confidence; receptstrukturering; veckoplan |
| Claude Opus 5 | $5 | $25 | Endast evals/svåraste moderering inte i volymflöden |
| Claude Fable 5 | $10 | $50 | Används ej i volymflöden |
Hävstänger: cache-träffar 10 % av inputpris; Batch API 50 % (perfekt för nattliga
minnes-/träningsjobb). AAMOS specialistmodeller ersätter över tid externa anrop för
de vanligaste bilduppgifterna (spec §33) → kostnaden nedan är ett tak, inte ett golv.
## Kostnad per uppgift (ANTAGANDEN markerade)
Antaget: bild ≈ 1 500 tokens (A1), systemprompt+kontext 1 000 in / strukturerad output
800 ut (A2), 80 % av anropen klaras av Haiku, 20 % eskalerar till Sonnet (A3),
växelkurs 10,50 SEK/USD (A4).
| Uppgift | Tokens (in/ut) | Haiku | Sonnet | Blandad (80/20) |
| ------------------------------- | -------------- | --------------- | ------ | -------------------- |
| Kyl-/skafferibild (2 bilder) | 4 000 / 800 | $0,008 | $0,016 | **$0,010 ≈ 0,10 kr** |
| Kvitto | 3 000 / 1 000 | $0,008 | $0,016 | $0,010 |
| Tallriksfoto | 2 500 / 500 | $0,005 | $0,010 | $0,006 |
| Näringsetikett/datum | 2 000 / 400 | $0,004 | $0,008 | $0,005 |
| Receptstrukturering (text) | 2 500 / 1 200 | | $0,017 | $0,017 |
| Veckoplan | 3 000 / 800 | | $0,014 | $0,014 |
| Minnesuppdatering (batch, natt) | 4 000 / 600 | $0,0035 (batch) | | $0,0035 |
**Tumregel: ≈ $0,01 ≈ 0,11 kr per AI-skanning.** Deterministiska flöden
(rekommendationer, nutrition, streckkod, FEFO) kostar $0.
## Kostnad per användartyp och månad
| Profil | Antagande om användning | AI-kostnad/mån |
| --------------------------- | ----------------------------------------- | ----------------------------------------------- |
| Gratis | 10 skanningar (kvottak) | $0,10 ≈ 1,1 kr |
| Household (aktivt) | 60 skanningar + 4 planer + nattligt minne | $0,76 ≈ 8 kr |
| Family (aktivt) | 100 skanningar + 4 planer + minne | $1,17 ≈ 12 kr |
| Family (maxar fair use 600) | värsta fallet | $6,66 ≈ 70 kr — fortfarande under 129 kr-priset |
Fair use-taken (300/600/1000, gratis 10) gör att ingen enskild användare kan bli
förlustaffär ens i värsta fallet.
## Total kostnad per skala
Antaget (A5): 60 % av registrerade är månadsaktiva; mix 80 % gratis / 20 % betalande;
betalande mix 60/30/10 (Household/Family/Large); aktiva gratisanvändare gör 6
skanningar, betalande enligt tabellen ovan.
| Registrerade | Aktiva | AI-kostnad/mån (USD) | ≈ SEK/mån | Intäkt/mån (SEK) | AI-andel av intäkt |
| ------------ | ------ | -------------------- | ---------- | ---------------- | ------------------ |
| 1 000 | 600 | ≈ $140 | 1 500 kr | ≈ 11 900 kr | 12 % |
| 10 000 | 6 000 | ≈ $1 400 | 15 000 kr | ≈ 119 000 kr | 12 % |
| 100 000 | 60 000 | ≈ $14 000 | 147 000 kr | ≈ 1 190 000 kr | 12 % |
Intäktsantagande (A6): 20 % betalande × viktat pris ≈ 99 kr. Skalan är linjär tills
specialistmodeller (spec §33) sänker styckkostnaden rimligt mål är att halvera
bildkostnaden vid >10 000 användare genom AAMOS-specialister + cachade prompts + batch.
## Känslighet
Dubbla bildstorleken (A1) → +60 % på skanningskostnad. Halverad Haiku-andel (A3) →
+50 %. Bevaka därför: tokens/bild i verklig trafik, eskaleringsandel, kostnad per
uppgiftstyp (finns i scan_jobs.cost_usd → adminpanelen) med larm på dagsbudget (§58).
+61
View File
@@ -0,0 +1,61 @@
# Del 11 Receptstrategi
Mål (spec §14): flera tusen kvalitetssäkrade recept och varianter vid lansering,
på juridiskt vattentät grund (spec §15). Ingen scraping, aldrig kopierade texter.
## Volymplan till lansering
| Källa | Antal | Rättigheter | Kostnad (uppskattning) |
| ------------------------------------------------------------------- | --------------- | --------------------------------------------------------------------- | ------------------------------------------------------------ |
| Egen redaktion (frilansande kock + redaktör) | 600 grundrecept | Fulla (source registry: proprietary) | ~250400 kr/recept |
| AI-assisterade original, redaktionellt granskade | 1 400 | Fulla; AAMOS `GENERATE_RECIPE_OPTIONS` → mänsklig granskning av varje | granskning ~60100 kr/recept |
| Systematiska varianter av ovanstående | 2 000+ | Ärvda | Motorgenererade (substitutionsregler) + stickprovsgranskning |
| Öppet licensierade/public domain (skrivs OM med egna formuleringar) | 200 | Per källa i source registry | Redaktionstid |
| Creators med avtal (post-beta) | löpande | Avtal med attribution | Intäktsdelning/exponering |
Summa: **~4 200 recept/varianter** vid lansering med 22 redan seedade som
kvalitetsreferens (struktur, ton, timers, DNA).
## Fördelning
Kök enligt spec §14 med svensk/nordisk tyngd (~35 %), därefter italienskt/asiatiskt/
mexikanskt (husmanskostens faktiska konkurrenter). Kategorier viktas mot vardagen:
minst 50 % middag ≤35 min, 25 % matlådevänligt, 20 % vegetariskt/veganskt, full täckning
av högtidstaggarna (midsommar, jul, påsk, kräftskiva, nyår …) före respektive högtid.
## Kvalitetssäkring (varje recept, spec §15)
Checklista i modereringsflödet: mängder rimliga (motorn räknar täthet/portion),
teknik och temperaturer korrekta (kritiskt: kyckling 72 °C etc.), tillagningstid
verifierad, allergener härledda deterministiskt ur ingredienser (aldrig manuellt),
näring beräknad av nutrition-engine (aldrig ur källtext), livsmedelssäkerhet
(varmhållning, återuppvärmning i förvaringsråd), svenska språket.
## Source registry (implementerat)
`recipe_source_registry`: källa, licens, rätt att lagra/ändra/visa, attribution,
kommersiell användning, giltighetsperiod. Varje recept FK:ar till sin post. Recept
utan registerpost kan inte få status `published` för redaktionellt innehåll policyn
upprätthålls i moderering.
## Bilder
Lansering: egenproducerade foton för topp-300 (matfotograf, batchdagar ~150 st/dag),
stilren platshållare per kök/kategori för övriga. Aldrig bilder från andra sajter.
AI-genererade matbilder används inte för riktiga recept (förtroenderisk) endast som
skisser i redaktionsverktyg.
## Creator-flödet (spec §35, byggt)
submission → AAMOS `MODERATE_RECIPE` (säkerhet/rimlighet/spam) → Jaccard-dubblettkontroll
mot publicerade (§36: dubblett/variant/inspirerat/självständigt människan avgör,
vanliga rätter får ha varianter) → mänsklig moderering i adminpanelen → publicering med
cred ("Skapat av X"; forks: "Baserat på recept av Y, modifierat av X").
Krav för publicering: fullständig data (motorberäknad näring + allergener), inga
säkerhetsflaggor. Verifierat recept (§37): ≥25 lagningar, snitt ≥4,0, inga rapporter.
## Moderering & duplicate detection i drift
Beta: grundaren/redaktören modererar (kön i admin är byggd). >50 inskick/vecka:
utse community-moderatorer med begränsad adminroll. Dubblettmotorn förfinas post-launch
med embeddings via AAMOS (kontraktet `DEDUPLICATE_INVENTORY`-mönstret återanvänds).
+57
View File
@@ -0,0 +1,57 @@
# Del 12 Säkerhet & GDPR
Konkret checklista (spec §56). Status: ✅ implementerat i kod, 🔧 process/deploy-steg.
Teknisk hardening-lista per miljö: `infrastructure/security/hardening-checklist.md`.
## Autentisering & åtkomst
- ✅ Lösenord: scrypt (OWASP-parametrar N=2^15, r=8, p=1), unika salter, timingSafeEqual
- ✅ JWT access 15 min; refresh-tokens hashade + roterande; återanvändning revokerar hela familjen
- ✅ Rollmodell user/moderator/admin; admin dubbelkollas mot DB, alla admin-anrop auditloggas
- ✅ Hushållsåtkomst: `requireMembership` är enda vägen till hushållsdata; roller owner/adult/member/child
- ✅ Rate limiting: 300/min globalt, 10/min på auth
- ✅ Least privilege i DB: separat databas + egen användare med minsta privilegier utan superuser (skript i infrastructure/deployment)
## Dataseparation (spec §7, §56)
- ✅ Hälsodata (`user_health_profiles`) i egen tabell; hushålls-endpoints returnerar ALDRIG medlemmars hälsodata/allergier/mål
- ✅ Individuellt: mål, allergier, smakprofil, måltidshistorik. Delat: lager, lista, plan, matlådor, budget
- ✅ Pseudonymiserat subjectRef till AAMOS aldrig e-post/namn
## Samtycke & minne (spec §3233)
- ✅ Separata samtycken: personalization / anonymized_improvement / image_training / health_integration / location_weather / push med grant/revoke-tidsstämplar + audit
- ✅ Minne transparent ("Vad appen vet om mig"), korrigerbart, pausbart, raderbart; användarrättelser vinner alltid
- ✅ Träningsexport filtrerar på samtyckessnapshot taget VID korrigeringstillfället
- ✅ Ingen hälso-/kostdata till reklam: inga annons-SDK:er; policybeslut D-014
## Registrerades rättigheter
- ✅ Export: `GET /v1/me/export` (konto, profil, preferenser, samtycken, måltider, minne, betyg)
- ✅ Radering: `DELETE /v1/me` hälsodata/minne/måltider/tokens raderas hårt, kontot anonymiseras
- 🔧 Backup-retention: raderad data roteras ur backupper enligt policy (30 dagar) dokumenteras i DPA
## Applikationssäkerhet
- ✅ Zod-validering på ALL indata; enhetliga felsvar utan stackspår; user enumeration förhindrad vid login
- ✅ Säkerhetsheaders (nosniff, DENY, no-referrer, no-store); CORS låst till kända origins
- ✅ Correlation-ID på varje request → loggar → jobb → AAMOS → audit (spec §58)
- ✅ Idempotens: unika nycklar för butiks-transaktioner; idempotency_keys-tabell för klient-retries
- ✅ Säker filuppladdning: presignade URL:er med HMAC, content-type-krav, 15 MB-tak, nycklar utan användardata
- ✅ Signerad entitlement-token (HS256) med TTL + grace aldrig permanent lokal boolean (§44)
- ✅ Produktionsstart vägrar dev-hemligheter och AAMOS_MODE=mock
- ✅ Mobilen: tokens i SecureStore; inga AI-nycklar i appen (§61.13)
## Drift (🔧 vid deploy, se hardening-checklistan)
Egen Linux-user + secrets 600 → senare SSM/Secrets Manager; egen IAM-roll endast mot
egen S3-bucket; S3: block public access, SSE, lifecycle (temporary 7d, scans 90d);
TLS via reverse proxy på egen domän; backupper: RDS-snapshots + logisk dump före varje
deploy (deploy.sh vägrar migrera utan lyckad dump); **restore-test kvartalsvis,
protokollfört**; incidentplan i Del 17.
## Kvarstående juridiska uppgifter
🔧 DPA med AWS och AAMOS-driften · 🔧 registerförteckning (art. 30) ·
🔧 integritetspolicy + villkor (jurist före TestFlight-extern) ·
🔧 DPIA rekommenderas (hälsorelaterad data + profilering) före publik lansering.
+60
View File
@@ -0,0 +1,60 @@
# Del 13 Subscriptions
Planer (spec §45): Household 79 kr (3 pers), Family 129 kr (6), Large Household 169 kr (12).
Fair use-kvoter för AI: 300/600/1000 skanningar/mån. Gratis: 10. Trial: 7 dagar full
tillgång (Family-nivå) utan kort, startar vid registrering (spec §46).
## Arkitekturprincip
**Backend är source of truth (spec §47, §61.14).** Klientens kvitto är ett påstående;
`subscriptions`-tabellen efter serververifiering är sanningen. Appen läser
`GET /v1/me/entitlements` och cachar den signerade offline-token.
## Flöden
**Köp:** StoreKit 2 / Play Billing i appen (fas 7: `react-native-purchases` eller
expo-iap beslut D-021 tas då) → kvitto till `POST /v1/subscriptions/verify`
`StoreVerifier` verifierar mot butiken → upsert på `original_transaction_id`
(idempotent; kopplat-till-annat-konto ger 409) → entitlements + ny token i svaret.
**Verifiering:**
- Apple: App Store Server API v2 JWS-signaturkedja mot Apples rotcertifikat,
bundleId + environment kontrolleras. Kräver APPLE_ISSUER_ID/KEY_ID/PRIVATE_KEY.
- Google: Play Developer API `purchases.subscriptionsv2.get` med service account.
- Dev/CI: `APP_STORE_MODE=sandbox` accepterar JSON-payloads så att hela flödet
(köp → entitlements → grace → expiry) E2E-testas utan butikskonton. Produktion
kräver `production`-läge.
**Livscykel via webhooks (byggt):** App Store Server Notifications V2 och Play RTDN
tas emot råa på `/v1/subscriptions/webhooks/*`, sparas i `store_notifications`, och
processas asynkront (`PROCESS_STORE_NOTIFICATION`). Mappning när butiksnycklar finns:
DID_RENEW/RECOVERED→active · GRACE_PERIOD→in_grace (+grace_period_expires_at) ·
ON_HOLD→on_hold · CANCELED/EXPIRED→canceled/expired · REFUND→expired + audit.
Nattligt svep (`VERIFY_SUBSCRIPTION`) markerar passerade prenumerationer som utgångna
även om webhooks missats.
**Trial → betald/free:** trial-raden styr entitlements t.o.m. endsAt; därefter free
automatiskt (ingen åtgärd krävs). En trial per användare (PK på user).
**Restore:** klienten hämtar aktuellt kvitto från butiken → samma verify-endpoint.
**Grace & offline (spec §44):** butiks-grace speglas i status `in_grace` med full
åtkomst. Offline litar appen på den signerade token: TTL 24 h + grace 7 dagar,
HMAC-verifierad, aldrig en permanent boolean. Efter graceExp krävs server.
**Hushållsdelning:** ägarens plan sätter maxHouseholdMembers; join-endpointen
blockerar över gränsen med uppgraderingsuppmaning (402).
## Tabeller
`subscriptions` (status/plan/expires/grace/last_verified) · `subscription_events`
(logg) · `store_transactions` (råa, unika per provider+transactionId) ·
`store_notifications` (webhook-inbox) · `trials` · `ai_usage_counters` (fair use).
## Kvarstår till fas 7
Riktiga butiksnycklar + JWS/PDA-verifiering i `StoreVerifier` (interfacet är fryst),
klientköp via vald IAP-modul, produkt-id:n registrerade i båda konsolerna
(`<iosBundleId>.<plan>_monthly` för Apple / `<plan>_monthly` för Google härleds ur `brand.config.json`),
sandbox-testkonton, kvittotest av alla livscykelhändelser (Del 16).
+56
View File
@@ -0,0 +1,56 @@
# Del 14 Infrastruktur
## Nu: befintlig server (spec §51, steg 1)
```text
/opt/<slug>-platform/
repo/ git-klon av detta repo
secrets/ api.env, worker.env (ägare: appens användare, chmod 600)
backups/ logiska dumpar före varje deploy
logs/ via Docker json-file (rotation 20 MB × 5)
```
Containrar (docker-compose.production.yml): **app-api** (1 CPU/768 MB,
127.0.0.1:4100 bakom er reverse proxy → https://<apiDomain>), **app-worker**
(1 CPU/768 MB), **app-admin** (nginx, 0.25 CPU/128 MB), **app-redis**
(appendonly, 512 MB tak, noeviction BullMQ-krav). Allt som non-root, egna loggar,
healthchecks. Deploy: `infrastructure/deployment/deploy.sh` bygger, tar dump
(vägrar annars), migrerar, rullar om, healthcheckar, rullar tillbaka vid fel.
**Databas:** befintlig RDS-instans men separat databas + egen användare (create-database.sql)
med minsta privilegier (`create-database.sql`). Ingen delning av QuixZoom-tabeller.
**S3:** egen bucket `<slug>-production` med prefixen ur spec §53, SSE, block public
access, lifecycle: `temporary/` 7 dagar, scans 90 dagar (efter bekräftelse behövs
originalbilden inte för funktion endast för samtyckt träning). Presignade URL:er
för både upp- och nedladdning; mock-läget i dev speglar exakt samma flöde.
**Secrets:** .env-filer nu → SSM Parameter Store i steg 2. **Övervakning:**
`infrastructure/monitoring/README.md` (larmpunkter, correlation-id, healthchecks).
Kapacitet steg 1: ~5 000 registrerade/600 samtidiga utan svett trafiken är låg-QPS
och det tunga (AI) är asynkront via kön.
## Utvecklingsmiljö
`docker-compose.dev.yml` (Postgres 16 + Redis 7) → `pnpm db:migrate && pnpm db:seed`
`pnpm api:dev / worker:dev / admin:dev / mobile:start`. AAMOS_MODE=mock fungerar
helt utan nätverk.
## Steg 2 (≈ >5 000 användare eller första driftsincidenten)
API/worker till ECS Fargate (Dockerfiles är redo), ALB framför, secrets till SSM,
CloudWatch Container Insights. Databasen ligger kvar. Ingen appkodändring samma
API-domän (spec §59).
## Steg 3 (≈ >25 000 användare)
Egen VPC; egen RDS (flytt: replika → failover-fönster nattetid, dokumenterad
pg_dump-fallback); ElastiCache Redis; S3+CloudFront för receptbilder; separata
IAM-roller per tjänst; WAF på ALB. AAMOS förblir separat plattform bakom samma
kontrakt. Motorerna är rena paket → kan brytas ut till egna tjänster om profilering
någonsin motiverar det (sannolikt inte före 100k användare).
## Backup & återställning
RDS automatiska snapshots (PITR) + logisk dump före deploy + veckodump till S3 med
30 dagars retention. **Restore-test kvartalsvis mot staging, protokollfört**
en backup som inte testats räknas som obefintlig (spec §56).
+25
View File
@@ -0,0 +1,25 @@
# Beslutslogg låsta beslut med motivering
> Beslut refereras löpande i respektive dokument (arkitektur, subscriptions,
> säkerhet, i18n). Här samlas de centralt. Nummerserien är stabil återanvänd
> aldrig ett nummer.
| # | Beslut | Motivering | Var det syns |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| D-011 | Zod-validering via central `parse()`-hjälpare i stället för Fastify type-provider | Full kontroll över felformatet (VALIDATION_ERROR + correlationId) och samma mönster i alla routes | `apps/api/src/lib/errors.ts` |
| D-014 | Ingen hälso-/kostdata till reklam; inga annons-SDK:er i appen | Policybeslut (spec §56): förtroende är produktens kärna | docs/12 |
| D-021 | Val av butiksbibliotek i mobilen (StoreKit2/expo-iap) skjuts till fas 7 | Backend-verifieringen är kontraktet; klientbiblioteket kan väljas när butikskonton finns | docs/13 |
| D-023 | Interims-t() byggdes namespace-format ("home._", "scan._") för mekanisk flytt till i18next | Undvika total omskrivning i M4 flytten blev ren datamigrering av nycklar | docs/20, M4 |
| D-024 | Adminpanelen förblir svensk tills internationell ops finns | Internverktyg; nyckel-migrering (M9) kostar mer än den ger före internationell drift | docs/20 |
| D-025 | i18next med inbyggd Intl.PluralRules-plural (`_one`/`_other`) i stället för ICU-tilläggspaket | Samma korrekthet för våra språk utan extra runtime-beroende; ICU-syntax kan läggas till per-nyckel senare om behov uppstår | `apps/mobile/src/lib/i18n.ts`, M4 |
| D-026 | Varumärket är ENDAST konfiguration: neutrala platshållare i `brand.config.json` tills namnet beslutas; git-historiken tillplattad så inget arbetsnamn förekommer någonstans | Namnet är inte beslutat; ett byte ska vara en enradsändring, inte en kodsanering. Extern historik-backup finns utanför repot | brand.config.json, docs/namnbyte.md |
| D-027 | Översättningar är RADER per språk (aldrig kolumner), svensk källtext är sanningen, endast `status=published` serveras användare; AI-utkast verifieras deterministiskt (stegantal, bevarade tal, konfidens) före mänsklig publicering | Skalar till godtyckligt många språk utan schemaändring; AI kan aldrig tyst ändra mängder/struktur (spec §61.1) | packages/database/schema/translations.ts, worker/translation.ts, M2M3 |
| D-028 | Katalogpriser (ingredienser, receptkostnad, inköpsuppskattningar) ligger i basvaluta SEK tills M8 inför per-marknadspriser; hushållets `currency_code` styr användarens egna belopp | Undviker låtsas-valutakonvertering utan riktiga växelkurser ärlighet före skenprecision | schema/ingredients.ts, routes/shopping.ts, M1 |
| D-029 | Måttvisning konverterar ALDRIG lagrade värden: kanoniskt värde är sanning, `displayQuantity` räknar fram visning per måttsystem och märker konverterade värden med ≈ | Förlustfrihet (i18n-spec §30) + ärlig osäkerhet (spec §61.4) | shared-types/measurement.ts, M5 |
| D-030 | Marknadsprofiler (nutrition/allergener) styr endast VISNING användarens personliga allergisäkerhet filtrerar alltid, oavsett marknad | En marknadsregel får aldrig försvaga en säkerhetsspärr (spec §61.2) | schema/markets.ts, M6 |
| D-031 | Språkpolicy: (1) första start → enhetens språk om stött, (2) ostött → engelska, (3) manuellt val i profilen vinner alltid och synkas till backend; registreringen bär enhetens locale så att välkomstmejl + preferenser är rätt från första sekunden | Spanjoren som laddar ner appen får spanska automatiskt; spanjoren i Sverige väljer själv. Nytt språk = en JSON-fil + SUPPORTED_LANGUAGES-rad + seed-rader (paritet testvaktad) | apps/mobile/src/lib/i18n.ts, routes/auth.ts, D-031-tester |
| D-032 | Första utgåvans språk: sv, en, es, it, de, fr, da, nb, fi, nl, pl, pt (Norden + de stora EU-marknaderna + engelska). Norska varianter (no/nn) mappas till nb. Fler EU-språk läggs till efter behov via språkmallen | Täcker spec-fasernas lanseringsmarknader (Norden→EU→UK/US/CA) med korrekt pluralhantering (polskans one/few/many). Seed-översättningar är AI-skrivna native-granskning före lansering står i runbooken | locales/, D-031/D-032-tester, seed |
| D-033 | UTC-kalenderpolicy: alla deterministiska motorer och API:ts server-side-datummatte räknar i UTC (`Date.UTC`/`getUTC*`, datumsträngar tolkas `T00:00:00Z`); tester skrivs med UTC-datum och verifieras under flera tidszoner | Fältfynd från första OpenClaw-deployen: två tester var tidszonkänsliga (lokal `new Date(y,m,d)` mot UTC-`toISOString`). Motorer ska ge identiskt resultat oavsett serverns TZ och UTC saknar sommartid | inventory-engine/expiry.ts, recommendation-engine/season.ts, budget.ts |
| D-034 | Docker är valfritt i first-deploy: Grind 0 noterar bara om docker finns; Grind 2 avgör på faktisk nåbarhet (Postgres via pg-klient, Redis via rå TCP-PING) och använder Docker enbart som reservstart i staging | Fältfynd: WSL-värd utan Docker tvingade agenten bygga ett docker-shim trots att native Postgres/Redis fungerade perfekt. Nåbarhet är sanningen, inte verktygslistan | infrastructure/deployment/first-deploy.sh, OPENCLAW-DEPLOY-PROMPT.md |
| D-035 | Mjölkprincipen: passerat "bäst före" är en KVALITETSsignal (status expiring + pastBestBefore, aldrig expired; FEFO räddar den FÖRST; täckning räknar den som tillgänglig; UI säger "lukta och smaka"), medan "sista förbrukningsdag" är en hård säkerhetsgräns. Användaren väljer datumtyp vid skanningsgranskning | "Bara för att mjölken gick ut igår är den inte automatiskt dålig" appen får varken döma ätbar mat (svinn) eller mjuka upp riktiga säkerhetsgränser (spec §13, §61.3). Ingen automatisk kassering finns någonstans | expiry.ts, fefo.ts, matching.ts, scan-review, home, mjölkprincip-tester |
| D-036 | Inga hårdkodade UI-strängar: samtliga användarsynliga texter i mobilappen går via i18n-katalogen (svep 2026-08-05: ~130 nycklar flyttade ur 15 skärmar till alla 12 språk) | Testprotokollets löfte "byt språk → HELA appen byter direkt" ska vara sant bokstavligen; hårdkodade etikettkartor (mål, allergener, butikssektioner, måltidstyper, dialoger) bröt det | apps/mobile/src/app/_, locales/_/common.json |
+112
View File
@@ -0,0 +1,112 @@
# Del 20 Internationalisering: audit, målarkitektur & migreringsplan
Styrande spec: den uppladdade i18n-specifikationen (`*_Internationalisering_Claude.md`). Detta dokument är
den audit + plan som i18n-spec §34 kräver före implementation, samt kvitto på vad som
redan är genomfört. Status: ✅ klart · 🔨 genomfört i denna leverans · 📋 planerat (fas angiven).
## 1. Nulägesrapport (audit av kodbasen)
### Redan korrekt (byggt språkneutralt från start)
| Område | i18n-spec | Läge |
| ------------------------------------------------------------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Interna enums (måltid, kök, diet, plats, svårighet, notistyp, plan, creator-nivå …) | §4, §16 | ✅ Engelska maskinidentifierare överallt (`dinner`, `fridge`, `vegetarian`) |
| Canonical ingredient-IDs | §12 | ✅ Engelska slugs (`chicken_breast`); `aliases[]` finns; nameSv/nameEn-kolumner |
| Ingrediensdensitet | §10 | ✅ `density_g_per_ml` + `grams_per_piece` per ingrediens; generell volym↔massa-konvertering vägrar utan densitet (returnerar null) |
| Nutrition canonical per 100 g + provenance | §19 | ✅ (kJ + natrium/salt-profiler 📋 fas i7) |
| Allergener som canonical IDs, deterministiska | §18 | ✅ (marknadsregler/visningsprofiler 📋 fas i7) |
| Högtider datadrivna med market-kod + datumregler | §17 | ✅ `season_events(market, date_rule, lead_days …)` uppfyller market_events-syftet |
| Datum: timestamps UTC (`timestamptz`), bäst före som lokalt kalenderdatum (`date`) | §21 | ✅ |
| AI: canonical separerat från displaytext i kontrakten (`canonicalIngredientId` + `detectedName`) | §22, §30 | ✅ |
| Minne: strukturerad `value` + key, inte fritextsanning | §23 | ✅ delvis (summarySv finns som cache renderas om vid språkbyte 📋 fas i6) |
| Recept: struktur (ingrediens-IDs, mängder, tider, temperaturer, DNA) skild från text | §13 | ✅ struktur; översättningstabeller 📋 fas i5 |
| Varumärket som konfiguration | §28 | 🔨 `brand.config.json` enda platsen; kod svep-verifierad |
| Store-produkt-IDs språkneutrala | §25 | 🔨 `household_monthly` m.fl., härledda ur brand-config |
### Avvikelser som åtgärdas i DENNA leverans (🔨)
1. **Enhetskoder var svenska** (`msk`, `tsk`, `krm`, `dl`, `st`, `portion`) brott mot §11.
Åtgärd: språkneutrala koder `GRAM, KILOGRAM, MILLILITER, DECILITER, LITER, TEASPOON,
TABLESPOON, CUP_US, FLUID_OUNCE_US, OUNCE, POUND, COUNT, PORTION, PINCH, SLICE, CLOVE,
CAN, PACKAGE` i enum/DB/motorer/seed/API/app. Dokumenterad standard (§10):
TEASPOON=5 ml, TABLESPOON=15 ml (metrisk), CUP_US=236,59 ml, FL_OZ_US=29,57 ml,
OUNCE=28,35 g, POUND=453,59 g, PINCH≈0,5 ml. `krm` i svenska recept = 1 ml →
lagras som MILLILITER; svensk visning ("krm", "msk") sker via översättningslagret.
2. **UserLocalePreferences saknades** (§6): ny tabell `user_locale_preferences`
(languageTag BCP 47, regionCode ISO 3166-1, timeZone IANA, measurementSystem
METRIC/US_CUSTOMARY/MIXED, temperatureUnit, currencyCode ISO 4217, firstDayOfWeek,
use24HourTime) + `GET/PATCH /v1/me/locale-preferences`; språk ≠ region ≠ enheter.
3. **AAMOS-anrop saknade full lokaliseringskontext** (§22): kuvertets metadata har nu
`localeContext` (languageTag, regionCode, timeZone, measurementSystem,
temperatureUnit, currencyCode) och skickas från API/worker.
4. **Hårdkodade UI-texter i mobilappen utanför i18n-modulen** (§7): options-/etikett-
kartor (onboarding, samtycken, måltidstyper, butiksavdelningar, enhetsetiketter,
Alert-rubriker) flyttade till översättningsnycklar; `t()` injicerar `{brand}`.
5. **Notiser sparade endast färdig text** (§26): `notifications` har nu
`template_key`, `variables`, `locale`; renderad text behålls som cache och
renderas om vid utskick.
6. **Namnet i koden** (§28): all kod avvarumärkad paketscope `@app/*`, UI via
`{brand}`, tjänste-/könamn ur brand-slug, neutrala standardvärden i config.
### Status M1M10
| Fas | Status | Verifiering |
| --- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| M1 | ✅ Genomförd | Alla pengakolumner `*_minor` (integer) + `households.currency_code`; E2E: budget i minor + valuta, receptfilter på minor-kostnad |
| M2 | ✅ Genomförd | `ingredient_translations` (101 en-rader seedade ur nameEn) + `unit_translations` (36 rader sv+en) + `/v1/i18n/units`; en-sök träffar översättningar |
| M3 | ✅ Genomförd | `recipe_translations` + `recipe_step_translations`; TRANSLATE_RECIPE-jobb → deterministisk verifiering (stegantal, bevarade tal, konfidens) → admin publicerar; E2E: en-användare får översatt titel/steg/ingrediensnamn med sv-fallback |
| M4 | ✅ Genomförd | i18next med Intl-plural (`_one`/`_other`), locales/{sv,en}/common.json (175 nycklar, paritetstestad), språkbyte utan omstart (versionsbaserad re-mount), språkval i profilen synkat mot locale-preferenser |
| M5 | ✅ Genomförd | `displayQuantity` i shared-types (METRIC/US_CUSTOMARY/MIXED, kvartsavrundning för cups/tsp, ≈-märkning); mobilens formatQuantity konverterar per användarens måttsystem; 15 tester |
| M6 | ✅ Genomförd | `nutrition_display_profiles` + `allergen_market_rules` seedade (EU/SE/GB 14, US 9, CA 12) + `/v1/i18n/nutrition-profile` med EU-fallback; kJ- och natrium↔salt-hjälpare med tester |
| M7 | ✅ Genomförd | unaccent + pg_trgm + trigramindex (skapas idempotent i migrate, master-varianten i create-database.sql); sök: "creme"→Crème fraiche, "jordgubar"→Jordgubbar |
| M8 | 📋 Kvar (ops) | Store-metadata, skärmbilder och prissättning per marknad görs i butikskonsolerna inför lansering produkt-ID:n redan varumärkes- och språkneutrala |
| M9 | 📋 Kvar (beslut D-024) | Adminpanelen förblir svensk tills internationell ops finns |
| M10 | ✅ Genomförd | `buildMemoryOverview(items, languageTag)`: rubriker per språk, deterministisk summary-rendering ur strukturerad value med sv-fallback; nya minnen får lokaliserad summary via localeContext |
### Ursprunglig plan (behålls som referens)
| # | Åtgärd | i18n-spec | Fas | Kommentar |
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| M1 | Pengar i minor units + valuta: alla `*_sek`-kolumner → `*_minor int` + `currency_code` (hushållsnivå), `Money`-typ i API | §20 | i5 | Typ + hjälpare levererade i `shared-types/money.ts`; kolumnbyte görs som nästa migration MEDAN databasen är produktionstom ingen datamigrering behövs |
| M2 | `ingredient_translations` + flytt av nameSv/nameEn → rader; `unit_translations` i DB (visning finns nu i app-lagret) | §1112 | i3 | Canonical-IDs är redan rätt → ren additiv migration |
| M3 | `recipe_translations` + `recipe_step_translations`; AI-utkast med status DRAFT_AI→…→PUBLISHED + automatiska verifieringar (samma IDs/mängder/tider/allergener) | §1314 | i5 | Receptstrukturen är redan språkoberoende |
| M4 | i18next + ICU-plural i mobilen (ersätter interims-t()), namespaces `/locales/{lng}/{ns}.json`, expo-localization för förslag, språkbyte utan omstart, CI-vakt mot hårdkodad text | §5, §78, §31 | i4 | Nyckelstrukturen är redan namespace-formad (`home.*`, `scan.*` …) → mekanisk flytt. Motivering i beslutslogg D-023 |
| M5 | Måttvisningstjänst: canonical→display per measurementSystem (ml→cup/tsp, g→oz/lb, °C→°F) med förlustfri lagring (canonical + original + källa + confidence) | §910, §30 | i5 | Konverteringstabellen finns; visningsvalet styrs av locale-prefs |
| M6 | `nutrition_display_profiles`, `allergen_market_rules`, kJ + natrium | §1819 | i7 | |
| M7 | Sök: unaccent + pg_trgm + alias/synonymtabeller per locale | §24 | i5 | `ingredient_aliases`-fältet finns redan |
| M8 | Lokaliserad store-metadata, screenshots, prissättning per marknad | §25 | i8 | Produkt-IDs redan neutrala |
| M9 | Adminpanelen till nycklar | §7 | i8 | Beslut: internt verktyg får vara svenskt tills internationell ops finns (D-024) |
| M10 | AAMOS-minne: rendera summaries ur strukturerad value vid läsning i valt språk | §23 | i6 | value är redan strukturerad |
## 2. Berörda filer (denna leverans)
`packages/shared-types` (enums/units/brand/money/locale) · `packages/validation`
(unit-schema, locale-prefs-schema) · `packages/database` (unit-enum, user_locale_preferences,
notifications-kolumner, seed) · motorerna (enhetsberäkning via kodtabellen) ·
`packages/ai-contracts` (localeContext) · `apps/api` (locale-prefs-routes, kontext till
AAMOS, neutrala defaults) · `apps/worker` (notismallar, kontext) · `apps/mobile`
(unit-etiketter, nya nycklar, brand-injektion) · alla package.json (scope `@app/*`).
## 3. Migreringsplan & rollback
Pre-launch utan produktionsdata ⇒ enum-/kolumnbyten görs i **basmigrationen**
(0000 regenererad; loggat i Del 7). Efter lansering gäller i stället spec §29-mönstret:
nya kolumner → mapping → dubbelskrivning → verifiering → borttag efter kontroll; varje
migration med backup (deploy.sh vägrar utan dump) och nedskriven rollback.
Rollback för denna leverans: git-tag före sveper + regenererbar databas.
## 4. Testplan (§31)
🔨 Nu: enhetstester för alla nya enhetskoder inkl. cup/oz/lb-konvertering och
"vägra-utan-densitet"; locale-prefs-API; oförändrade motorresultat (61 befintliga tester
gröna efter kodbytet). 📋 i4i8: pseudolokal-test (nycklar aldrig synliga), plural-ICU,
formatteringstester (decimal/tusental/datum/valuta) för sv-SE, en-US, en-GB, de-DE,
fi-FI, snapshot av receptöversättningsverifieringen, CI-grep-vakt mot hårdkodade strängar.
## 5. Exit criteria-läge (§33)
Uppfyllda redan: språkneutrala enums, canonical ingredienser, datadrivna högtider,
UTC/lokaldatum, AAMOS-kontext, enhetsbyte utan dataförlust (canonical lagring),
nytt språk = nya rader (ingen schemaändring), nytt land = data (market-rader).
Återstår före internationell lansering: M1M8 ovan uppskattat 23 sprintar, kan ske
parallellt med svensk beta eftersom allt är additivt.
+97
View File
@@ -0,0 +1,97 @@
# Lanseringsplan från namnbeslut till butik
> Körbar runbook. Allt som går att förbereda utan namn/konton är REDAN gjort
> och verifierat (se STATUS.md). Stegen nedan är i ordning; varje steg listar
> vem/vad som krävs. Kommandon körs från repo-roten om inget annat anges.
## Fas 0 Namnbeslut (Johan)
1. Uppdatera `brand.config.json`: name, slug, urlScheme, iosBundleId,
androidPackage, apiDomain, adminDomain, supportEmail. (ENDA kodändringen.)
2. Lägg det gamla platshållarnamnet i `FORBIDDEN`-listan i
`scripts/brand-guard.sh` så CI vaktar även mot "Matappen".
3. Verifiera: `pnpm typecheck && pnpm test && pnpm build && ./scripts/brand-guard.sh`
4. Registrera domäner (apiDomain, adminDomain) + e-postdomän med SPF/DKIM/DMARC.
## Fas 1 Infrastruktur (ops, ~en halvdag)
1. **RDS**: kör `infrastructure/deployment/create-database.sql` som master
(skapar databas, minsta-privilegie-användare och sök-extensions).
2. **Secrets**: generera och lägg i AWS Secrets Manager/SSM:
```bash
# Kör tre gånger JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, ENTITLEMENT_SIGNING_SECRET
node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
```
Fyll `/opt/<slug>-platform/secrets/*.env` enligt `.env.example` (chmod 600).
Produktionen VÄGRAR starta med dev-hemligheter eller AAMOS_MODE=mock.
3. **S3**: skapa bucket `<slug>-production`, block public access, SSE-S3,
lifecycle: `temporary/` 7 dagar, `scans/` 90 dagar. IAM-roll `<slug>-app`
med åtkomst ENDAST till denna bucket (policy i infrastructure/security/).
4. **Server**: dedikerad Linux-användare, Docker, reverse proxy med TLS mot
`127.0.0.1:4100`. Deploy: `infrastructure/deployment/deploy.sh`
(kräver pg_dump-backup före migrationer inbyggt).
5. **Larm**: sätt `ERROR_WEBHOOK_URL` (Slack/Discord-webhook) 5xx och
fallerade jobb postas automatiskt.
6. **Backup-övning mot produktion**:
`DATABASE_URL=… ADMIN_DATABASE_URL=… ./infrastructure/deployment/backup-restore-test.sh`
(protokoll arkiveras i infrastructure/backups/; upprepa kvartalsvis).
## Fas 2 AAMOS på riktigt (störst teknisk risk gör tidigt)
1. Sätt `AAMOS_MODE=http`, `AAMOS_API_URL`, `AAMOS_API_KEY` mot AAMOS staging.
2. Kör utvärderingssviten: `pnpm eval:aamos`
samma svit som körs mot mocken; resultatet loggas i ai_eval_runs och
syns i adminpanelen. UNDERKÄND = åtgärda innan vidare.
3. Regel därefter (spec §40): ingen modell-/promptändring i AAMOS tas i
produktion utan godkänd `pnpm eval:aamos`-körning.
4. Personuppgiftsbiträdesavtal med AAMOS-driften (juridik, fas 5).
## Fas 3 E-postleverantör
1. Välj SES/Postmark/Resend. Implementera transporten i
`apps/api/src/lib/mailer.ts` (SmtpMailerPlaceholder en klass, ~20 rader).
2. Sätt `EMAIL_MODE=smtp` + leverantörsnycklar. Mallar och flöden
(verifiering + lösenordsåterställning) är klara och E2E-testade i log-läge.
3. Verifiera DKIM/SPF på e-postdomänen; skicka testmejl till Gmail/Outlook.
## Fas 4 Butikerna (kräver namn + konton)
1. Apple Developer Program + Play Console-konto.
2. App Store Connect/Play Console: skapa apparna med `iosBundleId`/`androidPackage`
och produkt-ID:n (`<iosBundleId>.<plan>_monthly` / `<plan>_monthly` se
PLAN_DEFINITIONS; genereras ur brand-konfig).
3. Fyll i `apps/mobile/eas.json` submit-fälten; `eas build --profile production`.
4. Implementera butiksverifiering i produktionsläge
(`packages/subscriptions/src/stores.ts`: App Store Server API v2 +
Play Developer API sandbox-flödet och entitlement-modellen är klara).
5. Push-notiser: `EXPO_PUSH_ENABLED=true` (tokenhantering + mallar klara).
6. Store-metadata per marknad: utkast i `docs/22-butiksmetadata.md`.
7. TestFlight/interna testare → rätta → skicka till granskning.
## Fas 5 Juridik & innehåll (parallellt med fas 14)
1. Integritetspolicy + användarvillkor: UTKAST i `docs/23-juridik-utkast.md`
**kräver granskning av jurist före publicering**.
2. Biträdesavtal: AWS + AAMOS-driften.
3. Recept: fler än dagens 22 seedade M3-flödet (AI-utkast → granska →
publicera per språk) är byggt för att skala innehållet.
4. Licensgranskning innan Livsmedelsverket/OFF-connectorerna aktiveras.
## Fas 6 Genrep & lansering
1. `pnpm typecheck && pnpm test && pnpm build` grönt i CI (inkl. varumärkesvakt).
2. Lasttest mot produktionsmiljön: `API_BASE_URL=https://<apiDomain> node scripts/loadtest.mjs`
(lokala referensvärden i docs/24-lasttest.md).
3. `pnpm eval:aamos` mot produktions-AAMOS GODKÄND.
4. Aktivera admin-2FA för samtliga admin-konton (adminpanelen → 2FA).
5. Soft launch: Sverige först (spec-fas i7), sedan marknader per
marknadsprofilerna (EU/GB/US/CA är seedade och verifierade).
## Checklista "dagen före"
- [ ] Backup-restore-protokoll < 3 månader gammalt
- [ ] ERROR_WEBHOOK_URL testad (kasta ett testfel i staging)
- [ ] healthz/readyz gröna bakom TLS
- [ ] Admin-2FA aktiverat för alla admins
- [ ] eval:aamos GODKÄND mot produktion
- [ ] Butiksgranskningen godkänd i båda butikerna
+90
View File
@@ -0,0 +1,90 @@
# Butiksmetadata utkast per marknad (i18n M8)
> `{NAMN}` byts vid namnbeslut. Texterna följer butikernas längdgränser.
> Skärmbilder tas ur appen efter namnbyte (checklista längst ner).
## App Store / Google Play svenska (SE)
**Titel (30 tecken):** `{NAMN} hushållets mat-OS`
**Undertitel (30):** `Skanna. Ät smart. Släng mindre.`
**Kort beskrivning (80, Play):**
`Fota kylen, få middagsförslag på det du har och släng mindre mat.`
**Beskrivning:**
{NAMN} håller koll på hushållets mat så att du slipper. Fota kylen, skafferiet
eller kvittot AI:n fyller matlagret, och "Vad ska vi äta?" föreslår middagar
utifrån det ni faktiskt har hemma, vad som bör användas snart och vad ni gillar.
• Skanna kyl, frys, skafferi, kvitton, streckkoder och näringsdeklarationer
• Middagsförslag med förklaring ju mer ni använder appen, desto bättre blir de
• Allergifiltrering som aldrig kompromissar rätter med dina allergener visas inte
• Recept med cooking mode, portionsskalning och automatiska lageravdrag
• Delat matlager, inköpslista, veckoplan och matlådor för hela hushållet
• Matbudget och matsvinn i kronor se vad ni sparar
• Kalorier och näring beräknas alltid deterministiskt AI:n gissar aldrig siffror
• Full insyn: "Vad {NAMN} vet om mig" går att rätta, pausa och radera
Gratis: 10 AI-skanningar/månad, manuellt lager, sparade recept och enkel loggning.
Premium (7 dagar gratis, inget kort): obegränsat lager, veckoplan, delning och
fler AI-skanningar.
**Nyckelord (100, App Store):**
`matplanering,matsvinn,kylskåp,recept,middagstips,matlager,inköpslista,kalorier,veckoplan,hushåll`
## App Store / Google Play engelska (US/GB/CA)
**Title:** `{NAME} your household food OS`
**Subtitle:** `Scan. Eat smart. Waste less.`
**Short description (Play):**
`Snap your fridge, get dinner ideas from what you have and waste less food.`
**Description:**
{NAME} keeps track of your household's food so you don't have to. Snap your
fridge, pantry or receipt AI stocks your inventory, and "What's for dinner?"
suggests meals based on what you actually have, what should be used soon and
what you like.
• Scan fridge, freezer, pantry, receipts, barcodes and nutrition labels
• Dinner suggestions with reasons the more you use it, the better it gets
• Allergy filtering that never compromises dishes with your allergens are never shown
• Recipes with cooking mode, serving scaling and automatic inventory deduction
• Shared inventory, shopping list, week plan and meal boxes for the whole household
• Food budget and waste in your currency see what you save
• Calories and nutrition are always computed deterministically AI never invents numbers
• Full transparency: "What {NAME} knows about me" can be corrected, paused and deleted
Free: 10 AI scans/month, manual inventory, saved recipes and simple logging.
Premium (7-day free trial, no card): unlimited inventory, week planning, sharing
and more AI scans.
**Keywords:**
`meal planning,food waste,fridge,recipes,dinner ideas,food inventory,grocery list,calories,meal prep,household`
## Marknadsanpassning (styrs redan av koden)
| Marknad | Enheter | Näring | Allergener | Valuta |
| ------- | ---------------- | ---------------- | ---------------- | ------- |
| SE/EU | metriskt | kJ + kcal, salt | EU14 | SEK/EUR |
| GB | metriskt (MIXED) | kJ + kcal, salt | EU14 | GBP |
| US | cups/oz/lb, °F | Calories, sodium | FDA Big 9 | USD |
| CA | metriskt (MIXED) | Calories, sodium | Health Canada 12 | CAD |
## Skärmbildschecklista (per språk: sv + en)
1. "Vad ska vi äta?" med förslag + förklaring
2. Skanningsgranskning (AI-osäkerhet + korrigering)
3. Receptdetalj med säkerhetsmarkering och kostnad/portion
4. Hemma-fliken: lager med bäst före + budget
5. Cooking mode med timer
6. "Vad {NAMN} vet om mig"-vyn (integritet är ett säljargument)
## Åldersgräns & kategorier
- Kategori: Mat & dryck (primär), Hälsa & fitness (sekundär)
- Åldersgräns: 4+ (App Store) / PEGI 3 (Play) ingen användargenererad
bild delas publikt utan moderering (spec §35)
- Deklarationer: kamera (skanning), inga spårnings-SDK:er, inga annonser
+93
View File
@@ -0,0 +1,93 @@
# Juridiska dokument UTKAST
> ⚠️ **Dessa är tekniska utkast skrivna utifrån hur systemet faktiskt fungerar.
> De MÅSTE granskas och fastställas av jurist före publicering.** Utkasten är
> avsiktligt sanna mot implementationen varje påstående går att verifiera i koden.
---
## Integritetspolicy (utkast)
**Personuppgiftsansvarig:** {BOLAG}, org.nr {ORGNR}, {ADRESS}. Kontakt: {supportEmail}.
### Vilka uppgifter vi behandlar och varför
| Uppgifter | Ändamål | Rättslig grund |
| -------------------------------------------------------------------- | -------------------------------------------- | ----------------------------------------------- |
| Konto (e-post, visningsnamn, lösenordshash) | Inloggning och kontosäkerhet | Avtal |
| Hushållsdata (matlager, inköpslistor, veckoplaner, matlådor, budget) | Appens kärnfunktioner | Avtal |
| Bilder du fotar (kyl, kvitton, tallrikar) | AI-analys för att fylla lager/logga måltider | Avtal |
| Mål, allergier, kostmönster, hälsoprofil | Personliga förslag och säkerhetsfiltrering | Uttryckligt samtycke (art. 9 där tillämpligt) |
| Minnesposter ("Vad appen vet om mig") | Bättre förslag över tid | Samtycke (personalization) kan pausas/raderas |
| Språk-, region- och måttinställningar | Visa appen på ditt språk och i dina enheter | Avtal |
| Teknisk logg (correlation-id, IP vid säkerhetshändelser) | Drift, felsökning, säkerhet | Berättigat intresse |
### Det vi INTE gör
- Ingen hälso- eller kostdata används för reklam. Appen innehåller inga annons-SDK:er.
- Hälsodata delas aldrig med andra i hushållet delning gäller lager, listor,
planer och budget; mål, allergier och hälsoprofil är alltid privata.
- Dina bilder används för AI-träning ENDAST med separat, frivilligt samtycke
(image_training) som kan återkallas när som helst.
- Personligt minne blir aldrig automatiskt träningsdata.
### AI-behandling
Bild- och textanalys utförs av vår AI-plattform (AAMOS) via server-till-server-
anrop. Appen skickar aldrig data direkt till modelleverantörer. Identiteter
pseudonymiseras före AI-anrop. AI-resultat är förslag: du granskar och godkänner
innan något sparas, och all närings- och allergiberäkning görs deterministiskt
i vår kod aldrig av AI.
### Dina rättigheter
- **Insyn:** "Vad appen vet om mig" i profilen visar allt minne; GET /v1/me/export
ger fullständig maskinläsbar export.
- **Rättelse:** rätta felaktiga minnesposter direkt i appen din rättelse vinner alltid.
- **Radering:** radera enskilda poster, allt minne, eller hela kontot
(radering + anonymisering, spec §56).
- **Återkallat samtycke:** varje samtycke är separat och kan slås av i profilen.
### Lagring och gallring
Uppgifter lagras inom EU/EES ({REGION}). Automatisk gallring: säkerhetstokens
7 dagar, återkallade sessioner 30 dagar, skanningsjobb och notiser 90 dagar,
tillfälliga bilder 7 dagar. Backuper roteras enligt driftpolicy.
### Mottagare
Driftleverantörer (AWS {REGION}), AI-drift (AAMOS) samtliga under
personuppgiftsbiträdesavtal. Inga uppgifter säljs eller delas för marknadsföring.
---
## Användarvillkor (utkast)
1. **Tjänsten**: {NAMN} är ett hjälpmedel för matplanering, matlager och
näringsöversikt för privat bruk.
2. **Inte medicinsk rådgivning**: närings- och kalorivärden är vägledning,
inte medicinsk rådgivning. Allergifiltreringen är ett hjälpmedel kontrollera
alltid förpackningen; vi ansvarar inte för felmärkta livsmedel.
3. **Konto**: du ansvarar för dina inloggningsuppgifter. Konton kan inte delas
utanför hushållsfunktionen.
4. **Uppskattningar**: AI-analyser är uppskattningar och markeras som sådana.
Du granskar och godkänner innan de sparas.
5. **Användarinnehåll**: recept du publicerar modereras. Du garanterar att du
har rätt till innehåll du laddar upp; upphovsrättsintrång tas bort.
6. **Prenumerationer**: köp och förnyelser hanteras av App Store/Google Play
enligt deras villkor; provperioden är 7 dagar utan kort.
7. **Ansvarsbegränsning**: tjänsten tillhandahålls "i befintligt skick";
ansvar begränsas till vad tvingande lag medger.
8. **Ändringar**: väsentliga villkorsändringar meddelas i appen minst 30 dagar
i förväg.
9. **Tillämplig lag**: svensk lag; tvist prövas av svensk domstol. Konsument-
skyddsregler i din hemvist påverkas inte.
---
### Att fastställa med jurist
- Bolagsuppgifter, DPO-behov, art. 9-bedömning för hälsoprofilen
- Åldersgräns (rekommendation: 16 år i EU utan målsmans samtycke)
- Biträdesavtalens status (AWS, AAMOS-drift) och tredjelandsöverföringar
- Marknadsspecifika krav: UK GDPR, US state privacy laws (CCPA m.fl.), PIPEDA (CA)
+25
View File
@@ -0,0 +1,25 @@
# Lasttest referensvärden
Skript: `scripts/loadtest.mjs` (autocannon, 25 samtidiga anslutningar, 10 s per scenario).
Kör mot valfri miljö: `API_BASE_URL=https://… node scripts/loadtest.mjs`
Trösklar: p99 < 500 ms, 0 fel, > 100 req/s på receptlistan. UNDERKÄND ger exitkod 1 (CI-vänligt).
## Senaste körning (dev-container, 1 process, lokal Postgres)
| Scenario | req/s | p50 | p99 | fel |
| ------------------------------------------------------ | ----- | ------ | ------ | --- |
| healthz (baslinje) | 12040 | 1 ms | 6 ms | 0 |
| GET /v1/recipes (lista + i18n-upplösning) | 754 | 31 ms | 74 ms | 0 |
| GET /v1/recommendations/what-to-eat (tyngsta läsvägen) | 135 | 173 ms | 290 ms | 0 |
Kommentar: healthz visar ren Fastify-overhead. Receptlistan inkluderar
i18n-titelupplösning mot översättningstabellen. Rekommendationsvägen är
medvetet tyngst (lager + FEFO + motor + förklaringar) ~126 req/s på en
utvecklingscontainer motsvarar med god marginal förväntad lanseringstrafik;
skala horisontellt (fler API-containrar) före databasen.
## Vid produktionssättning
1. Kör mot staging bakom TLS förvänta ~1020 % lägre värden (TLS + nät).
2. Kör om efter varje kapacitetspåverkande ändring (nya index, tunga endpoints).
3. Rate limit: produktion har 300 req/min/IP lasttest kräver RATE_LIMIT_MAX-överstyrning.
+136
View File
@@ -0,0 +1,136 @@
# Testguide testa hela appen i er infra, före namn och App Store
> Målgrupp: du och eventuella testare. Förutsätter att staging är uppe via
> `first-deploy.sh` (se OPENCLAW-DEPLOY-PROMPT.md). Namn behövs inte;
> App Store behövs inte.
## Tre saker går att testa, i stigande ordning av "känns som riktig app"
| Yta | Hur | Kräver |
| ------------------------------- | ------------------------------------------------ | ------------------ |
| 1. API:t | Swagger UI i webbläsaren | Bara servern |
| 2. Adminpanelen | Webbläsare | Bara servern |
| 3. **Mobilappen i din telefon** | Expo Go (gratis app) eller sidladdad Android-APK | Telefon + se nedan |
## 1. API:t 5 minuter
Öppna `http://<server>:4000/docs` alla 110+ endpoints, körbara direkt i
webbläsaren. Snabbaste vitalkontrollen:
```bash
curl -s http://<server>:4000/healthz # {"ok":true,...}
curl -s http://<server>:4000/readyz # {"ok":true,...}
```
## 2. Adminpanelen 10 minuter
```bash
cd apps/admin/dist && python3 -m http.server 5173
```
Öppna `http://<server>:5173`. Skapa ett admin-konto:
```bash
# 1. Registrera via API:t (eller appen), 2. ge admin-roll i databasen:
psql "$DATABASE_URL" -c "UPDATE users SET role='admin' WHERE email='din@mejl.se';"
```
OBS: sätt `CORS_ORIGINS=http://<server>:5173` i `.env` och starta om API:t,
annars blockeras webbläsaranrop. (Mobilappen berörs inte av CORS.)
Testa: systemöversikt (hälsa/köer), användare, feature flags, prenumerations-
grant, auditloggen, 2FA-aktivering på ditt konto.
## 3. Mobilappen i telefonen det riktiga testet
### Alternativ A: Expo Go (enklast 10 minuter, funkar för iPhone OCH Android)
1. Installera **Expo Go** (gratis, App Store/Google Play) på telefonen.
2. På en dator med repot (kan vara din laptop appen ska bara PEKA på er infra):
```bash
pnpm install
API_BASE_URL=http://<server>:4000 pnpm mobile:start
# Telefon på annat nätverk än datorn? Lägg till --tunnel:
# API_BASE_URL=https://<staging-domän> pnpm --filter @app/mobile exec expo start --tunnel
```
3. Skanna QR-koden med telefonen (kamera-appen på iPhone, Expo Go på Android).
4. Appen öppnas i telefonen och pratar med ER server.
Krav: telefonen måste kunna NÅ `API_BASE_URL` samma wifi som servern,
eller en exponerad staging-domän med TLS. Testa i telefonens webbläsare:
öppna `http://<server>:4000/healthz` svarar den där, funkar appen.
### Alternativ B: Android-APK (känns exakt som en riktig app, ingen dator vid test)
Kräver ett gratis Expo-konto (inte Google/Apple):
```bash
cd apps/mobile
# Sätt er staging-URL i eas.json under "preview".env.API_BASE_URL först!
npx eas login
npx eas build --platform android --profile preview
```
Bygget ger en `.apk`-länk skicka till testarna, installera direkt på
Android-telefoner ("okända källor" godkänns vid installation). iPhone på
detta sätt kräver Apple-konto → det väntar tills butiksfasen, använd Expo Go.
## Kör hellre allt PÅ RIKTIGT (rekommenderat)
Mockarna är bara reservläge. Med era nycklar körs allt äkta från start
se "PÅ RIKTIGT FRÅN START" i OPENCLAW-DEPLOY-PROMPT.md (`REQUIRE_REAL=1`
gör det till en grind). Då är AAMOS-svaren äkta, mejlen landar i riktiga
inkorgar och bilderna i riktig S3. Avsnittet nedan gäller ENDAST om ni
medvetet kör utan nycklar.
## Om ni ändå kör mock-läge tillfälligt
Staging kör då `AAMOS_MODE=mock`: **skanningar ger realistiska men påhittade
svar** (samma varje gång bl.a. en osäker vara som kräver bekräftelse, för
att öva gransknings-UX:et). Det är avsiktligt: allt RUNT AI:n testas på
riktigt flöden, lager, kvitton i minor units, korrigeringar. När riktiga
AAMOS kopplas (`AAMOS_MODE=http` + nyckel) blir svaren äkta utan att något
annat ändras. Mejl: staging kör `EMAIL_MODE=log` verifierings- och
återställningsmejl hamnar i `logs/api.log` i stället för i inkorgar
(sök på `mailer:log` för att hämta länkarna).
## Manuellt testprotokoll (ca 30 min per språk)
| # | Gör | Förväntat |
| --- | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | Installera/öppna appen på telefon med svenska som systemspråk | UI på svenska direkt |
| 2 | Registrera konto, gå igenom onboarding (mål, allergier välj t.ex. gluten, hushåll, läge) | Trial startar; hushåll skapat |
| 3 | Hemma-fliken → lägg till varor manuellt (t.ex. pasta 500 g, färs 400 g, lök 3 st) | Lagret visar varorna med rätt enheter |
| 4 | "Vad ska vi äta?" | Förslag med förklaringar; rätter med dina allergener syns ALDRIG |
| 5 | Öppna ett recept → skala portioner → "Laga nu" → cooking mode → klart → logga | Ingredienser dras från lagret; måltiden syns i Min dag |
| 6 | Skanna → Kyl → ta bild | Mock-svar med varor; minst en "Osäker kontrollera"; godkänn → lagret uppdateras |
| 7 | Inköpslista: lägg till varor, bocka av, "Avsluta köprundan" | Avbockade varor hamnar i lagret |
| 8 | Matlådor: laga recept med portioner till lådor | Lådan syns med "ät senast"-datum |
| 9 | Budget på Hemma-fliken | Belopp i kr, vettiga summor |
| 10 | Profil → byt språk till Español | HELA appen byter direkt, utan omstart |
| 11 | Sök ingrediens på spanska ("ajo") | Träffar Ajo (vitlök) |
| 12 | Profil → "Vad appen vet om mig" | Minnesvyn med rubriker på valt språk; pausa/radera fungerar |
| 13 | Logga ut → "Glömt lösenord?" → hämta länken ur `logs/api.log` → återställ | Nytt lösenord funkar; gamla sessioner utloggade |
| 14 | Profil → Exportera min data | JSON-export levereras |
| 15 | Byt telefonens systemspråk till danska, avinstallera + installera om (Expo Go: rensa), registrera nytt konto | Appen och välkomstmejlet (i loggen) på danska, DKK-defaults |
| 16 | Mjölkprincipen: ge en vara "Bäst före" i går (datumtyp väljs i skanningsgranskningen) och en annan "Sista förbrukningsdag" i går | Bäst före-varan: gul tagg "Passerat bäst före lukta och smaka", finns kvar i recept/förslag och räddas först. Sista förbrukning-varan: röd "Utgången". Ingen kasseras automatiskt |
Upprepa 15 på engelska/tyska/valfritt språk stickprovsvis.
## Buggrapportmall
```
Skärm/steg: (t.ex. protokoll #6, skanningsgranskning)
Språk: sv/en/…
Förväntat: …
Faktiskt: …
Correlation-ID: (syns i API-svar vid fel gör felsökning exakt)
```
## När testet är klart
1. Namnbeslut → `brand.config.json` (runbook: `namnbyte.md`).
2. Riktiga AAMOS → `pnpm eval:aamos` mot produktion (lanseringsplanen fas 2).
3. Butikskonton → EAS production-bygge → TestFlight/intern testning → granskning.
+136
View File
@@ -0,0 +1,136 @@
# Ändringslogg
## 2026-08-05 fältfynd åtgärdade + mjölkprincipen + i18n-svep
Svar på första skarpa OpenClaw-deployen (WSL, alla grindar gröna) samt
Johans princip om bäst före-datum:
- **Tidszonsoberoende tester (D-033)**: de två TZ-känsliga testerna ur
deployrapporten är fixade i grunden inventory-engine (expiry) och
recommendation-engine (season: påsk/midsommar/advent/årstider) räknar nu
helt i UTC, liksom API:ts vecko-/månadsbudget och ålder/veckodag. Hela
sviten verifierad under Europe/Stockholm, Pacific/Auckland och
America/Los_Angeles (30/30 tasks utan cache i alla tre). TZ=UTC-workaround
behövs inte längre.
- **Docker valfritt (D-034)**: Grind 0 kräver inte längre docker den noterar
bara. Grind 2 avgör på faktisk nåbarhet och testar nu även Redis (rå
TCP-PING) utöver Postgres; Docker används enbart som reservstart i staging.
WSL/native-tjänster är en förstklassig väg (inga shims). Prompt uppdaterad.
- **Mjölkprincipen (D-035)**: "gick ut igår ≠ dålig". Motorn hade grunden
(bäst före → expiring + pastBestBefore, aldrig auto-expired; endast sista
förbrukningsdag → expired). Nu förstärkt hela vägen: skanningsgranskningen
låter användaren välja datumtyp (bäst före/sista förbrukningsdag → rätt
kolumn), kontextnot förklarar kvalitet vs säkerhet, Hemma-fliken visar
"lukta och smaka"-legend när något passerat bäst före, och nya tester låser
beteendet: FEFO räddar passerade varor FÖRST (aldrig utesluter), täckningen
räknar dem som tillgängliga, ingen automatisk kassering existerar.
- **i18n-svep (D-036)**: ~130 hårdkodade svenska UI-strängar i 15 skärmar
(onboarding-mål/dieter/allergener, butikssektioner, måltidstyper,
samtyckesetiketter, roller, dialoger m.m.) flyttade till i18n-katalogen och
översatta till alla 12 språk (1 564 nya strängar, paritetstestat, polska
pluraler). "Byt språk → hela appen byter" gäller nu bokstavligen.
## 2026-08-04 inga mockar i drift: riktig S3 + riktig SMTP + äkthetsgrind
- Riktig S3-lagring (@aws-sdk/client-s3 + presigner): presignade upp-/
nedladdnings-URL:er mot AWS S3 eller S3-kompatibel lagring (S3_ENDPOINT för
MinIO/R2). Bucket-hälsokontroll vid uppstart; produktion vägrar starta mot
onåbar bucket. VERIFIERAD med äkta rundtur (presign→PUT→läs-URL→GET,
byte-identiskt) och fullstack via API:t (fysisk fil i bucket).
- Riktig SMTP (nodemailer): leverantörsoberoende (SES/Postmark/Resend/egen);
okonfigurerad SMTP vägrar högljutt. VERIFIERAD med äkta nätverksrundtur och
fullstack (välkomst- + återställningsmejl levererade till SMTP-server).
Rättade även SMTP_SECURE-tolkningen ("false" blev true med z.coerce).
- first-deploy.sh: INTEGRATIONSSTATUS i slututskriften + REQUIRE_REAL=1-grind
som underkänner deployen om någon integration kör mock; riktiga värden kan
ges som miljövariabler vid första körningen och skrivs in i .env.
- OpenClaw-prompten: "PÅ RIKTIGT FRÅN START"-sektion med ifyllnadstabell +
innehållsförteckning över vad zipen innehåller.
## 2026-08-04 tolv språk (D-032) + deploy-paket för agentkörning
- 6 nya språk: danska, norska (bokmål, no/nn mappas), finska, nederländska,
polska (fullständiga one/few/many-pluraler), portugisiska → totalt 12.
- 1111 ingrediensöversättningar (101 × 11 språk), 216 enhetsetiketter (12 språk),
mejlmallar + minnesrubriker på alla 12, regiondefaults NL/PL/PT.
- first-deploy.sh: idempotent helautomatisk uppsättning med 8 grindar
(staging: genererade hemligheter + Docker-DB + seed; produktion: vägrar
dev-värden, kräver AAMOS http, backup före migration, seedar aldrig).
Körd skarpt i staging alla grindar gröna.
- OPENCLAW-DEPLOY-PROMPT.md: komplett agent-prompt med hårda regler,
grindtabell, egen-verifiering, rapportformat, felsökningslista och rollback.
## 2026-08-04 sex språk + automatisk språkdetektering (D-031)
- UI på 6 språk: sv/en/es/it/de/fr 200 nycklar per språk, paritetstestade,
pluralformer per språk via Intl.PluralRules.
- Automatik: enhetens språk vid första start (stött → det, ostött → engelska);
registreringen bär enhetens locale → välkomstmejl OCH locale-preferenser
(region, valuta, tidszon, måttsystem) rätt från första sekunden.
- Manuellt språkval i profilen (6 språk, dynamisk väljare) vinner alltid och
synkas till backend mejl, notiser, innehåll och sök följer valet.
- Innehåll: 505 ingrediensöversättningar (101 × 5 språk), 108 enhetsetiketter
(18 × 6), mejlmallar och minnesrubriker på alla språk, regiondefaults ES/IT.
- Receptöversättning till nya språk sker via M3-flödet (AAMOS-utkast →
deterministisk verifiering → mänsklig publicering) skalar utan kodändring.
## 2026-08-04 "allt som kan göras klart är klart"
- AAMOS-utvärderingssvit (spec §40): `pnpm eval:aamos`, 6 golden cases med 30
semantiska kontroller, resultat till ai_eval_runs, exitkod för CI-grind.
- Admin-2FA: TOTP enligt RFC 6238 utan externa beroenden (testvektorer gröna),
step-up-inloggning med preauth-token, MFA-tvång på admin-API, UI i panelen.
- E-postverifiering: icke-blockerande, mallar sv/en, resend, deep link-skärm.
- Retentionsjobb (GDPR): dagligt, konservativa fönster; larmwebhook för 5xx och
fallerade jobb; backup-restore-övning skriptad och GENOMFÖRD (protokoll arkiverat).
- Lasttest med trösklar: GODKÄND (recept 754 req/s p99 74 ms; rekommendationer
135 req/s p99 290 ms; 0 fel).
- Lanseringsrunbook (docs/21), butiksmetadata-utkast sv/en (docs/22),
juridiska utkast med juristkrav (docs/23), lasttestreferens (docs/24).
## 2026-08-04 deploy-förberedelser
- **Lösenordsåterställning**: komplett flöde (forgot → mejl → engångstoken →
nytt lösenord) med anti-enumeration, hashade tokens, familjeåterkallelse,
mejlmallar sv/en via utbytbar mailer, mobilskärmar + app-länk. E2E-verifierat
inklusive säkerhetsegenskaperna (återanvänd token 401, gamla sessioner 401).
- **CI-hårdning**: varumärkesvakt (scripts/brand-guard.sh), migrate+seed mot
riktig Postgres 16 med sök-extensions, API-röktest i pipelinen.
- **EAS**: byggprofiler (development/preview/production) förberedda
butiksfält fylls i när konto + namn finns.
## 2026-08-04 i18n M1M7 + M10 komplett, varumärkesneutralt
- **M1 Pengar**: alla `*_sek`-kolumner → `*_minor` (integer, minor units);
`households.currency_code` (ISO 4217); Money-typ + Intl-formattering i mobilen;
planpriser och seed-priser i minor. Validering kräver heltal.
- **M2 Översättningsdata**: `ingredient_translations` (en seedad ur nameEn),
`unit_translations` (sv+en) + `GET /v1/i18n/units`; ingredienssök matchar
och visar användarens språk.
- **M3 Receptöversättning**: `recipe_translations` + `recipe_step_translations`;
AAMOS-uppgift TRANSLATE_RECIPE; worker-verifiering (stegantal, bevarade tal,
konfidens); adminflöde beställ → granska → publicera (med redaktionell
rättning); API serverar publicerat språk med sv-fallback.
- **M4 i18next**: locales/{sv,en}/common.json (175 nycklar, paritetstestade),
Intl-plural, {brand}-injektion, språkbyte utan omstart, språkval i profilen
synkat med backend-preferenser.
- **M5 Måttvisning**: `displayQuantity` (METRIC/US_CUSTOMARY/MIXED) med
kvartsavrundning och ≈-märkning; mobilen visar per användarens måttsystem.
- **M6 Marknadsprofiler**: `nutrition_display_profiles` + `allergen_market_rules`
(EU/SE/GB 14, US 9, CA 12) + `GET /v1/i18n/nutrition-profile` (EU-fallback);
kJ- och natrium↔salt-hjälpare.
- **M7 Sök**: unaccent + pg_trgm + trigramindex; accentokänslig och
stavfelstolerant ingredienssök på alla språk.
- **M10 Minne**: minnesvyn renderas på användarens språk (rubriker + deterministisk
summary ur strukturerad value, sv-fallback).
- **Varumärke**: namnet lever endast i `brand.config.json` (neutrala platshållare
tills namnbeslut); historik tillplattad; runbook i `namnbyte.md`.
- **Verifiering**: typecheck 17/17, 103 tester, build 3/3, migrate+seed,
E2E på hela språk-/valuta-/måttflödet.
## Tidigare
- Grundplattform: mobilapp, Food API (~70 endpoints), worker, admin,
deterministiska motorer, AAMOS-kontrakt, subscriptions, GDPR, infrastruktur.
- i18n steg 1: språkneutrala enhetskoder, locale-preferenser,
AAMOS-localeContext, notismallar, Money-typ.
+36
View File
@@ -0,0 +1,36 @@
# Dokumentation (arbetsnamn: se `brand.config.json`)
Dokumentationen följer master-specifikationens leveransstruktur (Del 120).
| Del | Dokument | Innehåll |
| --- | -------------------------------------------------- | ---------------------------------------------------------- |
| 1 | [01-executive-summary.md](01-executive-summary.md) | Kärnprodukt, målgrupper, differentiering, affärsvärde |
| 2 | [02-kritisk-bedomning.md](02-kritisk-bedomning.md) | Styrkor, svagheter, risker, betalningsvilja, retention |
| 3 | [03-funktionskatalog.md](03-funktionskatalog.md) | Launch Core / Advanced / Post-launch / Partnerships |
| 4 | [04-ux.md](04-ux.md) | Onboarding, navigation, skärmar, flöden, paywall |
| 56 | [05-arkitektur.md](05-arkitektur.md) | Komponenter, dataflöden, repostruktur, paketansvar |
| 7 | [07-datamodell.md](07-datamodell.md) | Tabeller, relationer, constraints, index, migrationer |
| 8 | [08-api-referens.md](08-api-referens.md) | Samtliga endpoints per domän |
| 9 | [09-aamos-integration.md](09-aamos-integration.md) | Kontrakt, task types, routing, memory, feedback, evals |
| 10 | [10-ai-kostnad.md](10-ai-kostnad.md) | Modeller, verifierade priser, kostnad per skala |
| 11 | [11-receptstrategi.md](11-receptstrategi.md) | Volym, rättigheter, redaktion, creators, moderering |
| 12 | [12-sakerhet-gdpr.md](12-sakerhet-gdpr.md) | Konkret checklista + implementationsstatus |
| 13 | [13-subscriptions.md](13-subscriptions.md) | StoreKit, Play Billing, verifiering, trial, grace, offline |
| 14 | [14-infrastruktur.md](14-infrastruktur.md) | Befintlig server nu, Docker, DB, S3, migration senare |
| 15 | [15-utvecklingsfaser.md](15-utvecklingsfaser.md) | Fas 110 med mål, test, exit criteria, rollback |
| 16 | [16-teststrategi.md](16-teststrategi.md) | Unit, integration, AI-evals, allergier, load, release |
| 17 | [17-launch-checklista.md](17-launch-checklista.md) | App Store, Play, TestFlight, privacy, incident |
| 18 | [18-riskregister.md](18-riskregister.md) | Risk, sannolikhet, påverkan, åtgärd, ägare |
| 19 | [19-beslutslogg.md](19-beslutslogg.md) | Alla låsta beslut med motivering |
| 20 | [STATUS.md](STATUS.md) | Vad som är byggt, stubbar, nästa steg |
| | [CHANGELOG.md](CHANGELOG.md) | Ändringslogg |
| 21 | [21-lanseringsplan.md](21-lanseringsplan.md) | Körbar runbook: namnbeslut → butik |
| 22 | [22-butiksmetadata.md](22-butiksmetadata.md) | Store-listning sv/en + marknadsmatris (M8-utkast) |
| 23 | [23-juridik-utkast.md](23-juridik-utkast.md) | Integritetspolicy/villkor UTKAST, kräver jurist |
| 24 | [24-lasttest.md](24-lasttest.md) | Lasttestreferens + trösklar |
| 25 | [25-testguide.md](25-testguide.md) | Testa API/admin/mobilapp i egen infra före namn & butik |
| | [namnbyte.md](namnbyte.md) | Namnstatus + runbook för namnbyte (spec §65) |
Levande dokument som ska uppdateras vid varje förändring (spec §63):
**STATUS.md**, **CHANGELOG.md**, **19-beslutslogg.md**, **05-arkitektur.md**,
samt migrationsloggen i [07-datamodell.md](07-datamodell.md).
+51
View File
@@ -0,0 +1,51 @@
# Status vad som är byggt, vad som är stubbar, vad som återstår
> Uppdaterad efter i18n-faserna M1M7 + M10. Varumärket är INTE beslutat:
> alla namn/ID:n är platshållare ur `brand.config.json` (se `namnbyte.md`).
## Verifierat grönt (senaste körning)
| Kontroll | Resultat |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pnpm typecheck` | 17/17 paket |
| `pnpm test` | 30 tasks, 113 tester (motorer inkl. mjölkprincipen, subscriptions, i18n, måttvisning) tidszonsoberoende: hela sviten körd utan cache under Europe/Stockholm, Pacific/Auckland och America/Los_Angeles |
| `pnpm build` | api + worker + admin |
| `pnpm db:migrate && pnpm db:seed` | 54+ tabeller, 101 ingredienser (+ en-översättningar), 22 recept, enhetsetiketter, marknadsprofiler |
| E2E-smoke | register → hushåll (valuta) → lager (kanoniska enheter) → rekommendationer → AI-översättning → publicering → engelsk användare får engelskt innehåll → accentokänslig sök → marknadsprofiler |
## Färdigbyggt
- **Mobilapp** (Expo/React Native): auth, onboarding, fyra flikar (Vad ska vi äta / Skanna / Min dag / Hemma), receptdetalj + cooking mode, skanningsgranskning, inköpslista, matlådor, hushåll, minne ("Vad appen vet om mig"), paywall, profil med språkval. i18next på 12 språk sv/en/es/it/de/fr/da/nb/fi/nl/pl/pt (~200 basnycklar per språk, paritetstestade, korrekta pluralformer inkl. polskans one/few/many), automatisk enhetsspråksdetektering vid första start (D-031, D-032), pengar via Intl, måttvisning per måttsystem.
- **Food API** (Fastify): ~70 endpoints auth/JWT-rotation, hushåll, lager (FEFO, bäst före), recept (säkerhetsfiltrering, skalning, varianter), rekommendationer, veckoplan, inköpslista, skanningar, måltidsloggning, nutrition, minne, GDPR (export/radering), subscriptions/entitlements, budget (minor units + valuta), i18n-endpoints (locale-preferenser, enhetsetiketter, näringsprofil), adminpanel-API med översättningsflöde.
- **Worker** (BullMQ): bildanalys/kvitton via AAMOS, moderering, veckoplan, receptöversättning med deterministisk verifiering, notiser, minnessync, prenumerationssvep, träningsexport, outbox.
- **Deterministiska motorer**: nutrition (Mifflin-St Jeor, per-100, enhetskonvertering som vägrar volym↔massa utan densitet), inventory (transaktionsbalans, FEFO, utgångsklassning), recipe (allergen/diet/religion-säkerhet, täckningsgrad, substitution, kostnad i minor), recommendation (poängsättning + förklaringar, cravings, säsonger).
- **AAMOS-integration**: 16 typade kontrakt (Zod-validerade kuvert med localeContext + pseudonymiserat subjectRef + consentFlags), HTTP-klient med retries + mock för dev/test.
- **Internationalisering (M1M7, M10)**: språkneutrala enhetskoder, locale-preferenser, minor units + valuta per hushåll, översättningstabeller med AI-utkastflöde, i18next, måttvisning, marknadsprofiler för nutrition/allergener, accentokänslig fuzzy-sök, minnesrendering per språk.
- **Admin** (Vite/React): användare, moderering, flags, subscriptions, jobböversikt, hälsa, auditlogg, AI-korrigeringar.
- **Lösenordsåterställning**: forgot/reset-endpoints med hashade engångstokens (30 min TTL), familjeåterkallelse av alla sessioner, mejlmallar per språk via utbytbar mailer (log-läge i dev, leverantör kopplas i fas 7), mobilskärmar med app-länk.
- **Admin-2FA**: TOTP (RFC 6238, inga externa beroenden) med step-up-inloggning, engångsskydd per steg och MFA-tvång på admin-API:t; UI i adminpanelen.
- **E-postverifiering**: icke-blockerande verifiering med mallar per språk, resend och profilhint.
- **AAMOS-utvärderingssvit**: golden cases med semantiska kontroller per uppgiftstyp; resultat till ai_eval_runs; CI-vänlig exitkod.
- **Driftverktyg**: dagligt retentionsjobb (GDPR), skriptad backup-restore-övning, lasttest med trösklar, larmwebhook för 5xx/fallerade jobb.
- **CI**: GitHub Actions med varumärkesvakt, Prettier, typecheck, tester, build, migrate+seed mot riktig Postgres 16 (med sök-extensions) och API-röktest.
- **Infrastruktur**: Dockerfiles (non-root), compose dev/prod, deploy-skript med backup-krav, create-database.sql (minsta privilegier + sök-extensions), hardening-checklista, larm/dashboards-underlag.
## Stubbar / medvetet senare
| Vad | Läge |
| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Apple/Google butiksverifiering i produktionsläge | Sandbox-läget komplett; App Store Server API v2 + Play Developer API kopplas i fas 7 (kräver riktiga konton) |
| Hälsointegrationer (Apple Health / Health Connect) | Connector-interface + samtycken klara; native-koppling görs i app-butiksfasen |
| Livsmedelsverket/Open Food Facts-import | Connector-stubbar med rätt interface; juridisk granskning av licenser först (spec §61.5) |
| Push-notiser via Expo | Tokenhantering + notistabeller klara; EXPO_PUSH_ENABLED=false tills appen finns i butikerna |
| M8 store-metadata per marknad | Ops-uppgift i butikskonsolerna inför lansering |
| M9 admin på flera språk | Beslut D-024: svenskt internverktyg tills internationell ops finns |
| Riktig AAMOS-drift | AAMOS_MODE=http + URL/nyckel sätts i produktion; mock används endast lokalt (produktion vägrar starta med mock) |
## Nästa steg
Allt som kan byggas utan namn/externa konton ÄR byggt. Återstår (kräver Johan/konton):
se **docs/21-lanseringsplan.md** körbar runbook fas 06 med exakta kommandon.
Kortversion: namn → infra-provisionering (halvdag) → `pnpm eval:aamos` mot riktig
AAMOS → e-postleverantör (en klass) → butikskonton + EAS → juristgranskning av
docs/23-utkasten → soft launch SE.
+53
View File
@@ -0,0 +1,53 @@
# Namnbyte status och runbook (spec §65)
## Status
**Namnet är inte beslutat.** Projektet använder neutrala platshållare
(`Matappen`, `com.example.matappen`, `api.example.com`) tills ett slutgiltigt
namn är valt. Varumärket definieras på EXAKT ett ställe: `brand.config.json`
i repo-roten. Ingen kod, dokumentation, databas, migrering eller
infrastrukturfil innehåller något riktigt varumärkesnamn.
## Så här styr `brand.config.json` allt
| Fält | Används av |
| ---------------- | ------------------------------------------------------------------------------------------------- |
| `name` | Mobilappens visningsnamn, iOS-behörighetstexter, alla UI-strängar (`{brand}` i i18n), admin-titel |
| `slug` | BullMQ-könamn, healthz-tjänstnamn, S3-bucket-default (`<slug>-production`), Expo-slug |
| `urlScheme` | Deep links i mobilappen |
| `iosBundleId` | Expo iOS bundle ID, Apple-produkt-ID:n (`<iosBundleId>.<plan>_monthly`) |
| `androidPackage` | Expo Android-paket, Google Play-verifiering |
| `apiDomain` | Dokumentation/drift; API:s bas-URL i produktion |
| `adminDomain` | Adminpanelens produktionsdomän |
| `supportEmail` | Supportlänkar i appen |
## Runbook vid namnbeslut
1. Uppdatera samtliga fält i `brand.config.json` (enda kodändringen).
2. Drift: sätt riktiga värden i produktions-`.env` om de ska avvika från
brand-defaults (S3_BUCKET, APPLE_BUNDLE_ID, GOOGLE_PACKAGE_NAME tomma
värden faller automatiskt tillbaka på brand.config.json).
3. Butikskonsoler: skapa appen med `iosBundleId`/`androidPackage` och
produkt-ID:n enligt tabellen ovan (App Store Connect + Play Console).
4. Domäner: registrera `apiDomain`/`adminDomain`, uppdatera TLS/reverse proxy.
5. Databas/infra: skapa databas + användare enligt
`infrastructure/deployment/create-database.sql` (namn efter `slug`).
6. Verifiera: `pnpm typecheck && pnpm test && pnpm build` samt
`grep -ri "<gamla namnet>"` ska ge noll träffar utanför `brand.config.json`.
## Vakt mot återinträde
- Inga hårdkodade namn i kod, tester, seed, docs eller infrastruktur
kontrollerat med grind-grep (se commit-historik).
- CI-förslag: lägg ett lint-steg som failar på varumärkesnamn utanför
`brand.config.json` när namnet är beslutat.
## Deploya före namnbeslut? Gränsdragningen
| Åtgärd | Före namn? | Varför |
| ------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Staging i egen infra + testning | ✅ Gör det | Noll varumärke i DB/API; namnbyte = konfigrad + omstart |
| Riktiga AAMOS + eval-svit | ✅ Gör det | Största tekniska risken avverka den tidigt |
| Produktionsinfra (RDS, server, larm) | ✅ Går bra | Välj NEUTRALT S3-bucketnamn (buckets kan aldrig döpas om) och gärna en varumärkesfri infra-domän för API:t |
| Riktiga användare | ⚠️ Undvik | Fungerar tekniskt, men mejl hälsar med platshållarnamnet |
| App Store / Google Play-inlämning | ❌ ALDRIG | Bundle-ID och produkt-ID:n blir permanenta vid publicering kan aldrig bytas, bara ersättas med ny app (recensioner/prenumeranter går förlorade) |