51 KiB
Technische Invulling — Alle Componenten (C01–C43)
Type: Technisch Referentiedocument Versie: 1.0 Datum: 2026-02-14 Doelgroep: Developers, architecten, LLM-agents Doel: Eén overzicht van alle 43 componenten, ingedeeld per architectuurlaag (frontend, API, backend, middleware, infra), met concrete technologiekeuzes per component Relatie: Consolideert alle keuzes uit de overige architectuurdocumenten
Leeswijzer
Dit document is het technische naslagwerk — geen strategie, geen rationale, puur de invulling. Per component staat:
- Waar het draait (frontend / API / backend / middleware / infra)
- Welke technologie, library of service
- Welke bestanden er ontstaan (file structure)
- Dependencies (npm packages of externe services)
De architectuurkeuzes en rationale staan in de andere documenten. Dit document geeft antwoord op de vraag: "wat heb ik nodig om dit te bouwen?"
Technologie Stack — Overzicht
Runtime & Frameworks
| Technologie | Versie | Doel |
|---|---|---|
| Node.js | 22 LTS | Runtime voor beide services |
| Next.js | 16.1 LTS (App Router) | Frontend + API routes (EPD Service + Intent Platform) |
| TypeScript | 5.x | Type safety overal |
| React | 19 | UI rendering |
Database & Data
| Technologie | Versie | Doel |
|---|---|---|
| PostgreSQL | 17 | Primaire database (self-hosted, Docker) |
| Drizzle ORM | latest | Type-safe database access, SQL-first (RLS alleen in handmatige SQL) |
| pgvector | 0.7+ | Vector embeddings voor RAG pipeline |
| pg_trgm | (ingebouwd) | Trigram similarity search |
| Valkey | 8+ | Job queue (BullMQ), caching — Redis-compatible, open source (Linux Foundation) |
Auth & Security
| Technologie | Versie | Doel |
|---|---|---|
| Keycloak | 26.5 | SSO/SAML, MFA, rolbeheer, multi-tenancy |
| Auth.js (NextAuth) | v5 | Keycloak integratie in Next.js |
| node:crypto | (ingebouwd) | AES-256-GCM field encryption, HMAC signing |
AI & Spraak
| Technologie | — | Doel |
|---|---|---|
| Vercel AI SDK | @ai-sdk/anthropic |
Intent classificatie, samenvattingen, chat — provider-agnostisch, native Next.js streaming |
| Deepgram API | — | Streaming speech-to-text (Nederlands) |
UI & Styling
| Technologie | Versie | Doel |
|---|---|---|
| Tailwind CSS | 4.x | Utility-first styling (5x sneller builds, zero-config) |
| shadcn/ui | latest | Accessible component library |
| Lucide React | latest | Icon library |
| Zustand | 5.x | Client-side state management |
| date-fns | 4.x | Datumformattering (NL locale) |
| Zod | 4.x | Runtime validatie op API boundaries |
| Fuse.js | latest | Client-side fuzzy search |
DevOps & Monitoring
| Technologie | Versie | Doel |
|---|---|---|
| Docker + Docker Compose | latest | Containerisatie |
| Caddy | 2 | Reverse proxy, automatische HTTPS |
| Docker Compose + GitHub Actions | — | Deployment pipeline (Coolify is nog beta — niet geschikt voor productie healthcare) |
| Hetzner VPS | — | Hosting (Amsterdam, EU data residency) |
| Pino | 10.x | Structured JSON logging |
| OpenTelemetry | latest | Traces, metrics |
| BullMQ | 5.x | Job queue, scheduled jobs |
| Bull Board | latest | Job monitoring dashboard |
| Postal | latest | Self-hosted email |
Twee Services — Deployment Architectuur
┌─────────────────────────────────────────────────────────────────┐
│ CADDY (:443) — reverse proxy, TLS termination │
├─────────────────┬───────────────────┬───────────────────────────┤
│ │ │ │
│ app.domein.nl │ platform.domein.nl│ auth.domein.nl │
│ ▼ │ ▼ │ ▼ │
│ EPD SERVICE │ INTENT PLATFORM │ KEYCLOAK │
│ Next.js :3000 │ Next.js :3001 │ :8080 │
│ │ │ │
│ Frontend (SSR) │ Platform API │ OIDC / SAML │
│ API Routes │ Intent Engine │ User management │
│ EPD Business │ Knowledge Layer │ Realm per tenant │
│ Logic │ Connector API │ │
│ │ │ │
├─────────────────┴───────────────────┴───────────────────────────┤
│ │
│ PostgreSQL 17 :5432 Valkey :6379 Postal :25 │
│ ├── epd_db (BullMQ + cache) (self-hosted │
│ ├── platform_db email) │
│ └── keycloak_db │
└─────────────────────────────────────────────────────────────────┘
FRONTEND — Componenten
Alles wat in de browser draait: React components, client-side state, UI interactie.
C01 — Traditionele EPD UI
| Eigenschap | Waarde |
|---|---|
| Laag | Frontend (EPD Service) |
| Technologie | Next.js App Router, React Server Components + Client Components |
| Styling | Tailwind CSS + shadcn/ui |
| Routing | app/epd/ directory (file-based routing) |
| Data fetching | Server Components met async + Drizzle queries |
| Forms | React Hook Form + Zod validation |
| Icons | Lucide React |
| Datum | date-fns met nl locale |
app/epd/
├── dashboard/page.tsx # Hoofddashboard
├── verpleegrapportage/ # Overdracht overzicht
│ └── rapportage/page.tsx # Tijdlijn view
├── patients/[id]/page.tsx # Patiëntdossier
├── agenda/page.tsx # Kalender (FullCalendar)
├── clients/page.tsx # Cliëntbeheer
├── behandelplan/[id]/page.tsx # Behandelplan detail
├── medicatie/page.tsx # Medicatieoverzicht
├── facturatie/page.tsx # ZPM prestatie-overzicht
├── admin/ # Beheer (tenant admin)
│ ├── users/page.tsx
│ ├── workflows/page.tsx
│ └── protocols/page.tsx
└── layout.tsx # Sidebar, header, notificatie-badge
npm dependencies: next, react, tailwindcss, @radix-ui/* (via shadcn), lucide-react, date-fns, react-hook-form, @hookform/resolvers, zod
C02 — Intent Command Center
| Eigenschap | Waarde |
|---|---|
| Laag | Frontend (EPD Service, embed in Intent Platform) |
| Technologie | React Client Components |
| Activatie | ⌘K (keyboard shortcut) |
| Invoer | Tekst + spraak (C03) |
| Submit | ⌘Enter |
| Streaming | Server-Sent Events (SSE) voor AI responses |
components/cortex/command-center/
├── command-center.tsx # Container, focus management
├── command-input.tsx # Tekst/spraak input, ⌘K listener
├── context-bar.tsx # Actieve patiënt, afdeling context
├── canvas-area.tsx # Render action blocks
└── result-stream.tsx # SSE stream consumer
npm dependencies: (onderdeel van React app, geen extra deps)
C03 — Spraak Input
| Eigenschap | Waarde |
|---|---|
| Laag | Frontend + API |
| Technologie | Web Audio API (browser) → Deepgram API (server) |
| Protocol | WebSocket (streaming transcriptie) |
| Taal | Nederlands (nl-NL) |
components/cortex/voice/
├── voice-button.tsx # Microfoon toggle
├── audio-recorder.ts # Web Audio API capture
└── transcript-stream.ts # WebSocket naar /api/deepgram
npm dependencies: geen extra (Web Audio API is browser-native) Externe service: Deepgram API (betaald, per minuut)
C04 — Block/Artifact Systeem
| Eigenschap | Waarde |
|---|---|
| Laag | Frontend |
| Technologie | React components, composable blocks |
| Doel | Render formulieren, voorbeelden, resultaten na intent classificatie |
components/cortex/blocks/
├── dagnotitie-block.tsx # Rapport formulier (prefilled)
├── search-results-block.tsx # Zoekresultaten lijst
├── appointment-block.tsx # Afspraak formulier
├── overdracht-block.tsx # AI samenvatting view
├── nudge-block.tsx # Protocol suggestie card
├── intake-block.tsx # Intake navigatie
└── block-renderer.tsx # Dynamisch block type → component mapping
npm dependencies: geen extra
C13 — Regelvalidatie UI (Admin)
| Eigenschap | Waarde |
|---|---|
| Laag | Frontend (Admin sectie EPD Service) |
| Technologie | React, shadcn/ui DataTable |
| Doel | Beheer protocol rules: goedkeuren, afwijzen, bewerken |
app/epd/admin/protocols/
├── page.tsx # Overzicht alle regels
├── [id]/page.tsx # Detail + bewerken
└── import/page.tsx # Bulk import uit kennisbronnen
API LAAG — Routes & Endpoints
Alle HTTP endpoints. Draaien server-side in Next.js API routes (app/api/).
EPD Service API Routes
app/api/
├── reports/route.ts # C15 — CRUD rapportages
├── overdracht/
│ ├── route.ts # C16 — Patiëntlijst voor overdracht
│ ├── [patientId]/route.ts # C16 — Patient detail
│ └── generate/route.ts # C16 — AI samenvatting genereren
├── behandelplan/route.ts # C18 — CRUD behandelplannen
├── medicatie/route.ts # C19 — Medicatieoverzicht
├── patients/
│ ├── route.ts # C21 — CRUD patiënten
│ └── search/route.ts # C21 — Patiënt zoeken
├── appointments/route.ts # C17 — CRUD afspraken
├── consents/route.ts # C22 — Toestemming registreren
├── facturatie/
│ ├── prestaties/route.ts # C40 — ZPM prestatie CRUD
│ ├── trajecten/route.ts # C40 — DBC trajecten
│ └── batches/route.ts # C40 — Declaratie batches
├── notifications/
│ ├── route.ts # C34 — Lijst + markeer gelezen
│ ├── stream/route.ts # C34 — SSE real-time stream
│ └── preferences/route.ts # C34 — Notificatie voorkeuren
├── workflows/
│ ├── route.ts # C35 — Actieve workflows
│ ├── [id]/route.ts # C35 — Workflow detail
│ └── [id]/transition/route.ts # C35 — State transitie
├── search/route.ts # C36 — Full-text search
├── webhooks/
│ ├── route.ts # C39 — CRUD webhook registraties
│ └── [id]/deliveries/route.ts # C39 — Delivery log
├── bi/
│ ├── query/route.ts # C43 — BI queries
│ └── export/route.ts # C43 — CSV export
├── fhir/
│ ├── Patient/[id]/route.ts # C28 — FHIR Patient export
│ └── Patient/[id]/$everything/ # C28 — FHIR Bundle export
├── cds-hooks/
│ └── patient-view/route.ts # C29 — CDS Hooks cards
├── health/route.ts # C32 — Health check
├── audit/route.ts # C26 — Audit log query (admin)
└── deepgram/route.ts # C03 — Spraak proxy
Intent Platform API Routes
app/api/platform/
├── classify/route.ts # C06+C07 — Intent classificatie
├── chat/route.ts # C07 — Streaming chat (SSE)
├── transcribe/route.ts # C03 — Spraak-naar-tekst
├── nudges/route.ts # C10 — Actieve nudges ophalen
├── nudges/[id]/accept/route.ts # C10 — Nudge accepteren
├── protocols/route.ts # C11 — Protocol rules CRUD
├── connectors/route.ts # Connector registry beheer
├── capabilities/route.ts # Wat kan dit platform?
└── health/route.ts # C32 — Health check
API Patronen — Thin Controller + Service Layer
Architectuurbeslissing: API routes zijn thin controllers — ze doen alleen HTTP-afhandeling (auth, validatie, response). Alle business logic zit in de Service Layer (
lib/services/). Dit geeft ons de structuur van een NestJS-achtige backend zonder de overhead van een apart framework.
Waarom geen NestJS?
| Criterium | NestJS | Next.js + Service Layer |
|---|---|---|
| Leercurve | Hoog (DI, decorators, modules) | Laag (gewoon TypeScript functies) |
| Deployment | Apart proces, eigen build pipeline | Eén Next.js deploy voor alles |
| Geschikt voor teamgrootte | 5-20 developers | 1-5 developers |
| Structuur | Afgedwongen door framework | Afgedwongen door conventie |
| Testbaarheid | Excellent | Excellent (pure functions) |
| Migratiemogelijkheid | — | Services zijn 1:1 verplaatsbaar naar NestJS later |
De regel: als je team groeit naar 5+ developers en je hebt 200+ endpoints, migreer dan de Service Layer naar een losse NestJS of Hono API service. De services hoeven niet te veranderen — alleen de HTTP-laag eromheen.
API Route (thin controller)
// app/api/reports/route.ts — THIN: alleen HTTP concerns
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { reportService } from '@/lib/services/report.service';
import { CreateReportSchema } from '@/lib/types/report';
export async function POST(req: Request) {
// 1. Auth (HTTP concern)
const session = await auth();
if (!session) return NextResponse.json({ error: 'Niet ingelogd' }, { status: 401 });
// 2. Validatie (HTTP concern — parsing van request body)
const body = CreateReportSchema.safeParse(await req.json());
if (!body.success) return NextResponse.json({ error: body.error.format() }, { status: 400 });
// 3. Delegeer alles naar service
const result = await reportService.create(session.user, body.data);
return NextResponse.json(result, { status: 201 });
}
Service (alle business logic)
// lib/services/report.service.ts — DIK: alle business logic, side effects, cross-cutting concerns
import { withTenantContext } from '@/lib/multi-tenancy/tenant-context';
import { evaluateAccess } from '@/lib/access-control/evaluator';
import { logAuditEvent } from '@/lib/audit/logger';
import { webhookService } from '@/lib/services/webhook.service';
import { notificationService } from '@/lib/services/notification.service';
import { cacheService } from '@/lib/cache/cache-service';
import { db } from '@/lib/db';
import { reports } from '@/lib/db/schema';
export const reportService = {
async create(user: AuthUser, data: CreateReport) {
// 1. RBAC check
const access = await evaluateAccess({ userId: user.id, action: 'report:create', resource: data });
if (!access.allowed) throw new ForbiddenError(access.reason);
// 2. Business logic (binnen tenant context)
const report = await withTenantContext(user.tenantId, user.id, async () => {
return await db.insert(reports).values({
...data,
shiftDate: calculateShiftDate(new Date()),
createdBy: user.id,
tenantId: user.tenantId,
}).returning();
});
// 3. Side effects (fire-and-forget, fouttolerant)
await Promise.allSettled([
logAuditEvent({ entityType: 'report', entityId: report.id, action: 'create', userId: user.id }),
webhookService.dispatch('report.created', user.tenantId, { reportId: report.id }),
notificationService.send({ recipientUserId: data.assignedTo, category: 'rapportage', payload: report }),
cacheService.invalidateByTag(`patient:${data.patientId}:reports`),
]);
return report;
},
async getByPatient(user: AuthUser, patientId: string, filters: ReportFilters) {
await evaluateAccess({ userId: user.id, action: 'report:read', resource: { patientId } });
return cacheService.getOrSet(
`reports:patient:${patientId}:${hashFilters(filters)}`,
() => withTenantContext(user.tenantId, user.id, () => queryReports(patientId, filters)),
{ ttl: 60, tags: [`patient:${patientId}:reports`] }
);
},
// ... update, softDelete, restore, getVersionHistory
};
Service Layer — Overzicht
Alle EPD business logic services:
lib/services/
├── report.service.ts # C15 — Rapportages CRUD, versioning, shift-date
├── overdracht.service.ts # C16 — AI samenvattingen, patiëntoverzicht
├── agenda.service.ts # C17 — Afspraken, conflictdetectie
├── behandelplan.service.ts # C18 — Behandelplannen, doelen
├── medicatie.service.ts # C19 — Medicatie, interactie-check
├── intake.service.ts # C20 — Intake, triage, workflow
├── patient.service.ts # C21 — Patiënt CRUD, zoeken, toewijzing
├── consent.service.ts # C22 — Wvggz + AVG toestemming
├── notification.service.ts # C34 — SSE, email, escalatie
├── workflow.service.ts # C35 — State machine, transitions
├── search.service.ts # C36 — Full-text search
├── webhook.service.ts # C39 — Dispatch, retry, signing
├── facturatie.service.ts # C40 — ZPM, DBC, declaraties
└── bi.service.ts # C43 — BI queries, materialized views
Intent Platform services:
lib/cortex/
├── registry/ # C05 — Intent Registry
├── reflex/ # C06 — Reflex Classifier
├── orchestrator/ # C07 — LLM Classificatie
├── entities/ # C08 — Entity Extractor
├── actions/ # C09 — Action System
├── protocol-engine/ # C10 — Nudge Engine
└── knowledge/ # C11-C14 — Protocol Rules, RAG, Connectors
Gedeelde services (beide platforms):
lib/shared/
├── cache/cache-service.ts # C37 — Caching met tag invalidatie
├── jobs/job-registry.ts # C38 — BullMQ job definities
├── auth/ # C23 — Auth helpers
├── multi-tenancy/ # C24 — Tenant context
├── access-control/ # C25 — RBAC + ABAC
├── audit/ # C26 — Audit trail
├── encryption/ # C27 — Field encryption
├── observability/ # C30 — Logging, metrics, traces
└── health/ # C32 — Health checks
Shared Middleware Wrapper
Om boilerplate in API routes te minimaliseren:
// lib/api/with-auth.ts — optionele wrapper voor herhalende patterns
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { logger } from '@/lib/observability/logger';
type AuthHandler = (req: Request, user: AuthUser) => Promise<Response>;
export function withAuth(handler: AuthHandler) {
return async (req: Request) => {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: 'Niet ingelogd' }, { status: 401 });
}
try {
return await handler(req, session.user);
} catch (error) {
if (error instanceof ForbiddenError) {
return NextResponse.json({ error: error.message }, { status: 403 });
}
logger.error({ err: error, path: req.url }, 'Onverwachte fout');
return NextResponse.json({ error: 'Interne serverfout' }, { status: 500 });
}
};
}
// Gebruik:
// export const POST = withAuth(async (req, user) => {
// const body = CreateReportSchema.parse(await req.json());
// const result = await reportService.create(user, body);
// return NextResponse.json(result, { status: 201 });
// });
npm dependencies (API laag): zod, drizzle-orm, drizzle-kit, pg, pino, node-redis, bullmq
BACKEND — Business Logic & Services
Server-side logica die niet direct aan een HTTP endpoint hangt, maar door API routes en jobs wordt aangeroepen.
C05 — Intent Registry
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) |
| Technologie | TypeScript config files → database (fase 3) |
| Doel | Single source of truth voor alle intent definities |
lib/cortex/registry/
├── intent-registry.ts # Registry class (in-memory, laadt uit config/DB)
├── types.ts # IntentDefinition interface
└── definitions/
├── dagnotitie.ts # Patronen, entities, artifact mapping
├── zoeken.ts
├── overdracht.ts
├── agenda-query.ts
├── create-appointment.ts
├── cancel-appointment.ts
├── reschedule-appointment.ts
└── index.ts # Barrel export
npm dependencies: geen extra
C06 — Reflex Classifier
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) |
| Technologie | Pure TypeScript, regex matching |
| Latency | < 20ms |
| Patronen | ~200 regex patronen, geladen uit Intent Registry |
lib/cortex/reflex/
├── reflex-classifier.ts # classifyWithReflex(input) → ClassificationResult | null
├── pattern-matcher.ts # Regex engine met gewogen scoring
└── confidence-scorer.ts # Berekent confidence op basis van match quality
npm dependencies: geen (pure TypeScript + RegExp)
C07 — Orchestrator (LLM Classificatie)
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) |
| Technologie | Vercel AI SDK (@ai-sdk/anthropic, claude-sonnet-4-5) |
| Latency | 250ms–3s |
| Activatie | Alleen als Reflex confidence < 0.7 |
| Output parsing | Zod schema validation op LLM response |
lib/cortex/orchestrator/
├── orchestrator.ts # classifyWithOrchestrator(input, context) → ClassificationResult
├── prompt-builder.ts # System prompt + few-shot examples generatie
├── response-parser.ts # Zod parse van LLM JSON output
└── streaming.ts # SSE streaming voor chat mode
npm dependencies: ai, @ai-sdk/anthropic
C08 — Entity Extractor + Date Parser
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) |
| Technologie | TypeScript regex + Fuse.js (fuzzy matching) |
| Doel | "Jan" → Patient #427, "morgen" → 2026-02-15 |
lib/cortex/entities/
├── entity-extractor.ts # Extractie pipeline
├── patient-resolver.ts # Fuzzy match patiëntnamen (Fuse.js)
├── date-time-parser.ts # Nederlandse datum/tijd parsing
└── category-resolver.ts # "medicatie" → NursingCategory
npm dependencies: fuse.js
C09 — Action System
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) → Frontend (C04) |
| Technologie | TypeScript, EPD Connector interface |
| Doel | Geclassificeerd intent → UI artifact instructie |
lib/cortex/actions/
├── action-resolver.ts # Intent → ActionInstruction mapping
├── action-types.ts # ActionInstruction interface per intent
└── prefill-builder.ts # Bouw prefill data voor formulier
C10 — Protocol Engine / Nudge
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) |
| Technologie | TypeScript rule evaluator, leest uit C11 Protocol Rules Store |
| Patroon | Pure function: (completedAction, patientContext, rules) → suggestions[] |
lib/cortex/protocol-engine/
├── evaluator.ts # evaluateRules() — kernfunctie
├── condition-matcher.ts # Match rule conditions tegen context
└── nudge-formatter.ts # Formatting naar UI-compatible suggesties
C11 — Protocol Rules Store
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform — database) |
| Technologie | PostgreSQL (platform_db), Drizzle schema |
| Tabel | protocol_rules |
lib/knowledge/
├── protocol-rule.ts # ProtocolRule interface + Drizzle schema
├── rule-repository.ts # CRUD operaties op rules
└── rule-validator.ts # Validatie logica (status transitions)
C12 — RAG Pipeline
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) |
| Technologie | pgvector (PostgreSQL extensie), Anthropic Embeddings API |
| Doel | Protocol documenten indexeren → vector search → context voor nudges |
lib/knowledge/rag/
├── indexer.ts # Document → chunks → embeddings → pgvector insert
├── retriever.ts # Query → embedding → vector similarity search
├── chunk-splitter.ts # Document splitting strategie
└── embedding-client.ts # Anthropic/OpenAI embeddings API wrapper
npm dependencies: ai, @ai-sdk/anthropic (of MedRoBERTa.nl voor Nederlandse klinische embeddings)
PostgreSQL extensie: pgvector
C14 — Kennisbron Connectors
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) |
| Technologie | TypeScript, HTTP clients voor externe bronnen |
lib/knowledge/connectors/
├── connector-interface.ts # KennisbronConnector interface
├── ggz-standaarden.ts # GGZ Standaarden scraper/importer
├── venvn-richtlijnen.ts # V&VN richtlijnen
├── wetten-overheid.ts # Wetten.overheid.nl API
└── document-parser.ts # PDF/HTML → tekst extractie
C15 — Rapportage Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM, PostgreSQL |
| Features | CRUD, versioning, soft delete, shift-date logica |
lib/services/rapportage/
├── rapportage-service.ts # ReportService class
├── shift-calculator.ts # calculateShiftDate() — voor 07:00 = vorige dag
└── version-manager.ts # Versioning bij updates
Database tabel: reports (met version, previous_version_id, deleted_at)
C16 — Overdracht Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM + Vercel AI SDK (@ai-sdk/anthropic) |
| Doel | AI-samenvatting van rapportages per patiënt per shift |
lib/services/overdracht/
├── overdracht-service.ts # Patiëntlijst, details, samenvattingen
├── summary-generator.ts # Claude API prompt + response parsing
└── source-linker.ts # Bronverwijzingen in samenvatting
C17 — Agenda Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM |
| Features | CRUD, conflictdetectie, no-show tracking |
lib/services/agenda/
├── agenda-service.ts # AppointmentService class
└── conflict-detector.ts # Overlap check bij create/update
Frontend: FullCalendar (@fullcalendar/react, @fullcalendar/daygrid, @fullcalendar/timegrid)
C18 — Behandelplan Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM |
lib/services/behandelplan/
├── behandelplan-service.ts # CRUD, versioning
└── goal-tracker.ts # Behandeldoel voortgang
C19 — Medicatie Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM |
lib/services/medicatie/
├── medicatie-service.ts # Overzicht, wijzigingen
└── interaction-checker.ts # Placeholder voor medicatie-interactie check
C20 — Intake Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM + Workflow Engine (C35) |
lib/services/intake/
├── intake-service.ts # Intake data management
└── triage-rules.ts # Triage criteria
C21 — Patiënt Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM, Fuse.js (zoeken), pg_trgm (server-side) |
lib/services/patient/
├── patient-service.ts # CRUD, zoeken, toewijzing
└── assignment-manager.ts # Patient-zorgverlener toewijzing
C22 — Toestemming Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM |
| Doel | Wvggz + AVG toestemmingsregistratie |
lib/services/consent/
├── consent-service.ts # Registreren, intrekken, controleren
└── wvggz-checker.ts # Wvggz-specifieke validaties
C34 — Notificatie Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Real-time | Server-Sent Events (SSE) via API route |
| Postal (self-hosted SMTP) via BullMQ job | |
| Escalatie | BullMQ delayed jobs |
lib/services/notifications/
├── notification-service.ts # send(), sendBulk(), getUnread()
├── sse-broadcaster.ts # SSE endpoint beheer
├── email-sender.ts # Postal SMTP integratie
├── escalation-manager.ts # Escalatie regels evalueren
└── digest-builder.ts # Dagelijkse email samenvattingen
npm dependencies: nodemailer (voor Postal SMTP)
Database tabellen: notifications, notification_preferences, escalation_rules
C35 — Workflow Engine (State Machine)
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Eigen TypeScript state machine (~200 regels) |
| Geen externe library | XState niet nodig voor server-side workflows |
lib/services/workflow/
├── workflow-engine.ts # State machine core: transition(), getAvailable()
├── workflow-service.ts # Service layer: start, transition, query
├── deadline-monitor.ts # Check maxDurationDays overschrijdingen
└── definitions/
├── intake-ggz.ts # Intake workflow definitie
├── wvggz-procedure.ts # Wvggz workflow met wettelijke termijnen
├── mic-melding.ts # MIC/incident workflow
├── behandelplan-lifecycle.ts # Concept → Actief → Evaluatie
└── index.ts
Database tabellen: workflow_definitions, workflow_instances, workflow_transitions_log (immutable)
C36 — Full-Text Search Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service — database) |
| Technologie | PostgreSQL tsvector + ts_rank_cd |
| Taal | Nederlandse stemmer (ingebouwd in PostgreSQL) |
| Indexering | GENERATED ALWAYS AS ... STORED (automatisch bij write) |
lib/services/search/
├── search-service.ts # search(), reindex()
├── query-builder.ts # Bouw SQL met filters, highlighting
└── snippet-extractor.ts # ts_headline() voor context snippets
Geen extra npm dependencies — puur PostgreSQL functionaliteit
C37 — Caching Layer
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (beide services) |
| Technologie | Valkey (hergebruik van BullMQ instance) |
| Client | node-redis (BullMQ gebruikt intern ioredis — dat is OK, twee clients naast elkaar) |
| Patroon | Namespace-based keys met tenant isolatie, tag-based invalidatie |
lib/cache/
├── cache-service.ts # get(), set(), getOrSet(), invalidateByTag()
├── namespaces.ts # TTL strategie per namespace
└── key-builder.ts # Tenant-aware key generatie
npm dependencies: redis (node-redis)
C38 — Scheduled Jobs Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (beide services) |
| Technologie | BullMQ (repeatable jobs) + Redis |
| Monitoring | Bull Board (web UI) |
lib/jobs/
├── job-registry.ts # Registreer alle job handlers
├── job-worker.ts # BullMQ Worker setup
├── definitions/
│ ├── audit-partition.ts # Maandelijkse audit partitie aanmaken
│ ├── workflow-deadlines.ts # Dagelijkse deadline check
│ ├── notification-digest.ts # Dagelijkse email digest
│ ├── zpm-batch.ts # Maandelijkse facturatie batch
│ ├── protocol-sync.ts # Wekelijkse RAG re-index
│ ├── cache-refresh.ts # Dashboard cache verversing
│ └── escalation-check.ts # 5-minuten escalatie check
└── bull-board.ts # Admin monitoring dashboard
npm dependencies: bullmq, @bull-board/api, @bull-board/express
C39 — Webhook System
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Signing | HMAC-SHA256 (node:crypto) |
| Retry | BullMQ delayed jobs met exponential backoff |
| Geen PII in payloads — alleen IDs |
lib/services/webhooks/
├── webhook-service.ts # register(), dispatch(), retry()
├── webhook-signer.ts # HMAC-SHA256 signature generatie
├── delivery-manager.ts # Dispatch + retry via BullMQ
└── payload-sanitizer.ts # Strip PII, alleen IDs behouden
Database tabellen: webhook_registrations, webhook_delivery_log
C40 — ZPM Facturatie Service
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Technologie | Drizzle ORM |
| Doel | Zorgprestatie registratie, DBC trajecten, declaratie batches |
lib/services/facturatie/
├── facturatie-service.ts # CRUD prestaties, trajecten, batches
├── prestatie-validator.ts # NZa validatieregels
├── batch-generator.ts # Maandelijkse batch generatie
└── vecozo-interface.ts # Placeholder voor Vecozo koppeling
Database tabellen: zorgprestaties, dbc_trajecten, declaratie_batches
C41 — Vecozo Connector (voorbereiding)
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Status | Interface + mock — geen live koppeling |
| Vereisten | UZI-certificaat, AGB-code, Vektis EI-standaard |
lib/integrations/vecozo/
├── types.ts # VecozoConnector interface
├── mock-connector.ts # Mock implementatie voor development
└── README.md # Aansluitvereisten documentatie
C42 — Zorgdomein Connector (voorbereiding)
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service) |
| Status | Interface + mock |
lib/integrations/zorgdomein/
├── types.ts # ZorgdomeinConnector interface
├── mock-connector.ts # Mock implementatie
└── README.md # Aansluitvereisten
C43 — BI Module
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (EPD Service — database) |
| Technologie | PostgreSQL Materialized Views |
| Verversing | REFRESH MATERIALIZED VIEW CONCURRENTLY via C38 scheduled job |
lib/services/bi/
├── bi-service.ts # query(), refreshViews(), exportCSV()
├── views/
│ ├── mv-productie.sql # Productie per behandelaar per maand
│ ├── mv-wachttijden.sql # Aanmelding → intake doorlooptijden
│ └── mv-no-shows.sql # No-show percentage per maand
└── csv-exporter.ts # Resultaten naar CSV
Toekomstig: Metabase (self-hosted) als visuele BI frontend bovenop dezelfde PostgreSQL
MIDDLEWARE — Cross-Cutting Concerns
Logica die door alle lagen heen loopt: auth, multi-tenancy, audit, access control, encryptie, observability.
C23 — Auth + SSO
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (beide services) |
| Technologie | Keycloak (OIDC/SAML) + Auth.js v5 |
| SSO | Azure AD federatie (standaard bij GGZ-instellingen) |
| MFA | TOTP, WebAuthn (Keycloak native) |
| Session | JWT tokens, 15 min timeout (NEN 7510) |
lib/auth/
├── auth.ts # Auth.js v5 config met KeycloakProvider
├── middleware.ts # Next.js middleware: redirect als niet ingelogd
├── session.ts # Session helpers: getUser(), getRole(), getTenantId()
└── guards.ts # Route guards: requireRole(), requirePermission()
npm dependencies: next-auth@5, @auth/core
Externe service: Keycloak (self-hosted Docker container)
C24 — Multi-tenancy
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (database + applicatie) |
| Technologie | PostgreSQL RLS + Drizzle tenant context helper |
| Model | Shared database, tenant_id kolom op elke tabel |
lib/multi-tenancy/
├── tenant-context.ts # withTenantContext() — zet PostgreSQL session vars
├── tenant-middleware.ts # Extract tenant_id uit JWT, inject in request
└── tenant-config.ts # TenantSettings interface + laden uit DB
Kern: SET app.current_tenant_id = '...' per database connection → RLS policies filteren automatisch
C25 — RBAC + ABAC Engine
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (beide services) |
| Technologie | TypeScript policy evaluator |
| RBAC | Rollen uit Keycloak JWT (verpleegkundige, arts, psycholoog, admin, audit) |
| ABAC | Context-afhankelijke policies (toewijzing, toestemming, tijdvenster, Wvggz) |
lib/access-control/
├── evaluator.ts # evaluateAccess() → { allowed, reason }
├── policies/
│ ├── patient-access.ts # Basisbeleid patiënt inzage
│ ├── wvggz-access.ts # Wvggz-specifieke restricties
│ ├── report-write.ts # Wie mag rapportages schrijven
│ └── admin-access.ts # Admin/audit specifiek
├── role-permissions.ts # RBAC matrix: rol → basis permissions
└── types.ts # AccessPolicy, PolicyCondition interfaces
C26 — Audit Trail (NEN 7513)
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (beide services) |
| Technologie | PostgreSQL partitioned table + node:crypto (hash chain) |
| Immutabiliteit | Triggers blokkeren UPDATE en DELETE |
| Partitionering | Per maand, automatisch via C38 scheduled job |
lib/audit/
├── logger.ts # logAuditEvent() — kernfunctie
├── hash-chain.ts # SHA-256 hash + previous_hash keten
├── query-builder.ts # Forensische queries (wie/wat/wanneer)
└── anomaly-detector.ts # Anomalie: >50 dossiers in 1 uur
Database tabel: audit_logs (partitioned by range on timestamp)
C27 — Field Encryption
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (EPD Service) |
| Technologie | AES-256-GCM via node:crypto |
| Gevoelige velden | BSN, diagnoses, medicatie in audit logs |
lib/encryption/
├── field-encryption.ts # encryptField(), decryptField()
├── bsn-handler.ts # storeBSN(): hash (lookup) + encrypted (weergave)
└── key-management.ts # Key rotation helpers
Geen extra npm dependencies — node:crypto is ingebouwd
C28 — FHIR Export Service
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (EPD Service API) |
| Technologie | TypeScript mapping functies |
| Standaard | FHIR R4 |
| Strategie | FHIR-compatible (export), niet FHIR-native (storage) |
lib/fhir/
├── patient-mapper.ts # Interne Patient → FHIR Patient
├── observation-mapper.ts # Report → FHIR Observation
├── condition-mapper.ts # Diagnosis → FHIR Condition
├── bundle-builder.ts # $everything → FHIR Bundle
└── types.ts # FHIR R4 type definities
C29 — CDS Hooks Service
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (Intent Platform API) |
| Technologie | CDS Hooks 1.0 spec implementatie |
| Doel | Nudges aanbieden in CDS Hooks formaat voor externe EPD's |
lib/cds-hooks/
├── hook-handler.ts # patient-view hook implementatie
├── card-builder.ts # Nudge → CDS Hooks Card mapping
└── types.ts # CDS Hooks type definities
C30 — Observability
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (beide services) |
| Logging | Pino (structured JSON) → stdout → log aggregator |
| Metrics | OpenTelemetry SDK → Grafana/Prometheus |
| Traces | OpenTelemetry traces door hele pipeline |
lib/observability/
├── logger.ts # Pino instance met PII filtering
├── pii-redactor.ts # Automatisch BSN, namen etc. redacten in logs
├── metrics.ts # OpenTelemetry meter: latency, errors, classificaties
├── tracer.ts # OpenTelemetry tracer setup
└── cortex-metrics.ts # Cortex-specifiek: reflex hits, orchestrator fallbacks
npm dependencies: pino, @opentelemetry/sdk-node, @opentelemetry/api, @opentelemetry/exporter-prometheus
C31 — Circuit Breaker / Resilience
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (Intent Platform) |
| Technologie | Eigen TypeScript implementatie (~50 regels) |
| Fallback | AI niet beschikbaar → alleen Reflex (lokale patronen) |
lib/resilience/
├── circuit-breaker.ts # callWithCircuitBreaker() generic
└── degradation-levels.ts # Niveau 0 (volledig) → 1 (reflex-only) → 2 (minimaal)
C32 — Health Check Service
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (beide services) |
| Technologie | Next.js API route |
| Checks | Database, Keycloak, Claude API, Deepgram, Redis |
lib/health/
├── health-checker.ts # Parallel health checks
└── capability-reporter.ts # Wat werkt? (graceful degradation status)
C33 — PII Filter
| Eigenschap | Waarde |
|---|---|
| Laag | Middleware (Intent Platform) |
| Technologie | TypeScript regex + sanitisatie |
| Doel | Voorkom dat PII in AI prompts en logs terechtkomt |
lib/pii/
├── pii-filter.ts # sanitizeForAI() — verwijder namen, BSN, etc.
├── log-sanitizer.ts # Pino serializer die PII redact
└── patterns.ts # Regex patronen voor BSN, telefoonnummers, etc.
CONNECTIVITEITSLAAG — EPD Connectors
De adapter-laag tussen Intent Platform en EPD systemen.
Connector Interface (platform-breed)
| Eigenschap | Waarde |
|---|---|
| Laag | Backend (Intent Platform) |
| Patroon | Adapter pattern — elke EPD implementeert dezelfde interface |
lib/connectors/
├── connector-interface.ts # EPDConnector interface
├── connector-registry.ts # Registry: welke connectors beschikbaar
├── connector-factory.ts # createConnector(config) → EPDConnector
├── types.ts # PatientSummary, ReportSummary, etc.
└── implementations/
├── own-epd-connector.ts # Directe Drizzle DB calls (standalone model)
├── hix-connector.ts # Chipsoft HiX via FHIR/HL7 API
├── fhir-connector.ts # Generiek FHIR R4 systeem
└── mock-connector.ts # Voor tests en development
INFRASTRUCTUUR — DevOps & Deployment
Docker Services
| Service | Image | Poort | Doel |
|---|---|---|---|
caddy |
caddy:2-alpine |
80, 443 | Reverse proxy, HTTPS |
intent-platform |
Custom Dockerfile | 3001 | Intent Platform Next.js |
epd-service |
Custom Dockerfile | 3000 | EPD Service Next.js |
postgres |
postgres:17-alpine |
5432 | Databases (epd_db, platform_db, keycloak_db) |
keycloak |
quay.io/keycloak/keycloak:26.5 |
8080 | Auth server |
valkey |
valkey/valkey:8-alpine |
6379 | BullMQ + caching (Redis-compatible) |
postal |
postalserver/postal:latest |
25 |
PostgreSQL Extensions
| Extensie | Doel | Component |
|---|---|---|
pgvector |
Vector embeddings voor RAG | C12 |
pg_trgm |
Trigram similarity search | C21, C36 |
pgcrypto |
gen_random_uuid(), crypto functies | Alle tabellen |
Hosting
| Eigenschap | Waarde |
|---|---|
| Provider | Hetzner |
| Locatie | Amsterdam (EU data residency) |
| Specs | 4 vCPU, 8GB RAM, 160GB NVMe |
| OS | Ubuntu 22.04 |
| Deployment | Docker Compose + GitHub Actions (CI/CD) |
| Kosten | €20-30/mnd |
Volledige npm Dependencies
EPD Service — package.json
{
"dependencies": {
"next": "^16.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwindcss": "^4.0.0",
"drizzle-orm": "latest",
"pg": "8.x",
"zod": "^4.0.0",
"zustand": "^5.0.0",
"date-fns": "^4.0.0",
"fuse.js": "latest",
"lucide-react": "latest",
"react-hook-form": "latest",
"@hookform/resolvers": "latest",
"next-auth": "5.x",
"@auth/core": "latest",
"ai": "latest",
"@ai-sdk/anthropic": "latest",
"bullmq": "5.x",
"redis": "latest",
"pino": "^10.0.0",
"nodemailer": "latest",
"@fullcalendar/react": "latest",
"@fullcalendar/daygrid": "latest",
"@fullcalendar/timegrid": "latest",
"@opentelemetry/sdk-node": "latest",
"@opentelemetry/api": "latest",
"@bull-board/api": "latest",
"@bull-board/express": "latest"
},
"devDependencies": {
"typescript": "5.x",
"drizzle-kit": "latest",
"@types/node": "latest",
"@types/react": "latest",
"@types/pg": "latest",
"eslint": "latest",
"vitest": "latest"
}
}
Intent Platform — package.json
{
"dependencies": {
"next": "^16.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"drizzle-orm": "latest",
"pg": "8.x",
"zod": "^4.0.0",
"ai": "latest",
"@ai-sdk/anthropic": "latest",
"fuse.js": "latest",
"next-auth": "5.x",
"@auth/core": "latest",
"bullmq": "5.x",
"redis": "latest",
"pino": "^10.0.0",
"@opentelemetry/sdk-node": "latest",
"@opentelemetry/api": "latest"
},
"devDependencies": {
"typescript": "5.x",
"drizzle-kit": "latest",
"@types/node": "latest",
"@types/pg": "latest",
"vitest": "latest"
}
}
Externe Services (betaald)
| Service | Kosten | Doel | Component |
|---|---|---|---|
| Anthropic Claude API (via Vercel AI SDK) | ~$3/1M tokens | Intent classificatie, samenvattingen, chat | C07, C16 |
| Deepgram API | ~$0.0043/min | Spraak-naar-tekst (Nederlands) | C03 |
| Hetzner VPS | €15-25/mnd | Hosting | Infra |
Alles overige is self-hosted en open source — geen verdere SaaS kosten.
Samenvattingstabel — Alle 43 Componenten
| # | Component | Laag | Technologie | Service |
|---|---|---|---|---|
| C01 | Traditionele EPD UI | Frontend | Next.js, React, Tailwind, shadcn/ui | EPD |
| C02 | Intent Command Center | Frontend | React, SSE | EPD + Platform |
| C03 | Spraak Input | Frontend + API | Web Audio API, Deepgram | Platform |
| C04 | Block/Artifact systeem | Frontend | React components | EPD |
| C05 | Intent Registry | Backend | TypeScript config → DB | Platform |
| C06 | Reflex Classifier | Backend | TypeScript, RegExp | Platform |
| C07 | Orchestrator | Backend | Vercel AI SDK (@ai-sdk/anthropic), Zod | Platform |
| C08 | Entity Extractor | Backend | TypeScript, Fuse.js | Platform |
| C09 | Action System | Backend | TypeScript, Connector API | Platform |
| C10 | Protocol Engine | Backend | TypeScript rule evaluator | Platform |
| C11 | Protocol Rules Store | Backend (DB) | PostgreSQL, Drizzle | Platform |
| C12 | RAG Pipeline | Backend | pgvector, MedRoBERTa.nl / Vercel AI SDK | Platform |
| C13 | Regelvalidatie UI | Frontend | React, shadcn/ui DataTable | EPD (admin) |
| C14 | Kennisbron Connectors | Backend | TypeScript HTTP clients | Platform |
| C15 | Rapportage Service | Backend | Drizzle ORM | EPD |
| C16 | Overdracht Service | Backend | Drizzle ORM, Vercel AI SDK | EPD |
| C17 | Agenda Service | Backend | Drizzle ORM, FullCalendar (FE) | EPD |
| C18 | Behandelplan Service | Backend | Drizzle ORM | EPD |
| C19 | Medicatie Service | Backend | Drizzle ORM | EPD |
| C20 | Intake Service | Backend | Drizzle ORM, Workflow Engine | EPD |
| C21 | Patiënt Service | Backend | Drizzle ORM, Fuse.js, pg_trgm | EPD |
| C22 | Toestemming Service | Backend | Drizzle ORM | EPD |
| C23 | Auth + SSO | Middleware | Keycloak, Auth.js v5 | Beide |
| C24 | Multi-tenancy | Middleware | PostgreSQL RLS, Drizzle context | Beide |
| C25 | RBAC + ABAC Engine | Middleware | TypeScript policy evaluator | Beide |
| C26 | Audit Trail | Middleware | PostgreSQL partitioned, crypto hash | Beide |
| C27 | Field Encryption | Middleware | AES-256-GCM (node:crypto) | EPD |
| C28 | FHIR Export | Middleware (API) | TypeScript mappers, FHIR R4 | EPD |
| C29 | CDS Hooks | Middleware (API) | CDS Hooks 1.0 spec | Platform |
| C30 | Observability | Middleware | Pino, OpenTelemetry | Beide |
| C31 | Circuit Breaker | Middleware | TypeScript (~50 regels) | Platform |
| C32 | Health Check | Middleware (API) | Next.js API route | Beide |
| C33 | PII Filter | Middleware | TypeScript regex, Pino serializer | Platform |
| C34 | Notificatie Service | Backend | SSE, BullMQ, Postal (SMTP) | EPD |
| C35 | Workflow Engine | Backend | TypeScript state machine | EPD |
| C36 | Full-Text Search | Backend (DB) | PostgreSQL tsvector (NL stemmer) | EPD |
| C37 | Caching Layer | Backend | Valkey (node-redis) | Beide |
| C38 | Scheduled Jobs | Backend | BullMQ repeatable jobs | Beide |
| C39 | Webhook System | Backend | BullMQ, HMAC-SHA256 | EPD |
| C40 | ZPM Facturatie | Backend | Drizzle ORM | EPD |
| C41 | Vecozo Connector | Backend | Interface + mock (voorbereiding) | EPD |
| C42 | Zorgdomein Connector | Backend | Interface + mock (voorbereiding) | EPD |
| C43 | BI Module | Backend (DB) | PostgreSQL Materialized Views | EPD |
Appendix: Relatie architectuurdocumenten
docs/architecture/
│
├── intent-system-architectuur-nl.md
│ → Hoe het intent systeem intern werkt (5 bouwblokken)
│
├── enterprise-epd-architectuur.md
│ → Enterprise schil: multi-tenancy, audit, security, compliance
│
├── usecases-technische-componenten.md
│ → 33 use cases × 43 componenten validatie + gap analyse
│
├── platform-connectiviteit-selfhosted.md
│ → Self-hosted stack, platform splitsing, connector API
│
├── enterprise-componenten-uitbreiding.md
│ → 10 nieuwe componenten (C34-C43): interfaces, schemas
│
└── technische-invulling-componenten.md ← DIT DOCUMENT
→ Alle 43 componenten: frontend/backend/API/middleware indeling
→ Concrete technologiekeuzes, file structures, npm dependencies