OmnipodKitAlertEmissionTests.swift 96 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881
  1. import Foundation
  2. import LoopKit
  3. import Testing
  4. @testable import Trio
  5. // MARK: - Manager Emissions: OmnipodKit
  6. //
  7. // This suite PINS the current alert-routing behavior for every OmnipodKit
  8. // (managerIdentifier "Omni", plus the pod-fault channel "Omni:pumpFault")
  9. // emission that actually reaches Trio. Rows come from the synthesis audit of
  10. // the managers/pump OmnipodKit sources (PumpManagerAlert.swift, OmniPumpManager.swift,
  11. // FaultEventCode.swift) cross-referenced against Trio's AlertCatalogRegistry and
  12. // TrioAlertCategory routing.
  13. //
  14. // Two emission channels reach Trio:
  15. // 1. LoopKit Alerts issued via issueAlert (Alert.Identifier carries
  16. // managerIdentifier/alertIdentifier). TrioAlertManager.issueAlert OVERRIDES the
  17. // issued interruptionLevel with AlertCatalogRegistry.lookup(...)'s level when a
  18. // (managerIdentifier, alertIdentifier) entry is found, else falls back to the
  19. // plugin's own level. This is the PRIMARY table below.
  20. // 2. PumpManager errors handed back through completion handlers that
  21. // APSManager.processError routes through TrioAlertClassifier.categorize(error:)
  22. // (ordered substring match over String(describing: error).lowercased()).
  23. //
  24. // The two tests that assert CURRENT behavior (registry pin, classifier pin) MUST stay
  25. // green. The gap-ratchet test documents the known under-escalations and stays green
  26. // until someone fixes a gap, at which point it FAILS to prompt updating this file.
  27. //
  28. // GAP SUMMARY: Several taxonomy-Critical emissions reach Trio at a sub-critical
  29. // effective level. The dominant gap is end-of-life / suspend escalation:
  30. // - podExpiring (N6-pump, 72h "Pod Expired") registered .timeSensitive.
  31. // - suspendEnded (N2, delivery suspended -> "Resume Insulin") registered .timeSensitive.
  32. // - pod-fault 0x1C exceededMaximumPodLife80Hrs (N6-pump, 80h hard stop, delivery
  33. // stopped) mapped .timeSensitive by omniPodFaultEntry.
  34. // - userPodExpiration (F3) registered .active (minor under-escalation vs .timeSensitive).
  35. // Classifier-channel gaps: podSuspended, noResponse, podNotConnected, unfinalizedBolus
  36. // all land at .active where taxonomy wants .timeSensitive.
  37. @Suite("Trio Alert Emission: OmnipodKit") struct OmnipodKitAlertEmissionTests {
  38. // MARK: Helpers
  39. private func id(_ manager: String, _ alertID: String) -> Alert.Identifier {
  40. Alert.Identifier(managerIdentifier: manager, alertIdentifier: alertID)
  41. }
  42. /// Feeds a known String(describing:) value through the same substring classifier
  43. /// APSManager.processError uses. CustomStringConvertible makes String(describing:)
  44. /// return exactly `description`, matching the case-name shape of a real Swift error enum.
  45. private struct StubError: Error, CustomStringConvertible {
  46. let description: String
  47. }
  48. // MARK: - Registry pin (PRIMARY LoopKit-Alert table)
  49. //
  50. // (manager, alertIdentifier, expected current registry interruptionLevel or nil).
  51. // "Omni:pumpFault" rows resolve through omniPodFaultEntry's bounded hex parser.
  52. // These assert CURRENT behavior, NOT the taxonomy-ideal level.
  53. static let registryRows: [(manager: String, alertID: String, level: Alert.InterruptionLevel?)] = [
  54. // managerIdentifier "Omni" — static omniEntries table
  55. ("Omni", "podExpireImminent", .timeSensitive),
  56. ("Omni", "userPodExpiration", .active),
  57. ("Omni", "lowReservoir", .timeSensitive),
  58. ("Omni", "suspendEnded", .timeSensitive),
  59. ("Omni", "podExpiring", .timeSensitive),
  60. ("Omni", "unexpectedAlert", .critical),
  61. ("Omni", "timeOffsetChangeDetected", .active),
  62. ("Omni", "lowRLBattery", .timeSensitive),
  63. // managerIdentifier "Omni:pumpFault" — omniPodFaultEntry hex-code mapping
  64. ("Omni:pumpFault", "Fault Event Code 0x14: occluded", .critical),
  65. ("Omni:pumpFault", "Fault Event Code 0x18: reservoirEmpty", .critical),
  66. ("Omni:pumpFault", "Fault Event Code 0x1C: exceededMaximumPodLife80Hrs", .timeSensitive),
  67. ("Omni:pumpFault", "Fault Event Code 0xNN: <other>", .critical)
  68. ]
  69. @Test(
  70. "registry behavior is pinned for every emitted alert",
  71. arguments: registryRows
  72. ) func registryLevelIsPinned(_ row: (
  73. manager: String,
  74. alertID: String,
  75. level: Alert.InterruptionLevel?
  76. )) {
  77. let entry = AlertCatalogRegistry.lookup(id(row.manager, row.alertID))
  78. #expect(entry?.interruptionLevel == row.level)
  79. }
  80. // MARK: - Classifier pin (PumpManager errors via APSManager.processError)
  81. //
  82. // (String(describing: error), expected current TrioAlertCategory). Asserts CURRENT
  83. // substring-match behavior, NOT taxonomy-ideal. Note podSuspended/unfinalizedBolus
  84. // fall through to .other (no matching rule) and are .active.
  85. static let classifierRows: [(describing: String, category: TrioAlertCategory)] = [
  86. ("podFault(OmnipodKit.FaultEventCode(...))", .hardwareFault),
  87. ("podSuspended", .other("podSuspended")),
  88. ("unacknowledgedCommandPending", .deliveryUncertain),
  89. ("noResponse", .commsTransient),
  90. ("podNotConnected", .commsTransient),
  91. ("unfinalizedBolus", .other("unfinalizedBolus"))
  92. ]
  93. @Test(
  94. "classifier behavior is pinned for every routed error",
  95. arguments: classifierRows
  96. ) func classifierCategoryIsPinned(_ row: (describing: String, category: TrioAlertCategory)) {
  97. let category = TrioAlertClassifier.categorize(error: StubError(description: row.describing))
  98. #expect(category == row.category)
  99. }
  100. // MARK: - Documented escalation-gap ratchet
  101. //
  102. // Each entry is an emission whose EFFECTIVE current level is LESS severe than its
  103. // taxonomy level. Recorded here verbatim with the taxonomy level it SHOULD have and
  104. // why. The test below recomputes the gap set from the pinned tables + taxonomy and
  105. // asserts it equals this documented set. Fixing a gap (raising its level) shrinks the
  106. // recomputed set and FAILS this test, forcing an update here.
  107. static let knownEscalationGaps: Set<String> = [
  108. // LoopKit-Alert (registry) gaps — keyed by alertIdentifier
  109. // userPodExpiration: F3 -> SHOULD be .timeSensitive; registry .active.
  110. // PumpManagerAlert.swift:42,69 / AlertCatalogRegistry.swift:72
  111. "userPodExpiration",
  112. // suspendEnded: N2 (delivery suspended/stopped, "Resume Insulin") -> SHOULD be
  113. // .critical; registry .timeSensitive. PumpManagerAlert.swift:52,81,124 /
  114. // AlertCatalogRegistry.swift:77
  115. "suspendEnded",
  116. // podExpiring: N6-pump (72h "Pod Expired", delivery ending) -> SHOULD be .critical;
  117. // registry .timeSensitive. PumpManagerAlert.swift:44,71 / AlertCatalogRegistry.swift:73
  118. "podExpiring",
  119. // pod-fault 0x1C exceededMaximumPodLife80Hrs: N6-pump (80h hard stop, delivery
  120. // stopped) -> SHOULD be .critical; omniPodFaultEntry returns .timeSensitive.
  121. // FaultEventCode.swift:650,659 / AlertCatalogRegistry.swift:43-47
  122. "Fault Event Code 0x1C: exceededMaximumPodLife80Hrs",
  123. // Classifier-channel gaps — keyed by String(describing: error)
  124. // podSuspended: N9 (Medium) -> SHOULD be .timeSensitive; .other -> .active.
  125. // PodCommsSession.swift:80 / TrioAlertCategory.swift:173
  126. "podSuspended",
  127. // noResponse: N8 (Medium) -> SHOULD be .timeSensitive; .commsTransient -> .active.
  128. // PodCommsSession.swift:56 / TrioAlertCategory.swift:167-171
  129. "noResponse",
  130. // podNotConnected: N8 (Medium) -> SHOULD be .timeSensitive; .commsTransient -> .active.
  131. // PodCommsSession.swift:72 / TrioAlertCategory.swift:167
  132. "podNotConnected",
  133. // unfinalizedBolus: N9 (Medium) -> SHOULD be .timeSensitive; .other -> .active.
  134. // PodCommsSession.swift:74 / TrioAlertCategory.swift:173
  135. "unfinalizedBolus"
  136. ]
  137. // The taxonomy-required minimum level for each gap row, used to recompute the gap set.
  138. // (identifier, taxonomy-required level).
  139. static let gapTaxonomyRequirements: [(identifier: String, required: Alert.InterruptionLevel)] = [
  140. ("userPodExpiration", .timeSensitive),
  141. ("suspendEnded", .critical),
  142. ("podExpiring", .critical),
  143. ("Fault Event Code 0x1C: exceededMaximumPodLife80Hrs", .critical),
  144. ("podSuspended", .timeSensitive),
  145. ("noResponse", .timeSensitive),
  146. ("podNotConnected", .timeSensitive),
  147. ("unfinalizedBolus", .timeSensitive)
  148. ]
  149. /// Numeric severity for comparison: .active < .timeSensitive < .critical.
  150. private func severity(_ level: Alert.InterruptionLevel) -> Int {
  151. switch level {
  152. case .active: return 0
  153. case .timeSensitive: return 1
  154. case .critical: return 2
  155. @unknown default: return -1
  156. }
  157. }
  158. /// Resolves the EFFECTIVE current level of a gap-candidate identifier from the pinned
  159. /// tables: registry entry (LoopKit-Alert rows) or classifier category interruptionLevel.
  160. private func effectiveLevel(for identifier: String) -> Alert.InterruptionLevel? {
  161. // LoopKit-Alert rows (match by alertIdentifier across both manager keys).
  162. if let row = Self.registryRows.first(where: { $0.alertID == identifier }) {
  163. return AlertCatalogRegistry.lookup(id(row.manager, row.alertID))?.interruptionLevel
  164. }
  165. // Classifier rows (match by String(describing:)).
  166. if let row = Self.classifierRows.first(where: { $0.describing == identifier }) {
  167. return TrioAlertClassifier.categorize(error: StubError(description: row.describing)).interruptionLevel
  168. }
  169. return nil
  170. }
  171. @Test("known escalation gaps are exactly as documented") func knownGapsAreExactlyAsDocumented() {
  172. var recomputed: Set<String> = []
  173. for requirement in Self.gapTaxonomyRequirements {
  174. // Assume .timeSensitive if effective level is somehow unresolved (none expected).
  175. let effective = effectiveLevel(for: requirement.identifier) ?? .timeSensitive
  176. if severity(effective) < severity(requirement.required) {
  177. recomputed.insert(requirement.identifier)
  178. }
  179. }
  180. #expect(recomputed == Self.knownEscalationGaps)
  181. }
  182. }
  183. // MARK: - Message Classification: OmnipodKit
  184. //
  185. // SPEC. In production, APSManager.processError hands the caught Swift error to
  186. // TrioAlertClassifier.categorize(error:), which classifies over
  187. // String(describing: error).lowercased() — i.e. the ERROR CASE NAME shape
  188. // (e.g. "podSuspended", "unacknowledgedCommandPending"), NOT the user-facing
  189. // display text. This suite instead catalogs every reportable OmnipodKit message
  190. // — the alertTitle/alertBody/errorMessage/notificationTitle/validation strings a
  191. // user actually sees — keyed by its emitting identifier, and PINS how the
  192. // substring classifier would handle that real prose if it were fed verbatim.
  193. //
  194. // Why this matters: the classifier's checks are SPACELESS substrings
  195. // ("podexpired", "reservoirempty", "noresponse", "communication", "fault", ...).
  196. // Natural-language prose contains spaces, so "Pod Expired" (-> "pod expired")
  197. // does NOT contain "podexpired", "Empty Reservoir" does NOT contain
  198. // "emptyreservoir"/"reservoirempty", "No response from pod" does NOT contain
  199. // "noresponse". The overwhelming majority of OmnipodKit prose therefore falls
  200. // through to .other — a large coverage gap relative to the message taxonomy.
  201. // A handful land in a real bucket by accident: any string containing the bare
  202. // token "fault" ("No faults", "Pod Fault: …", "Internal pod fault …") ->
  203. // .hardwareFault; "occlusion"/"occluded" -> .occlusion; "comms"/"communication"
  204. // -> .commsTransient; "timeout" -> .commsTransient; "unacknowledged" ->
  205. // .deliveryUncertain.
  206. //
  207. // Source: managers/pump OmnipodKit (managerIdentifier "Omni"). Per-row sourceRef
  208. // comments cite the emitting file/line. Classifier under test:
  209. // Trio/Sources/Services/Alerts/TrioAlertCategory.swift
  210. // (TrioAlertClassifier.categorize / categorize(pumpError:)).
  211. //
  212. // NOTE on the four rows where this suite's pinned category differs from the audit
  213. // JSON's hand-traced currentCategory: the audit mis-applied the spaceless rule.
  214. // The pins below reflect the ACTUAL classifier output and stay green:
  215. // - podExpireImminent "Pod Expired / …" -> .other (no "podexpired" token; space).
  216. // - "No faults" -> .hardwareFault (contains "fault").
  217. // - DeactivationError "…communicating…" -> .other ("communicating" != "communication").
  218. // - unableToReachPod "…Communication was…" -> .commsTransient (contains "communication").
  219. //
  220. // Two @Tests assert CURRENT behavior and MUST stay green:
  221. // 1. each (identifier, message) classifies as pinned.
  222. // 2. the recomputed coverage-gap set equals classifierCoverageGaps.
  223. // The gap ratchet fails the moment the classifier improves (a gap row starts
  224. // hitting a real bucket), forcing this file to be updated.
  225. @Suite("Trio Alert Emission: OmnipodKit — Classification") struct OmnipodKitMessageClassificationTests {
  226. /// Error whose String(describing:) is exactly `description`, so the substring
  227. /// classifier matches over the supplied display text verbatim.
  228. private struct StubError: Error, CustomStringConvertible {
  229. let description: String
  230. }
  231. /// One reportable message: its emitting identifier, the exact user-facing text,
  232. /// the message role, its taxonomy id, and the pinned classifier category.
  233. struct Row {
  234. let identifier: String
  235. let message: String
  236. let role: String
  237. let taxonomy: String
  238. let expected: TrioAlertCategory
  239. }
  240. static let rows: [Row] = [
  241. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:46,73
  242. Row(
  243. identifier: "podExpireImminent",
  244. message: "Pod Expired / Change Pod now. Insulin delivery will stop in 1 hour.",
  245. role: "alertBody",
  246. taxonomy: "F3",
  247. expected: .other("Pod Expired / Change Pod now. Insulin delivery will stop in 1 hour.")
  248. ),
  249. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:42,69
  250. Row(
  251. identifier: "userPodExpiration",
  252. message: "Pod Expiration Reminder / Pod expires in %1$@.",
  253. role: "alertTitle",
  254. taxonomy: "F3",
  255. expected: .other("Pod Expiration Reminder / Pod expires in %1$@.")
  256. ),
  257. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:48,77
  258. Row(
  259. identifier: "lowReservoir",
  260. message: "Low Reservoir / %1$@ insulin or less remaining in Pod. Change Pod soon.",
  261. role: "alertTitle",
  262. taxonomy: "F1",
  263. expected: .other("Low Reservoir / %1$@ insulin or less remaining in Pod. Change Pod soon.")
  264. ),
  265. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:50,79
  266. Row(
  267. identifier: "suspendInProgress",
  268. message: "Suspend In Progress Reminder / Suspend In Progress Reminder",
  269. role: "alertTitle",
  270. taxonomy: "N2",
  271. expected: .other("Suspend In Progress Reminder / Suspend In Progress Reminder")
  272. ),
  273. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:52,81,124
  274. Row(
  275. identifier: "suspendEnded",
  276. message: "Resume Insulin / The insulin suspension period has ended.\n\nYou can resume delivery from the banner on the home screen or from your pump settings screen. You will be reminded again in 15 minutes.",
  277. role: "alertBody",
  278. taxonomy: "N2",
  279. expected: .other(
  280. "Resume Insulin / The insulin suspension period has ended.\n\nYou can resume delivery from the banner on the home screen or from your pump settings screen. You will be reminded again in 15 minutes."
  281. )
  282. ),
  283. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:52,81,124
  284. Row(
  285. identifier: "suspendEnded",
  286. message: "Suspension time is up. Open the app and resume.",
  287. role: "alertBody",
  288. taxonomy: "N2",
  289. expected: .other("Suspension time is up. Open the app and resume.")
  290. ),
  291. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:44,71
  292. Row(
  293. identifier: "podExpiring",
  294. message: "Pod Expired / Change Pod now. Pod has been active for 72 hours.",
  295. role: "alertBody",
  296. taxonomy: "N6",
  297. expected: .other("Pod Expired / Change Pod now. Pod has been active for 72 hours.")
  298. ),
  299. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:54,83
  300. Row(
  301. identifier: "finishSetupReminder",
  302. message: "Pod Pairing Incomplete / Please finish pairing your pod.",
  303. role: "alertTitle",
  304. taxonomy: "N14",
  305. expected: .other("Pod Pairing Incomplete / Please finish pairing your pod.")
  306. ),
  307. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:56,86
  308. Row(
  309. identifier: "unexpectedAlert",
  310. message: "Unexpected Alert / Unexpected Pod Alert #%1@!",
  311. role: "alertTitle",
  312. taxonomy: "N1",
  313. expected: .other("Unexpected Alert / Unexpected Pod Alert #%1@!")
  314. ),
  315. // OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:58,88
  316. Row(
  317. identifier: "timeOffsetChangeDetected",
  318. message: "Time Change Detected / The time on your pump is different from the current time. You can review the pump time and and sync to current time in settings.",
  319. role: "alertTitle",
  320. taxonomy: "N14",
  321. expected: .other(
  322. "Time Change Detected / The time on your pump is different from the current time. You can review the pump time and and sync to current time in settings."
  323. )
  324. ),
  325. // OmnipodKit/OmnipodCommon/AlertSlot.swift:101,141
  326. Row(
  327. identifier: "PodAlert.autoOff",
  328. message: "Auto-off",
  329. role: "errorMessage",
  330. taxonomy: "N2",
  331. expected: .other("Auto-off")
  332. ),
  333. // OmnipodKit/OmnipodCommon/AlertSlot.swift:108,147
  334. Row(
  335. identifier: "PodAlert.shutdownImminent",
  336. message: "Shutdown imminent",
  337. role: "errorMessage",
  338. taxonomy: "F3",
  339. expected: .other("Shutdown imminent")
  340. ),
  341. // OmnipodKit/OmnipodCommon/AlertSlot.swift:113,150
  342. Row(
  343. identifier: "PodAlert.expirationReminder",
  344. message: "Expiration reminder",
  345. role: "errorMessage",
  346. taxonomy: "F3",
  347. expected: .other("Expiration reminder")
  348. ),
  349. // OmnipodKit/OmnipodCommon/AlertSlot.swift:116,153
  350. Row(
  351. identifier: "PodAlert.lowReservoir",
  352. message: "Low reservoir",
  353. role: "errorMessage",
  354. taxonomy: "F1",
  355. expected: .other("Low reservoir")
  356. ),
  357. // OmnipodKit/OmnipodCommon/AlertSlot.swift:120,156
  358. Row(
  359. identifier: "PodAlert.podSuspendedReminder",
  360. message: "Pod suspended reminder",
  361. role: "errorMessage",
  362. taxonomy: "N2",
  363. expected: .other("Pod suspended reminder")
  364. ),
  365. // OmnipodKit/OmnipodCommon/AlertSlot.swift:125,159
  366. Row(
  367. identifier: "PodAlert.suspendTimeExpired",
  368. message: "Suspend time expired",
  369. role: "errorMessage",
  370. taxonomy: "N2",
  371. expected: .other("Suspend time expired")
  372. ),
  373. // OmnipodKit/OmnipodCommon/AlertSlot.swift:128,162
  374. Row(
  375. identifier: "PodAlert.waitingForPairingReminder",
  376. message: "Waiting for pairing reminder",
  377. role: "errorMessage",
  378. taxonomy: "N14",
  379. expected: .other("Waiting for pairing reminder")
  380. ),
  381. // OmnipodKit/OmnipodCommon/AlertSlot.swift:131,164
  382. Row(
  383. identifier: "PodAlert.finishSetupReminder",
  384. message: "Finish setup reminder",
  385. role: "errorMessage",
  386. taxonomy: "N14",
  387. expected: .other("Finish setup reminder")
  388. ),
  389. // OmnipodKit/OmnipodCommon/AlertSlot.swift:134,166
  390. Row(
  391. identifier: "PodAlert.expired",
  392. message: "Pod expired",
  393. role: "errorMessage",
  394. taxonomy: "N6",
  395. expected: .other("Pod expired")
  396. ),
  397. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:646,659
  398. Row(
  399. identifier: "reservoirEmpty",
  400. message: "Empty Reservoir / Insulin delivery stopped. Change Pod now.",
  401. role: "notificationTitle",
  402. taxonomy: "N4",
  403. expected: .other("Empty Reservoir / Insulin delivery stopped. Change Pod now.")
  404. ),
  405. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:648,659
  406. Row(
  407. identifier: "occlusion",
  408. message: "Occlusion Detected / Insulin delivery stopped. Change Pod now.",
  409. role: "notificationTitle",
  410. taxonomy: "N1",
  411. expected: .occlusion
  412. ),
  413. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:650,659
  414. Row(
  415. identifier: "exceededMaximumPodLife80Hrs",
  416. message: "Pod Expired / Insulin delivery stopped. Change Pod now.",
  417. role: "notificationTitle",
  418. taxonomy: "N6",
  419. expected: .other("Pod Expired / Insulin delivery stopped. Change Pod now.")
  420. ),
  421. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:652,659
  422. Row(
  423. identifier: "criticalPodFault",
  424. message: "Critical Pod Fault %1$@ / Insulin delivery stopped. Change Pod now.",
  425. role: "notificationTitle",
  426. taxonomy: "N1",
  427. expected: .hardwareFault
  428. ),
  429. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:619-640
  430. Row(
  431. identifier: "FaultEventCode.localizedDescription",
  432. message: "No faults",
  433. role: "errorMessage",
  434. taxonomy: "N1",
  435. expected: .hardwareFault
  436. ),
  437. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:619-640
  438. Row(
  439. identifier: "FaultEventCode.localizedDescription",
  440. message: "Empty reservoir",
  441. role: "errorMessage",
  442. taxonomy: "N1",
  443. expected: .other("Empty reservoir")
  444. ),
  445. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:619-640
  446. Row(
  447. identifier: "FaultEventCode.localizedDescription",
  448. message: "Pod expired",
  449. role: "errorMessage",
  450. taxonomy: "N1",
  451. expected: .other("Pod expired")
  452. ),
  453. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:619-640
  454. Row(
  455. identifier: "FaultEventCode.localizedDescription",
  456. message: "Occlusion detected",
  457. role: "errorMessage",
  458. taxonomy: "N1",
  459. expected: .occlusion
  460. ),
  461. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:619-640
  462. Row(
  463. identifier: "FaultEventCode.localizedDescription",
  464. message: "Internal pod fault %1$@",
  465. role: "errorMessage",
  466. taxonomy: "N1",
  467. expected: .hardwareFault
  468. ),
  469. // OmnipodKit/OmnipodCommon/FaultEventCode.swift:619-640
  470. Row(
  471. identifier: "FaultEventCode.localizedDescription",
  472. message: "Unknown pod fault %1$@",
  473. role: "errorMessage",
  474. taxonomy: "N1",
  475. expected: .hardwareFault
  476. ),
  477. // OmnipodKit/PumpManager/PodCommsSession.swift:83
  478. Row(
  479. identifier: "PodCommsError.podFault",
  480. message: "Pod Fault: %1$@",
  481. role: "errorMessage",
  482. taxonomy: "N1",
  483. expected: .hardwareFault
  484. ),
  485. // OmnipodKit/PumpManager/PodCommsSession.swift:52
  486. Row(
  487. identifier: "PodCommsError.noPodPaired",
  488. message: "No pod paired",
  489. role: "errorMessage",
  490. taxonomy: "N9",
  491. expected: .other("No pod paired")
  492. ),
  493. // OmnipodKit/PumpManager/PodCommsSession.swift:56
  494. Row(
  495. identifier: "PodCommsError.noResponse",
  496. message: "No response from pod",
  497. role: "errorMessage",
  498. taxonomy: "N8",
  499. expected: .other("No response from pod")
  500. ),
  501. // OmnipodKit/PumpManager/PodCommsSession.swift:56
  502. Row(
  503. identifier: "PodCommsError.noResponse",
  504. message: "Make sure iPhone is nearby the active pod",
  505. role: "errorMessage",
  506. taxonomy: "N8",
  507. expected: .other("Make sure iPhone is nearby the active pod")
  508. ),
  509. // OmnipodKit/PumpManager/PodCommsSession.swift:56
  510. Row(
  511. identifier: "PodCommsError.noResponseRL",
  512. message: "No response from pod",
  513. role: "errorMessage",
  514. taxonomy: "N8",
  515. expected: .other("No response from pod")
  516. ),
  517. // OmnipodKit/PumpManager/PodCommsSession.swift:56
  518. Row(
  519. identifier: "PodCommsError.noResponseRL",
  520. message: "Please try repositioning the pod or the RileyLink and try again",
  521. role: "errorMessage",
  522. taxonomy: "N8",
  523. expected: .other("Please try repositioning the pod or the RileyLink and try again")
  524. ),
  525. // OmnipodKit/PumpManager/PodCommsSession.swift:58
  526. Row(
  527. identifier: "PodCommsError.emptyResponse",
  528. message: "Empty response from pod",
  529. role: "errorMessage",
  530. taxonomy: "N8",
  531. expected: .other("Empty response from pod")
  532. ),
  533. // OmnipodKit/PumpManager/PodCommsSession.swift:60
  534. Row(
  535. identifier: "PodCommsError.podAckedInsteadOfReturningResponse",
  536. message: "Pod sent ack instead of response",
  537. role: "errorMessage",
  538. taxonomy: "N8",
  539. expected: .other("Pod sent ack instead of response")
  540. ),
  541. // OmnipodKit/PumpManager/PodCommsSession.swift:60
  542. Row(
  543. identifier: "PodCommsError.podAckedInsteadOfReturningResponse",
  544. message: "Try again",
  545. role: "errorMessage",
  546. taxonomy: "N8",
  547. expected: .other("Try again")
  548. ),
  549. // OmnipodKit/PumpManager/PodCommsSession.swift:64
  550. Row(
  551. identifier: "PodCommsError.unexpectedResponse",
  552. message: "Unexpected response from pod",
  553. role: "errorMessage",
  554. taxonomy: "N8",
  555. expected: .other("Unexpected response from pod")
  556. ),
  557. // OmnipodKit/PumpManager/PodCommsSession.swift:68
  558. Row(
  559. identifier: "PodCommsError.invalidAddress",
  560. message: "Invalid address 0x%x. Expected 0x%x",
  561. role: "errorMessage",
  562. taxonomy: "N8",
  563. expected: .other("Invalid address 0x%x. Expected 0x%x")
  564. ),
  565. // OmnipodKit/PumpManager/PodCommsSession.swift:68
  566. Row(
  567. identifier: "PodCommsError.invalidAddress",
  568. message: "Crosstalk possible. Please move to a new location",
  569. role: "errorMessage",
  570. taxonomy: "N8",
  571. expected: .other("Crosstalk possible. Please move to a new location")
  572. ),
  573. // OmnipodKit/PumpManager/PodCommsSession.swift:70
  574. Row(
  575. identifier: "PodCommsError.noRileyLinkAvailable",
  576. message: "No RileyLink available",
  577. role: "errorMessage",
  578. taxonomy: "N8",
  579. expected: .other("No RileyLink available")
  580. ),
  581. // OmnipodKit/PumpManager/PodCommsSession.swift:70
  582. Row(
  583. identifier: "PodCommsError.noRileyLinkAvailable",
  584. message: "Make sure your RileyLink is nearby and powered on",
  585. role: "errorMessage",
  586. taxonomy: "N8",
  587. expected: .other("Make sure your RileyLink is nearby and powered on")
  588. ),
  589. // OmnipodKit/PumpManager/PodCommsSession.swift:72
  590. Row(
  591. identifier: "PodCommsError.podNotConnected",
  592. message: "Pod not connected",
  593. role: "errorMessage",
  594. taxonomy: "N8",
  595. expected: .other("Pod not connected")
  596. ),
  597. // OmnipodKit/PumpManager/PodCommsSession.swift:72
  598. Row(
  599. identifier: "PodCommsError.podNotConnected",
  600. message: "Make sure your pod is nearby and try again",
  601. role: "errorMessage",
  602. taxonomy: "N8",
  603. expected: .other("Make sure your pod is nearby and try again")
  604. ),
  605. // OmnipodKit/PumpManager/PodCommsSession.swift:74
  606. Row(
  607. identifier: "PodCommsError.unfinalizedBolus",
  608. message: "Bolus in progress",
  609. role: "errorMessage",
  610. taxonomy: "N9",
  611. expected: .other("Bolus in progress")
  612. ),
  613. // OmnipodKit/PumpManager/PodCommsSession.swift:74
  614. Row(
  615. identifier: "PodCommsError.unfinalizedBolus",
  616. message: "Wait for existing bolus to finish, or cancel bolus",
  617. role: "errorMessage",
  618. taxonomy: "N9",
  619. expected: .other("Wait for existing bolus to finish, or cancel bolus")
  620. ),
  621. // OmnipodKit/PumpManager/PodCommsSession.swift:76
  622. Row(
  623. identifier: "PodCommsError.unfinalizedTempBasal",
  624. message: "Temp basal in progress",
  625. role: "errorMessage",
  626. taxonomy: "N9",
  627. expected: .other("Temp basal in progress")
  628. ),
  629. // OmnipodKit/PumpManager/PodCommsSession.swift:76
  630. Row(
  631. identifier: "PodCommsError.unfinalizedTempBasal",
  632. message: "Wait for existing temp basal to finish, or suspend to cancel",
  633. role: "errorMessage",
  634. taxonomy: "N9",
  635. expected: .other("Wait for existing temp basal to finish, or suspend to cancel")
  636. ),
  637. // OmnipodKit/PumpManager/PodCommsSession.swift:80
  638. Row(
  639. identifier: "PodCommsError.podSuspended",
  640. message: "Pod is suspended",
  641. role: "errorMessage",
  642. taxonomy: "N9",
  643. expected: .other("Pod is suspended")
  644. ),
  645. // OmnipodKit/PumpManager/PodCommsSession.swift:80
  646. Row(
  647. identifier: "PodCommsError.podSuspended",
  648. message: "Resume delivery",
  649. role: "errorMessage",
  650. taxonomy: "N9",
  651. expected: .other("Resume delivery")
  652. ),
  653. // OmnipodKit/PumpManager/PodCommsSession.swift:89
  654. Row(
  655. identifier: "PodCommsError.unacknowledgedMessage",
  656. message: "wrapped error's localizedDescription",
  657. role: "errorMessage",
  658. taxonomy: "N3",
  659. expected: .other("wrapped error's localizedDescription")
  660. ),
  661. // OmnipodKit/PumpManager/PodCommsSession.swift:92
  662. Row(
  663. identifier: "PodCommsError.unacknowledgedCommandPending",
  664. message: "Communication issue: Unacknowledged command pending.",
  665. role: "errorMessage",
  666. taxonomy: "N3",
  667. expected: .deliveryUncertain
  668. ),
  669. // OmnipodKit/PumpManager/PodCommsSession.swift:93-97
  670. Row(
  671. identifier: "PodCommsError.rejectedMessage",
  672. message: "Command error %1$@: %2$@",
  673. role: "errorMessage",
  674. taxonomy: "N9",
  675. expected: .other("Command error %1$@: %2$@")
  676. ),
  677. // OmnipodKit/PumpManager/PodCommsSession.swift:99
  678. Row(
  679. identifier: "PodCommsError.podChange",
  680. message: "Unexpected pod change",
  681. role: "errorMessage",
  682. taxonomy: "N12",
  683. expected: .other("Unexpected pod change")
  684. ),
  685. // OmnipodKit/PumpManager/PodCommsSession.swift:99
  686. Row(
  687. identifier: "PodCommsError.podChange",
  688. message: "Please bring only original pod in range or deactivate original pod",
  689. role: "errorMessage",
  690. taxonomy: "N12",
  691. expected: .other("Please bring only original pod in range or deactivate original pod")
  692. ),
  693. // OmnipodKit/PumpManager/PodCommsSession.swift:101
  694. Row(
  695. identifier: "PodCommsError.activationTimeExceeded",
  696. message: "Activation time exceeded",
  697. role: "errorMessage",
  698. taxonomy: "N12",
  699. expected: .other("Activation time exceeded")
  700. ),
  701. // OmnipodKit/PumpManager/PodCommsSession.swift:103
  702. Row(
  703. identifier: "PodCommsError.rssiTooLow",
  704. message: "Poor signal strength",
  705. role: "errorMessage",
  706. taxonomy: "N12",
  707. expected: .other("Poor signal strength")
  708. ),
  709. // OmnipodKit/PumpManager/PodCommsSession.swift:103
  710. Row(
  711. identifier: "PodCommsError.rssiTooLow",
  712. message: "Please reposition the RileyLink relative to the pod",
  713. role: "errorMessage",
  714. taxonomy: "N12",
  715. expected: .other("Please reposition the RileyLink relative to the pod")
  716. ),
  717. // OmnipodKit/PumpManager/PodCommsSession.swift:105
  718. Row(
  719. identifier: "PodCommsError.rssiTooHigh",
  720. message: "Signal strength too high",
  721. role: "errorMessage",
  722. taxonomy: "N12",
  723. expected: .other("Signal strength too high")
  724. ),
  725. // OmnipodKit/PumpManager/PodCommsSession.swift:105
  726. Row(
  727. identifier: "PodCommsError.rssiTooHigh",
  728. message: "Please reposition the RileyLink further from the pod",
  729. role: "errorMessage",
  730. taxonomy: "N12",
  731. expected: .other("Please reposition the RileyLink further from the pod")
  732. ),
  733. // OmnipodKit/PumpManager/PodCommsSession.swift:107
  734. Row(
  735. identifier: "PodCommsError.diagnosticMessage",
  736. message: "Received resynchronization SQN for the second time",
  737. role: "errorMessage",
  738. taxonomy: "N15",
  739. expected: .other("Received resynchronization SQN for the second time")
  740. ),
  741. // OmnipodKit/PumpManager/PodCommsSession.swift:107
  742. Row(
  743. identifier: "PodCommsError.diagnosticMessage",
  744. message: "Pod type not configured",
  745. role: "errorMessage",
  746. taxonomy: "N15",
  747. expected: .other("Pod type not configured")
  748. ),
  749. // OmnipodKit/PumpManager/PodCommsSession.swift:109
  750. Row(
  751. identifier: "PodCommsError.podIncompatible",
  752. message: "Pod version %@ ... not supported",
  753. role: "errorMessage",
  754. taxonomy: "N12",
  755. expected: .other("Pod version %@ ... not supported")
  756. ),
  757. // OmnipodKit/PumpManager/PodCommsSession.swift:111
  758. Row(
  759. identifier: "PodCommsError.noPodsFound",
  760. message: "No pods found",
  761. role: "errorMessage",
  762. taxonomy: "N12",
  763. expected: .other("No pods found")
  764. ),
  765. // OmnipodKit/PumpManager/PodCommsSession.swift:111
  766. Row(
  767. identifier: "PodCommsError.noPodsFound",
  768. message: "Make sure your pod is filled and nearby",
  769. role: "errorMessage",
  770. taxonomy: "N12",
  771. expected: .other("Make sure your pod is filled and nearby")
  772. ),
  773. // OmnipodKit/PumpManager/PodCommsSession.swift:113
  774. Row(
  775. identifier: "PodCommsError.tooManyPodsFound",
  776. message: "Too many pods found",
  777. role: "errorMessage",
  778. taxonomy: "N12",
  779. expected: .other("Too many pods found")
  780. ),
  781. // OmnipodKit/PumpManager/PodCommsSession.swift:113
  782. Row(
  783. identifier: "PodCommsError.tooManyPodsFound",
  784. message: "Move to a new area away from any other pods and try again",
  785. role: "errorMessage",
  786. taxonomy: "N12",
  787. expected: .other("Move to a new area away from any other pods and try again")
  788. ),
  789. // OmnipodKit/PumpManager/PodCommsSession.swift:115
  790. Row(
  791. identifier: "PodCommsError.setupNotComplete",
  792. message: "Pod setup is not complete",
  793. role: "errorMessage",
  794. taxonomy: "N9",
  795. expected: .other("Pod setup is not complete")
  796. ),
  797. // OmnipodKit/PumpManager/PodCommsSession.swift:117
  798. Row(
  799. identifier: "PodCommsError.noCertificateFound",
  800. message: "No certificate found",
  801. role: "errorMessage",
  802. taxonomy: "N10",
  803. expected: .other("No certificate found")
  804. ),
  805. // OmnipodKit/PumpManager/PodCommsSession.swift:117
  806. Row(
  807. identifier: "PodCommsError.noCertificateFound",
  808. message: "Retrieve an Omnipod 5 Pod Certificate to continue.",
  809. role: "errorMessage",
  810. taxonomy: "N10",
  811. expected: .other("Retrieve an Omnipod 5 Pod Certificate to continue.")
  812. ),
  813. // OmnipodKit/PumpManager/OmniPumpManager.swift:46
  814. Row(
  815. identifier: "OmniPumpManagerError.noPodPaired",
  816. message: "No pod paired",
  817. role: "errorMessage",
  818. taxonomy: "N9",
  819. expected: .other("No pod paired")
  820. ),
  821. // OmnipodKit/PumpManager/OmniPumpManager.swift:46
  822. Row(
  823. identifier: "OmniPumpManagerError.noPodPaired",
  824. message: "Please pair a new pod",
  825. role: "errorMessage",
  826. taxonomy: "N9",
  827. expected: .other("Please pair a new pod")
  828. ),
  829. // OmnipodKit/PumpManager/OmniPumpManager.swift:48
  830. Row(
  831. identifier: "OmniPumpManagerError.insulinTypeNotConfigured",
  832. message: "Insulin type not configured",
  833. role: "validation",
  834. taxonomy: "N13",
  835. expected: .other("Insulin type not configured")
  836. ),
  837. // OmnipodKit/PumpManager/OmniPumpManager.swift:50
  838. Row(
  839. identifier: "OmniPumpManagerError.notReadyForCannulaInsertion",
  840. message: "Pod is not in a state ready for cannula insertion",
  841. role: "errorMessage",
  842. taxonomy: "N12",
  843. expected: .other("Pod is not in a state ready for cannula insertion")
  844. ),
  845. // OmnipodKit/PumpManager/OmniPumpManager.swift:52
  846. Row(
  847. identifier: "OmniPumpManagerError.invalidSetting",
  848. message: "Invalid Setting",
  849. role: "validation",
  850. taxonomy: "N13",
  851. expected: .other("Invalid Setting")
  852. ),
  853. // OmnipodKit/PumpManager/OmniPumpManager.swift:54
  854. Row(
  855. identifier: "OmniPumpManagerError.podTypeNotConfigured",
  856. message: "Pod type not configured",
  857. role: "validation",
  858. taxonomy: "N13",
  859. expected: .other("Pod type not configured")
  860. ),
  861. // OmnipodKit/PumpManagerUI/Views/OmniSettingsView.swift:749-753
  862. Row(
  863. identifier: "OmniSettingsViewAlert.suspendError",
  864. message: "Failed to Suspend Insulin Delivery",
  865. role: "alertTitle",
  866. taxonomy: "N9",
  867. expected: .other("Failed to Suspend Insulin Delivery")
  868. ),
  869. // OmnipodKit/PumpManagerUI/Views/OmniSettingsView.swift:755-759
  870. Row(
  871. identifier: "OmniSettingsViewAlert.resumeError",
  872. message: "Failed to Resume Insulin Delivery",
  873. role: "alertTitle",
  874. taxonomy: "N9",
  875. expected: .other("Failed to Resume Insulin Delivery")
  876. ),
  877. // OmnipodKit/PumpManagerUI/Views/OmniSettingsView.swift:761-765
  878. Row(
  879. identifier: "OmniSettingsViewAlert.syncTimeError",
  880. message: "Failed to Set Pump Time",
  881. role: "alertTitle",
  882. taxonomy: "N9",
  883. expected: .other("Failed to Set Pump Time")
  884. ),
  885. // OmnipodKit/PumpManagerUI/Views/OmniSettingsView.swift:767-771
  886. Row(
  887. identifier: "OmniSettingsViewAlert.cancelManualBasalError",
  888. message: "Failed to Cancel Manual Basal",
  889. role: "alertTitle",
  890. taxonomy: "N9",
  891. expected: .other("Failed to Cancel Manual Basal")
  892. ),
  893. // OmnipodKit/PumpManagerUI/ViewModels/DeactivatePodViewModel.swift:198-214
  894. Row(
  895. identifier: "DeactivationError.OmniPumpManagerError",
  896. message: "There was a problem communicating with the pod. If this problem persists, tap Discard Pod. You can then activate a new Pod.",
  897. role: "errorMessage",
  898. taxonomy: "N12",
  899. expected: .other(
  900. "There was a problem communicating with the pod. If this problem persists, tap Discard Pod. You can then activate a new Pod."
  901. )
  902. ),
  903. // OmnipodKit/OmnipodCommon/Message.swift:26
  904. Row(
  905. identifier: "MessageError.notEnoughData",
  906. message: "Not enough data",
  907. role: "errorMessage",
  908. taxonomy: "N8",
  909. expected: .other("Not enough data")
  910. ),
  911. // OmnipodKit/OmnipodCommon/Message.swift:28
  912. Row(
  913. identifier: "MessageError.invalidCrc",
  914. message: "Invalid CRC",
  915. role: "errorMessage",
  916. taxonomy: "N8",
  917. expected: .other("Invalid CRC")
  918. ),
  919. // OmnipodKit/OmnipodCommon/Message.swift:30
  920. Row(
  921. identifier: "MessageError.invalidSequence",
  922. message: "Unexpected message sequence number",
  923. role: "errorMessage",
  924. taxonomy: "N8",
  925. expected: .other("Unexpected message sequence number")
  926. ),
  927. // OmnipodKit/OmnipodCommon/Message.swift:32
  928. Row(
  929. identifier: "MessageError.invalidAddress",
  930. message: "Invalid address: (%1$@)",
  931. role: "errorMessage",
  932. taxonomy: "N8",
  933. expected: .other("Invalid address: (%1$@)")
  934. ),
  935. // OmnipodKit/OmnipodCommon/Message.swift:34
  936. Row(
  937. identifier: "MessageError.parsingError",
  938. message: "Parsing Error: %1$@ in (%2$@)",
  939. role: "errorMessage",
  940. taxonomy: "N8",
  941. expected: .other("Parsing Error: %1$@ in (%2$@)")
  942. ),
  943. // OmnipodKit/OmnipodCommon/Message.swift:36
  944. Row(
  945. identifier: "MessageError.unknownValue",
  946. message: "Unknown Value (%1$@) for type %2$@",
  947. role: "errorMessage",
  948. taxonomy: "N8",
  949. expected: .other("Unknown Value (%1$@) for type %2$@")
  950. ),
  951. // OmnipodKit/OmnipodCommon/Message.swift:38
  952. Row(
  953. identifier: "MessageError.validationFailed",
  954. message: "Validation failed: %1$@",
  955. role: "errorMessage",
  956. taxonomy: "N8",
  957. expected: .other("Validation failed: %1$@")
  958. ),
  959. // OmnipodKit/Bluetooth/PeripheralManagerError.swift:29
  960. Row(
  961. identifier: "PeripheralManagerError.notReady",
  962. message: "Peripheral Not Ready",
  963. role: "errorMessage",
  964. taxonomy: "N8",
  965. expected: .other("Peripheral Not Ready")
  966. ),
  967. // OmnipodKit/Bluetooth/PeripheralManagerError.swift:31
  968. Row(
  969. identifier: "PeripheralManagerError.incorrectResponse",
  970. message: "Incorrect Response",
  971. role: "errorMessage",
  972. taxonomy: "N8",
  973. expected: .other("Incorrect Response")
  974. ),
  975. // OmnipodKit/Bluetooth/PeripheralManagerError.swift:33
  976. Row(
  977. identifier: "PeripheralManagerError.timeout",
  978. message: "Timeout",
  979. role: "errorMessage",
  980. taxonomy: "N8",
  981. expected: .commsTransient
  982. ),
  983. // OmnipodKit/Bluetooth/PeripheralManagerError.swift:35
  984. Row(
  985. identifier: "PeripheralManagerError.emptyValue",
  986. message: "Empty Value",
  987. role: "errorMessage",
  988. taxonomy: "N8",
  989. expected: .other("Empty Value")
  990. ),
  991. // OmnipodKit/Bluetooth/PeripheralManagerError.swift:37
  992. Row(
  993. identifier: "PeripheralManagerError.unknownCharacteristic",
  994. message: "Unknown Characteristic",
  995. role: "errorMessage",
  996. taxonomy: "N8",
  997. expected: .other("Unknown Characteristic")
  998. ),
  999. // OmnipodKit/Bluetooth/PeripheralManagerError.swift:39
  1000. Row(
  1001. identifier: "PeripheralManagerError.nack",
  1002. message: "Nack",
  1003. role: "errorMessage",
  1004. taxonomy: "N8",
  1005. expected: .other("Nack")
  1006. ),
  1007. // OmnipodKit/Bluetooth/PeripheralManagerError.swift:41
  1008. Row(
  1009. identifier: "PeripheralManagerError.unknownPodType",
  1010. message: "Unknown Pod Type",
  1011. role: "errorMessage",
  1012. taxonomy: "N8",
  1013. expected: .other("Unknown Pod Type")
  1014. ),
  1015. // OmnipodKit/Bluetooth/PodProtocolError.swift:25
  1016. Row(
  1017. identifier: "PodProtocolError.invalidLTKKey",
  1018. message: "Invalid LTK Key: %1$@",
  1019. role: "errorMessage",
  1020. taxonomy: "N10",
  1021. expected: .other("Invalid LTK Key: %1$@")
  1022. ),
  1023. // OmnipodKit/Bluetooth/PodProtocolError.swift:27
  1024. Row(
  1025. identifier: "PodProtocolError.pairingException",
  1026. message: "Pairing Exception: %1$@",
  1027. role: "errorMessage",
  1028. taxonomy: "N10",
  1029. expected: .other("Pairing Exception: %1$@")
  1030. ),
  1031. // OmnipodKit/Bluetooth/PodProtocolError.swift:29
  1032. Row(
  1033. identifier: "PodProtocolError.messageIOException",
  1034. message: "Message IO Exception: %1$@",
  1035. role: "errorMessage",
  1036. taxonomy: "N8",
  1037. expected: .other("Message IO Exception: %1$@")
  1038. ),
  1039. // OmnipodKit/Bluetooth/PodProtocolError.swift:31
  1040. Row(
  1041. identifier: "PodProtocolError.couldNotParseMessageException",
  1042. message: "Could not parse message: %1$@",
  1043. role: "errorMessage",
  1044. taxonomy: "N8",
  1045. expected: .other("Could not parse message: %1$@")
  1046. ),
  1047. // OmnipodKit/Bluetooth/PodProtocolError.swift:33-34
  1048. Row(
  1049. identifier: "PodProtocolError.incorrectPacketException",
  1050. message: "Incorrect Packet Exception: %1$@ (location=%2$d)",
  1051. role: "errorMessage",
  1052. taxonomy: "N8",
  1053. expected: .other("Incorrect Packet Exception: %1$@ (location=%2$d)")
  1054. ),
  1055. // OmnipodKit/Bluetooth/PodProtocolError.swift:35-36
  1056. Row(
  1057. identifier: "PodProtocolError.invalidCrc",
  1058. message: "Payload crc32 %1$@ does not match computed crc32 %2$@",
  1059. role: "errorMessage",
  1060. taxonomy: "N8",
  1061. expected: .other("Payload crc32 %1$@ does not match computed crc32 %2$@")
  1062. ),
  1063. // OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1064. Row(
  1065. identifier: "BluetoothManagerError.bluetoothNotAvailable",
  1066. message: "Bluetooth is powered off",
  1067. role: "errorMessage",
  1068. taxonomy: "N8",
  1069. expected: .other("Bluetooth is powered off")
  1070. ),
  1071. // OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1072. Row(
  1073. identifier: "BluetoothManagerError.bluetoothNotAvailable",
  1074. message: "Bluetooth is resetting",
  1075. role: "errorMessage",
  1076. taxonomy: "N8",
  1077. expected: .other("Bluetooth is resetting")
  1078. ),
  1079. // OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1080. Row(
  1081. identifier: "BluetoothManagerError.bluetoothNotAvailable",
  1082. message: "Bluetooth use is unauthorized",
  1083. role: "errorMessage",
  1084. taxonomy: "N8",
  1085. expected: .other("Bluetooth use is unauthorized")
  1086. ),
  1087. // OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1088. Row(
  1089. identifier: "BluetoothManagerError.bluetoothNotAvailable",
  1090. message: "Bluetooth use unsupported on this device",
  1091. role: "errorMessage",
  1092. taxonomy: "N8",
  1093. expected: .other("Bluetooth use unsupported on this device")
  1094. ),
  1095. // OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1096. Row(
  1097. identifier: "BluetoothManagerError.bluetoothNotAvailable",
  1098. message: "Bluetooth is unavailable for an unknown reason.",
  1099. role: "errorMessage",
  1100. taxonomy: "N8",
  1101. expected: .other("Bluetooth is unavailable for an unknown reason.")
  1102. ),
  1103. // OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1104. Row(
  1105. identifier: "BluetoothManagerError.bluetoothNotAvailable",
  1106. message: "Bluetooth is unavailable: %1$@",
  1107. role: "errorMessage",
  1108. taxonomy: "N8",
  1109. expected: .other("Bluetooth is unavailable: %1$@")
  1110. ),
  1111. // OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1112. Row(
  1113. identifier: "BluetoothManagerError.bluetoothNotAvailable",
  1114. message: "Turn bluetooth on",
  1115. role: "errorMessage",
  1116. taxonomy: "N8",
  1117. expected: .other("Turn bluetooth on")
  1118. ),
  1119. // OmnipodKit/OmnipodCommon/MessageBlocks/ErrorResponse.swift:23-35
  1120. Row(
  1121. identifier: "ErrorResponseCode.badNonce",
  1122. message: "Bad nonce",
  1123. role: "errorMessage",
  1124. taxonomy: "N9",
  1125. expected: .other("Bad nonce")
  1126. ),
  1127. // OmnipodKit/OmnipodCommon/MessageBlocks/ErrorResponse.swift:23-35
  1128. Row(
  1129. identifier: "ErrorResponseCode.o5InvalidCommand",
  1130. message: "Omnipod 5 invalid command",
  1131. role: "errorMessage",
  1132. taxonomy: "N9",
  1133. expected: .other("Omnipod 5 invalid command")
  1134. ),
  1135. // OmnipodKit/OmnipodCommon/MessageBlocks/ErrorResponse.swift:23-35
  1136. Row(
  1137. identifier: "ErrorResponseCode.unknown",
  1138. message: "Unknown error code %u (0x%02X)",
  1139. role: "errorMessage",
  1140. taxonomy: "N9",
  1141. expected: .other("Unknown error code %u (0x%02X)")
  1142. ),
  1143. // OmnipodKit/Bluetooth/Session/Milenage.swift:14-16
  1144. Row(
  1145. identifier: "MilenageError.Error",
  1146. message: "supplied string",
  1147. role: "errorMessage",
  1148. taxonomy: "N10",
  1149. expected: .other("supplied string")
  1150. ),
  1151. // OmnipodKit/Services/O5AppAttestService.swift:201-208
  1152. Row(
  1153. identifier: "O5AuthError.offline",
  1154. message: "The Internet connection appears to be offline.",
  1155. role: "errorMessage",
  1156. taxonomy: "N10",
  1157. expected: .other("The Internet connection appears to be offline.")
  1158. ),
  1159. // OmnipodKit/Services/O5AppAttestService.swift:201-208
  1160. Row(
  1161. identifier: "O5AuthError.offline",
  1162. message: "Please connect to Wi-Fi or Cellular Data and try again.",
  1163. role: "errorMessage",
  1164. taxonomy: "N10",
  1165. expected: .other("Please connect to Wi-Fi or Cellular Data and try again.")
  1166. ),
  1167. // OmnipodKit/Services/O5AppAttestService.swift:134-136
  1168. Row(
  1169. identifier: "O5AuthError.tokenPromptDismissed",
  1170. message: "Setup cancelled.",
  1171. role: "errorMessage",
  1172. taxonomy: "N10",
  1173. expected: .other("Setup cancelled.")
  1174. ),
  1175. // OmnipodKit/Services/O5AppAttestService.swift:247-258
  1176. Row(
  1177. identifier: "O5AuthError.malformedStatus",
  1178. message: "The key-management server is temporarily unavailable: received unexpected response.",
  1179. role: "errorMessage",
  1180. taxonomy: "N10",
  1181. expected: .other("The key-management server is temporarily unavailable: received unexpected response.")
  1182. ),
  1183. // OmnipodKit/Services/O5AppAttestService.swift:247-258
  1184. Row(
  1185. identifier: "O5AuthError.malformedStatus",
  1186. message: "Please try again later.",
  1187. role: "errorMessage",
  1188. taxonomy: "N10",
  1189. expected: .other("Please try again later.")
  1190. ),
  1191. // OmnipodKit/Services/O5AppAttestService.swift:144
  1192. Row(
  1193. identifier: "O5AuthError.appAttestUnsupported",
  1194. message: "App Attest is not supported on this device.",
  1195. role: "errorMessage",
  1196. taxonomy: "N10",
  1197. expected: .other("App Attest is not supported on this device.")
  1198. ),
  1199. // OmnipodKit/Services/O5AppAttestService.swift:282
  1200. Row(
  1201. identifier: "O5AuthError.keyGeneration",
  1202. message: "Failed to generate App Attest key: \\(error.localizedDescription)",
  1203. role: "errorMessage",
  1204. taxonomy: "N10",
  1205. expected: .other("Failed to generate App Attest key: \\(error.localizedDescription)")
  1206. ),
  1207. // OmnipodKit/Services/O5AppAttestService.swift:290
  1208. Row(
  1209. identifier: "O5AuthError.attestation",
  1210. message: "App Attest attestation failed: \\(error.localizedDescription)",
  1211. role: "errorMessage",
  1212. taxonomy: "N10",
  1213. expected: .other("App Attest attestation failed: \\(error.localizedDescription)")
  1214. ),
  1215. // OmnipodKit/Services/O5AppAttestService.swift:298
  1216. Row(
  1217. identifier: "O5AuthError.bundleId",
  1218. message: "Could not determine bundle identifier.",
  1219. role: "errorMessage",
  1220. taxonomy: "N10",
  1221. expected: .other("Could not determine bundle identifier.")
  1222. ),
  1223. // OmnipodKit/Services/O5AppAttestService.swift:301
  1224. Row(
  1225. identifier: "O5AuthError.teamId",
  1226. message: "Could not determine Team ID from provisioning profile.",
  1227. role: "errorMessage",
  1228. taxonomy: "N10",
  1229. expected: .other("Could not determine Team ID from provisioning profile.")
  1230. ),
  1231. // OmnipodKit/Services/O5AppAttestService.swift:442
  1232. Row(
  1233. identifier: "O5AuthError.badResponse",
  1234. message: "Invalid response from server.",
  1235. role: "errorMessage",
  1236. taxonomy: "N10",
  1237. expected: .other("Invalid response from server.")
  1238. ),
  1239. // OmnipodKit/Services/O5AppAttestService.swift:457-465
  1240. Row(
  1241. identifier: "O5AuthError.httpError",
  1242. message: "HTTP \\(statusCode)",
  1243. role: "errorMessage",
  1244. taxonomy: "N10",
  1245. expected: .other("HTTP \\(statusCode)")
  1246. ),
  1247. // OmnipodKit/PumpManager/OmniPumpManager.swift:461-466
  1248. Row(
  1249. identifier: "lowRLBattery",
  1250. message: "Low RileyLink Battery / \"%1$@\" has a low battery",
  1251. role: "alertTitle",
  1252. taxonomy: "F2",
  1253. expected: .other("Low RileyLink Battery / \"%1$@\" has a low battery")
  1254. ),
  1255. // OmnipodKit/PumpManagerUI/Views/ManualTempBasalEntryView.swift:131-144
  1256. Row(
  1257. identifier: "ManualTempBasalEntryView.temporaryBasalFailed",
  1258. message: "Temporary Basal Failed / Unable to set a temporary basal rate: %1$@",
  1259. role: "alertTitle",
  1260. taxonomy: "N9",
  1261. expected: .other("Temporary Basal Failed / Unable to set a temporary basal rate: %1$@")
  1262. ),
  1263. // OmnipodKit/PumpManagerUI/Views/ManualTempBasalEntryView.swift:145-149
  1264. Row(
  1265. identifier: "ManualTempBasalEntryView.missingConfig",
  1266. message: "Missing Config / This PumpManager has not been configured with a maximum basal rate because it was added before manual temp basal was a feature. Please set a new maximum basal rate.",
  1267. role: "alertTitle",
  1268. taxonomy: "N13",
  1269. expected: .other(
  1270. "Missing Config / This PumpManager has not been configured with a maximum basal rate because it was added before manual temp basal was a feature. Please set a new maximum basal rate."
  1271. )
  1272. ),
  1273. // OmnipodKit/PumpManagerUI/Views/AttachPodView.swift:83-88
  1274. Row(
  1275. identifier: "AttachPodView.confirmAttachment",
  1276. message: "Confirm Pod Attachment / Please confirm that the Pod is securely attached to your body.\n\nThe cannula can be inserted only once with each Pod. Tap \"Confirm\" when Pod is attached.",
  1277. role: "alertTitle",
  1278. taxonomy: "N12",
  1279. expected: .other(
  1280. "Confirm Pod Attachment / Please confirm that the Pod is securely attached to your body.\n\nThe cannula can be inserted only once with each Pod. Tap \"Confirm\" when Pod is attached."
  1281. )
  1282. ),
  1283. // OmnipodKit/PumpManagerUI/Views/AttachPodView.swift:92-97
  1284. Row(
  1285. identifier: "AttachPodView.cancelSetup",
  1286. message: "Are you sure you want to cancel Pod setup? / If you cancel Pod setup, the current Pod will be deactivated and will be unusable.",
  1287. role: "alertTitle",
  1288. taxonomy: "N12",
  1289. expected: .other(
  1290. "Are you sure you want to cancel Pod setup? / If you cancel Pod setup, the current Pod will be deactivated and will be unusable."
  1291. )
  1292. ),
  1293. // OmnipodKit/PumpManagerUI/Views/DeactivatePodView.swift:86-91
  1294. Row(
  1295. identifier: "DeactivatePodView.removePod",
  1296. message: "Remove Pod from Body / Your Pod may still be delivering Insulin.\nRemove it from your body, then tap \"Continue.\"",
  1297. role: "alertTitle",
  1298. taxonomy: "N12",
  1299. expected: .other(
  1300. "Remove Pod from Body / Your Pod may still be delivering Insulin.\nRemove it from your body, then tap \"Continue.\""
  1301. )
  1302. ),
  1303. // OmnipodKit/PumpManagerUI/Views/ScheduledExpirationReminderEditView.swift:149-152
  1304. Row(
  1305. identifier: "ScheduledExpirationReminderEditView.updateFailed",
  1306. message: "Failed to Update Expiration Reminder",
  1307. role: "alertTitle",
  1308. taxonomy: "N9",
  1309. expected: .other("Failed to Update Expiration Reminder")
  1310. ),
  1311. // OmnipodKit/PumpManagerUI/Views/SilencePodSelectionView.swift:192-195
  1312. Row(
  1313. identifier: "SilencePodSelectionView.updateFailed",
  1314. message: "Failed to update silence pod preference",
  1315. role: "alertTitle",
  1316. taxonomy: "N9",
  1317. expected: .other("Failed to update silence pod preference")
  1318. ),
  1319. // OmnipodKit/PumpManagerUI/Views/BeepPreferenceSelectionView.swift:131-134
  1320. Row(
  1321. identifier: "BeepPreferenceSelectionView.updateFailed",
  1322. message: "Failed to update confidence reminder preference",
  1323. role: "alertTitle",
  1324. taxonomy: "N9",
  1325. expected: .other("Failed to update confidence reminder preference")
  1326. ),
  1327. // OmnipodKit/PumpManagerUI/Views/LowReservoirView.swift:195-198
  1328. Row(
  1329. identifier: "LowReservoirView.updateFailed",
  1330. message: "Failed to Update Low Reservoir Value",
  1331. role: "alertTitle",
  1332. taxonomy: "N9",
  1333. expected: .other("Failed to Update Low Reservoir Value")
  1334. ),
  1335. // OmnipodKit/PumpManagerUI/Views/ReadPodStatusView.swift:95-98
  1336. Row(
  1337. identifier: "ReadPodStatusView.readFailed",
  1338. message: "Failed to read pod status",
  1339. role: "alertTitle",
  1340. taxonomy: "N9",
  1341. expected: .other("Failed to read pod status")
  1342. ),
  1343. // OmnipodKit/PumpManagerUI/Views/PlayTestBeepsView.swift:80-83
  1344. Row(
  1345. identifier: "PlayTestBeepsView.failed",
  1346. message: "Failed to play test beeps",
  1347. role: "alertTitle",
  1348. taxonomy: "N9",
  1349. expected: .other("Failed to play test beeps")
  1350. ),
  1351. // OmnipodKit/PumpManagerUI/Views/ReadPodInfoView.swift:99-102
  1352. Row(
  1353. identifier: "ReadPodInfoView.readPulseLogFailed",
  1354. message: "Failed to read pulse log",
  1355. role: "alertTitle",
  1356. taxonomy: "N9",
  1357. expected: .other("Failed to read pulse log")
  1358. ),
  1359. // OmnipodKit/PumpManagerUI/Views/ReadPodInfoView.swift:99-102
  1360. Row(
  1361. identifier: "ReadPodInfoView.readPulseLogPlusFailed",
  1362. message: "Failed to read pulse log plus",
  1363. role: "alertTitle",
  1364. taxonomy: "N9",
  1365. expected: .other("Failed to read pulse log plus")
  1366. ),
  1367. // OmnipodKit/PumpManagerUI/Views/ReadPodInfoView.swift:99-102
  1368. Row(
  1369. identifier: "ReadPodInfoView.readActivationTimeFailed",
  1370. message: "Failed to read activation time",
  1371. role: "alertTitle",
  1372. taxonomy: "N9",
  1373. expected: .other("Failed to read activation time")
  1374. ),
  1375. // OmnipodKit/PumpManagerUI/Views/ReadPodInfoView.swift:99-102
  1376. Row(
  1377. identifier: "ReadPodInfoView.readTriggeredAlertsFailed",
  1378. message: "Failed to read triggered alerts",
  1379. role: "alertTitle",
  1380. taxonomy: "N9",
  1381. expected: .other("Failed to read triggered alerts")
  1382. ),
  1383. // OmnipodKit/PumpManagerUI/Views/CertificateDetailsViews.swift:95-108
  1384. Row(
  1385. identifier: "CertificateDetailsViews.importFailed",
  1386. message: "Import Failed",
  1387. role: "alertTitle",
  1388. taxonomy: "N10",
  1389. expected: .other("Import Failed")
  1390. ),
  1391. // OmnipodKit/PumpManagerUI/Views/DeliveryUncertaintyRecoveryView.swift:25,47
  1392. Row(
  1393. identifier: "DeliveryUncertaintyRecoveryView.unableToReachPod",
  1394. message: "Unable to Reach Pod / %1$@ has been unable to communicate with the pod on your body since %2$@.\n\nDo not Discard Pod without scrolling to read this entire screen.\n\nCommunication was interrupted at a critical time. …",
  1395. role: "alertTitle",
  1396. taxonomy: "N3",
  1397. expected: .commsTransient
  1398. ),
  1399. // OmnipodKit/PumpManagerUI/Views/UncertaintyRecoveredView.swift:21,34
  1400. Row(
  1401. identifier: "UncertaintyRecoveredView.commsRecovered",
  1402. message: "Comms Recovered / %1$@ has recovered communication with the pod on your body.\n\nInsulin delivery records have been updated and should match what has actually been delivered. …",
  1403. role: "alertTitle",
  1404. taxonomy: "N14",
  1405. expected: .commsTransient
  1406. ),
  1407. // OmnipodKit/PumpManager/OmniPumpManager.swift:821
  1408. Row(
  1409. identifier: "PumpStatusHighlight.commsIssue",
  1410. message: "Comms Issue",
  1411. role: "notificationTitle",
  1412. taxonomy: "N3",
  1413. expected: .commsTransient
  1414. ),
  1415. // OmnipodKit/PumpManager/OmniPumpManager.swift:829
  1416. Row(
  1417. identifier: "PumpStatusHighlight.finishSetup",
  1418. message: "Finish Setup",
  1419. role: "notificationTitle",
  1420. taxonomy: "N14",
  1421. expected: .other("Finish Setup")
  1422. ),
  1423. // OmnipodKit/PumpManager/OmniPumpManager.swift:834
  1424. Row(
  1425. identifier: "PumpStatusHighlight.finishDeactivation",
  1426. message: "Finish Deactivation",
  1427. role: "notificationTitle",
  1428. taxonomy: "N14",
  1429. expected: .other("Finish Deactivation")
  1430. ),
  1431. // OmnipodKit/PumpManager/OmniPumpManager.swift:839
  1432. Row(
  1433. identifier: "PumpStatusHighlight.noPod",
  1434. message: "No Pod",
  1435. role: "notificationTitle",
  1436. taxonomy: "N14",
  1437. expected: .other("No Pod")
  1438. ),
  1439. // OmnipodKit/PumpManager/OmniPumpManager.swift:843-848
  1440. Row(
  1441. identifier: "PumpStatusHighlight.podError",
  1442. message: "Pod Error",
  1443. role: "notificationTitle",
  1444. taxonomy: "N1",
  1445. expected: .other("Pod Error")
  1446. ),
  1447. // OmnipodKit/PumpManager/OmniPumpManager.swift:852
  1448. Row(
  1449. identifier: "PumpStatusHighlight.noInsulinFault",
  1450. message: "No Insulin",
  1451. role: "notificationTitle",
  1452. taxonomy: "N4",
  1453. expected: .other("No Insulin")
  1454. ),
  1455. // OmnipodKit/PumpManager/OmniPumpManager.swift:854
  1456. Row(
  1457. identifier: "PumpStatusHighlight.podExpiredFault",
  1458. message: "Pod Expired",
  1459. role: "notificationTitle",
  1460. taxonomy: "N6",
  1461. expected: .other("Pod Expired")
  1462. ),
  1463. // OmnipodKit/PumpManager/OmniPumpManager.swift:856
  1464. Row(
  1465. identifier: "PumpStatusHighlight.podOcclusion",
  1466. message: "Pod Occlusion",
  1467. role: "notificationTitle",
  1468. taxonomy: "N1",
  1469. expected: .occlusion
  1470. ),
  1471. // OmnipodKit/PumpManager/OmniPumpManager.swift:868
  1472. Row(
  1473. identifier: "PumpStatusHighlight.noInsulinReservoir",
  1474. message: "No Insulin",
  1475. role: "notificationTitle",
  1476. taxonomy: "N4",
  1477. expected: .other("No Insulin")
  1478. ),
  1479. // OmnipodKit/PumpManager/OmniPumpManager.swift:873
  1480. Row(
  1481. identifier: "PumpStatusHighlight.insulinSuspended",
  1482. message: "Insulin Suspended",
  1483. role: "notificationTitle",
  1484. taxonomy: "N2",
  1485. expected: .other("Insulin Suspended")
  1486. ),
  1487. // OmnipodKit/PumpManager/OmniPumpManager.swift:878
  1488. Row(
  1489. identifier: "PumpStatusHighlight.signalLoss",
  1490. message: "Signal Loss",
  1491. role: "notificationTitle",
  1492. taxonomy: "N8",
  1493. expected: .other("Signal Loss")
  1494. ),
  1495. // OmnipodKit/PumpManager/OmniPumpManager.swift:883
  1496. Row(
  1497. identifier: "PumpStatusHighlight.manualBasal",
  1498. message: "Manual Basal",
  1499. role: "notificationTitle",
  1500. taxonomy: "N14",
  1501. expected: .other("Manual Basal")
  1502. )
  1503. ]
  1504. @Test(
  1505. "each (identifier, message) classifies as pinned",
  1506. arguments: rows
  1507. ) func messageClassifiesAsPinned(_ row: Row) {
  1508. let category = TrioAlertClassifier.categorize(error: StubError(description: row.message))
  1509. #expect(category == row.expected)
  1510. }
  1511. // MARK: - Classifier coverage-gap ratchet
  1512. //
  1513. // Each key is "identifier — message" for a row whose pinned category is
  1514. // .other(message) even though the message taxonomy assigns it a non-other
  1515. // bucket. The per-entry comment names the bucket it SHOULD hit and why the
  1516. // spaceless-substring check misses the spaced prose (with sourceRef). The test
  1517. // recomputes this set from `rows` (expected == .other AND taxonomyBucket would
  1518. // be non-other) and asserts equality, so improving the classifier shrinks the
  1519. // recomputed set and FAILS the test — prompting an update here.
  1520. static let classifierCoverageGaps: Set<String> = [
  1521. // SHOULD hit .deviceExpired (deviceExpired); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:46,73
  1522. "podExpireImminent — Pod Expired / Change Pod now. Insulin delivery will stop in 1 hour.",
  1523. // SHOULD hit .deviceExpired (deviceExpired); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:42,69
  1524. "userPodExpiration — Pod Expiration Reminder / Pod expires in %1$@.",
  1525. // SHOULD hit .reservoirLow (reservoirLow); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:48,77
  1526. "lowReservoir — Low Reservoir / %1$@ insulin or less remaining in Pod. Change Pod soon.",
  1527. // SHOULD hit .deviceExpired (deviceExpired); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:44,71
  1528. "podExpiring — Pod Expired / Change Pod now. Pod has been active for 72 hours.",
  1529. // SHOULD hit .hardwareFault (hardwareFault); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/PumpManagerAlert.swift:56,86
  1530. "unexpectedAlert — Unexpected Alert / Unexpected Pod Alert #%1@!",
  1531. // SHOULD hit .deviceExpired (deviceExpired); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/AlertSlot.swift:108,147
  1532. "PodAlert.shutdownImminent — Shutdown imminent",
  1533. // SHOULD hit .deviceExpired (deviceExpired); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/AlertSlot.swift:113,150
  1534. "PodAlert.expirationReminder — Expiration reminder",
  1535. // SHOULD hit .reservoirLow (reservoirLow); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/AlertSlot.swift:116,153
  1536. "PodAlert.lowReservoir — Low reservoir",
  1537. // SHOULD hit .deviceExpired (deviceExpired); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/AlertSlot.swift:134,166
  1538. "PodAlert.expired — Pod expired",
  1539. // SHOULD hit .reservoirEmpty (reservoirEmpty); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/FaultEventCode.swift:646,659
  1540. "reservoirEmpty — Empty Reservoir / Insulin delivery stopped. Change Pod now.",
  1541. // SHOULD hit .deviceExpired (deviceExpired); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/FaultEventCode.swift:650,659
  1542. "exceededMaximumPodLife80Hrs — Pod Expired / Insulin delivery stopped. Change Pod now.",
  1543. // SHOULD hit .hardwareFault (hardwareFault); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/FaultEventCode.swift:619-640
  1544. "FaultEventCode.localizedDescription — Empty reservoir",
  1545. // SHOULD hit .hardwareFault (hardwareFault); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/FaultEventCode.swift:619-640
  1546. "FaultEventCode.localizedDescription — Pod expired",
  1547. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:56
  1548. "PodCommsError.noResponse — No response from pod",
  1549. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:56
  1550. "PodCommsError.noResponse — Make sure iPhone is nearby the active pod",
  1551. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:56
  1552. "PodCommsError.noResponseRL — No response from pod",
  1553. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:56
  1554. "PodCommsError.noResponseRL — Please try repositioning the pod or the RileyLink and try again",
  1555. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:58
  1556. "PodCommsError.emptyResponse — Empty response from pod",
  1557. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:60
  1558. "PodCommsError.podAckedInsteadOfReturningResponse — Pod sent ack instead of response",
  1559. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:60
  1560. "PodCommsError.podAckedInsteadOfReturningResponse — Try again",
  1561. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:64
  1562. "PodCommsError.unexpectedResponse — Unexpected response from pod",
  1563. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:68
  1564. "PodCommsError.invalidAddress — Invalid address 0x%x. Expected 0x%x",
  1565. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:68
  1566. "PodCommsError.invalidAddress — Crosstalk possible. Please move to a new location",
  1567. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:70
  1568. "PodCommsError.noRileyLinkAvailable — No RileyLink available",
  1569. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:70
  1570. "PodCommsError.noRileyLinkAvailable — Make sure your RileyLink is nearby and powered on",
  1571. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:72
  1572. "PodCommsError.podNotConnected — Pod not connected",
  1573. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:72
  1574. "PodCommsError.podNotConnected — Make sure your pod is nearby and try again",
  1575. // SHOULD hit .deliveryUncertain (deliveryUncertain); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/PodCommsSession.swift:89
  1576. "PodCommsError.unacknowledgedMessage — wrapped error's localizedDescription",
  1577. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/Message.swift:26
  1578. "MessageError.notEnoughData — Not enough data",
  1579. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/Message.swift:28
  1580. "MessageError.invalidCrc — Invalid CRC",
  1581. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/Message.swift:30
  1582. "MessageError.invalidSequence — Unexpected message sequence number",
  1583. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/Message.swift:32
  1584. "MessageError.invalidAddress — Invalid address: (%1$@)",
  1585. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/Message.swift:34
  1586. "MessageError.parsingError — Parsing Error: %1$@ in (%2$@)",
  1587. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/Message.swift:36
  1588. "MessageError.unknownValue — Unknown Value (%1$@) for type %2$@",
  1589. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/OmnipodCommon/Message.swift:38
  1590. "MessageError.validationFailed — Validation failed: %1$@",
  1591. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PeripheralManagerError.swift:29
  1592. "PeripheralManagerError.notReady — Peripheral Not Ready",
  1593. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PeripheralManagerError.swift:31
  1594. "PeripheralManagerError.incorrectResponse — Incorrect Response",
  1595. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PeripheralManagerError.swift:35
  1596. "PeripheralManagerError.emptyValue — Empty Value",
  1597. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PeripheralManagerError.swift:37
  1598. "PeripheralManagerError.unknownCharacteristic — Unknown Characteristic",
  1599. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PeripheralManagerError.swift:39
  1600. "PeripheralManagerError.nack — Nack",
  1601. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PeripheralManagerError.swift:41
  1602. "PeripheralManagerError.unknownPodType — Unknown Pod Type",
  1603. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PodProtocolError.swift:29
  1604. "PodProtocolError.messageIOException — Message IO Exception: %1$@",
  1605. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PodProtocolError.swift:31
  1606. "PodProtocolError.couldNotParseMessageException — Could not parse message: %1$@",
  1607. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PodProtocolError.swift:33-34
  1608. "PodProtocolError.incorrectPacketException — Incorrect Packet Exception: %1$@ (location=%2$d)",
  1609. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/PodProtocolError.swift:35-36
  1610. "PodProtocolError.invalidCrc — Payload crc32 %1$@ does not match computed crc32 %2$@",
  1611. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1612. "BluetoothManagerError.bluetoothNotAvailable — Bluetooth is powered off",
  1613. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1614. "BluetoothManagerError.bluetoothNotAvailable — Bluetooth is resetting",
  1615. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1616. "BluetoothManagerError.bluetoothNotAvailable — Bluetooth use is unauthorized",
  1617. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1618. "BluetoothManagerError.bluetoothNotAvailable — Bluetooth use unsupported on this device",
  1619. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1620. "BluetoothManagerError.bluetoothNotAvailable — Bluetooth is unavailable for an unknown reason.",
  1621. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1622. "BluetoothManagerError.bluetoothNotAvailable — Bluetooth is unavailable: %1$@",
  1623. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/Bluetooth/BluetoothManager.swift:16,20-56
  1624. "BluetoothManagerError.bluetoothNotAvailable — Turn bluetooth on",
  1625. // SHOULD hit .hardwareFault (hardwareFault); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/OmniPumpManager.swift:843-848
  1626. "PumpStatusHighlight.podError — Pod Error",
  1627. // SHOULD hit .reservoirEmpty (reservoirEmpty); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/OmniPumpManager.swift:852
  1628. "PumpStatusHighlight.noInsulinFault — No Insulin",
  1629. // SHOULD hit .deviceExpired (deviceExpired); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/OmniPumpManager.swift:854
  1630. "PumpStatusHighlight.podExpiredFault — Pod Expired",
  1631. // SHOULD hit .reservoirEmpty (reservoirEmpty); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/OmniPumpManager.swift:868
  1632. "PumpStatusHighlight.noInsulinReservoir — No Insulin",
  1633. // SHOULD hit .commsTransient (commsTransient); display prose contains spaces, so the spaceless substring check misses it. OmnipodKit/PumpManager/OmniPumpManager.swift:878
  1634. "PumpStatusHighlight.signalLoss — Signal Loss"
  1635. ] /// key -> whether the message taxonomy assigns a non-.other bucket.
  1636. static let taxonomyBucketIsNonOther: [(String, Bool)] = [
  1637. ("podExpireImminent — Pod Expired / Change Pod now. Insulin delivery will stop in 1 hour.", true),
  1638. ("userPodExpiration — Pod Expiration Reminder / Pod expires in %1$@.", true),
  1639. ("lowReservoir — Low Reservoir / %1$@ insulin or less remaining in Pod. Change Pod soon.", true),
  1640. ("suspendInProgress — Suspend In Progress Reminder / Suspend In Progress Reminder", false),
  1641. (
  1642. "suspendEnded — Resume Insulin / The insulin suspension period has ended.\n\nYou can resume delivery from the banner on the home screen or from your pump settings screen. You will be reminded again in 15 minutes.",
  1643. false
  1644. ),
  1645. ("suspendEnded — Suspension time is up. Open the app and resume.", false),
  1646. ("podExpiring — Pod Expired / Change Pod now. Pod has been active for 72 hours.", true),
  1647. ("finishSetupReminder — Pod Pairing Incomplete / Please finish pairing your pod.", false),
  1648. ("unexpectedAlert — Unexpected Alert / Unexpected Pod Alert #%1@!", true),
  1649. (
  1650. "timeOffsetChangeDetected — Time Change Detected / The time on your pump is different from the current time. You can review the pump time and and sync to current time in settings.",
  1651. false
  1652. ),
  1653. ("PodAlert.autoOff — Auto-off", false),
  1654. ("PodAlert.shutdownImminent — Shutdown imminent", true),
  1655. ("PodAlert.expirationReminder — Expiration reminder", true),
  1656. ("PodAlert.lowReservoir — Low reservoir", true),
  1657. ("PodAlert.podSuspendedReminder — Pod suspended reminder", false),
  1658. ("PodAlert.suspendTimeExpired — Suspend time expired", false),
  1659. ("PodAlert.waitingForPairingReminder — Waiting for pairing reminder", false),
  1660. ("PodAlert.finishSetupReminder — Finish setup reminder", false),
  1661. ("PodAlert.expired — Pod expired", true),
  1662. ("reservoirEmpty — Empty Reservoir / Insulin delivery stopped. Change Pod now.", true),
  1663. ("occlusion — Occlusion Detected / Insulin delivery stopped. Change Pod now.", true),
  1664. ("exceededMaximumPodLife80Hrs — Pod Expired / Insulin delivery stopped. Change Pod now.", true),
  1665. ("criticalPodFault — Critical Pod Fault %1$@ / Insulin delivery stopped. Change Pod now.", true),
  1666. ("FaultEventCode.localizedDescription — No faults", true),
  1667. ("FaultEventCode.localizedDescription — Empty reservoir", true),
  1668. ("FaultEventCode.localizedDescription — Pod expired", true),
  1669. ("FaultEventCode.localizedDescription — Occlusion detected", true),
  1670. ("FaultEventCode.localizedDescription — Internal pod fault %1$@", true),
  1671. ("FaultEventCode.localizedDescription — Unknown pod fault %1$@", true),
  1672. ("PodCommsError.podFault — Pod Fault: %1$@", true),
  1673. ("PodCommsError.noPodPaired — No pod paired", false),
  1674. ("PodCommsError.noResponse — No response from pod", true),
  1675. ("PodCommsError.noResponse — Make sure iPhone is nearby the active pod", true),
  1676. ("PodCommsError.noResponseRL — No response from pod", true),
  1677. ("PodCommsError.noResponseRL — Please try repositioning the pod or the RileyLink and try again", true),
  1678. ("PodCommsError.emptyResponse — Empty response from pod", true),
  1679. ("PodCommsError.podAckedInsteadOfReturningResponse — Pod sent ack instead of response", true),
  1680. ("PodCommsError.podAckedInsteadOfReturningResponse — Try again", true),
  1681. ("PodCommsError.unexpectedResponse — Unexpected response from pod", true),
  1682. ("PodCommsError.invalidAddress — Invalid address 0x%x. Expected 0x%x", true),
  1683. ("PodCommsError.invalidAddress — Crosstalk possible. Please move to a new location", true),
  1684. ("PodCommsError.noRileyLinkAvailable — No RileyLink available", true),
  1685. ("PodCommsError.noRileyLinkAvailable — Make sure your RileyLink is nearby and powered on", true),
  1686. ("PodCommsError.podNotConnected — Pod not connected", true),
  1687. ("PodCommsError.podNotConnected — Make sure your pod is nearby and try again", true),
  1688. ("PodCommsError.unfinalizedBolus — Bolus in progress", false),
  1689. ("PodCommsError.unfinalizedBolus — Wait for existing bolus to finish, or cancel bolus", false),
  1690. ("PodCommsError.unfinalizedTempBasal — Temp basal in progress", false),
  1691. ("PodCommsError.unfinalizedTempBasal — Wait for existing temp basal to finish, or suspend to cancel", false),
  1692. ("PodCommsError.podSuspended — Pod is suspended", false),
  1693. ("PodCommsError.podSuspended — Resume delivery", false),
  1694. ("PodCommsError.unacknowledgedMessage — wrapped error's localizedDescription", true),
  1695. ("PodCommsError.unacknowledgedCommandPending — Communication issue: Unacknowledged command pending.", true),
  1696. ("PodCommsError.rejectedMessage — Command error %1$@: %2$@", false),
  1697. ("PodCommsError.podChange — Unexpected pod change", false),
  1698. ("PodCommsError.podChange — Please bring only original pod in range or deactivate original pod", false),
  1699. ("PodCommsError.activationTimeExceeded — Activation time exceeded", false),
  1700. ("PodCommsError.rssiTooLow — Poor signal strength", false),
  1701. ("PodCommsError.rssiTooLow — Please reposition the RileyLink relative to the pod", false),
  1702. ("PodCommsError.rssiTooHigh — Signal strength too high", false),
  1703. ("PodCommsError.rssiTooHigh — Please reposition the RileyLink further from the pod", false),
  1704. ("PodCommsError.diagnosticMessage — Received resynchronization SQN for the second time", false),
  1705. ("PodCommsError.diagnosticMessage — Pod type not configured", false),
  1706. ("PodCommsError.podIncompatible — Pod version %@ ... not supported", false),
  1707. ("PodCommsError.noPodsFound — No pods found", false),
  1708. ("PodCommsError.noPodsFound — Make sure your pod is filled and nearby", false),
  1709. ("PodCommsError.tooManyPodsFound — Too many pods found", false),
  1710. ("PodCommsError.tooManyPodsFound — Move to a new area away from any other pods and try again", false),
  1711. ("PodCommsError.setupNotComplete — Pod setup is not complete", false),
  1712. ("PodCommsError.noCertificateFound — No certificate found", false),
  1713. ("PodCommsError.noCertificateFound — Retrieve an Omnipod 5 Pod Certificate to continue.", false),
  1714. ("OmniPumpManagerError.noPodPaired — No pod paired", false),
  1715. ("OmniPumpManagerError.noPodPaired — Please pair a new pod", false),
  1716. ("OmniPumpManagerError.insulinTypeNotConfigured — Insulin type not configured", false),
  1717. ("OmniPumpManagerError.notReadyForCannulaInsertion — Pod is not in a state ready for cannula insertion", false),
  1718. ("OmniPumpManagerError.invalidSetting — Invalid Setting", false),
  1719. ("OmniPumpManagerError.podTypeNotConfigured — Pod type not configured", false),
  1720. ("OmniSettingsViewAlert.suspendError — Failed to Suspend Insulin Delivery", false),
  1721. ("OmniSettingsViewAlert.resumeError — Failed to Resume Insulin Delivery", false),
  1722. ("OmniSettingsViewAlert.syncTimeError — Failed to Set Pump Time", false),
  1723. ("OmniSettingsViewAlert.cancelManualBasalError — Failed to Cancel Manual Basal", false),
  1724. (
  1725. "DeactivationError.OmniPumpManagerError — There was a problem communicating with the pod. If this problem persists, tap Discard Pod. You can then activate a new Pod.",
  1726. false
  1727. ),
  1728. ("MessageError.notEnoughData — Not enough data", true),
  1729. ("MessageError.invalidCrc — Invalid CRC", true),
  1730. ("MessageError.invalidSequence — Unexpected message sequence number", true),
  1731. ("MessageError.invalidAddress — Invalid address: (%1$@)", true),
  1732. ("MessageError.parsingError — Parsing Error: %1$@ in (%2$@)", true),
  1733. ("MessageError.unknownValue — Unknown Value (%1$@) for type %2$@", true),
  1734. ("MessageError.validationFailed — Validation failed: %1$@", true),
  1735. ("PeripheralManagerError.notReady — Peripheral Not Ready", true),
  1736. ("PeripheralManagerError.incorrectResponse — Incorrect Response", true),
  1737. ("PeripheralManagerError.timeout — Timeout", true),
  1738. ("PeripheralManagerError.emptyValue — Empty Value", true),
  1739. ("PeripheralManagerError.unknownCharacteristic — Unknown Characteristic", true),
  1740. ("PeripheralManagerError.nack — Nack", true),
  1741. ("PeripheralManagerError.unknownPodType — Unknown Pod Type", true),
  1742. ("PodProtocolError.invalidLTKKey — Invalid LTK Key: %1$@", false),
  1743. ("PodProtocolError.pairingException — Pairing Exception: %1$@", false),
  1744. ("PodProtocolError.messageIOException — Message IO Exception: %1$@", true),
  1745. ("PodProtocolError.couldNotParseMessageException — Could not parse message: %1$@", true),
  1746. ("PodProtocolError.incorrectPacketException — Incorrect Packet Exception: %1$@ (location=%2$d)", true),
  1747. ("PodProtocolError.invalidCrc — Payload crc32 %1$@ does not match computed crc32 %2$@", true),
  1748. ("BluetoothManagerError.bluetoothNotAvailable — Bluetooth is powered off", true),
  1749. ("BluetoothManagerError.bluetoothNotAvailable — Bluetooth is resetting", true),
  1750. ("BluetoothManagerError.bluetoothNotAvailable — Bluetooth use is unauthorized", true),
  1751. ("BluetoothManagerError.bluetoothNotAvailable — Bluetooth use unsupported on this device", true),
  1752. ("BluetoothManagerError.bluetoothNotAvailable — Bluetooth is unavailable for an unknown reason.", true),
  1753. ("BluetoothManagerError.bluetoothNotAvailable — Bluetooth is unavailable: %1$@", true),
  1754. ("BluetoothManagerError.bluetoothNotAvailable — Turn bluetooth on", true),
  1755. ("ErrorResponseCode.badNonce — Bad nonce", false),
  1756. ("ErrorResponseCode.o5InvalidCommand — Omnipod 5 invalid command", false),
  1757. ("ErrorResponseCode.unknown — Unknown error code %u (0x%02X)", false),
  1758. ("MilenageError.Error — supplied string", false),
  1759. ("O5AuthError.offline — The Internet connection appears to be offline.", false),
  1760. ("O5AuthError.offline — Please connect to Wi-Fi or Cellular Data and try again.", false),
  1761. ("O5AuthError.tokenPromptDismissed — Setup cancelled.", false),
  1762. (
  1763. "O5AuthError.malformedStatus — The key-management server is temporarily unavailable: received unexpected response.",
  1764. false
  1765. ),
  1766. ("O5AuthError.malformedStatus — Please try again later.", false),
  1767. ("O5AuthError.appAttestUnsupported — App Attest is not supported on this device.", false),
  1768. ("O5AuthError.keyGeneration — Failed to generate App Attest key: \\(error.localizedDescription)", false),
  1769. ("O5AuthError.attestation — App Attest attestation failed: \\(error.localizedDescription)", false),
  1770. ("O5AuthError.bundleId — Could not determine bundle identifier.", false),
  1771. ("O5AuthError.teamId — Could not determine Team ID from provisioning profile.", false),
  1772. ("O5AuthError.badResponse — Invalid response from server.", false),
  1773. ("O5AuthError.httpError — HTTP \\(statusCode)", false),
  1774. ("lowRLBattery — Low RileyLink Battery / \"%1$@\" has a low battery", false),
  1775. (
  1776. "ManualTempBasalEntryView.temporaryBasalFailed — Temporary Basal Failed / Unable to set a temporary basal rate: %1$@",
  1777. false
  1778. ),
  1779. (
  1780. "ManualTempBasalEntryView.missingConfig — Missing Config / This PumpManager has not been configured with a maximum basal rate because it was added before manual temp basal was a feature. Please set a new maximum basal rate.",
  1781. false
  1782. ),
  1783. (
  1784. "AttachPodView.confirmAttachment — Confirm Pod Attachment / Please confirm that the Pod is securely attached to your body.\n\nThe cannula can be inserted only once with each Pod. Tap \"Confirm\" when Pod is attached.",
  1785. false
  1786. ),
  1787. (
  1788. "AttachPodView.cancelSetup — Are you sure you want to cancel Pod setup? / If you cancel Pod setup, the current Pod will be deactivated and will be unusable.",
  1789. false
  1790. ),
  1791. (
  1792. "DeactivatePodView.removePod — Remove Pod from Body / Your Pod may still be delivering Insulin.\nRemove it from your body, then tap \"Continue.\"",
  1793. false
  1794. ),
  1795. ("ScheduledExpirationReminderEditView.updateFailed — Failed to Update Expiration Reminder", false),
  1796. ("SilencePodSelectionView.updateFailed — Failed to update silence pod preference", false),
  1797. ("BeepPreferenceSelectionView.updateFailed — Failed to update confidence reminder preference", false),
  1798. ("LowReservoirView.updateFailed — Failed to Update Low Reservoir Value", false),
  1799. ("ReadPodStatusView.readFailed — Failed to read pod status", false),
  1800. ("PlayTestBeepsView.failed — Failed to play test beeps", false),
  1801. ("ReadPodInfoView.readPulseLogFailed — Failed to read pulse log", false),
  1802. ("ReadPodInfoView.readPulseLogPlusFailed — Failed to read pulse log plus", false),
  1803. ("ReadPodInfoView.readActivationTimeFailed — Failed to read activation time", false),
  1804. ("ReadPodInfoView.readTriggeredAlertsFailed — Failed to read triggered alerts", false),
  1805. ("CertificateDetailsViews.importFailed — Import Failed", false),
  1806. (
  1807. "DeliveryUncertaintyRecoveryView.unableToReachPod — Unable to Reach Pod / %1$@ has been unable to communicate with the pod on your body since %2$@.\n\nDo not Discard Pod without scrolling to read this entire screen.\n\nCommunication was interrupted at a critical time. …",
  1808. true
  1809. ),
  1810. (
  1811. "UncertaintyRecoveredView.commsRecovered — Comms Recovered / %1$@ has recovered communication with the pod on your body.\n\nInsulin delivery records have been updated and should match what has actually been delivered. …",
  1812. false
  1813. ),
  1814. ("PumpStatusHighlight.commsIssue — Comms Issue", true),
  1815. ("PumpStatusHighlight.finishSetup — Finish Setup", false),
  1816. ("PumpStatusHighlight.finishDeactivation — Finish Deactivation", false),
  1817. ("PumpStatusHighlight.noPod — No Pod", false),
  1818. ("PumpStatusHighlight.podError — Pod Error", true),
  1819. ("PumpStatusHighlight.noInsulinFault — No Insulin", true),
  1820. ("PumpStatusHighlight.podExpiredFault — Pod Expired", true),
  1821. ("PumpStatusHighlight.podOcclusion — Pod Occlusion", true),
  1822. ("PumpStatusHighlight.noInsulinReservoir — No Insulin", true),
  1823. ("PumpStatusHighlight.insulinSuspended — Insulin Suspended", false),
  1824. ("PumpStatusHighlight.signalLoss — Signal Loss", true),
  1825. ("PumpStatusHighlight.manualBasal — Manual Basal", false)
  1826. ]
  1827. /// Identifier — message keys whose message taxonomy assigns a non-other bucket.
  1828. /// Mirrors the per-row taxonomyBucket from the audit; used to recompute gaps.
  1829. /// (key, hasNonOtherTaxonomyBucket).
  1830. static let nonOtherTaxonomyKeys: Set<String> = {
  1831. var s: Set<String> = []
  1832. for (key, nonOther) in taxonomyBucketIsNonOther where nonOther {
  1833. s.insert(key)
  1834. }
  1835. return s
  1836. }()
  1837. @Test("classifier coverage gaps are exactly as pinned") func coverageGapsAreExactlyAsPinned() {
  1838. var recomputed: Set<String> = []
  1839. for row in Self.rows {
  1840. let key = "\(row.identifier) — \(row.message)"
  1841. let landedInOther: Bool = {
  1842. if case .other = row.expected { return true }
  1843. return false
  1844. }()
  1845. if landedInOther, Self.nonOtherTaxonomyKeys.contains(key) {
  1846. recomputed.insert(key)
  1847. }
  1848. }
  1849. #expect(recomputed == Self.classifierCoverageGaps)
  1850. }
  1851. }