Medical Cards — Patient Health Summary Cards
Audiences: doctor, nurse, patient, clinical-buyer, developer
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-summarywhich 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.tsinpatient-documentsuses 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/summary→PatientSummaryResponseDto. Requires X-Clinic-ID. Accepts both UUID and FHIR Patient ID. All 6 sub-components are fetched in parallel viaPromise.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
├── PatientIdResolverService — UUID or FHIR ID → canonical FHIR Patient ID
├── IFhirClient — FHIR reads for Patient + all sub-resources
├── FhirResourceMapperService — FHIR resource → typed DTO mapping
├── PatientFavoritesActivityService — isFavorite flag + last-viewed metadata
└── PatientStatsService — counts (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):
demographics—DemographicSummaryDto: fullName, birthDate, age, gender, phone, email, addressactiveConditions—ActiveConditionDto[]: id, display, code, codeSystem, onsetDate, clinicalStatuscurrentMedications—CurrentMedicationDto[]: id, display, dosage, frequency (1-0-1 format), startDate, statuscriticalAllergies—CriticalAllergyDto[]: allergen, allergyType, criticality (HIGH/LOW), severity, reaction, onsetrecentEncounters—RecentEncounterDto[]: class (AMB/IMP), date, status, practitioner, reasonDisplayupcomingAppointments—UpcomingAppointmentDto[]: description, start, end, status, practitionerlastUpdated— timestamp of aggregation
Tech Stack & Choices
| Layer | Technology | Notes |
|---|---|---|
| Aggregation | PatientAggregationService (NestJS service) | Pure FHIR aggregation; no Prisma model for summaries |
| FHIR reads | IFhirClient (FHIR_CLIENT token) | Parallel reads for each sub-resource type |
| FHIR mapping | FhirResourceMapperService | Centralised FHIR resource → DTO mapping; shared across detail and summary responses |
| DTO | PatientSummaryResponseDto | 6 typed sub-DTOs with @ApiProperty decorators for Swagger |
| Stats | PatientStatsService | FHIR search count queries (active conditions, medications, encounters) |
| Document export | medical-history-summary.template.ts | Template for PDF medical history generation via ddx-pdf-engine |
| Auth | Patient: own summary only via PATIENT role; clinical roles: any patient | Role enforced at controller |
| Patient ID | PatientIdResolverService | Handles 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:
GET /api/v1/patients/{uuid}/summarywith X-Clinic-ID header.PatientsControllerdelegates toPatientAggregationService.getPatientSummary().PatientIdResolverService.resolveFhirPatientId(patientId)translates UUID → FHIR Patient ID.fhirClient.read<FhirPatientResource>('Patient', fhirPatientId, clinicId)(patient-aggregation.service.ts:124) fetches patient demographics.Promise.all([getFavoriteMetadata, getActivityMetadata, getDetailedPatientStats, getUserAccount])(patient-aggregation.service.ts:137) executes 4 parallel queries.FhirResourceMapperServicemaps each FHIR sub-resource to its typed DTO.- Returns
PatientSummaryResponseDtowith 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:72—PatientAggregationService— parallel FHIR aggregation orchestratorddx-api/src/clinical/patients/services/patient-aggregation.service.ts:111—getPatientDetail()— full aggregation with favorites, activity, stats, user accountddx-api/src/clinical/patients/services/patient-aggregation.service.ts:124— FHIR Patient read — validatesfhirPatient.idexists (never coerces missing ID)ddx-api/src/clinical/patients/services/patient-aggregation.service.ts:137—Promise.all([...])— parallel fetch of 4 sub-componentsddx-api/src/clinical/patients/services/fhir-resource-mapper.service.ts:1—FhirResourceMapperService— FHIR → DTO mapping (shared between detail and summary)ddx-api/src/clinical/patients/services/patient-stats.service.ts:1—PatientStatsService— FHIR search count queries for condition/medication/encounter totalsddx-api/src/clinical/patients/dto/patient-summary-response.dto.ts:172—PatientSummaryResponseDto— 6-component aggregate DTO with Swagger annotationsddx-api/src/clinical/patients/dto/patient-summary-response.dto.ts:127—CriticalAllergyDto— HIGH/LOW criticality, allergen, severity, reaction fieldsddx-api/src/documents/patient-documents/patient-documents.service.ts:40—PatientDocumentsService— 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:
PatientIdResolverServicethrowsNotFoundExceptionif no FHIR Patient record exists for the given UUID. EnsurePatientsService.ensureFhirPatient()is called (or the seeder run) before accessing summaries for new patients. - Criticality display priority:
CriticalAllergyDto.criticality=HIGHshould be visually highlighted in any UI consuming this DTO. The field value comes directly from the FHIRAllergyIntolerance.criticalityfield — no transformation. - Medication frequency format:
CurrentMedicationDto.frequencyuses 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.
Related Topics
- Clinical Data — Conditions — Active conditions shown on medical card
- Clinical Data — Prescriptions — Current medications on medical card
- Clinical Data — Vitals — Latest vitals may be surfaced in extended summary views
- Drugs Management — Critical allergies feed drug-allergy checking
- Clinical Visits — Recent encounters data sourced from FHIR Encounters
- Clinical Activities — Patient chart views tracked via ActivityInterceptor