04-clinicalwave: W2filled23 citations

Diagnosis Engine — Disease Cards, Curator, and Document Generation

Audiences: doctor, clinical-buyer, developer, investor

Diagnosis Engine — Disease Cards, Curator, and Document Generation

The Diagnosis Engine is three coordinated sub-systems: a curated DiseaseCard knowledge base (structured disease definitions with comorbidity graphs and RAG-indexed guidelines), an AI Curator agent (LLM-driven curation runs that mutate and version disease knowledge with rollback), and a Document Generation Engine (parallel AI section generation from versioned templates, exported as PDF via ddx-pdf-engine).

Business Purpose

Clinical decision support requires two things: reliable, up-to-date clinical knowledge, and the ability to express that knowledge in patient-facing documents. Manually maintaining disease encyclopedias and generating per-patient documents from scratch would take hours per case.

Dudoxx HMS resolves this at three levels:

  1. DiseaseCards + Curator: Clinical administrators curate a structured disease knowledge base (ICD-coded cards with symptoms, treatments, comorbidities, diagnostic flows). The AI Curator agent automatically proposes knowledge mutations from external guidelines (WHO, AWMF, PubMed PDF uploads) and commits them as auditable KnowledgeMutation rows with beforeJson/afterJson diffs — fully rollback-safe.
  2. DiagnosticFlows: Each disease card can carry a JSON decision-tree graph, validated server-side, that guides clinicians through a structured diagnostic protocol step-by-step.
  3. Document Generation: TUCAN tools generate clinical documents (intake summaries, patient letters, diagnostic reports) by instantiating versioned templates, running sections in parallel through an AI sub-agent, and exporting the final HTML to PDF via the ddx-pdf-engine (Playwright/WeasyPrint).

Audiences

  • Investor: The Curator agent demonstrates autonomous clinical knowledge management — a clinic can upload a new AWMF guideline PDF and the AI proposes, diffs, and applies all changes to the disease catalog with one click. This is a strong SaaS moat: competitors sell static medical databases.
  • Clinical buyer (doctor/nurse/receptionist): Doctors see diagnostic flow decision trees when opening a disease card. The TUCAN assistant can generate a patient intake summary document (PDF) in real-time while the doctor speaks. All documents are versioned and reviewable before publication.
  • Developer/partner: POST /api/v1/document-templates creates a template. POST /api/v1/generated-documents instantiates it. GET /api/v1/generated-documents/:id/export?format=pdf exports the finished document. Curator runs: POST /api/v1/diagnosis-engine/curator/runs.
  • Internal (ops/support): DiseaseCards and DiagnosticFlows in Prisma ddx_api_main. Document templates, generated instances, and sections in Prisma ddx_api_ai. Curator SSE events: CURATOR_RUN_STARTED, CURATOR_RUN_COMPLETED, CURATOR_MUTATION_APPLIED. Document SSE: DOCUMENT_GENERATION_START, DOCUMENT_SECTION_COMPLETE, DOCUMENT_GENERATION_COMPLETE.

Architecture

Diagnosis Engine — 4 subsystems, all on ddx_api_main:

Subsystem (store)ComponentResponsibility
DiseaseCards (ddx_api_main)DiseaseCardsController
DiseaseCardsService
DiseaseCardCrudService
DiseaseCardSearchService
DiseaseCardVersionService
ComorbidityGraphService
DiagnosisRagServiceQdrant semantic search over disease corpus
DiagnosticFlows (ddx_api_main)DiagnosticFlowsService
FlowValidatorServicevalidates JSON flow graph before save
PatientDiagnoses (ddx_api_main)PatientDiagnosesServicelinks PatientDiagnosis to DiseaseCard + Practitioner
Curator (ddx_api_main)CuratorController/diagnosis-engine/curator
CurationRunServicestart / execute / cancel runs
knowledgeCuratorWorkflowMastra AI workflow (in-process)
KnowledgeMutationServiceper-mutation CRUD + rollback
ExternalReferenceServiceattach references to entities
EntityAssetServiceMinIO image upload for entities
GuidelinesRagServiceQdrant wing ddx-guidelines PDF ingest

Document Generation Engine (cross-cutting):

ComponentResponsibility
DocumentTemplatesController/api/v1/document-templates
DocumentTemplatesServicePUBLISHED template lifecycle (ddx_api_ai)
DocumentTemplateVersionsServicesemantic versioning, immutability policy
GeneratedDocumentsController/api/v1/generated-documents
DocumentGenerationOrchestratorServicecreate instance + input context
ParallelReportGenerationServiceparallel section AI generation (p-limit)
DocumentExportServicePDF/HTML export (PDF Engine → PDFKit fallback)

Curator fires-and-forgets: POST /diagnosis-engine/curator/runs returns the CurationRun immediately, then uses setTimeout(100ms) to let the HTTP response return and let the frontend mount its SSE subscription before CURATOR_STEP_PROGRESS events start firing (curator.controller.ts:116).

Document generation is parallel: ParallelReportGenerationService uses p-limit concurrency control to process template sections in dependency-respecting batches, each section emitting SSE events.

Tech Stack & Choices

LayerTechnologyNotes
DiseaseCard storagePrisma ddx_api_mainDiseaseCard, DiagnosticFlow, PatientDiagnosis, CurationRun, KnowledgeMutation
Document storagePrisma ddx_api_aiDocumentTemplate, DocumentTemplateVersion, GeneratedDocumentInstance, GeneratedDocumentSection
Curator AIMastra knowledgeCuratorWorkflow (in-process)LLM-driven mutation proposal from text + PDF sources
Section AIParallel agent sub-calls via ParallelReportGenerationServicep-limit concurrency; retry logic (max 2 retries per section)
PDF exportDocumentExportServicePdfEngineService (Playwright, port 6450)Falls back to PDFKit if PDF engine unavailable (document-export.service.ts:29)
Disease RAGDiagnosisRagService → QdrantDisease corpus semantic search
Guidelines RAGGuidelinesRagService → Qdrant ddx-guidelinesPDF upload → chunk → embed → Qdrant; POST /curator/rag/guidelines/ingest
SSEEventBusService (Redis DB1)CURATOR_* and DOCUMENT_* event families
RBAC`@RequirePermission('diagnosis-engine-curator', 'curaterollback')`
Document RBACDOCUMENT_MANAGE_ROLES, DOCUMENT_VIEW_ROLES, DOCUMENT_GENERATE_ROLES, DOCUMENT_REVIEW_ROLESDefined in rbac.ts:3

Design choice: Curator mutations carry beforeJson snapshots. Rolling back a mutation replays the beforeJson to the entity — no migration needed, no data loss. This means the knowledge base is a fully auditable event log of AI-proposed changes.

Data Flow

Curator enriches a disease card from a guideline PDF

Business outcome: Clinical admin uploads an AWMF guideline PDF for "Hypertension". The AI Curator extracts updated treatment recommendations, proposes mutations to the Hypertension DiseaseCard, and shows a diff for review.

Technical mechanism:

  1. POST /diagnosis-engine/curator/rag/guidelines/ingest with file=awmf-hypertension.pdfGuidelinesRagService.ingestPdf() chunks + embeds the document into Qdrant ddx-guidelines.
  2. POST /diagnosis-engine/curator/runs with { scope: { kind: 'disease-card', entityId } }CurationRunService.start() creates a CurationRun in status PENDING.
  3. CuratorController.startRun() (curator.controller.ts:116) returns the run immediately, then fires setTimeout(100ms)runs.executeRun(run.id).
  4. executeRun() invokes knowledgeCuratorWorkflow (Mastra in-process) which calls GuidelinesRagService for relevant chunks, proposes mutations, emits CURATOR_STEP_PROGRESS SSE events.
  5. On completion, each mutation is written as a KnowledgeMutation row with beforeJson + afterJson snapshots and status APPLIED.
  6. GET /diagnosis-engine/curator/runs/:id/mutations → clinician reviews the diff list.
  7. If rejected: POST /diagnosis-engine/curator/mutations/:id/rollbackKnowledgeMutationService.rollback() replays beforeJson to the entity.

TUCAN generates a patient intake document

Business outcome: Doctor asks TUCAN: "Generate an intake summary for Maria Schmidt". A PDF appears in the patient's document list within 30 seconds.

Technical mechanism:

  1. TUCAN tool calls DocumentGenerationOrchestratorService.createDocumentInstance() (document-generation-orchestrator.service.ts:167) with templateKey: 'patient-intake-summary'.
  2. Orchestrator finds the PUBLISHED template in ddx_api_ai.documentTemplate, creates an DocumentInputContext row with patient data payload, creates a GeneratedDocumentInstance with N GeneratedDocumentSection rows in PENDING status.
  3. ParallelReportGenerationService.processSectionsParallel() processes sections in dependency order. For each section: emit DOCUMENT_SECTION_START SSE → call AI sub-agent → parse JSON response → write section content → emit DOCUMENT_SECTION_COMPLETE SSE.
  4. On all sections complete: emit DOCUMENT_GENERATION_COMPLETE SSE → instance status = COMPLETED.
  5. GET /api/v1/generated-documents/:id/export?format=pdfDocumentExportService.generatePdfFromAssembledHtml() (document-export.service.ts:45) tries PdfEngineService (Playwright, port 6450), falls back to PDFKit.
  6. PDF streamed as application/pdf attachment.

Clinician saves a diagnostic flow for a disease card

PUT /diagnosis-engine/disease-cards/:id/flow with { flowGraph: { nodes: [...], edges: [...] } }DiagnosticFlowsService.saveFlow()FlowValidatorService.validate(dto.flowGraph) (diagnostic-flows.service.ts:56); if invalid, throws UnprocessableEntityException with an errors array. If valid, upserts DiagnosticFlow Prisma row (Prisma-only, no FHIR sync).

Implicated Code

  • ddx-api/src/diagnosis-engine/curator/curator.controller.ts:73CURATOR_RESOURCE + ACTION_ROLLBACK constants — rollback requires separate permission from curate; doctors may NEVER rollback per RBAC pitfall note
  • ddx-api/src/diagnosis-engine/curator/curator.controller.ts:116setTimeout(100ms) fire-and-forget — lets HTTP response + SSE subscription mount before first CURATOR_STEP_PROGRESS event fires
  • ddx-api/src/diagnosis-engine/curator/curator.controller.ts:244POST /curator/rag/guidelines/ingest — PDF-only (MIME validated), routed to GuidelinesRagService.ingestPdf()
  • ddx-api/src/diagnosis-engine/curator/services/curation-run.service.ts:10CurationStatus, CurationTriggerKind enums from @ddx/prisma-main
  • ddx-api/src/diagnosis-engine/curator/services/curation-run.service.ts:17knowledgeCuratorWorkflow — Mastra AI workflow for curation logic
  • ddx-api/src/diagnosis-engine/diagnostic-flows/services/diagnostic-flows.service.ts:13DiagnosticFlowsService — Prisma-only (no FHIR); validates flow graph before upsert
  • ddx-api/src/diagnosis-engine/diagnostic-flows/services/diagnostic-flows.service.ts:56FlowValidatorService.validate(dto.flowGraph) — throws UnprocessableEntityException with errors array on invalid graph
  • ddx-api/src/documents/document-generation/services/document-generation-orchestrator.service.ts:139DocumentGenerationOrchestratorService — creates document instance from template; generateDocument() deprecated in favour of ParallelReportGenerationService
  • ddx-api/src/documents/document-generation/services/document-generation-orchestrator.service.ts:167createDocumentInstance() — resolves PUBLISHED template by templateKey, creates input context + instance + pending sections
  • ddx-api/src/documents/document-generation/services/parallel-report-generation.service.ts:1ParallelReportGenerationServicep-limit concurrency; dependency-aware batching; 2-retry per section; SSE progress
  • ddx-api/src/documents/document-generation/services/document-export.service.ts:29@Optional() pdfEngineService — graceful degradation: Playwright → PDFKit fallback
  • ddx-api/src/documents/document-generation/services/document-export.service.ts:45generatePdfFromAssembledHtml() — primary PDF export path; accepts pre-assembled branded HTML
  • ddx-api/src/documents/document-generation/document-templates.controller.ts:44@RequireRole(...DOCUMENT_MANAGE_ROLES) — DOCTOR/CLINIC_ADMIN/SUPER_ADMIN can create/patch templates
  • ddx-api/src/documents/document-generation/rbac.ts:3 — 4 constant arrays: DOCUMENT_MANAGE_ROLES, DOCUMENT_VIEW_ROLES, DOCUMENT_GENERATE_ROLES, DOCUMENT_REVIEW_ROLES

Operational Notes

  • Curator SSE dependency: The setTimeout(100ms) delay in CuratorController.startRun() is intentional — removes the race condition where CURATOR_STEP_PROGRESS fires before the client's SSE subscription is established. Do NOT remove this delay.
  • Rollback is gated separately: ACTION_ROLLBACK (diagnosis-engine-curator:rollback) must NOT be granted to the DOCTOR role. Only CLINIC_ADMIN and SUPER_ADMIN can revert curator mutations. This is called out as an RBAC pitfall at curator.controller.ts:72.
  • Guidelines RAG ingest: Only application/pdf files are accepted at POST /curator/rag/guidelines/ingest. MIME type is validated server-side. Qdrant wing: ddx-guidelines collection.
  • Document templates are org-scoped: DocumentTemplate.isGlobalTemplate allows platform-wide templates visible to all orgs. Org-specific templates are filtered by organizationId. Only PUBLISHED status templates can be instantiated.
  • generateDocument() is deprecated: DocumentGenerationOrchestratorService.generateDocument() always throws BadRequestException with a METHOD_DEPRECATED error code. All new code must use ParallelReportGenerationService.processSectionsParallel().
  • PDF Engine fallback: DocumentExportService injects PdfEngineService with @Optional(). If ddx-pdf-engine (port 6450) is unavailable, export falls back to PDFKit — lower visual fidelity but always available. Monitor pdf-engine uptime when document export is a critical workflow.
  • Template [tag:field_name] placeholders: HTML templates use [tag:field_name] syntax for merge fields. ParallelReportGenerationService resolves these after section generation. Malformed placeholders silently produce blank fields — validate template HTML before publishing.
  • Cross-DB boundary: DiseaseCards live in ddx_api_main (PrismaService); generated documents live in ddx_api_ai (PrismaAiService). A service touching both must inject both Prisma services — never mix or alias them.
  • Clinical Data — Conditions — PatientDiagnosis links to DiseaseCard; FHIR Condition records created on diagnosis
  • Clinical Visits — Visit agent context feeds document generation for intake summaries
  • Assessments — Assessment scores (PHQ-9, GAF) feed AI diagnostic evaluation
  • Medical Cards — Patient summary aggregation feeds document template input contexts