Atlas (Backend)
De Atlas-backend (backend/features/atlas/) levert de standalone Atlas-chat: een RAG-retrieval-assistent die bronnen ophaalt en citeert. Hij voert geen klinische analyse uit en geeft geen patientspecifiek oordeel; dat houdt Atlas buiten de MDR-scope (zie documentation/docs/compliance/ai_boundary.md). Deze module is gescheiden van de Atlas-zijbalk in de medicatiebeoordeling, die eigen modellen (ReviewChatThread/ReviewChatMessage) en een eigen stream-view heeft. Beide delen wel de gedeelde streaming-pipeline (pipeline.py) en de Bedrock-laag (bedrock.py).
Technisch Ontwerp
Atlas draait op Anthropic Claude via Amazon Bedrock, met cross-region inference in eu-central-1. Atlas Pro is BEDROCK_MODEL_PRO (Claude Sonnet 4.6), Atlas Fast is BEDROCK_MODEL_FAST (Claude Haiku 4.5). De gekozen variant volgt uit request.user.preferred_model, gemapt via ATLAS_MODEL_MAP.
- Expliciete RAG-retrieval: de applicatie roept zelf de Bedrock Knowledge Base aan (
_kb_retrieveinbedrock.py, hybride search, met retry-backoff voor de cold Aurora-resume). Het model beslist niet zelf of het grondt; triviale vragen (begroetingen) slaan de retrieve over. De chunks gaan als genummerd evidence-blok naar Claude. - Citaties: het model schrijft zelf
[N]-markers tegen de genummerde bronnen.CitationStreamParser(citations.py) valideert ze en bouwt een chunk-bewuste citatie-map; de gebruiker ziet geen ruwe chunk-ids. - PubMed-escalatie (HITL): dekt de richtlijn een gevraagd aspect niet, dan kan Claude de tool
search_pubmedaanroepen. Dat pauzeert de stream (human-in-the-loop): de pending-tool-state wordt op de thread opgeslagen en de gebruiker moet akkoord geven. Bij akkoord haalt_run_phase2_with_pubmedde literatuur op en draait fase 2 met gecombineerd bewijs; bij weigeren draait fase 2 alleen op de richtlijnen. De keuze (pubmed_approved) blijft per thread bewaard, zodat vervolgvragen de pauze overslaan. - SSE-streaming: de antwoorden streamen via Server-Sent Events met events voor
thinking,sources_ready,delta,tool_request,pubmed_results,system_warning,doneenerror. - Prompt caching: het stabiele prefix (system-prompt, tool-definities en de conversatie tot en met de vorige assistant-turn) wordt door Bedrock gecached; de verse user-turn met net-geretrieveerde chunks blijft ongecached.
- Push bij start van een antwoord: zodra de eerste niet-lege delta binnenkomt, vuurt de pipeline eenmalig de push
atlas_response_readyaf vianotify_fn. Dat is model-agnostisch (Pro en Fast) en user-scoped: het gaat alleen uit als de gebruikeratlas_response_readyin zijn push-voorkeuren heeft aangezet enpush_disabled_alluit staat. - Patientcontext (RAG-retrieval verbetering): de standalone stream-view stuurt zelf geen patientcontext mee. De allowlist-helper
patient_context.pyen de tenant-kill-switchApotheek.atlas_patient_context_enabledworden gebruikt door de Atlas-zijbalk in de medicatiebeoordeling.PatientContextSettingsViewin deze module leest en zet diezelfde tenant-brede schakelaar (GET/PATCH), zonder aparte admin-RBAC. De pipeline ondersteunt een optioneelpatient_contextopThreadContext: is dat gevuld, dan prependtrun_pipelineeen framing-blok (PATIENT_CONTEXT_FRAMING) plus de gepseudonimiseerde JSON aan de user-message, uitsluitend om bronnen te selecteren.
flowchart TD
A[POST /api/atlas/stream/] --> L{is_over_limit fair-use?}
L -->|Ja| L403[403 atlas_fair_use_exhausted]
L -->|Nee| MSG[persist user ChatMessage + run_pipeline]
MSG --> P1[bedrock.ask_atlas_phase1: _kb_retrieve + stream]
P1 --> Q{search_pubmed nodig?}
Q -->|Nee, happy path| ANS[stream delta + done]
Q -->|Ja, pubmed_approved=None| HITL[persist_pending_fn + tool_request + notify_fn]
HITL --> APPR[POST /pubmed-search/ of /tool-decline/]
APPR --> P2[_run_phase2_with_pubmed: search_with_evidence_hierarchy_result]
P2 --> ANS
ANS --> N[notify_fn atlas_response_ready bij 1e delta]
ANS --> PERSIST[_persist_assistant_message: content, sources, citations, model_id]
Datamodel (ERD)
De Atlas-chatgegevens leven binnen het tenant-schema. content en thinking_content op ChatMessage zijn op rij-niveau versleuteld (EncryptedTextField). De tenant-kill-switch atlas_patient_context_enabled staat op het Apotheek-model in het public-schema.
erDiagram
APOTHEEK ||--o{ CHAT_THREAD : "tenant"
CHAT_THREAD ||--o{ CHAT_MESSAGE : "bevat"
CHAT_THREAD ||--o{ MESSAGE_REPORT : "gemeld"
APOTHEEK {
bool atlas_patient_context_enabled "tenant kill-switch (public schema)"
}
CHAT_THREAD {
string id PK
int user_id FK
string title
datetime created_at
datetime updated_at "laatste bericht"
string warm_s3_bucket
string warm_s3_key
datetime warm_archived_at
datetime warm_archived_until
bool pubmed_approved "null=niet gevraagd"
string pending_tool_name
json pending_tool_payload
datetime pending_tool_created_at
}
CHAT_MESSAGE {
int id PK
int thread_id FK
string role "user | assistant"
text content "encrypted"
json sources
json citations "chunk-bewuste map per [N]"
text thinking_content "encrypted"
int thinking_seconds
string model_id "MDR-traceability"
datetime created_at
}
MESSAGE_REPORT {
uuid id PK
int thread_id FK
text assistant_content "snapshot"
string model_id
text reason
int reported_by_id FK
string status "new | reviewed | dismissed"
int reviewed_by_id FK
datetime reviewed_at
text internal_notes
datetime created_at
}
WEEKLY_MESSAGE_REPORT_EMAIL_LOG {
int yyyyww PK "idempotency week"
datetime sent_at
int report_count
}
API & Communicatie
REST-endpoints (/api/atlas/)
GET /ping/: health-check voor de module.GET /threads/: lijst van zichtbare threads van de gebruiker (gesorteerd op laatste bericht, venster 90 dagen, hard-cap 200).GET /threads/{id}/: volledige thread met berichten (paginatie vialimit/before), plus eventuele pending PubMed-state.DELETE /threads/{id}/archive/: schrijft de thread versleuteld weg naar het compliance-archief (S3 Object Lock, 10 jaar) en verwijdert hem uit de database.POST /threads/{id}/report/: meldt een assistent-bericht (snapshot van de content). Idempotent binnen 24 uur voor dezelfde combinatie van melder, thread en content.POST /stream/: nieuw bericht; start de SSE-stroom (text/event-stream).POST /pubmed-search/: hervat de stream na akkoord op een PubMed-zoekopdracht (SSE).POST /tool-decline/: hervat de stream na weigeren van een PubMed-zoekopdracht (SSE).GET /patient-context-settings/: levert de tenant-brede schakelaar (apotheek_enabled) en de allowlist (patient_context_field_names).PATCH /patient-context-settings/: zet de tenant-brede schakelaar. Geen aparte admin-RBAC; iedere gebruiker metatlas.viewmag de schakelaar bedienen.
SSE-events op de stream-endpoints
De stream-endpoints leveren text/event-stream. Elke regel is data: <json>. Event-types: thinking, guidelines_results, sources_ready, delta, tool_request, pubmed_searching, pubmed_results, system_warning (fair-use), done (met sources, cited_text, citations) en error.
Foutafhandeling & Statuscodes
400 Bad Request: ongeldige payload, of een geweigerd bericht omdat PII is gedetecteerd (reject_if_pii, codepii_detected).403 Forbidden: maandlimiet voor Atlas bereikt (code: atlas_fair_use_exhausted), of geenatlas.view-permissie.404 Not Found: thread bestaat niet, is niet van de gebruiker, of valt buiten het zichtbaarheidsvenster.409 Conflict: geen actief PubMed-verzoek om te hervatten op/pubmed-search/of/tool-decline/.429 Too Many Requests: de scoped throttle is overschreden (atlas_chat,atlas_pubmed,atlas_tool_decline,atlas_patient_context).- Fouten tijdens het streamen worden niet als HTTP-status teruggegeven (de stream is al begonnen) maar als een
error-SSE-event.
Autorisatie & Beveiliging
- Tenant-isolatie: threads en berichten leven in het tenant-schema; queries zijn gefilterd op
user=request.user. Persistentie in de async stream-view re-entert expliciet het juiste schema (schema_context). - RBAC: alle Atlas-views vereisen
IsAuthenticatedplusHasPermissionCodemetrequired_permission = "atlas.view". - Versleuteling:
contententhinking_contentzijn op rij-niveau versleuteld. Archieven gaan versleuteld naar S3 met KMS en Object Lock (COMPLIANCE). - PII-gate: user-berichten worden geweigerd als er PII in staat. Prompt-injectie aan het begin van een regel wordt geneutraliseerd (
_neutralize_user_message); de system-prompt-rail blijft de primaire bescherming. - Audit: een melding van een bericht logt PHI-vrije metadata (tellingen en booleans) naar de audit-log (
ai.atlas.message_reported); de inhoud zit niet in de audit-event.
Bestandsstructuur & Verantwoordelijkheden
backend/features/atlas/views.py: SSE-views (ChatStreamView,PubMedSearchView,ToolDeclineView), thread-endpoints,MessageReportViewenPatientContextSettingsView(GET/PATCH tenant-kill-switch).backend/features/atlas/pipeline.py: gedeelde streaming-pipeline (retrieve -> HITL-pauze -> answer), tokenverbruik/fair-use, push-notificatie en patientcontext-injectie. Gedeeld met de medicatiebeoordeling-zijbalk viaThreadContext.backend/features/atlas/bedrock.py: Bedrock-laag. KB-retrieve (_kb_retrieve),ask_atlas_phase1/ask_atlas_phase2, system-instructies, prompt-caching en titel-generatie.backend/features/atlas/citations.py:CitationStreamParseren bronnummering voor de[N]-citaties.backend/features/atlas/pubmed.py: PubMed-zoekactie met evidence-hierarchie en bronopbouw.backend/features/atlas/patient_context.py: allowlist-helper. Single source of truth voor wat naar Bedrock gaat:ALLOWED_FIELDS,age_bucket(),build_patient_context(),audit_fields_sent(),PATIENT_CONTEXT_FRAMING.backend/features/atlas/models.py:ChatThread,ChatMessage,MessageReport,WeeklyMessageReportEmailLog.backend/features/atlas/serializers.py: request/response-serializers voor stream, threads en PubMed-flow.backend/features/atlas/s3_store.py: warm- en compliance-archief (gzip-JSON naar S3, Object Lock).backend/features/atlas/tasks.pyenservices/: Celery-taken voor titel-generatie, archivering naar warm S3, opschonen en de wekelijkse melding-mail.
Belangrijke bestanden
backend/features/atlas/views.pybackend/features/atlas/pipeline.pybackend/features/atlas/bedrock.pybackend/features/atlas/models.py