Forráskód Böngészése

Test: MinimedKit message classification catalog

Augment the MinimedKit emission suite with a complete (identifier, message) ->
TrioAlertClassifier.categorize table sourced from the loopkit-manager-synthesis
audit: every reported alert/error string paired with its identifier, asserting
the current classifier category (green) plus a coverage-gap ratchet for
messages whose taxonomy implies a bucket the substring classifier misses.
Spec note: production feeds the classifier String(describing: error) (case
names), not these display strings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
James Woglom 2 hete
szülő
commit
27e6ab93f7
1 módosított fájl, 724 hozzáadás és 0 törlés
  1. 724 0
      TrioTests/MinimedKitAlertEmissionTests.swift

+ 724 - 0
TrioTests/MinimedKitAlertEmissionTests.swift

@@ -108,3 +108,727 @@ import Testing
         #expect(computed == Self.knownEscalationGaps)
     }
 }
+
+// MARK: - Message classification
+
+/// SPEC — This suite catalogs every user-reportable string MinimedKit can
+/// surface (PumpAlarmType titles, PumpErrorCode / PumpOpsError /
+/// PumpCommandError / MinimedPumpManagerError descriptions, SetBolusError,
+/// the four LoopKit `issueAlert` constructions, PumpStatusHighlight banners,
+/// settings-view alerts, the time-change action sheet, and history/glucose
+/// page parse errors) and pins how `TrioAlertClassifier.categorize(error:)`
+/// routes each one.
+///
+/// IMPORTANT mismatch this pins: in production the classifier is fed
+/// `String(describing: error)` — i.e. the Swift *case name* of an enum error,
+/// not the localized display string a user sees. Its checks then match
+/// concatenated, lowercased tokens (`"reservoirempty"`, `"noresponse"`,
+/// `"comms"`, …) that only ever appear in case-name form. We instead feed each
+/// row's real DISPLAY string through a `StubError` whose `String(describing:)`
+/// IS that exact prose. Because natural-language prose has spaces and never
+/// contains those concatenated tokens, almost every real MinimedKit message
+/// falls through to `.other`. The only display strings that DO hit a bucket
+/// are ones whose substrings coincidentally coincide with a classifier token:
+/// "Comms with another pump detected" -> `.commsTransient` (contains "comms")
+/// and "Bolus may have failed: …" -> `.deliveryUncertain`.
+///
+/// This is the audit over the managers / pump / MinimedKit sources
+/// (`MinimedKit/...`). Each row pins CURRENT behavior (must be green) and the
+/// gap ratchet below fails when the classifier is improved to map the
+/// taxonomy-intended messages.
+@Suite("Message Classification: MinimedKit") struct MinimedKitMessageClassificationTests {
+    private struct StubError: Error, CustomStringConvertible { let description: String }
+
+    struct Row {
+        let identifier: String
+        let message: String
+        let role: String
+        let taxonomy: String
+        let expected: TrioAlertCategory
+    }
+
+    static let rows: [Row] = [
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:53
+        Row(
+            identifier: "PumpAlarmType.autoOff",
+            message: "Auto-Off Alarm",
+            role: "errorMessage",
+            taxonomy: "N1",
+            expected: .other("Auto-Off Alarm")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:55
+        Row(
+            identifier: "PumpAlarmType.batteryOutLimitExceeded",
+            message: "Battery Out Limit",
+            role: "errorMessage",
+            taxonomy: "N5",
+            expected: .other("Battery Out Limit")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:57
+        Row(
+            identifier: "PumpAlarmType.noDelivery",
+            message: "No Delivery Alarm",
+            role: "errorMessage",
+            taxonomy: "N1",
+            expected: .other("No Delivery Alarm")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:59
+        Row(
+            identifier: "PumpAlarmType.batteryDepleted",
+            message: "Battery Depleted",
+            role: "errorMessage",
+            taxonomy: "N5",
+            expected: .other("Battery Depleted")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:61
+        Row(
+            identifier: "PumpAlarmType.deviceReset",
+            message: "Device Reset",
+            role: "errorMessage",
+            taxonomy: "N1",
+            expected: .other("Device Reset")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:63
+        Row(
+            identifier: "PumpAlarmType.deviceResetBatteryIssue17",
+            message: "BatteryIssue17",
+            role: "errorMessage",
+            taxonomy: "N1",
+            expected: .other("BatteryIssue17")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:65
+        Row(
+            identifier: "PumpAlarmType.deviceResetBatteryIssue21",
+            message: "BatteryIssue21",
+            role: "errorMessage",
+            taxonomy: "N1",
+            expected: .other("BatteryIssue21")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:67
+        Row(
+            identifier: "PumpAlarmType.reprogramError",
+            message: "Reprogram Error",
+            role: "errorMessage",
+            taxonomy: "N1",
+            expected: .other("Reprogram Error")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:69
+        Row(
+            identifier: "PumpAlarmType.emptyReservoir",
+            message: "Empty Reservoir",
+            role: "errorMessage",
+            taxonomy: "N4",
+            expected: .other("Empty Reservoir")
+        ),
+        // MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:71
+        Row(
+            identifier: "PumpAlarmType.unknownType",
+            message: "Unknown Alarm",
+            role: "errorMessage",
+            taxonomy: "N1",
+            expected: .other("Unknown Alarm")
+        ),
+        // MinimedKit/PumpManager/DoseStore.swift:133
+        Row(
+            identifier: "ClearAlarmPumpEvent",
+            message: "Clear Alarm",
+            role: "errorMessage",
+            taxonomy: "N14",
+            expected: .other("Clear Alarm")
+        ),
+        // MinimedKit/PumpManager/DoseStore.swift:144
+        Row(
+            identifier: "JournalEntryPumpLowBatteryPumpEvent",
+            message: "Low Battery",
+            role: "errorMessage",
+            taxonomy: "F2",
+            expected: .other("Low Battery")
+        ),
+        // MinimedKit/PumpManager/DoseStore.swift:147
+        Row(
+            identifier: "JournalEntryPumpLowReservoirPumpEvent",
+            message: "Low Reservoir",
+            role: "errorMessage",
+            taxonomy: "F1",
+            expected: .other("Low Reservoir")
+        ),
+        // MinimedKit/Messages/PumpErrorMessageBody.swift:21
+        Row(
+            identifier: "PumpErrorCode.commandRefused",
+            message: "Command refused",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Command refused")
+        ),
+        // MinimedKit/Messages/PumpErrorMessageBody.swift:21
+        Row(
+            identifier: "PumpErrorCode.commandRefused",
+            message: "Check that the pump is not suspended or priming, or has a percent temp basal type",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Check that the pump is not suspended or priming, or has a percent temp basal type")
+        ),
+        // MinimedKit/Messages/PumpErrorMessageBody.swift:23
+        Row(
+            identifier: "PumpErrorCode.maxSettingExceeded",
+            message: "Max setting exceeded",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Max setting exceeded")
+        ),
+        // MinimedKit/Messages/PumpErrorMessageBody.swift:25
+        Row(
+            identifier: "PumpErrorCode.bolusInProgress",
+            message: "Bolus in progress",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Bolus in progress")
+        ),
+        // MinimedKit/Messages/PumpErrorMessageBody.swift:27
+        Row(
+            identifier: "PumpErrorCode.pageDoesNotExist",
+            message: "History page does not exist",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("History page does not exist")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpMessageSender.swift:90
+        Row(
+            identifier: "PumpOpsError.unknownPumpErrorCode",
+            message: "Unknown pump error code: %1$@",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Unknown pump error code: %1$@")
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:41
+        Row(
+            identifier: "PumpOpsError.bolusInProgress",
+            message: "A bolus is already in progress",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("A bolus is already in progress")
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:43
+        Row(
+            identifier: "PumpOpsError.couldNotDecode",
+            message: "Invalid response during %1$@: %2$@",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("Invalid response during %1$@: %2$@")
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:45
+        Row(
+            identifier: "PumpOpsError.crosstalk",
+            message: "Comms with another pump detected",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .commsTransient
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:47
+        Row(
+            identifier: "PumpOpsError.noResponse",
+            message: "Pump did not respond",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("Pump did not respond")
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:49
+        Row(
+            identifier: "PumpOpsError.pumpSuspended",
+            message: "Pump is suspended",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Pump is suspended")
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:53
+        Row(
+            identifier: "PumpOpsError.unexpectedResponse",
+            message: "Unexpected response %1$@",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("Unexpected response %1$@")
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:55
+        Row(
+            identifier: "PumpOpsError.unknownPumpErrorCode",
+            message: "Unknown pump error code: %1$@",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Unknown pump error code: %1$@")
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:57
+        Row(
+            identifier: "PumpOpsError.unknownPumpModel",
+            message: "Unknown pump model: %@",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Unknown pump model: %@")
+        ),
+        // MinimedKit/PumpManager/PumpOpsError.swift:59
+        Row(
+            identifier: "PumpOpsError.unknownResponse",
+            message: "Unknown response during %1$@: %2$@",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("Unknown response during %1$@: %2$@")
+        ),
+        // MinimedKit/PumpManager/PumpOpsSession.swift:939
+        Row(
+            identifier: "PumpOpsError.rfCommsFailure",
+            message: "No pump responses during scan",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("No pump responses during scan")
+        ),
+        // MinimedKit/PumpManager/PumpOpsSession.swift:554
+        Row(
+            identifier: "PumpOpsError.rfCommsFailure",
+            message: "Confirmed that temp basal failed, and ",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Confirmed that temp basal failed, and ")
+        ),
+        // MinimedKit/PumpManager/PumpOpsSession.swift:1042
+        Row(
+            identifier: "PumpOpsError.rfCommsFailure",
+            message: "Short history page: (n) bytes. Expected 1024",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("Short history page: (n) bytes. Expected 1024")
+        ),
+        // MinimedKit/PumpManager/PumpOpsSession.swift:1149
+        Row(
+            identifier: "PumpOpsError.rfCommsFailure",
+            message: "Short glucose history page: (n) bytes. Expected 1024",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("Short glucose history page: (n) bytes. Expected 1024")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManagerError.swift:26
+        Row(
+            identifier: "MinimedPumpManagerError.noRileyLink",
+            message: "No RileyLink Connected",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("No RileyLink Connected")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManagerError.swift:26
+        Row(
+            identifier: "MinimedPumpManagerError.noRileyLink",
+            message: "Make sure your RileyLink is nearby and powered on",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("Make sure your RileyLink is nearby and powered on")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManagerError.swift:28
+        Row(
+            identifier: "MinimedPumpManagerError.bolusInProgress",
+            message: "Bolus in Progress",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Bolus in Progress")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManagerError.swift:30
+        Row(
+            identifier: "MinimedPumpManagerError.pumpSuspended",
+            message: "Pump is Suspended",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Pump is Suspended")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManagerError.swift:32
+        Row(
+            identifier: "MinimedPumpManagerError.insulinTypeNotConfigured",
+            message: "Insulin Type is not configured",
+            role: "errorMessage",
+            taxonomy: "N13",
+            expected: .other("Insulin Type is not configured")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManagerError.swift:32
+        Row(
+            identifier: "MinimedPumpManagerError.insulinTypeNotConfigured",
+            message: "Go to pump settings and select insulin type",
+            role: "errorMessage",
+            taxonomy: "N13",
+            expected: .other("Go to pump settings and select insulin type")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManagerError.swift:36
+        Row(
+            identifier: "MinimedPumpManagerError.tuneFailed",
+            message: "RileyLink radio tune failed: (underlying error)",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("RileyLink radio tune failed: (underlying error)")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManagerError.swift:40
+        Row(
+            identifier: "MinimedPumpManagerError.storageFailure",
+            message: "Unable to store pump data",
+            role: "errorMessage",
+            taxonomy: "N9",
+            expected: .other("Unable to store pump data")
+        ),
+        // MinimedKit/PumpManager/PumpOpsSession.swift:30
+        Row(
+            identifier: "SetBolusError.uncertain",
+            message: "Bolus may have failed: %1$@",
+            role: "errorMessage",
+            taxonomy: "N3",
+            expected: .deliveryUncertain
+        ),
+        // MinimedKit/PumpManager/PumpOpsSession.swift:30
+        Row(
+            identifier: "SetBolusError.uncertain",
+            message: "Please check your pump bolus history to determine if the bolus was delivered.",
+            role: "errorMessage",
+            taxonomy: "N3",
+            expected: .other("Please check your pump bolus history to determine if the bolus was delivered.")
+        ),
+        // MinimedKit/PumpManager/PumpOpsSession.swift:30
+        Row(
+            identifier: "SetBolusError.uncertain",
+            message: "Loop sent a bolus command to the pump, but was unable to confirm…",
+            role: "errorMessage",
+            taxonomy: "N3",
+            expected: .other("Loop sent a bolus command to the pump, but was unable to confirm…")
+        ),
+        // MinimedKit/Messages/Models/HistoryPage.swift:21
+        Row(
+            identifier: "HistoryPageError.invalidCRC",
+            message: "History page failed crc check",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("History page failed crc check")
+        ),
+        // MinimedKit/Messages/Models/HistoryPage.swift:23
+        Row(
+            identifier: "HistoryPageError.unknownEventType",
+            message: "Unknown history record type: %$1@",
+            role: "errorMessage",
+            taxonomy: "N8",
+            expected: .other("Unknown history record type: %$1@")
+        ),
+        // MinimedKit/Messages/Models/GlucosePage.swift:20
+        Row(
+            identifier: "GlucosePageError.invalidCRC",
+            message: "Glucose page failed crc check",
+            role: "errorMessage",
+            taxonomy: "N11",
+            expected: .other("Glucose page failed crc check")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:263-268
+        Row(
+            identifier: "lowRLBattery",
+            message: "Low RileyLink Battery",
+            role: "alertTitle",
+            taxonomy: "F2",
+            expected: .other("Low RileyLink Battery")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:263-268
+        Row(
+            identifier: "lowRLBattery",
+            message: "\"%1$@\" has a low battery",
+            role: "alertBody",
+            taxonomy: "F2",
+            expected: .other("\"%1$@\" has a low battery")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:495-510
+        Row(
+            identifier: "PumpBatteryLow",
+            message: "Pump Battery Low",
+            role: "alertTitle",
+            taxonomy: "F2",
+            expected: .other("Pump Battery Low")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:495-510
+        Row(
+            identifier: "PumpBatteryLow",
+            message: "Change the pump battery immediately",
+            role: "alertBody",
+            taxonomy: "F2",
+            expected: .other("Change the pump battery immediately")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:560-600
+        Row(
+            identifier: "PumpReservoirEmpty",
+            message: "Pump Reservoir Empty",
+            role: "alertTitle",
+            taxonomy: "N4",
+            expected: .other("Pump Reservoir Empty")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:560-600
+        Row(
+            identifier: "PumpReservoirEmpty",
+            message: "Change the pump reservoir now",
+            role: "alertBody",
+            taxonomy: "N4",
+            expected: .other("Change the pump reservoir now")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:571-610
+        Row(
+            identifier: "PumpReservoirLow",
+            message: "Pump Reservoir Low",
+            role: "alertTitle",
+            taxonomy: "F1",
+            expected: .other("Pump Reservoir Low")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:571-610
+        Row(
+            identifier: "PumpReservoirLow",
+            message: "%1$@ U left: %2$@",
+            role: "alertBody",
+            taxonomy: "F1",
+            expected: .other("%1$@ U left: %2$@")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:571-610
+        Row(
+            identifier: "PumpReservoirLow",
+            message: "%1$@ U left",
+            role: "alertBody",
+            taxonomy: "F1",
+            expected: .other("%1$@ U left")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:468
+        Row(
+            identifier: "PumpStatusHighlight.suspended",
+            message: "Insulin Suspended",
+            role: "notificationBody",
+            taxonomy: "N2",
+            expected: .other("Insulin Suspended")
+        ),
+        // MinimedKit/PumpManager/MinimedPumpManager.swift:475
+        Row(
+            identifier: "PumpStatusHighlight.signalLoss",
+            message: "Signal Loss",
+            role: "notificationBody",
+            taxonomy: "N8",
+            expected: .other("Signal Loss")
+        ),
+        // MinimedKitUI/Views/MinimedPumpSettingsViewModel.swift:148
+        Row(
+            identifier: "MinimedSettingsViewAlert.resumeError",
+            message: "Error Resuming",
+            role: "alertTitle",
+            taxonomy: "N9",
+            expected: .other("Error Resuming")
+        ),
+        // MinimedKitUI/Views/MinimedPumpSettingsViewModel.swift:157
+        Row(
+            identifier: "MinimedSettingsViewAlert.suspendError",
+            message: "Error Suspending",
+            role: "alertTitle",
+            taxonomy: "N9",
+            expected: .other("Error Suspending")
+        ),
+        // MinimedKitUI/Views/MinimedPumpSettingsViewModel.swift:250
+        Row(
+            identifier: "MinimedSettingsViewAlert.syncTimeError",
+            message: "Error Syncing Time",
+            role: "alertTitle",
+            taxonomy: "N9",
+            expected: .other("Error Syncing Time")
+        ),
+        // MinimedKitUI/Views/MinimedPumpSettingsView.swift:324-333
+        Row(
+            identifier: "MinimedPumpSettingsView.timeChangeActionSheet",
+            message: "Time Change Detected",
+            role: "alertTitle",
+            taxonomy: "N14",
+            expected: .other("Time Change Detected")
+        ),
+        // MinimedKitUI/Views/MinimedPumpSettingsView.swift:324-333
+        Row(
+            identifier: "MinimedPumpSettingsView.timeChangeActionSheet",
+            message: "The time on your pump is different from the current time. Do you want to update the time on your pump to the current time?",
+            role: "alertBody",
+            taxonomy: "N14",
+            expected: .other(
+                "The time on your pump is different from the current time. Do you want to update the time on your pump to the current time?"
+            )
+        )
+    ]
+
+    // MARK: - Classification (pinned to CURRENT, must be green)
+
+    @Test("each (identifier, message) classifies as pinned", arguments: rows) func eachMessageClassifiesAsPinned(_ row: Row) {
+        #expect(TrioAlertClassifier.categorize(error: StubError(description: row.message)) == row.expected)
+    }
+
+    // MARK: - Classifier coverage gaps (ratchet)
+
+    /// "identifier — message" keys for rows whose DISPLAY string falls through
+    /// to `.other` today even though its taxonomy bucket is a real category the
+    /// classifier *could* map. Each entry names the bucket it SHOULD hit and
+    /// why the substring classifier misses it (the spaced prose never contains
+    /// the concatenated token the classifier looks for):
+    ///
+    ///  - "PumpAlarmType.noDelivery — No Delivery Alarm": SHOULD be `.occlusion`
+    ///    (N1). Classifier looks for "occlusion"/"occluded"; the title has
+    ///    neither. MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:57
+    ///  - "PumpAlarmType.autoOff — Auto-Off Alarm",
+    ///    "PumpAlarmType.deviceReset — Device Reset",
+    ///    "PumpAlarmType.deviceResetBatteryIssue17 — BatteryIssue17",
+    ///    "PumpAlarmType.deviceResetBatteryIssue21 — BatteryIssue21",
+    ///    "PumpAlarmType.reprogramError — Reprogram Error",
+    ///    "PumpAlarmType.unknownType — Unknown Alarm": SHOULD be
+    ///    `.hardwareFault` (N1). Classifier looks for "fault"/"patchfault";
+    ///    none of these titles contain it.
+    ///    MinimedKit/PumpEvents/PumpAlarmPumpEvent.swift:53-71
+    ///  - "PumpAlarmType.emptyReservoir — Empty Reservoir",
+    ///    "PumpReservoirEmpty — Pump Reservoir Empty",
+    ///    "PumpReservoirEmpty — Change the pump reservoir now": SHOULD be
+    ///    `.reservoirEmpty` (N4). Classifier looks for the concatenated tokens
+    ///    "reservoirempty"/"emptyreservoir"; the spaced prose ("empty
+    ///    reservoir", "reservoir empty") contains neither.
+    ///    PumpAlarmPumpEvent.swift:69; MinimedPumpManager.swift:560-600
+    ///  - "JournalEntryPumpLowReservoirPumpEvent — Low Reservoir",
+    ///    "PumpReservoirLow — Pump Reservoir Low",
+    ///    "PumpReservoirLow — %1$@ U left: %2$@",
+    ///    "PumpReservoirLow — %1$@ U left": SHOULD be `.reservoirLow` (F1).
+    ///    Classifier looks for "lowreservoir"; "low reservoir"/"reservoir low"
+    ///    (and the formatted bodies) never contain it.
+    ///    DoseStore.swift:147; MinimedPumpManager.swift:571-610
+    ///  - comms/transient rows that SHOULD be `.commsTransient` (N8) but miss
+    ///    because the classifier wants "communication"/"comms"/"notconnected"/
+    ///    "noresponse"/"timeout"/"rssi" and the prose never spells those tokens
+    ///    contiguously:
+    ///      "PumpOpsError.couldNotDecode — Invalid response during %1$@: %2$@",
+    ///      "PumpOpsError.noResponse — Pump did not respond" (spaced, not
+    ///        "noresponse"),
+    ///      "PumpOpsError.unexpectedResponse — Unexpected response %1$@",
+    ///      "PumpOpsError.unknownResponse — Unknown response during %1$@: %2$@",
+    ///      "PumpOpsError.rfCommsFailure — No pump responses during scan",
+    ///      "PumpOpsError.rfCommsFailure — Short history page: (n) bytes. Expected 1024",
+    ///      "PumpOpsError.rfCommsFailure — Short glucose history page: (n) bytes. Expected 1024",
+    ///      "MinimedPumpManagerError.noRileyLink — No RileyLink Connected"
+    ///        ("not connected" is two words, not "notconnected"),
+    ///      "MinimedPumpManagerError.noRileyLink — Make sure your RileyLink is nearby and powered on",
+    ///      "MinimedPumpManagerError.tuneFailed — RileyLink radio tune failed: (underlying error)",
+    ///      "HistoryPageError.invalidCRC — History page failed crc check",
+    ///      "HistoryPageError.unknownEventType — Unknown history record type: %$1@",
+    ///      "PumpStatusHighlight.signalLoss — Signal Loss".
+    ///    Sources: PumpOpsError.swift:43-59; PumpOpsSession.swift:554-1149;
+    ///    MinimedPumpManagerError.swift:26-36; HistoryPage.swift:21-23;
+    ///    MinimedPumpManager.swift:475
+    ///  - "SetBolusError.uncertain — Please check your pump bolus history to
+    ///    determine if the bolus was delivered.",
+    ///    "SetBolusError.uncertain — Loop sent a bolus command to the pump, but
+    ///    was unable to confirm…": SHOULD be `.deliveryUncertain` (N3). Only the
+    ///    sibling "Bolus may have failed: …" string matches (the classifier
+    ///    special-cases that exact spaced phrase); these accompanying bodies do
+    ///    not contain "bolus may have failed"/"uncertaindelivery"/
+    ///    "unacknowledged". MinimedKit/PumpManager/PumpOpsSession.swift:30
+    ///
+    /// Stays green now (pins the misses); FAILS — forcing this file to be
+    /// updated — once the classifier is improved to map these messages.
+    static let classifierCoverageGaps: Set<String> = [
+        "PumpAlarmType.autoOff — Auto-Off Alarm",
+        "PumpAlarmType.noDelivery — No Delivery Alarm",
+        "PumpAlarmType.deviceReset — Device Reset",
+        "PumpAlarmType.deviceResetBatteryIssue17 — BatteryIssue17",
+        "PumpAlarmType.deviceResetBatteryIssue21 — BatteryIssue21",
+        "PumpAlarmType.reprogramError — Reprogram Error",
+        "PumpAlarmType.emptyReservoir — Empty Reservoir",
+        "PumpAlarmType.unknownType — Unknown Alarm",
+        "JournalEntryPumpLowReservoirPumpEvent — Low Reservoir",
+        "PumpOpsError.couldNotDecode — Invalid response during %1$@: %2$@",
+        "PumpOpsError.noResponse — Pump did not respond",
+        "PumpOpsError.unexpectedResponse — Unexpected response %1$@",
+        "PumpOpsError.unknownResponse — Unknown response during %1$@: %2$@",
+        "PumpOpsError.rfCommsFailure — No pump responses during scan",
+        "PumpOpsError.rfCommsFailure — Short history page: (n) bytes. Expected 1024",
+        "PumpOpsError.rfCommsFailure — Short glucose history page: (n) bytes. Expected 1024",
+        "MinimedPumpManagerError.noRileyLink — No RileyLink Connected",
+        "MinimedPumpManagerError.noRileyLink — Make sure your RileyLink is nearby and powered on",
+        "MinimedPumpManagerError.tuneFailed — RileyLink radio tune failed: (underlying error)",
+        "SetBolusError.uncertain — Please check your pump bolus history to determine if the bolus was delivered.",
+        "SetBolusError.uncertain — Loop sent a bolus command to the pump, but was unable to confirm…",
+        "HistoryPageError.invalidCRC — History page failed crc check",
+        "HistoryPageError.unknownEventType — Unknown history record type: %$1@",
+        "PumpReservoirEmpty — Pump Reservoir Empty",
+        "PumpReservoirEmpty — Change the pump reservoir now",
+        "PumpReservoirLow — Pump Reservoir Low",
+        "PumpReservoirLow — %1$@ U left: %2$@",
+        "PumpReservoirLow — %1$@ U left",
+        "PumpStatusHighlight.signalLoss — Signal Loss"
+    ]
+
+    /// Taxonomy bucket each row was intended to map to (from the audit). A row
+    /// is a gap when it currently falls to `.other(message)` yet its taxonomy
+    /// bucket is something other than "other".
+    static let taxonomyBuckets: [String: String] = [
+        "PumpAlarmType.autoOff — Auto-Off Alarm": "hardwareFault",
+        "PumpAlarmType.batteryOutLimitExceeded — Battery Out Limit": "other",
+        "PumpAlarmType.noDelivery — No Delivery Alarm": "occlusion",
+        "PumpAlarmType.batteryDepleted — Battery Depleted": "other",
+        "PumpAlarmType.deviceReset — Device Reset": "hardwareFault",
+        "PumpAlarmType.deviceResetBatteryIssue17 — BatteryIssue17": "hardwareFault",
+        "PumpAlarmType.deviceResetBatteryIssue21 — BatteryIssue21": "hardwareFault",
+        "PumpAlarmType.reprogramError — Reprogram Error": "hardwareFault",
+        "PumpAlarmType.emptyReservoir — Empty Reservoir": "reservoirEmpty",
+        "PumpAlarmType.unknownType — Unknown Alarm": "hardwareFault",
+        "ClearAlarmPumpEvent — Clear Alarm": "other",
+        "JournalEntryPumpLowBatteryPumpEvent — Low Battery": "other",
+        "JournalEntryPumpLowReservoirPumpEvent — Low Reservoir": "reservoirLow",
+        "PumpErrorCode.commandRefused — Command refused": "other",
+        "PumpErrorCode.commandRefused — Check that the pump is not suspended or priming, or has a percent temp basal type": "other",
+        "PumpErrorCode.maxSettingExceeded — Max setting exceeded": "other",
+        "PumpErrorCode.bolusInProgress — Bolus in progress": "other",
+        "PumpErrorCode.pageDoesNotExist — History page does not exist": "other",
+        "PumpOpsError.unknownPumpErrorCode — Unknown pump error code: %1$@": "other",
+        "PumpOpsError.bolusInProgress — A bolus is already in progress": "other",
+        "PumpOpsError.couldNotDecode — Invalid response during %1$@: %2$@": "commsTransient",
+        "PumpOpsError.crosstalk — Comms with another pump detected": "commsTransient",
+        "PumpOpsError.noResponse — Pump did not respond": "commsTransient",
+        "PumpOpsError.pumpSuspended — Pump is suspended": "other",
+        "PumpOpsError.unexpectedResponse — Unexpected response %1$@": "commsTransient",
+        "PumpOpsError.unknownPumpModel — Unknown pump model: %@": "other",
+        "PumpOpsError.unknownResponse — Unknown response during %1$@: %2$@": "commsTransient",
+        "PumpOpsError.rfCommsFailure — No pump responses during scan": "commsTransient",
+        "PumpOpsError.rfCommsFailure — Confirmed that temp basal failed, and ": "other",
+        "PumpOpsError.rfCommsFailure — Short history page: (n) bytes. Expected 1024": "commsTransient",
+        "PumpOpsError.rfCommsFailure — Short glucose history page: (n) bytes. Expected 1024": "commsTransient",
+        "MinimedPumpManagerError.noRileyLink — No RileyLink Connected": "commsTransient",
+        "MinimedPumpManagerError.noRileyLink — Make sure your RileyLink is nearby and powered on": "commsTransient",
+        "MinimedPumpManagerError.bolusInProgress — Bolus in Progress": "other",
+        "MinimedPumpManagerError.pumpSuspended — Pump is Suspended": "other",
+        "MinimedPumpManagerError.insulinTypeNotConfigured — Insulin Type is not configured": "other",
+        "MinimedPumpManagerError.insulinTypeNotConfigured — Go to pump settings and select insulin type": "other",
+        "MinimedPumpManagerError.tuneFailed — RileyLink radio tune failed: (underlying error)": "commsTransient",
+        "MinimedPumpManagerError.storageFailure — Unable to store pump data": "other",
+        "SetBolusError.uncertain — Bolus may have failed: %1$@": "deliveryUncertain",
+        "SetBolusError.uncertain — Please check your pump bolus history to determine if the bolus was delivered.": "deliveryUncertain",
+        "SetBolusError.uncertain — Loop sent a bolus command to the pump, but was unable to confirm…": "deliveryUncertain",
+        "HistoryPageError.invalidCRC — History page failed crc check": "commsTransient",
+        "HistoryPageError.unknownEventType — Unknown history record type: %$1@": "commsTransient",
+        "GlucosePageError.invalidCRC — Glucose page failed crc check": "other",
+        "lowRLBattery — Low RileyLink Battery": "other",
+        "lowRLBattery — \"%1$@\" has a low battery": "other",
+        "PumpBatteryLow — Pump Battery Low": "other",
+        "PumpBatteryLow — Change the pump battery immediately": "other",
+        "PumpReservoirEmpty — Pump Reservoir Empty": "reservoirEmpty",
+        "PumpReservoirEmpty — Change the pump reservoir now": "reservoirEmpty",
+        "PumpReservoirLow — Pump Reservoir Low": "reservoirLow",
+        "PumpReservoirLow — %1$@ U left: %2$@": "reservoirLow",
+        "PumpReservoirLow — %1$@ U left": "reservoirLow",
+        "PumpStatusHighlight.suspended — Insulin Suspended": "other",
+        "PumpStatusHighlight.signalLoss — Signal Loss": "commsTransient",
+        "MinimedSettingsViewAlert.resumeError — Error Resuming": "other",
+        "MinimedSettingsViewAlert.suspendError — Error Suspending": "other",
+        "MinimedSettingsViewAlert.syncTimeError — Error Syncing Time": "other",
+        "MinimedPumpSettingsView.timeChangeActionSheet — Time Change Detected": "other",
+        "MinimedPumpSettingsView.timeChangeActionSheet — The time on your pump is different from the current time. Do you want to update the time on your pump to the current time?": "other"
+    ]
+
+    @Test("classifier coverage gaps are exactly as documented") func classifierCoverageGapsExact() {
+        let computed = Set(
+            Self.rows
+                .filter { row in
+                    let bucket = Self.taxonomyBuckets["\(row.identifier) — \(row.message)"] ?? "other"
+                    return row.expected == .other(row.message) && bucket != "other"
+                }
+                .map { "\($0.identifier) — \($0.message)" }
+        )
+        #expect(computed == Self.classifierCoverageGaps)
+    }
+}