BGL Assessment¶
Overview¶
The BGL Assessment feature provides a structured, guided workflow for recording and managing blood glucose level (BGL) readings for care recipients. Rather than a simple single-value entry, the assessment represents a full clinical protocol: an initial sensor or blood-test reading, then a branching state-machine for hypoglycemia (low BGL) or hyperglycemia (high BGL), each with timed treatment cycles, ketone measurements, and recheck loops. All assessments are persisted with full reading timelines and cycle details, and a history page presents them in an expandable table. The feature supports offline recording via IndexedDB and pushes timer notifications through the service worker.
Role Access¶
| Role | Create | View | Delete |
|---|---|---|---|
| SuperAdmin | ✓ (all CRs) | All | ✓ |
| Administrator | ✓ (all CRs) | All | ✓ |
| Carer | ✓ (linked CRs) | Linked CRs | — |
| SupportWorker | ✓ (linked CRs) | Linked CRs | — |
| CareRecipient | ✓ (self) | Own | — |
| HealthCareProvider | — | Linked CRs | — |
Backend¶
Controller: AssessmentController — /api/assessment¶
| Method | Route | Auth | Description |
|---|---|---|---|
| POST | /api/assessment |
Authorized | Record a new BGL assessment |
| GET | /api/assessment |
Authorized | List assessments scoped by role |
| DELETE | /api/assessment/{id} |
AdminOrHigher | Hard-delete an assessment |
Assessment Endpoint Details¶
POST /api/assessment
- Resolves CareRecipientId by role: CareRecipient → self; Carer/SupportWorker → must be a linked CR; Admin/SuperAdmin → any valid user.
- Reads termId from JWT claim and verifies the Cycle exists.
- Persists the Assessment entity including all JSON blobs.
- Sends push notification for critical outcomes: if outcome is CarerNotified or EmergencyRequiredCarerNotified, dispatches a AssessmentSeverity push to all linked carers.
GET /api/assessment
- Admin/SuperAdmin: returns all assessments.
- CareRecipient: returns own assessments.
- Carer/SupportWorker/HealthCareProvider: returns assessments for all linked CRs.
- Optional ?termId=N filter.
Key DTOs¶
CreateAssessmentDto
careRecipientId int? Omit for self-recording CareRecipient
initialReading string Numeric string, "LOW", or "HIGH"
type int 1=Normal, 2=Hypoglycemia, 3=Hyperglycemia
bloodTestReading decimal? Finger-prick confirmation value
recheckReading decimal? Post-treatment recheck value
treatmentCycles int Number of treatment cycles performed
ketoneLevel decimal? mmol/L ketone reading (required if BGL ≥ 15)
outcome int 1=Normal, 2=Resolved, 3=CarerNotified, 4=EmergencyRequiredCarerNotified
notes string? Free-text notes
durationSeconds int Total assessment duration
allReadingsJson string? JSON array of every reading taken
cycleDetailsJson string? JSON array of each treatment cycle detail
readingDate DateTime? Back-dated timestamp (defaults to server now if omitted)
clientTimestamp string? ISO string of client-side capture time
AssessmentDto (response)
id int
userId int
userName string
careRecipientId int?
careRecipientName string?
timestamp DateTime
initialReading string
type string "Hypoglycemia" | "Hyperglycemia" | "Normal"
bloodTestReading decimal?
recheckReading decimal?
treatmentCycles int
ketoneLevel decimal?
outcome string
notes string?
durationSeconds int
allReadingsJson string?
cycleDetailsJson string?
termName string?
Model: Assessment¶
| Field | Type | Notes |
|---|---|---|
Id |
int | PK |
UserId |
int | FK → User (recorder) |
CareRecipientId |
int? | FK → User (subject) |
Timestamp |
DateTime | Server-side record time |
InitialReading |
string | Sensor/strip value or "LOW"/"HIGH" |
Type |
AssessmentType | Normal / Hypoglycemia / Hyperglycemia |
BloodTestReading |
decimal? | Finger-prick |
RecheckReading |
decimal? | Post-treatment |
TreatmentCycles |
int | 0–4 |
KetoneLevel |
decimal? | mmol/L |
Outcome |
AssessmentOutcome | Normal → EmergencyRequiredCarerNotified |
Notes |
string? | |
TermId |
int? | FK → Cycle |
DurationSeconds |
int | |
AllReadingsJson |
string? | JSON array |
CycleDetailsJson |
string? | JSON array |
BGL Classification Service¶
File: server/src/Vitara.Api/Services/BglClassificationService.cs
- Holds CRUD for
BglClassificationRangeandKetoneThresholdentities. - Per-recipient ranges (where
CareRecipientIdis set) take priority over global defaults (whereCareRecipientIdis null). - Range matching:
MinValue <= value < MaxValue; null bounds are treated as unbounded. - Fallback: if no range matches (e.g., unexpected reading), defaults to
Hyperglycemia / Critical. GetEffectiveRangesAsync(careRecipientId?): returns the resolved active set of classification ranges + ketone thresholds for a given CR (or global).GetMaxBglValue(sensorValue, bloodTestValue): static helper — returns the higher of the two values for classification purposes.- Export endpoint
GET /api/bgl-classification/effective?careRecipientId=N— used byBglReadingComponentto load dynamic thresholds before starting an assessment.
Frontend¶
Routes¶
| Path | Component | Guard |
|---|---|---|
/assessment |
AssessmentHistoryComponent |
AuthGuard |
/assessment/reading |
BglReadingComponent |
AuthGuard, role check in component |
AssessmentHistoryComponent — /assessment¶
File: client/src/app/features/assessment/components/assessment-history.component.ts
On init:
- Calls AssessmentService.getAssessments(termId) (termId from AuthService.getTermId()).
- For Admin/SuperAdmin, also loads all users via GET /users to populate the user filter.
Table columns: Expand toggle | Timestamp | User | Care Recipient | Initial Reading | Assessment Type | Outcome | Treatment Cycles | Duration | Actions (delete for admins)
Expandable row: Shows the full allReadingsJson timeline (reading type, numeric value, timestamp) and each cycleDetailsJson entry (cycle number, start time, treatment type, recheck value).
Color coding:
- reading-normal: 4.0–7.89 mmol/L → green
- reading-warning: 7.9–14.99 mmol/L → amber
- reading-danger: ≥15 or <4 mmol/L → red
Duration formatting: Converts raw durationSeconds to "Xm Ys" display string.
Search: Client-side text filter across user name, care recipient name, type, outcome, reading values, and term name.
Excel export: GET /export/assessment-report?termId=N → downloads .xlsx blob.
BglReadingComponent — /assessment/reading¶
File: client/src/app/features/assessment/components/bgl-reading.component.ts
This is the core guided assessment workflow implemented as a state machine.
State Machine¶
initial
├── (BGL is LOW / < low threshold) → hypo-fingerprick
│ └── hypo-treatment (15-min countdown)
│ └── hypo-recheck
│ ├── (resolved, < 2 cycles) → complete
│ ├── (BGL=3.9 after cycle 2) → check-only cycle (up to 2 extra)
│ └── (not resolved, > 2 cycles) → carer-notified outcome
│
└── (BGL is HIGH / ≥ high threshold) → hyper-fingerprick
└── hyper-ketone
├── (ketone < 0.6) → monitoring-wait (2-hour countdown)
│ └── monitoring-recheck → complete
└── (ketone ≥ 0.6) → hyper-ketone-monitoring
└── hyper-ketone-recheck → complete / emergency
Care Recipient Selection¶
- Auto-selects if only one CR is linked.
- Shows a dropdown for carers/admins with multiple CRs.
- Admin uses
CareRecipientService.getAllActiveCareRecipients(); others usegetCareRecipients(). - On CR selection, loads effective BGL classification ranges via
BglClassificationService.getEffectiveRanges(careRecipientId).
Initial Reading¶
- Accepts numeric input (1.0–30.0 mmol/L) or text
LOW/HIGHfrom CGM. - Validates against loaded thresholds to determine the path (hypo vs hyper vs normal).
isKetoneOptional(): returnstruefor T2D care recipients — the ketone step shows a "Skip" button.
Timer Implementation¶
- Hypo treatment timer: 15 minutes (
BglRecheckWaitMinutesfrom AppConfig viaAppConfigService). - Hyper monitoring timer: 120 minutes.
- Page Visibility API correction: When the tab is hidden and restored (
visibilitychangeevent), the component recalculates elapsed time from a stored start timestamp to correct for timer drift. - Service Worker notification: At timer mid-points and expiry,
ServiceWorkerNotificationServiceschedules a local notification to alert even when the browser tab is not active.
Back-Dating¶
readingDateStrinput (datetime-local) defaults to the current time.- Sent to the API as
readingDatein the DTO; API uses this as the assessmentTimestamp.
Offline Fallback¶
- If the network request to
POST /assessmentfails with a network error or a 5xx status,AssessmentService.createAssessment()catches the error and enqueues the payload toOfflineQueueService(IndexedDB, 24-hour TTL, type'assessment'). - A toast notification informs the user that recording was saved locally.
Submission Payload¶
AssessmentService.createAssessment() sends the full CreateAssessmentDto including:
- allReadingsJson: every reading taken with type labels and timestamps.
- cycleDetailsJson: per-cycle detail objects with cycle number, treatment given, start time, and outcome reading.
- durationSeconds: Date.now() - startTime in seconds.
- clientTimestamp: ISO timestamp captured when the user tapped the first reading button.
Feature Service: AssessmentService¶
File: client/src/app/features/assessment/services/assessment.service.ts
| Method | HTTP | Endpoint | Notes |
|---|---|---|---|
createAssessment(dto) |
POST | /assessment |
Offline fallback via OfflineQueueService |
getAssessments(termId?) |
GET | /assessment?termId=N |
Role-scoped by server |
deleteAssessment(id) |
DELETE | /assessment/{id} |
Admin only |
getExportReport(termId?) |
GET | /export/assessment-report |
Returns Blob |
Shared Service: BglClassificationService¶
File: client/src/app/shared/services/bgl-classification.service.ts
| Method | HTTP | Endpoint |
|---|---|---|
getEffectiveRanges(careRecipientId?) |
GET | /bgl-classification/effective |
getRanges(careRecipientId?) |
GET | /bgl-classification/ranges |
createRange(dto) |
POST | /bgl-classification/ranges |
updateRange(id, dto) |
PUT | /bgl-classification/ranges/{id} |
deleteRange(id) |
DELETE | /bgl-classification/ranges/{id} |
getKetoneThresholds(careRecipientId?) |
GET | /bgl-classification/ketone-thresholds |
createKetoneThreshold(dto) |
POST | /bgl-classification/ketone-thresholds |
updateKetoneThreshold(id, dto) |
PUT | /bgl-classification/ketone-thresholds/{id} |
deleteKetoneThreshold(id) |
DELETE | /bgl-classification/ketone-thresholds/{id} |
AppConfig Keys Relevant to This Feature¶
| Key | Default | Description |
|---|---|---|
BglRecheckWaitMinutes |
15 | Hypo treatment cycle wait |
BglAdditionalWaitMinutes |
15 | Additional check-only cycle wait |
HyperGlucoseReminderIntervalMinutes |
120 | Hyper monitoring timer |
HypoMaxTreatmentCycles |
2 | Max full treatment cycles |
HypoMaxCheckOnlyCycles |
2 | Max extra check-only cycles after cycle 2 |
Push Notifications Sent¶
| Trigger | Notification Type | Recipients |
|---|---|---|
| Outcome = CarerNotified | AssessmentSeverity (3) |
All linked carers |
| Outcome = EmergencyRequiredCarerNotified | AssessmentSeverity (3) |
All linked carers |
| Timer expires (in-browser) | BgTimerReminder (6) |
Local service-worker notification only |
End-to-End Data Flow¶
Carer/CareRecipient Angular API DB
| | | |
| Select care recipient | | |
| |-- GET /bgl-classification/effective? --> |
| |<-- classification ranges --| |
| | | |
| Enter initial reading (e.g. 2.8)| | |
| | state → hypo-fingerprick | |
| Wait 15-min timer | | |
| Enter treatment (8g carbs) | state → hypo-treatment | |
| Timer expires | state → hypo-recheck | |
| Enter recheck reading (5.2) | state → complete | |
| | | |
| Confirm & submit | | |
| |-- POST /assessment ------->| |
| | |-- INSERT Assessment->|
| | | if carers notified: |
| | |-- push to carers |
| |<-- AssessmentDto ----------| |
|<-- navigate /assessment | | |