Clinical Assessments — Structured Patient Evaluations
Audiences: doctor, nurse, clinical-buyer, developer
Clinical assessments are completed instances of assessment form definitions — when a clinician assigns a PHQ-9 or GAF scale to a patient, the resulting filled form is a FHIR
QuestionnaireResponsebacked by a PrismaAssessmentFormInstance, progressing through a reviewed lifecycle.
Business Purpose
Assessment scales and structured evaluations (PHQ-9, GAF, ADL, pain scales, medication adherence questionnaires) are essential clinical tools. Without a digital instance lifecycle, scores are recorded in paper notes, are not queryable over time, and cannot feed AI models or trigger clinical alerts.
Dudoxx HMS models each completed evaluation as an AssessmentFormInstance with:
- A clear status machine:
DRAFT → IN_PROGRESS → SUBMITTED → APPROVED/REJECTED. - A FHIR
QuestionnaireResponseresource created on submission — making assessment data interoperable. - A review workflow: clinicians can approve or reject submitted assessments with a reason.
- Guest submission support: patients can complete assessments without logging in via time-limited public tokens.
- Proactive form assignment: the system can push assessment forms to patients at defined intervals or clinical triggers.
Audiences
- Investor: Scored assessments (PHQ-9, GAF) create a longitudinal patient mental health record that feeds AI trend analysis and enables value-based care reporting.
- Clinical buyer (doctor/nurse/receptionist): Clinicians assign assessments to patients and review submissions. The approval workflow ensures clinicians sign off on patient-reported outcomes before they enter the record.
- Developer/partner: Create instance:
POST /api/v1/assessment-form-instances. Submit:PATCH /api/v1/assessment-form-instances/:id/submit. Review:PATCH /api/v1/assessment-form-instances/:id/approveor/reject. Guest submit:POST /api/v1/public-forms/:token/submit. - Internal (ops/support): Instance data in Prisma
ddx_api_main(AssessmentFormInstance,AssessmentFormInstanceAttachment). FHIRQuestionnaireResponsecreated on submission. Proactive forms triggered viaProactiveFormsController(scheduled or event-driven).
Architecture
AssessmentFormInstancesController (/api/v1/assessment-form-instances)
└── AssessmentFormInstancesService — orchestrator
├── FormInstanceQueryService — list/filter instances by patient, form, status
├── FormInstanceSubmitService — SUBMITTED transition + FHIR QuestionnaireResponse write
├── FormInstanceReviewService — APPROVED/REJECTED transitions with reason
├── FormInstancePatientService — patient-scoped access (no cross-patient leakage)
└── QuestionnaireResponseDescriptor — FHIR resource sync
GuestSubmissionsController (/api/v1/public-forms/:token)
└── PublicFormAccessService — token validation + guest submission routing
ProactiveFormsController (/api/v1/proactive-forms)
└── (assignment push logic) — assigns forms to patients based on triggers/schedule
The QuestionnaireResponseDescriptor mirrors the pattern from encounter.descriptor.ts (NF2 facade) — on submission, it creates a FHIR QuestionnaireResponse resource in HAPI FHIR and upserts a fhir_resource_link row in Prisma.
Tech Stack & Choices
| Layer | Technology | Notes |
|---|---|---|
| Instance storage | Prisma ddx_api_main (AssessmentFormInstance) | Status machine, timestamps, assignedBy, patient link |
| FHIR sync | HAPI FHIR QuestionnaireResponse via descriptor | Created on SUBMITTED transition |
| Instance attachments | MinIO via AssessmentFormInstanceAttachmentsService | Attached files to assessment submissions |
| Guest access | Time-limited public tokens (PublicFormAccessService) | No JWT required; token scope: single form |
| Status machine | FormInstanceStatus enum | DRAFT → IN_PROGRESS → SUBMITTED → APPROVED/REJECTED/CANCELLED |
| Auth | @ProtectedRead/Write/Manage('assessment-forms') | Role-permission model; PATIENT role can access own instances via patient service |
| Zod | zod/v4 for DTO validation | NestJS canonical Zod import |
Data Flow
Clinician assigns and submits an assessment
Business outcome: Clinician assigns a PHQ-9 to a patient; patient fills it in (or clinician fills on behalf); clinician reviews and approves — score enters the patient's structured record.
Technical mechanism:
POST /api/v1/assessment-form-instanceswith{ formDefinitionId, patientId, assignedBy }→ creates instance inDRAFTstatus.- Clinician/patient fills field values →
PATCH .../starttransitions toIN_PROGRESS. PATCH .../submitwith field answers →FormInstanceSubmitService.submit(): a. Validates required fields are answered. b. Transitions status toSUBMITTED. c.QuestionnaireResponseDescriptorcreates FHIRQuestionnaireResponsewith answer items mapped from field values. d.fhir_resource_linkPrisma row upserted for cross-store audit.PATCH .../approvewith{ reviewerId, reason }→FormInstanceReviewService.approve()→ statusAPPROVED.
Patient completes a guest form
POST /api/v1/public-forms/:token/submit → GuestSubmissionsController validates token (time-limited, form-scoped), routes submission to FormInstanceSubmitService. No JWT required — guest route is @RbacExempt().
Implicated Code
ddx-api/src/clinical-forms/assessment-forms/assessment-form-instances.controller.ts:1—AssessmentFormInstancesController— instance lifecycle (create, start, submit, approve, reject)ddx-api/src/clinical-forms/assessment-forms/assessment-form-instances.service.ts:1—AssessmentFormInstancesService— orchestrates 4 sub-servicesddx-api/src/clinical-forms/assessment-forms/form-instance-submit.service.ts:1—FormInstanceSubmitService— SUBMITTED transition + FHIR QuestionnaireResponse writeddx-api/src/clinical-forms/assessment-forms/form-instance-review.service.ts:1—FormInstanceReviewService— APPROVED/REJECTED transitions with reason textddx-api/src/clinical-forms/assessment-forms/form-instance-query.service.ts:1—FormInstanceQueryService— paginated queries by patient, form definition, statusddx-api/src/clinical-forms/assessment-forms/form-instance-patient.service.ts:1—FormInstancePatientService— patient-scoped instance access (no cross-patient data)ddx-api/src/clinical-forms/assessment-forms/guest-submissions.controller.ts:1—GuestSubmissionsController— unauthenticated form submission via public tokenddx-api/src/clinical-forms/assessment-forms/proactive-forms.controller.ts:1—ProactiveFormsController— push form assignments to patientsddx-api/src/clinical-forms/assessment-forms/questionnaire-response.descriptor.ts:1— FHIRQuestionnaireResponseresource descriptor
Operational Notes
- FHIR write on submission: Every
SUBMITTEDtransition triggers a FHIRQuestionnaireResponsewrite. If HAPI FHIR is unavailable, submission fails. Ensure HAPI FHIR health before high-volume assessment campaigns. - Guest token security: Public form tokens are time-limited but not single-use by default. Evaluate rate-limiting and expiry settings before exposing patient-facing assessment URLs. Token management:
PublicFormAccessService+AssessmentFormsControllerpublic token endpoints. - Status is terminal after APPROVED/REJECTED: Once an instance reaches
APPROVEDorREJECTED, it cannot be re-submitted. If a patient needs to redo an assessment, create a new instance. - Patient service isolation:
FormInstancePatientServiceenforces patient-scoped access — a patient cannot read another patient's assessment instances even if they know the instance ID. This is enforced at service layer, not only by RBAC. - Attachment MIME types:
AssessmentFormInstanceAttachmentsServicestores files in MinIO. Validate MIME type allowlist before enabling attachment upload on public-facing forms.
Related Topics
- Clinical Forms Builder — Assessment form definitions that power instances
- Intake Engine — Functional assessments (GAF, ADL) captured during intake
- Clinical Visits — Assessments often linked to a specific visit
- Diagnosis Engine — Assessment scores feed AI diagnostic evaluation
- Clinical Activities — Assessment submissions appear in patient timeline