Initial commit (unpacked platform)
This commit is contained in:
@@ -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)** – 28–45 år, planerar mat för 3–6 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 79–129 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.
|
||||
@@ -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 5–6 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
|
||||
|
||||
79–129 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.
|
||||
@@ -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 | §9–12 | ✅ (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å" | §18–19 | ✅ |
|
||||
| Nutrition: deterministisk beräkning, BMR/TDEE-mål, Min dag | §21 | ✅ |
|
||||
| Måltidsloggning: recept/tidigare/manuell/tallriksfoto-intervall | §22–23 | ✅ |
|
||||
| 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 | §44–47 | ✅ (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 (kaloriintervall) | `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.
|
||||
@@ -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 (§23–24): 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 (§45–46, `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å.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Del 5–6 – 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.13–14).
|
||||
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/ §18–19/§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/ §44–47: 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 1–20).
|
||||
```
|
||||
|
||||
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 2–3 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.
|
||||
@@ -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 (§45–46). |
|
||||
| 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 (M1–M6)
|
||||
|
||||
- `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.
|
||||
@@ -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, §32–33, §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 (§7–8)
|
||||
|
||||
| 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 (§8–13)
|
||||
|
||||
| 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 (§10–12, §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 (§14–20)
|
||||
|
||||
| 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 (§18–19)
|
||||
|
||||
| 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 (§22–24)
|
||||
|
||||
| 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 (§25–27)
|
||||
|
||||
| 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 (§45–47)
|
||||
|
||||
| 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 (§35–38)
|
||||
|
||||
| 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 (M2–M6)
|
||||
|
||||
| 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 |
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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) | ~250–400 kr/recept |
|
||||
| AI-assisterade original, redaktionellt granskade | 1 400 | Fulla; AAMOS `GENERATE_RECIPE_OPTIONS` → mänsklig granskning av varje | granskning ~60–100 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).
|
||||
@@ -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 §32–33)
|
||||
|
||||
- ✅ 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.
|
||||
@@ -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).
|
||||
@@ -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).
|
||||
@@ -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, M2–M3 |
|
||||
| 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 |
|
||||
@@ -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 M1–M10
|
||||
|
||||
| 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) | §11–12 | 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) | §13–14 | 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, §7–8, §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) | §9–10, §30 | i5 | Konverteringstabellen finns; visningsvalet styrs av locale-prefs |
|
||||
| M6 | `nutrition_display_profiles`, `allergen_market_rules`, kJ + natrium | §18–19 | 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). 📋 i4–i8: 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: M1–M8 ovan – uppskattat 2–3 sprintar, kan ske
|
||||
parallellt med svensk beta eftersom allt är additivt.
|
||||
@@ -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 1–4)
|
||||
|
||||
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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 ~10–20 % 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.
|
||||
@@ -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 1–5 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.
|
||||
@@ -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 M1–M7 + 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.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Dokumentation (arbetsnamn: se `brand.config.json`)
|
||||
|
||||
Dokumentationen följer master-specifikationens leveransstruktur (Del 1–20).
|
||||
|
||||
| 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 |
|
||||
| 5–6 | [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 1–10 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).
|
||||
@@ -0,0 +1,51 @@
|
||||
# Status – vad som är byggt, vad som är stubbar, vad som återstår
|
||||
|
||||
> Uppdaterad efter i18n-faserna M1–M7 + 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 (M1–M7, 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 0–6 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.
|
||||
@@ -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) |
|
||||
Reference in New Issue
Block a user