Prechádzať zdrojové kódy

Alert taxonomy: broaden categories to cover CGM + uncertain delivery

Driven by the per-manager alert audit (951 emission sites across all
pump + CGM managers). User-facing UI is unchanged — three tiers still.
What changes is which alerts get classified, surface as alarms, and
fire at which tier.

* TrioAlertCategory: replace pump-specific cases with broader buckets.
  - .pumpFault → .hardwareFault (covers pump + transmitter + sensor
    hardware faults: occlusion, fault codes, transmitter critical
    fault, vibration motor, battery error, device reset, etc.)
  - .podExpired → .deviceExpired (pod expired, sensor expired,
    transmitter EOL — same tier regardless of source)
  - .podExpirationReminder → .deviceExpirationReminder
  - New .deliveryUncertain — N3 from the audit, distinct from
    .bolusFailed; covers unacknowledgedMessage,
    unacknowledgedCommandPending, uncertain bolus delivery
  - New .sensorFailure — N7 from the audit; sensor stopped/failed,
    invalid sensor, temperature out of range, glucose suspended
* TrioAlertClassifier substring tables expanded with the actual
  emission identifiers from the audit (autoOff, deviceReset,
  reprogram, unexpectedAlert, criticalFault, vibrationCurrent,
  batteryError, transmitterError, sensorFailed, sensorTemperature,
  transmitterEOL, sensorRetired, sensorAgedOut, etc.).
* Error-path classifier picks up uncertain delivery from error
  descriptions (uncertainDelivery, unacknowledged, "bolus may have
  failed").
* PumpAlertCategory renamed to match: hardwareFault, deviceExpired,
  deviceExpirationReminder, deliveryUncertain, sensorFailure. Default
  severity mapping: hardwareFault + deliveryUncertain → Critical;
  sensorFailure + deviceExpired → Time-Sensitive;
  deviceExpirationReminder → Normal.
* APSManager.issueAlertForCategory switch updated for the renamed
  cases.
Deniz Cengiz 4 týždňov pred
rodič
commit
57ee5f06f5

+ 5 - 1
Trio/Sources/APS/APSManager.swift

@@ -1283,13 +1283,17 @@ final class BaseAPSManager: APSManager, Injectable {
         let alertId: String
         let alertId: String
         switch category {
         switch category {
         case .bolusFailed: alertId = "bolusFailed"
         case .bolusFailed: alertId = "bolusFailed"
-        case .pumpFault: alertId = "pumpFault"
+        case .hardwareFault: alertId = "hardwareFault"
+        case .deliveryUncertain: alertId = "deliveryUncertain"
         case .occlusion: alertId = "occlusion"
         case .occlusion: alertId = "occlusion"
         case .reservoirEmpty: alertId = "reservoirEmpty"
         case .reservoirEmpty: alertId = "reservoirEmpty"
         case .reservoirLow: alertId = "reservoirLow"
         case .reservoirLow: alertId = "reservoirLow"
         case .batteryEmpty: alertId = "batteryEmpty"
         case .batteryEmpty: alertId = "batteryEmpty"
         case .batteryLow: alertId = "batteryLow"
         case .batteryLow: alertId = "batteryLow"
         case .manualTempBasalActive: alertId = "manualTempBasalActive"
         case .manualTempBasalActive: alertId = "manualTempBasalActive"
+        case .sensorFailure: alertId = "sensorFailure"
+        case .deviceExpired: alertId = "deviceExpired"
+        case .deviceExpirationReminder: alertId = "deviceExpirationReminder"
         case .glucoseDataStale: alertId = "glucoseDataStale"
         case .glucoseDataStale: alertId = "glucoseDataStale"
         default: alertId = "general"
         default: alertId = "general"
         }
         }

+ 22 - 14
Trio/Sources/Models/GlucoseAlerts/PumpAlertCategory.swift

@@ -3,18 +3,20 @@ import LoopKit
 
 
 /// Fixed categories of pump / device alarms. Each maps to a
 /// Fixed categories of pump / device alarms. Each maps to a
 /// `DeviceAlertSeverity` tier — the user configures three tiers globally
 /// `DeviceAlertSeverity` tier — the user configures three tiers globally
-/// (Critical / Time-Sensitive / Normal), not 13 categories individually.
+/// (Critical / Time-Sensitive / Normal), not 13+ categories individually.
 enum PumpAlertCategory: String, Codable, CaseIterable, Identifiable {
 enum PumpAlertCategory: String, Codable, CaseIterable, Identifiable {
     case occlusion
     case occlusion
-    case pumpFault
+    case hardwareFault
     case reservoirEmpty
     case reservoirEmpty
     case reservoirLow
     case reservoirLow
     case batteryEmpty
     case batteryEmpty
     case batteryLow
     case batteryLow
     case bolusFailed
     case bolusFailed
+    case deliveryUncertain
     case manualTempBasalActive
     case manualTempBasalActive
-    case podExpirationReminder
-    case podExpired
+    case sensorFailure
+    case deviceExpirationReminder
+    case deviceExpired
     case podShutdownImminent
     case podShutdownImminent
     case suspendTimeExpired
     case suspendTimeExpired
     case glucoseDataStale
     case glucoseDataStale
@@ -24,16 +26,18 @@ enum PumpAlertCategory: String, Codable, CaseIterable, Identifiable {
     var displayName: String {
     var displayName: String {
         switch self {
         switch self {
         case .occlusion: return String(localized: "Occlusion")
         case .occlusion: return String(localized: "Occlusion")
-        case .pumpFault: return String(localized: "Pump Fault")
+        case .hardwareFault: return String(localized: "Hardware Fault")
         case .reservoirEmpty: return String(localized: "Reservoir Empty")
         case .reservoirEmpty: return String(localized: "Reservoir Empty")
         case .reservoirLow: return String(localized: "Reservoir Low")
         case .reservoirLow: return String(localized: "Reservoir Low")
         case .batteryEmpty: return String(localized: "Battery Empty")
         case .batteryEmpty: return String(localized: "Battery Empty")
         case .batteryLow: return String(localized: "Battery Low")
         case .batteryLow: return String(localized: "Battery Low")
         case .bolusFailed: return String(localized: "Bolus Failed")
         case .bolusFailed: return String(localized: "Bolus Failed")
+        case .deliveryUncertain: return String(localized: "Delivery Uncertain")
         case .manualTempBasalActive: return String(localized: "Manual Temp Basal Active")
         case .manualTempBasalActive: return String(localized: "Manual Temp Basal Active")
-        case .podExpirationReminder: return String(localized: "Pod Expiration Reminder")
-        case .podExpired: return String(localized: "Pod Expired")
-        case .podShutdownImminent: return String(localized: "Pod Shutdown Imminent")
+        case .sensorFailure: return String(localized: "Sensor Failure")
+        case .deviceExpirationReminder: return String(localized: "Device Expiration Reminder")
+        case .deviceExpired: return String(localized: "Device Expired")
+        case .podShutdownImminent: return String(localized: "Shutdown Imminent")
         case .suspendTimeExpired: return String(localized: "Suspend Time Expired")
         case .suspendTimeExpired: return String(localized: "Suspend Time Expired")
         case .glucoseDataStale: return String(localized: "Glucose Data Stale")
         case .glucoseDataStale: return String(localized: "Glucose Data Stale")
         }
         }
@@ -43,20 +47,22 @@ enum PumpAlertCategory: String, Codable, CaseIterable, Identifiable {
     var defaultSeverity: DeviceAlertSeverity {
     var defaultSeverity: DeviceAlertSeverity {
         switch self {
         switch self {
         case .batteryEmpty,
         case .batteryEmpty,
+             .deliveryUncertain,
+             .hardwareFault,
              .occlusion,
              .occlusion,
-             .pumpFault,
              .reservoirEmpty:
              .reservoirEmpty:
             return .critical
             return .critical
         case .batteryLow,
         case .batteryLow,
              .bolusFailed,
              .bolusFailed,
+             .deviceExpired,
              .glucoseDataStale,
              .glucoseDataStale,
              .manualTempBasalActive,
              .manualTempBasalActive,
-             .podExpired,
              .podShutdownImminent,
              .podShutdownImminent,
              .reservoirLow,
              .reservoirLow,
+             .sensorFailure,
              .suspendTimeExpired:
              .suspendTimeExpired:
             return .timeSensitive
             return .timeSensitive
-        case .podExpirationReminder:
+        case .deviceExpirationReminder:
             return .normal
             return .normal
         }
         }
     }
     }
@@ -64,15 +70,17 @@ enum PumpAlertCategory: String, Codable, CaseIterable, Identifiable {
     init?(trioCategory: TrioAlertCategory) {
     init?(trioCategory: TrioAlertCategory) {
         switch trioCategory {
         switch trioCategory {
         case .occlusion: self = .occlusion
         case .occlusion: self = .occlusion
-        case .pumpFault: self = .pumpFault
+        case .hardwareFault: self = .hardwareFault
         case .reservoirEmpty: self = .reservoirEmpty
         case .reservoirEmpty: self = .reservoirEmpty
         case .reservoirLow: self = .reservoirLow
         case .reservoirLow: self = .reservoirLow
         case .batteryEmpty: self = .batteryEmpty
         case .batteryEmpty: self = .batteryEmpty
         case .batteryLow: self = .batteryLow
         case .batteryLow: self = .batteryLow
         case .bolusFailed: self = .bolusFailed
         case .bolusFailed: self = .bolusFailed
+        case .deliveryUncertain: self = .deliveryUncertain
         case .manualTempBasalActive: self = .manualTempBasalActive
         case .manualTempBasalActive: self = .manualTempBasalActive
-        case .podExpirationReminder: self = .podExpirationReminder
-        case .podExpired: self = .podExpired
+        case .sensorFailure: self = .sensorFailure
+        case .deviceExpirationReminder: self = .deviceExpirationReminder
+        case .deviceExpired: self = .deviceExpired
         case .podShutdownImminent: self = .podShutdownImminent
         case .podShutdownImminent: self = .podShutdownImminent
         case .suspendTimeExpired: self = .suspendTimeExpired
         case .suspendTimeExpired: self = .suspendTimeExpired
         case .glucoseDataStale: self = .glucoseDataStale
         case .glucoseDataStale: self = .glucoseDataStale

+ 131 - 47
Trio/Sources/Services/Alerts/TrioAlertCategory.swift

@@ -1,19 +1,42 @@
 import Foundation
 import Foundation
 import LoopKit
 import LoopKit
 
 
+/// Coarse-grained classification of an incoming LoopKit `Alert` or a Swift
+/// `Error` caught at the `APSManager` boundary. The user never sees these
+/// directly — they pick a *severity tier* on the Device Alarms screen, and
+/// each category maps to a tier via `PumpAlertCategory.defaultSeverity`.
+///
+/// Buckets follow the manager-audit taxonomy:
+///   - N1 Hardware Fault        → `.hardwareFault`
+///   - N2 Delivery Stopped      → `.suspendTimeExpired` / pump-managed
+///   - N3 Uncertain Delivery    → `.deliveryUncertain`
+///   - N4 Reservoir Empty       → `.reservoirEmpty`
+///   - N5 Battery Dead          → `.batteryEmpty`
+///   - N6 Device Expired        → `.deviceExpired` (pod / sensor / transmitter)
+///   - N7 Sensor / Session Fail → `.sensorFailure`
+///   - F1 Insulin Low           → `.reservoirLow`
+///   - F2 Battery Low           → `.batteryLow`
+///   - F3 Expiration Approaching → `.deviceExpirationReminder`
+///   - Bolus failure (confirmed) → `.bolusFailed`
+///   - Glucose alarms           → `.glucose*` (owned by `GlucoseAlertCoordinator`)
+///   - N8 Connectivity blips    → `.commsTransient` (dwell-suppressed)
+///   - Algorithm error          → `.algorithmError` (dwell-suppressed)
+///   - Other unclassified       → `.other(String)`
 enum TrioAlertCategory: Equatable {
 enum TrioAlertCategory: Equatable {
     case occlusion
     case occlusion
     case reservoirLow
     case reservoirLow
     case reservoirEmpty
     case reservoirEmpty
     case batteryLow
     case batteryLow
     case batteryEmpty
     case batteryEmpty
-    case pumpFault
-    case podExpirationReminder
-    case podExpired
+    case hardwareFault
+    case deliveryUncertain
+    case deviceExpirationReminder
+    case deviceExpired
     case podShutdownImminent
     case podShutdownImminent
     case suspendTimeExpired
     case suspendTimeExpired
     case bolusFailed
     case bolusFailed
     case manualTempBasalActive
     case manualTempBasalActive
+    case sensorFailure
     case glucoseUrgentLow
     case glucoseUrgentLow
     case glucoseLow
     case glucoseLow
     case glucoseForecastedLow
     case glucoseForecastedLow
@@ -28,20 +51,22 @@ enum TrioAlertCategory: Equatable {
         case .batteryEmpty,
         case .batteryEmpty,
              .batteryLow,
              .batteryLow,
              .bolusFailed,
              .bolusFailed,
+             .deliveryUncertain,
+             .deviceExpirationReminder,
+             .deviceExpired,
              .glucoseDataStale,
              .glucoseDataStale,
              .glucoseForecastedLow,
              .glucoseForecastedLow,
              .glucoseHigh,
              .glucoseHigh,
              .glucoseLow,
              .glucoseLow,
              .glucoseUrgentLow,
              .glucoseUrgentLow,
+             .hardwareFault,
              .manualTempBasalActive,
              .manualTempBasalActive,
              .occlusion,
              .occlusion,
              .other,
              .other,
-             .podExpirationReminder,
-             .podExpired,
              .podShutdownImminent,
              .podShutdownImminent,
-             .pumpFault,
              .reservoirEmpty,
              .reservoirEmpty,
              .reservoirLow,
              .reservoirLow,
+             .sensorFailure,
              .suspendTimeExpired:
              .suspendTimeExpired:
             return true
             return true
         case .algorithmError,
         case .algorithmError,
@@ -53,27 +78,29 @@ enum TrioAlertCategory: Equatable {
     var interruptionLevel: Alert.InterruptionLevel {
     var interruptionLevel: Alert.InterruptionLevel {
         switch self {
         switch self {
         case .batteryEmpty,
         case .batteryEmpty,
+             .deliveryUncertain,
              .glucoseUrgentLow,
              .glucoseUrgentLow,
+             .hardwareFault,
              .occlusion,
              .occlusion,
-             .pumpFault,
              .reservoirEmpty:
              .reservoirEmpty:
             return .critical
             return .critical
         case .batteryLow,
         case .batteryLow,
              .bolusFailed,
              .bolusFailed,
+             .deviceExpired,
              .glucoseDataStale,
              .glucoseDataStale,
              .glucoseForecastedLow,
              .glucoseForecastedLow,
              .glucoseHigh,
              .glucoseHigh,
              .glucoseLow,
              .glucoseLow,
              .manualTempBasalActive,
              .manualTempBasalActive,
-             .podExpired,
              .podShutdownImminent,
              .podShutdownImminent,
              .reservoirLow,
              .reservoirLow,
+             .sensorFailure,
              .suspendTimeExpired:
              .suspendTimeExpired:
             return .timeSensitive
             return .timeSensitive
         case .algorithmError,
         case .algorithmError,
              .commsTransient,
              .commsTransient,
-             .other,
-             .podExpirationReminder:
+             .deviceExpirationReminder,
+             .other:
             return .active
             return .active
         }
         }
     }
     }
@@ -81,47 +108,98 @@ enum TrioAlertCategory: Equatable {
 
 
 enum TrioAlertClassifier {
 enum TrioAlertClassifier {
     /// Classify a LoopKit alert identifier coming from a pump or CGM manager.
     /// Classify a LoopKit alert identifier coming from a pump or CGM manager.
-    /// Returns `.other(identifier)` for anything not recognized — keeps untyped
-    /// alerts flowing through but marks them as not-alert-worthy by default.
+    /// Substring-matched on the lowercased identifier. Returns `.other(identifier)`
+    /// for anything unrecognized.
     ///
     ///
-    /// Coverage mapping by pump manager:
-    ///   - Omni family (OmniBLE / OmniKit / OmnipodKit): `userPodExpiration`,
-    ///     `podExpiring`, `podExpireImminent`, `lowReservoir`, `suspendEnded`
-    ///     (+ untyped: `suspendInProgress`, `finishSetupReminder`,
-    ///     `unexpectedAlert`, `timeOffsetChangeDetected`).
-    ///   - DanaKit: `occlusion`, `pumpError`, `lowBattery`, `batteryZeroPercent`,
-    ///     `shutdown`, `emptyReservoir`, `remainingInsulinLevel`, `checkShaft`
-    ///     (+ untyped: `basalCompare`, `bloodSugarMeasure`, `basalMax`,
-    ///     `dailyMax`, `bloodSugarCheckMiss`, `ble5InvalidKeys`, `unknown`).
-    ///   - MinimedKit: `lowRLBattery`, `PumpBatteryLow`, `PumpReservoirEmpty`,
-    ///     `PumpReservoirLow`.
-    ///   - MedtrumKit: bypasses LoopKit `AlertIssuer` and posts to
-    ///     `UNUserNotificationCenter` directly — alerts do not reach this
-    ///     classifier today. Requires a submodule change to issue
-    ///     `delegate?.issueAlert(_:)` instead.
+    /// Coverage notes (manager audit):
+    ///   - Pumps: OmniBLE / OmniKit / OmnipodKit / DanaKit / MinimedKit /
+    ///     MedtrumKit (MedtrumKit bypasses LoopKit `AlertIssuer` today —
+    ///     submodule change required to route through this classifier).
+    ///   - CGMs: CGMBLEKit (Dexcom G5/G6), G7SensorKit, LibreTransmitter,
+    ///     EversenseKit, NightscoutRemoteCGM, dexcom-share-client-swift.
     static func categorize(alertIdentifier: String) -> TrioAlertCategory {
     static func categorize(alertIdentifier: String) -> TrioAlertCategory {
         let id = alertIdentifier.lowercased()
         let id = alertIdentifier.lowercased()
-        if id.contains("occlusion") { return .occlusion }
-        if id.contains("reservoirempty") || id.contains("emptyreservoir") { return .reservoirEmpty }
+
+        // Glucose family — owned by GlucoseAlertCoordinator, return early so the
+        // pump/device interception in TrioAlertManager doesn't apply tier config.
+        if id.contains("glucose.urgentlow") || id.contains("glucoseurgentlow") { return .glucoseUrgentLow }
+        if id.contains("glucose.forecastedlow") || id.contains("glucoseforecastedlow") { return .glucoseForecastedLow }
+        if id.contains("glucose.low") || id.contains("glucoselow") { return .glucoseLow }
+        if id.contains("glucose.high") || id.contains("glucosehigh") { return .glucoseHigh }
+        if id.contains("glucose"), id.contains("stale") { return .glucoseDataStale }
+
+        // Hard-fail device states first (most severe wins on substring overlap).
+        if id.contains("occlusion") || id.contains("occluded") { return .occlusion }
+        if id.contains("reservoirempty") || id.contains("emptyreservoir") || id.contains("nodelivery")
+            || id.contains("reservoirempty")
+        {
+            return .reservoirEmpty
+        }
+        if id.contains("batteryempty") || id.contains("batteryzero") || id.contains("batterydepleted")
+            || id.contains("batteryout") || id.contains("emptybattery")
+        {
+            return .batteryEmpty
+        }
+
+        // Uncertain delivery — N3, must come before generic bolusFailed.
+        if id.contains("unacknowledged") || id.contains("uncertaindelivery") || id.contains("uncertain delivery")
+            || id.contains("delivery-uncertain") || id.contains("unabletoreachpod") || id.contains("commsrecovery")
+        {
+            return .deliveryUncertain
+        }
+
+        // Hardware fault — N1. Covers pump faults, transmitter critical faults,
+        // CGM hardware errors.
+        if id.contains("fault") || id.contains("pumperror") || id.contains("checkshaft")
+            || id.contains("autooff") || id.contains("auto-off") || id.contains("devicereset")
+            || id.contains("reprogram") || id.contains("unexpectedalert") || id.contains("criticalfault")
+            || id.contains("vibrationcurrent") || id.contains("batteryerror") || id.contains("transmittererror")
+            || id == "shutdown" || id.contains("unknownalarm")
+        {
+            return .hardwareFault
+        }
+
+        // Sensor / session failure — N7 (CGM-side).
+        if id.contains("sensorfailed") || id.contains("sensor.failed") || id.contains("sensorstopped")
+            || id.contains("sensorerror") || id.contains("invalidsensor") || id.contains("encryptedsensor")
+            || id.contains("sensortemperature") || id.contains("sensorlowtemperature")
+            || id.contains("readertemperature") || id.contains("sensorretirement") || id.contains("nosensordetected")
+            || id.contains("transmitterdisconnected") || id.contains("glucosesuspended")
+            || id.contains("sensorconnection")
+        {
+            return .sensorFailure
+        }
+
+        // Device expired — N6 (pod, sensor, transmitter end-of-life).
+        if id.contains("podexpired") || id.contains("podexpiring") || id.contains("sensorexpired")
+            || id.contains("sensorretired") || id.contains("transmittereol") || id.contains("sensoragedout")
+            || id.contains("mspalarm") || id.contains("expiredsensor") || id.contains("sensorgrace")
+            || (id.contains("expired") && !id.contains("suspendtimeexpired"))
+        {
+            return .deviceExpired
+        }
+        if id.contains("shutdownimminent") || id.contains("expireimminent") { return .podShutdownImminent }
+        if id.contains("expirationreminder") || id.contains("userpodexpiration") || id.contains("retiringsoon")
+            || id.contains("sensorending") || id.contains("calibrationgrace") || id.contains("gracePeriod".lowercased())
+        {
+            return .deviceExpirationReminder
+        }
+
+        // Low-supply warnings — F1 / F2.
         if id.contains("lowreservoir") || id.contains("reservoirlow") || id.contains("remaininginsulin") {
         if id.contains("lowreservoir") || id.contains("reservoirlow") || id.contains("remaininginsulin") {
             return .reservoirLow
             return .reservoirLow
         }
         }
-        if id.contains("batteryempty") || id.contains("batteryzero") { return .batteryEmpty }
-        if id.contains("lowbattery") || id.contains("batterylow") || id.contains("rlbattery") { return .batteryLow }
-        if id.contains("shutdownimminent") || id.contains("expireimminent") { return .podShutdownImminent }
-        if id.contains("podexpired") || id.contains("podexpiring") || id.contains("expired") { return .podExpired }
-        if id.contains("expirationreminder") || id.contains("userpodexpiration") { return .podExpirationReminder }
-        if id.contains("suspendtimeexpired") || id.contains("suspendended") { return .suspendTimeExpired }
-        if id.contains("fault") || id.contains("pumperror") || id.contains("checkshaft") || id == "shutdown" {
-            return .pumpFault
+        if id.contains("lowbattery") || id.contains("batterylow") || id.contains("rlbattery")
+            || id.contains("verylowbattery") || id.contains("batterystatus")
+        {
+            return .batteryLow
         }
         }
+
+        // Bolus + delivery state.
         if id.contains("bolusfailed") { return .bolusFailed }
         if id.contains("bolusfailed") { return .bolusFailed }
+        if id.contains("suspendtimeexpired") || id.contains("suspendended") { return .suspendTimeExpired }
         if id.contains("manualtempbasal") { return .manualTempBasalActive }
         if id.contains("manualtempbasal") { return .manualTempBasalActive }
-        if id.contains("glucose.urgentlow") || id.contains("glucoseurgentlow") { return .glucoseUrgentLow }
-        if id.contains("glucose.forecastedlow") || id.contains("glucoseforecastedlow") { return .glucoseForecastedLow }
-        if id.contains("glucose.low") || id.contains("glucoselow") { return .glucoseLow }
-        if id.contains("glucose.high") || id.contains("glucosehigh") { return .glucoseHigh }
-        if id.contains("glucose"), id.contains("stale") { return .glucoseDataStale }
+
         return .other(alertIdentifier)
         return .other(alertIdentifier)
     }
     }
 
 
@@ -133,7 +211,7 @@ enum TrioAlertClassifier {
             case let .pumpError(inner):
             case let .pumpError(inner):
                 return categorize(pumpError: inner)
                 return categorize(pumpError: inner)
             case .invalidPumpState:
             case .invalidPumpState:
-                return .pumpFault
+                return .hardwareFault
             case .glucoseError:
             case .glucoseError:
                 return .glucoseDataStale
                 return .glucoseDataStale
             case .apsError:
             case .apsError:
@@ -147,17 +225,23 @@ enum TrioAlertClassifier {
 
 
     private static func categorize(pumpError: Error) -> TrioAlertCategory {
     private static func categorize(pumpError: Error) -> TrioAlertCategory {
         let description = String(describing: pumpError).lowercased()
         let description = String(describing: pumpError).lowercased()
-        if description.contains("occlusion") { return .occlusion }
+        if description.contains("uncertaindelivery") || description.contains("unacknowledged")
+            || description.contains("bolus may have failed")
+        {
+            return .deliveryUncertain
+        }
+        if description.contains("occlusion") || description.contains("occluded") { return .occlusion }
         if description.contains("reservoirempty") || description.contains("emptyreservoir") { return .reservoirEmpty }
         if description.contains("reservoirempty") || description.contains("emptyreservoir") { return .reservoirEmpty }
         if description.contains("lowreservoir") { return .reservoirLow }
         if description.contains("lowreservoir") { return .reservoirLow }
-        if description.contains("fault") { return .pumpFault }
-        if description.contains("podexpired") { return .podExpired }
+        if description.contains("fault") || description.contains("patchfault") { return .hardwareFault }
+        if description.contains("podexpired") || description.contains("sensorexpired") { return .deviceExpired }
+        if description.contains("sensorfailed") || description.contains("sensorstopped") { return .sensorFailure }
         if description.contains("communication") || description.contains("comms") || description.contains("notconnected")
         if description.contains("communication") || description.contains("comms") || description.contains("notconnected")
             || description.contains("noresponse") || description.contains("timeout") || description.contains("rssi")
             || description.contains("noresponse") || description.contains("timeout") || description.contains("rssi")
         {
         {
             return .commsTransient
             return .commsTransient
         }
         }
-        if description.contains("bolusfailed") || description.contains("uncertaindelivery") { return .bolusFailed }
+        if description.contains("bolusfailed") { return .bolusFailed }
         return .other(String(describing: pumpError))
         return .other(String(describing: pumpError))
     }
     }
 }
 }