Ga naar inhoud

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_retrieve in bedrock.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_pubmed aanroepen. 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_pubmed de 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, done en error.
  • 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_ready af via notify_fn. Dat is model-agnostisch (Pro en Fast) en user-scoped: het gaat alleen uit als de gebruiker atlas_response_ready in zijn push-voorkeuren heeft aangezet en push_disabled_all uit staat.
  • Patientcontext (RAG-retrieval verbetering): de standalone stream-view stuurt zelf geen patientcontext mee. De allowlist-helper patient_context.py en de tenant-kill-switch Apotheek.atlas_patient_context_enabled worden gebruikt door de Atlas-zijbalk in de medicatiebeoordeling. PatientContextSettingsView in deze module leest en zet diezelfde tenant-brede schakelaar (GET/PATCH), zonder aparte admin-RBAC. De pipeline ondersteunt een optioneel patient_context op ThreadContext: is dat gevuld, dan prependt run_pipeline een 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 via limit/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 met atlas.view mag 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, code pii_detected).
  • 403 Forbidden: maandlimiet voor Atlas bereikt (code: atlas_fair_use_exhausted), of geen atlas.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 IsAuthenticated plus HasPermissionCode met required_permission = "atlas.view".
  • Versleuteling: content en thinking_content zijn 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, MessageReportView en PatientContextSettingsView (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 via ThreadContext.
  • 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: CitationStreamParser en 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.py en services/: Celery-taken voor titel-generatie, archivering naar warm S3, opschonen en de wekelijkse melding-mail.

Belangrijke bestanden

  • backend/features/atlas/views.py
  • backend/features/atlas/pipeline.py
  • backend/features/atlas/bedrock.py
  • backend/features/atlas/models.py

API & Communicatie (Swagger)