Lab Results and Diagnostic Reports
Audiences: doctor, clinical-buyer, developer
Lab results in Dudoxx HMS are FHIR R4
DiagnosticReportresources stored in HAPI, indexed by LOINC code, and accessible through a REST API that supports per-patient filtering, full-text search on test names, and direct CRUD for clinic staff.
Business Purpose
Clinics generate lab results daily — blood panels, urine analyses, pathology reports, imaging reads. Before lab integration, doctors either re-entered results manually into their EMR or kept paper records separate from the clinical chart. Both patterns break the continuity of care: a doctor ordering a follow-up visit cannot quickly see whether the previous CBC result was in range.
Dudoxx HMS stores lab results as FHIR DiagnosticReport resources, which means:
- Interoperability: Lab results are immediately shareable with specialist referral networks, insurance gateways, and patient portals using standard FHIR queries.
- LOINC coding: Each result carries an optional LOINC code (
testCode), enabling downstream analytics and decision-support tools to identify result types without string matching. - Patient-scoped access: Patients can view their own results via the patient portal (PATIENT role can
GET /api/v1/lab-results/:id); staff can list all results for a clinic or filter by patient.
Audiences
- Investor: Lab results are a high-frequency data type in any busy clinic. Having structured, FHIR-compliant lab data enables future integrations with automated lab ordering systems (HL7 v2 → FHIR bridge), clinical decision support, and payer portals — each of which represents a upsell or integration revenue stream.
- Clinical buyer (doctor/nurse/receptionist): Doctors can record a lab result against a patient immediately after receiving it from the external lab. Results are listed chronologically on the patient chart. LOINC codes allow the system to surface abnormal patterns in the medical card's active conditions view.
- Developer/partner: REST CRUD at
GET|POST|PATCH|DELETE /api/v1/lab-results. Each result is identified by its FHIR DiagnosticReport numeric ID (not a UUID). Search supportspatientIdquery param and full-text fuzzy match ontestName. RequiresX-Clinic-IDheader. LOINC codes use systemhttp://loinc.org. - Internal (ops/support): Lab results are pure FHIR — no Prisma table for
LabResult. All data in HAPI. If a result is missing, check HAPI partition routing: theclinicSlugused in the write must match theX-Clinic-IDused in the read. Monitor HAPI atGET http://localhost:18080/fhir/metadata.
Architecture
LabResultsController (/api/v1/lab-results)
└── LabResultsService
└── IFhirClient (FHIR_CLIENT)
└── HAPI FHIR 8.4.0 (port 18080)
└── DiagnosticReport resources (partitioned by clinic)
LabResultsController metadata:
@ResourceMeta operations: [list, get, create, update, delete, count, search]
search.fields: [testName, testCode]
search.fuzzySupported: true
pagination.defaultSort: performedAt
The LabResultsController extends MetaMixin(MetaMixinBase), exposing a GET /lab-results/meta endpoint (RBAC-exempt) that returns field definitions, supported operations, and pagination config — used by the dynamic UI table renderer.
No ResourceFacade<R> is used here (lab results are Tier-3 FHIR-only, no fhir_resource_link bridge row). The service reads and writes FHIR directly through IFhirClient.
See coding_context/ddx-hms-context.md §Clinical Data for the module map.
Tech Stack & Choices
| Layer | Technology | Rationale |
|---|---|---|
| API | NestJS 11, @Controller('lab-results') | Uniform REST with Swagger auto-docs; ResourceMeta for dynamic UI |
| Auth | @RequireRole(DOCTOR, NURSE, CLINIC_ADMIN, SUPER_ADMIN) for write; PATIENT allowed on GET :id | Patients can view their own results; staff can view all |
| FHIR resource | DiagnosticReport (R4) | Standard HL7 type for lab reports; carries LOINC coding, subject (Patient), and effective date |
| Coding system | LOINC (http://loinc.org) | Universal lab test coding; required for external system interop |
| Storage | HAPI FHIR 8.4.0 (pure FHIR, no Prisma mirror) | Results never duplicated; single source of truth |
| FHIR client | IFhirClient (FHIR_CLIENT token) | Partition-aware; maps HAPI 404/400 to NestJS exceptions |
| Observations | ddx-api/src/clinical-data/observations/ | Separate module for FHIR Observation resources (individual test values within a DiagnosticReport) |
| Service requests | ddx-api/src/clinical-data/service-requests/ | FHIR ServiceRequest (lab orders placed before results exist) |
Key design decision: Lab results use numeric FHIR DiagnosticReport IDs (assigned by HAPI) as the external identifier — not UUIDs. This differs from Visit/Patient which use UUIDs. The controller's @ApiParam annotation explicitly documents 'FHIR DiagnosticReport ID' to avoid confusion.
Data Flow
Doctor records a lab result
Business outcome: A doctor receives a blood test result from the external lab and records it in the patient chart within 30 seconds.
Technical mechanism:
POST /api/v1/lab-resultswith body{ patientId, testName, testCode, status, performedAt }andX-Clinic-IDheader.LabResultsController.create()(lab-results.controller.ts:73) delegates toLabResultsService.create().LabResultsService.create()(lab-results.service.ts:44) constructs aFhirDiagnosticReportResource:code.coding[0]={ system: 'http://loinc.org', code: testCode, display: testName }subject.reference=Patient/{patientId}status='final'(default) or as specifiedeffectiveDateTime=performedAt
fhirClient.create<FhirDiagnosticReportResource>('DiagnosticReport', resource, clinicSlug)POSTs to HAPI with partition header.- HAPI assigns numeric ID; response is mapped to
LabResultResponseDtoand returned.
Doctor lists a patient's lab history
GET /api/v1/lab-results?patientId={uuid} → LabResultsService.findAll(clinicSlug, patientId) → fhirClient.search<FhirDiagnosticReportResource>('DiagnosticReport', { subject: 'Patient/{patientId}' }, clinicSlug) → mapped to LabResultResponseDto[].
Patient views own result
GET /api/v1/lab-results/:id with PATIENT role JWT → LabResultsService.findOne(id, clinicSlug) → fhirClient.read<FhirDiagnosticReportResource>('DiagnosticReport', id, clinicSlug). Patient authorization is enforced by GatewayAuthGuard (JWT contains role); no additional patient-ownership check is implemented at the service layer — the partition routing ensures the result belongs to the patient's clinic.
Partial update (e.g., status correction)
PATCH /api/v1/lab-results/:id → LabResultsService.update() (lab-results.service.ts:63) reads the existing resource, merges { status, performedAt, testName, testCode } fields, then calls fhirClient.update().
Implicated Code
ddx-api/src/clinical-data/lab-results/lab-results.controller.ts:33—LabResultsController—@ResourceMetadefinition: operations, fields (patientId, testName, testCode, status, performedAt), pagination, search configddx-api/src/clinical-data/lab-results/lab-results.controller.ts:73—create()—POST /lab-results; delegates to service withClinicId()contextddx-api/src/clinical-data/lab-results/lab-results.controller.ts:83—findAll()—GET /lab-results?patientId=; optional patient filterddx-api/src/clinical-data/lab-results/lab-results.controller.ts:92—findOne()—GET /lab-results/:id; PATIENT role allowedddx-api/src/clinical-data/lab-results/lab-results.service.ts:24—LabResultsService— injectsFHIR_CLIENT; pure FHIR service (no Prisma)ddx-api/src/clinical-data/lab-results/lab-results.service.ts:32—findAll()— FHIR search with optionalsubjectparamddx-api/src/clinical-data/lab-results/lab-results.service.ts:44—create()— buildsFhirDiagnosticReportResourcewith LOINC code andPatient/subject referenceddx-api/src/clinical-data/lab-results/lab-results.service.ts:63—update()— read-modify-write on HAPI DiagnosticReportddx-api/src/clinical-data/lab-results/lab-results.service.ts:80—mapToResponse()— maps FHIR DiagnosticReport →LabResultResponseDto(extracts patientId fromsubject.reference)ddx-api/src/clinical-data/observations/— FHIR Observation resources (individual test values, e.g., a single CBC component)ddx-api/src/clinical-data/diagnostic-reports/— extended DiagnosticReport handling (radiology reports with attachments)ddx-api/src/clinical-data/service-requests/— FHIR ServiceRequest for lab order management (pre-result state)
Operational Notes
- Numeric FHIR IDs, not UUIDs: Lab result IDs are HAPI-assigned numeric strings (e.g.,
"4912"). Do not pass UUIDs toGET /lab-results/:id— HAPI returns 404. This is the inverse of the Visit/Patient pattern which uses UUIDs as stable identifiers. - No Prisma mirror: Lab results have no
labResultPrisma table. If a result appears in HAPI but not in the API response, check partition routing: the writeclinicSlugmust equal the readX-Clinic-ID. A slug mismatch silently returns empty results. - Status vocabulary: FHIR DiagnosticReport
statusvalues are:registered,partial,preliminary,final,amended,corrected,appended,cancelled,entered-in-error,unknown. Thecreate()default isfinal; setregisteredfor pending results. - LOINC is optional: The
testCodefield is not required. Results without a LOINC code are stored withcode.textonly and are not classifiable by downstream decision-support tools. Encourage LOINC codes for structured results. - PATIENT role access: Patients can read their own individual lab results (
GET :id) but cannot list all results (GET /) — the list endpoint requires DOCTOR/NURSE/CLINIC_ADMIN/SUPER_ADMIN. Ownership enforcement relies on partition routing (patient's clinic matches the request clinic) not an explicit ownership check. - Fuzzy search:
@ResourceMetadeclaressearch.fuzzySupported: trueontestNameandtestCode. The dynamic UI uses this to enable client-side fuzzy filtering — the API itself uses FHIR text search.
Related Topics
- HAPI FHIR R4 Server — HAPI setup, partition routing, FhirClientService internals
- FHIR ResourceFacade — Higher-level FHIR storage primitive (used for Visit/Patient, not Lab Results)
- Clinical Visits — Lab results are associated with Encounters via
DiagnosticReport.encounterreference - Patients — Registration and Demographics — Patient FHIR IDs used as
subjectin DiagnosticReport - Medical Cards — Patient Health Summary — Lab result counts surfaced via PatientStatsService