Files
triqura-ecd/docs/architecture/technische-invulling-componenten.md
2026-07-09 23:16:28 +02:00

1462 lines
51 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Technische Invulling — Alle Componenten (C01C43)
> **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)
```typescript
// 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)
```typescript
// 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:
```typescript
// 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** | 250ms3s |
| **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 |
| **Email** | 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 | Email |
### 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`
```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`
```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
```