Files
triqura-ecd/docs/specs/archive/to-mini-ecd.md
2025-11-15 22:06:48 +01:00

15 KiB
Raw Blame History

⚙️ Technisch Ontwerp — MiniECD (MVP)

Datum: aug 2025 Scope: MVP voor demo tijdens AIinspiratiesessie (≤10 min) Bronnen: PRD (v1.1), UX/UIspecificatie, FO (MVP)


0) TL;DR Stack & Keuzes

  • Framework: Next.js (App Router)
  • UI: Tailwind CSS (v4; fallback v3.4 bij frictie) + lucide-react iconen; lichte componentlaag (shadcn/ui of eigen + headless)
  • Editor: TipTap (ProseMirror) met StarterKit + BasicNodes
  • Auth & Data: Supabase (PostgreSQL + Auth + Storage)
  • AI: Claude (Anthropic) via API
  • Hosting: Vercel (Next.js)
  • PDF (stretch): serverside HTML→PDF via Chromium (playwright/puppeteer) of cloudfunctie.
  • Test: Vitest (unit) + Playwright (e2e).

Past bij MVP: minimale libs, AIcalls serverside, EUdataregio (Supabase EU), TipTap voor rijke tekst.


1) Architectuur

1.1 Overzicht

Browser (UI)
  └─ Next.js (App Router)
       ├─ Supabase (PostgreSQL + Auth + Storage)
       ├─ Claude AI (Anthropic)
       └─ (Stretch) PDF service (Chromium in serverless)
  • Serverside AIcalls: keys blijven op de server; UI krijgt alleen resultaten.
  • Dataflow (kern): Intake (TipTap) → AIsamenvat → AIextract → Probleemprofiel → AIplan → Plan (concept → publiceer).

1.2 Routing & lagen

  • Pages (App Router): /clients, /clients/[id] (tabs: overzicht/intakes/profiel/plan).
  • API (Next.js Route Handlers): /api/clients, /api/intakes, /api/problem-profile, /api/treatment-plan, /api/ai/*.

1.3 State

De state van de applicatie wordt beheerd met React Context API + Zustand (lichtgewicht state library). Dit is eenvoudig genoeg voor de MVP-scope. State wordt georganiseerd in src/lib/stores/ of src/contexts/.

  • clientStore.ts (Zustand): Beheert de state gerelateerd aan cliëntdata.

    • selectedClientId: string | null: Houdt het ID van de actieve cliënt bij. Dit is de centrale spil van de applicatie-staat.
    • clients: Client[]: De lijst van alle cliënten voor de overzichtspagina.
    • currentClientDossier: Dossier | null: Reageert op wijzigingen in selectedClientId. Wanneer de ID verandert, haalt de store automatisch de volledige dossierinhoud (intakes, profiel, plan) op via Supabase.
  • uiStore.ts (Zustand): Beheert globale UI-state.

    • toasts: ToastMessage[]: Een array met actieve 'toast'-notificaties die globaal getoond kunnen worden.
  • Dataflow Patroon:

    1. De UI update selectedClientId via Zustand action.
    2. De store triggert een Supabase query om dossierdata op te halen.
    3. React componenten die subscribed zijn via useClientStore() updaten automatisch.
  • Alternatief (MVP): Voor kleinere state kan ook React Context + useState volstaan zonder externe library.

Dit patroon zorgt voor een efficiënte, voorspelbare en reactieve dataflow door de hele applicatie.


2) Datamodel (Supabase / PostgreSQL)

2.1 Entiteiten

  • clients — basisgegevens
  • intake_notes — TipTap JSON + afgeleide velden
  • problem_profiles — DSMlight categorie + severity
  • treatment_plans — JSONB plan (doelen/interventies/frequentie/meetmomenten), versie/status
  • ai_events — prompts/completions (telemetrie, debugging)
  • (Stretch) appointments, reports

2.2 Relaties (PostgreSQL)

clients (id UUID PRIMARY KEY)
├── intake_notes (client_id → clients.id, FK)
├── problem_profiles (client_id → clients.id, FK)
└── treatment_plans (client_id → clients.id, FK)

ai_events (id UUID PRIMARY KEY, client_id + note_id als optionele FKs)

Row Level Security (RLS): Alle tables hebben RLS policies voor auth.users().

2.3 Tables (PostgreSQL schema)

NB: Enums als TEXT met CHECK constraints; plan inhoud als JSONB voor flexibiliteit in MVP.

// PostgreSQL Tables Schema (TypeScript types)

// clients table
interface Client {
  id: string; // UUID (auto-generated)
  first_name: string;
  last_name: string;
  birth_date: string; // DATE (ISO 8601: YYYY-MM-DD)
  created_at: string; // TIMESTAMPTZ
  updated_at: string; // TIMESTAMPTZ
}

// intake_notes table
interface IntakeNote {
  id: string; // UUID
  client_id: string; // FK → clients.id
  title?: string;
  tag: 'Intake' | 'Evaluatie' | 'Plan'; // TEXT with CHECK
  content_json: object; // JSONB (ProseMirror document)
  content_text?: string; // TEXT (for full-text search)
  author?: string; // FK → auth.users.id (optioneel)
  created_at: string; // TIMESTAMPTZ
  updated_at: string; // TIMESTAMPTZ
}

// problem_profiles table
interface ProblemProfile {
  id: string; // UUID
  client_id: string; // FK → clients.id
  category: 'stemming_depressie' | 'angst' | 'gedrag_impuls' |
           'middelen_gebruik' | 'cognitief' | 'context_psychosociaal'; // TEXT with CHECK
  severity: 'laag' | 'middel' | 'hoog'; // TEXT with CHECK
  remarks?: string; // TEXT
  source_note_id?: string; // FK → intake_notes.id
  created_at: string; // TIMESTAMPTZ
  updated_at: string; // TIMESTAMPTZ
}

// treatment_plans table
interface TreatmentPlan {
  id: string; // UUID
  client_id: string; // FK → clients.id
  version: number; // INTEGER
  status: 'concept' | 'gepubliceerd'; // TEXT with CHECK
  plan: { // JSONB
    doelen: string[];
    interventies: string[];
    frequentie: string;
    meetmomenten: string[];
  };
  created_by?: string; // FK → auth.users.id
  created_at: string; // TIMESTAMPTZ
  published_at?: string; // TIMESTAMPTZ
  updated_at: string; // TIMESTAMPTZ
}

// ai_events table (telemetrie)
interface AiEvent {
  id: string; // UUID
  kind: 'summarize' | 'readability' | 'extract' | 'plan'; // TEXT with CHECK
  client_id?: string; // FK → clients.id (nullable)
  note_id?: string; // FK → intake_notes.id (nullable)
  request: object; // JSONB
  response: object; // JSONB
  duration_ms: number; // INTEGER
  created_at: string; // TIMESTAMPTZ
}

SQL voorbeelden voor table creation beschikbaar in /supabase/migrations/. Supabase TypeScript types kunnen automatisch gegenereerd worden via supabase gen types typescript.

2.4 Row Level Security (basis)

Voor demo kunnen RLS policies simpel zijn: alle rows zichtbaar voor authenticated users. In productie: per organisatie/therapeut scheiden met org_id kolom.

-- Demo RLS policies (apply to all tables)
-- Enable RLS
ALTER TABLE clients ENABLE ROW LEVEL SECURITY;
ALTER TABLE intake_notes ENABLE ROW LEVEL SECURITY;
ALTER TABLE problem_profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE treatment_plans ENABLE ROW LEVEL SECURITY;
ALTER TABLE ai_events ENABLE ROW LEVEL SECURITY;

-- Authenticated users kunnen alles lezen/schrijven (demo only!)
CREATE POLICY "Allow all for authenticated users" ON clients
  FOR ALL USING (auth.uid() IS NOT NULL);

CREATE POLICY "Allow all for authenticated users" ON intake_notes
  FOR ALL USING (auth.uid() IS NOT NULL);

CREATE POLICY "Allow all for authenticated users" ON problem_profiles
  FOR ALL USING (auth.uid() IS NOT NULL);

CREATE POLICY "Allow all for authenticated users" ON treatment_plans
  FOR ALL USING (auth.uid() IS NOT NULL);

CREATE POLICY "Allow all for authenticated users" ON ai_events
  FOR ALL USING (auth.uid() IS NOT NULL);

Productie: Voeg org_id toe en filter op auth.jwt() ->> 'org_id'.


3) API & Endpoints (Next.js Route Handlers)

3.1 CRUD

  • POST /api/clients — create

  • GET /api/clients?query= — list/search

  • GET /api/clients/:id — detail

  • PATCH /api/clients/:id — update

  • POST /api/intakes — create intake

  • GET /api/intakes?clientId= — list

  • GET /api/intakes/:id — detail

  • PATCH /api/intakes/:id — update

  • POST /api/problem-profile — create/update current

  • GET /api/problem-profile?clientId= — latest

  • POST /api/treatment-plan — create (concept)

  • PATCH /api/treatment-plan/:id/publish — publish vN

3.2 AIacties

  • POST /api/ai/summarize — TipTap JSON → bullets
  • POST /api/ai/readability — TipTap JSON → B1
  • POST /api/ai/extract — TipTap JSON → {category, severity, rationale}
  • POST /api/ai/generate-plan — {noteId | profile} → plan JSON

Patroon: alle AIendpoints valideren input, roepen Claude API aan serverside, loggen in ai_events, geven preview terug. UI beslist Insert/Apply.


4) AIintegratie (Claude / Anthropic)

4.1 Model & API

  • Model: Claude 3.5 Sonnet (of nieuwere versie) via Anthropic API.
  • Regio: Anthropic API is globally distributed; data blijft binnen EU waar mogelijk.
  • SDK: @anthropic-ai/sdk (officiële Node.js SDK).

4.2 Prompttemplates (schets)

  • Summarize: “Vat het onderstaande intakeverslag samen in 58 bullets. Schrijf in NL, klinisch neutraal, zonder PII.”
  • Readability (B1): “Herschrijf leesbaar op B1niveau. Behoud medische betekenis, vermijd jargon waar mogelijk.”
  • Extract: "Haal uit de tekst: DSMlight categorie (uit 6), severity (laag/middel/hoog), met korte toelichting en quotebronnen."
  • Plan: "Genereer behandelplan (Doelen, Interventies, Frequentie/Duur, Meetmomenten) op basis van intake/profiel. SMARTformuleer doelen."

Parameters (startwaarden):

  • model: "claude-3-5-sonnet-20241022" (of nieuwer)
  • temperature: 0.3 (deterministischer)
  • max_tokens: passend per taak (samenvat 8001200, plan 16002400)

Veiligheid: PII-verwijdering in post-processing (heuristiek); gebruik system prompt voor extra context guards.


5) Frontend implementatie

5.1 UIskelet

  • Layout: Topbar (cliëntcontext) + LeftNav (dossier) + Main (detail) + Toast area.
  • Componenten: Button, Card, Input, Select, Tabs, Badge, Dialog, Drawer, Toast, Tooltip, Breadcrumb.

5.2 TipTap

  • Nodes: paragraph, heading, bold/italic/underline, bullet/ordered list, blockquote, code (optioneel).
  • Opslag: content_json (ProseMirror doc).
  • AIRightrail: tabs: Samenvatten, B1, Extract; acties Preview → Insert.

5.4 Haalbaarheidsonderzoek: AI Source Highlighting

Een belangrijke UX-vereiste is het visueel aanduiden (highlighten) van de bronzinnen in de intaketekst die de AI heeft gebruikt voor een suggestie. Dit is technisch goed haalbaar.

Aanpak:

  1. Backend API Aanpassing: Het AI-endpoint (bv. /api/ai/extract) moet niet alleen de suggestie retourneren, maar ook een array van de exacte bronzinnen (sourceSentences: string[]).
  2. Frontend TipTap Implementatie:
    • De frontend gebruikt de TipTap Decorations API om de highlighting te realiseren. Decorations passen styling toe zonder de onderliggende content te wijzigen.
    • Bij ontvangst van de sourceSentences doorzoekt de frontend het TipTap-document naar de posities (from, to) van deze zinnen.
    • Voor elke gevonden positie wordt een Decoration.inline(from, to, { class: 'ai-source-highlight' }) aangemaakt.
  3. Styling: Een simpele CSS-klasse .ai-source-highlight (bv. met een lichtgele achtergrond) wordt toegevoegd aan de globale stylesheet.
  4. Lifecycle: De highlights worden gewist zodra de gebruiker de suggestie accepteert, negeert, of een nieuwe AI-actie initieert.

Conclusie: De aanpak is robuust en de complexiteit is laag tot gemiddeld. Het wordt meegenomen in de PoC voor de TipTap-editor.

5.3 Toetsenbord & UX

  • Ctrl/Cmd+S opslaan, Ctrl/Cmd+K zoek, Ctrl/Cmd+N nieuw verslag.
  • Leegstaten met CTAs; skeletons bij laden; nonblocking spinners bij AI.

6) Security, Privacy & Compliance (MVPproof)

  • Data: uitsluitend fictieve demodata; geen echte PII.
  • Regio's: EUhosting (Supabase EU: Frankfurt/London; Claude API global).
  • Secret handling: API keys via Vercel Envs; nooit in client bundelen.
  • RLS Policies: minimaal aan; authenticated users krijgen toegang (demo).
  • Audit (lichtgewicht): ai_events + timestamps op alle tables.
  • CORS: beperken tot demodomain.

7) Deployment & Environments

  • Dev: .env.local met Supabase keys + Claude API key
  • Preview/Prod: Vercel project → VERCEL_ENV gates; RLS policies via Supabase migrations.
  • Supabase: EU-regio (Frankfurt of London), schema deploy via supabase db push of migrations.

7.1 Environment variables (voorbeeld)

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xyz.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...            # Public anon key (frontend safe)
SUPABASE_SERVICE_ROLE_KEY=eyJ...                # Service role key (server only!)

# Claude AI
ANTHROPIC_API_KEY=sk-ant-...                    # Claude API key (server only!)

# App
NEXT_PUBLIC_APP_URL=https://mini-ecd.example
NODE_ENV=production

Security:

  • SUPABASE_SERVICE_ROLE_KEY en ANTHROPIC_API_KEY alleen server-side gebruiken
  • Vercel Environment Variables voor productie
  • Gebruik .env.local voor lokale ontwikkeling (niet in git!)

8) Setup stappen (korte gids)

  1. Repo & deps: init Next.js (App Router) + Tailwind + TipTap + Supabase client + Anthropic SDK.
  2. Supabase: project aanmaken (EU-regio) → Tables uit §2.3 aanmaken via migrations → RLS policies activeren.
  3. Env: Vercel + lokale .env.local vullen (Supabase keys + Claude API key).
  4. Endpoints: CRUD + AIroutes implementeren als Next.js Route Handlers (serverside).
  5. Screens: Clients list/detail, Intake editor + AIrail, Profiel form, Plan cards.
  6. Smoke test: Flow A/B/C endtoend met mock data.
  7. (Optioneel) PDF export & afspraken tab.

9) Test & Kwaliteit

  • Unit: parsers, validators, AIresponse mappers (Zod schemas).
  • E2E (Playwright): Flow A/B/C met seeded data.
  • Prompt tests: vaste inputs → snapshot op kernvelden (niet volledige tekst).

10) Bekende beperkingen & risico's

  • TipTap content search: extra index/afgeleide content_text nodig voor snelle zoek (PostgreSQL full-text search).
  • Claude API rate limits: monitor gebruik; overweeg caching voor veelvoorkomende prompts.
  • Serverless PDF: kan coldstart of memory issues geven → overweeg queue/edge function.
  • RLS Policies (demo): simplistisch; voor productie per organisatie/rol modelleren met org_id.

11) Wat ontbrak nog in je stack (aanvullingen)

  • Tailwind CSS (UIbasis) en iconen (lucide-react).
  • Auth: Supabase Auth (magic link, email/password, of OAuth).
  • Validatie: Zod voor request/response schemas.
  • Logging: lichte audit via ai_events table + Next.js middleware.
  • Testing: Vitest/Playwright.
  • PDF (stretch): keuze voor renderer (Puppeteer/Playwright).
  • Typesafety: Supabase auto-generated types via supabase gen types typescript.

12) Roadmap na demo

  • Rollen & rechten, multitenant (org_id) + stricte RLS policies.
  • Templates per zorgpad (verslag/plan).
  • Trendanalyse (meetmomenten) + grafieken.
  • Integraties (PinkRoccade modules), exportprofielen.
  • Real-time collaboratie via Supabase Realtime (optioneel).