04-clinicalwave: W2filled14 citations

Medical Cards — Patient Health Summary Cards

Audiences: doctor, nurse, patient, clinical-buyer, developer

Medical Cards — Patient Health Summary Cards

A "medical card" in Dudoxx HMS is the PatientSummaryResponseDto — a parallel-fetched FHIR aggregation of demographics, active conditions, current medications, critical allergies, recent encounters, and upcoming appointments, surfaced as a real-time patient health snapshot for clinicians and patients.

Business Purpose

When a doctor opens a patient's chart, they need a 10-second clinical overview — not a raw list of FHIR resources. They need to know: active diagnoses, current medications, known allergies (especially HIGH criticality), recent visits, and upcoming appointments. Looking each up separately costs 5+ API calls and 30+ seconds of navigation.

Dudoxx HMS's Medical Card (patient summary) aggregates all six data types in a single parallel FHIR fetch, returning a unified PatientSummaryResponseDto. This enables:

  • Dashboard widgets: Each data type maps to a card on the clinical portal home.
  • AI context injection: TUCAN agent tools call get-visit-summary which reads from the same aggregation pipeline.
  • Patient portal view: PATIENT role reads their own summary via GET /api/v1/patients/:id/summary.
  • Medical history document generation: medical-history-summary.template.ts in patient-documents uses the aggregated data to generate PDF medical histories.

Audiences

  • Investor: The medical card is the primary "wow moment" in product demos — a single API call produces a clinically actionable patient snapshot. It showcases the depth of FHIR integration and AI readiness.
  • Clinical buyer (doctor/nurse/receptionist): Doctors see the medical card on patient detail pages. Critical allergies (HIGH criticality) are highlighted. Active conditions are ICD/SNOMED coded. Current medications show dosage schedules (1-0-1 format).
  • Developer/partner: GET /api/v1/patients/:id/summaryPatientSummaryResponseDto. Requires X-Clinic-ID. Accepts both UUID and FHIR Patient ID. All 6 sub-components are fetched in parallel via Promise.all().
  • Internal (ops/support): Pure FHIR aggregation — no Prisma table for summaries. All data sourced from HAPI FHIR in real time. Cache miss = 6 simultaneous FHIR HTTP requests. Monitor HAPI FHIR response times if summary endpoints are slow.

Architecture

PatientsController (/api/v1/patients/:id/summary) → PatientAggregationService:

CollaboratorResponsibility
PatientIdResolverServiceUUID or FHIR ID → canonical FHIR Patient ID
IFhirClientFHIR reads for Patient + all sub-resources
FhirResourceMapperServiceFHIR resource → typed DTO mapping
PatientFavoritesActivityServiceisFavorite flag + last-viewed metadata
PatientStatsServicecounts (conditions, medications, encounters, etc.)

Parallel fetch pattern (patient-aggregation.service.ts:137):

Promise.all([
  getFavoriteMetadata(userId, fhirPatientId),
  getActivityMetadata(userId, fhirPatientId),
  getDetailedPatientStats(fhirPatientId, clinicId),
  getUserAccount(fhirPatientId),
])

PatientSummaryResponseDto shape (6 components):

  1. demographicsDemographicSummaryDto: fullName, birthDate, age, gender, phone, email, address
  2. activeConditionsActiveConditionDto[]: id, display, code, codeSystem, onsetDate, clinicalStatus
  3. currentMedicationsCurrentMedicationDto[]: id, display, dosage, frequency (1-0-1 format), startDate, status
  4. criticalAllergiesCriticalAllergyDto[]: allergen, allergyType, criticality (HIGH/LOW), severity, reaction, onset
  5. recentEncountersRecentEncounterDto[]: class (AMB/IMP), date, status, practitioner, reasonDisplay
  6. upcomingAppointmentsUpcomingAppointmentDto[]: description, start, end, status, practitioner
  7. lastUpdated — timestamp of aggregation

Tech Stack & Choices

LayerTechnologyNotes
AggregationPatientAggregationService (NestJS service)Pure FHIR aggregation; no Prisma model for summaries
FHIR readsIFhirClient (FHIR_CLIENT token)Parallel reads for each sub-resource type
FHIR mappingFhirResourceMapperServiceCentralised FHIR resource → DTO mapping; shared across detail and summary responses
DTOPatientSummaryResponseDto6 typed sub-DTOs with @ApiProperty decorators for Swagger
StatsPatientStatsServiceFHIR search count queries (active conditions, medications, encounters)
Document exportmedical-history-summary.template.tsTemplate for PDF medical history generation via ddx-pdf-engine
AuthPatient: own summary only via PATIENT role; clinical roles: any patientRole enforced at controller
Patient IDPatientIdResolverServiceHandles both UUID (Prisma User) and FHIR numeric Patient ID

Design choice: No caching layer on the summary endpoint — data is real-time from FHIR. The parallel Promise.all() pattern keeps latency to the worst-case single FHIR call time (not sum). If FHIR response times exceed 500ms, a Redis cache layer (TTL 30s) would be the next optimization.

Data Flow

Clinician opens patient chart

Business outcome: Doctor clicks on "Maria Schmidt" in the patient list; within 1 second, a dashboard shows her active hypertension and diabetes diagnoses, current lisinopril and metformin, HIGH-criticality penicillin allergy, last visit 2 weeks ago, and an upcoming follow-up in 3 days.

Technical mechanism:

  1. GET /api/v1/patients/{uuid}/summary with X-Clinic-ID header.
  2. PatientsController delegates to PatientAggregationService.getPatientSummary().
  3. PatientIdResolverService.resolveFhirPatientId(patientId) translates UUID → FHIR Patient ID.
  4. fhirClient.read<FhirPatientResource>('Patient', fhirPatientId, clinicId) (patient-aggregation.service.ts:124) fetches patient demographics.
  5. Promise.all([getFavoriteMetadata, getActivityMetadata, getDetailedPatientStats, getUserAccount]) (patient-aggregation.service.ts:137) executes 4 parallel queries.
  6. FhirResourceMapperService maps each FHIR sub-resource to its typed DTO.
  7. Returns PatientSummaryResponseDto with all 6 components + lastUpdated.

AI agent uses clinical summary

GET /api/v1/visits/:id/agent-context calls VisitsService.getVisitAgentContext() which assembles patient context. The TUCAN open-clinical-summary-modal tool (ai/tucan/tools/ui/open-clinical-summary-modal.tool.ts) references the patient summary endpoint to inject clinical context into the AI assistant session.

Medical history PDF generation

PatientDocumentsService.getPatientDocuments() (patient-documents.service.ts:53) aggregates documents from 3 sources (attachments, visit attachments, generated documents). medical-history-summary.template.ts uses patient summary data to generate a structured medical history PDF via ddx-pdf-engine.

Implicated Code

  • ddx-api/src/clinical/patients/services/patient-aggregation.service.ts:72PatientAggregationService — parallel FHIR aggregation orchestrator
  • ddx-api/src/clinical/patients/services/patient-aggregation.service.ts:111getPatientDetail() — full aggregation with favorites, activity, stats, user account
  • ddx-api/src/clinical/patients/services/patient-aggregation.service.ts:124 — FHIR Patient read — validates fhirPatient.id exists (never coerces missing ID)
  • ddx-api/src/clinical/patients/services/patient-aggregation.service.ts:137Promise.all([...]) — parallel fetch of 4 sub-components
  • ddx-api/src/clinical/patients/services/fhir-resource-mapper.service.ts:1FhirResourceMapperService — FHIR → DTO mapping (shared between detail and summary)
  • ddx-api/src/clinical/patients/services/patient-stats.service.ts:1PatientStatsService — FHIR search count queries for condition/medication/encounter totals
  • ddx-api/src/clinical/patients/dto/patient-summary-response.dto.ts:172PatientSummaryResponseDto — 6-component aggregate DTO with Swagger annotations
  • ddx-api/src/clinical/patients/dto/patient-summary-response.dto.ts:127CriticalAllergyDto — HIGH/LOW criticality, allergen, severity, reaction fields
  • ddx-api/src/documents/patient-documents/patient-documents.service.ts:40PatientDocumentsService — aggregates documents from 3 sources (attachments, visit attachments, generated docs)
  • ddx-api/src/documents/patient-documents/templates/medical-history-summary.template.ts:1 — PDF template for medical history export

Operational Notes

  • Pure FHIR — no Prisma cache: Patient summaries are always fetched live from HAPI FHIR. If HAPI FHIR is slow or unreachable, summary endpoints will time out. Monitor HAPI FHIR at GET http://localhost:18080/fhir/metadata.
  • Parallel fetch is critical for performance: The 4-way Promise.all() means total latency ≈ max(individual_latency), not sum. Do not serialize these calls — it would multiply response time by 4.
  • FHIR Patient ID must exist: PatientIdResolverService throws NotFoundException if no FHIR Patient record exists for the given UUID. Ensure PatientsService.ensureFhirPatient() is called (or the seeder run) before accessing summaries for new patients.
  • Criticality display priority: CriticalAllergyDto.criticality = HIGH should be visually highlighted in any UI consuming this DTO. The field value comes directly from the FHIR AllergyIntolerance.criticality field — no transformation.
  • Medication frequency format: CurrentMedicationDto.frequency uses the "1-0-1" German pharmacy notation (morning-noon-evening doses). UI components rendering this should display it with time labels, not just the raw string.
    Medical Cards — Patient Health Summary Cards — Dudoxx Docs | Dudoxx