Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Sources/LoopAlgorithm/LoopUnit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,19 @@ public enum LoopUnit: Sendable, CaseIterable {
(.milligramsPerDeciliterPerInternationalUnit, .millimolesPerLiterPerInternationalUnit):
return 1/UnitMolarMassBloodGlucoseDivisible
case (.milligramsPerDeciliterPerSecond, .millimolesPerLiterPerMinute):
return 1/UnitMolarMassBloodGlucoseDivisible / 60
case (.milligramsPerDeciliterPerMinute, .millimolesPerLiterPerSecond):
// Rate: per-second -> per-minute multiplies by 60 (opposite of a duration conversion)
return 1/UnitMolarMassBloodGlucoseDivisible * 60
case (.milligramsPerDeciliterPerMinute, .millimolesPerLiterPerSecond):
return 1/UnitMolarMassBloodGlucoseDivisible / 60
case (.millimolesPerLiter, .milligramsPerDeciliter),
(.millimolesPerLiterPerSecond, .milligramsPerDeciliterPerSecond),
(.millimolesPerLiterPerMinute, .milligramsPerDeciliterPerMinute),
(.millimolesPerLiterPerInternationalUnit, .milligramsPerDeciliterPerInternationalUnit):
return UnitMolarMassBloodGlucoseDivisible
case (.millimolesPerLiterPerSecond, .milligramsPerDeciliterPerMinute):
return UnitMolarMassBloodGlucoseDivisible / 60
case (.millimolesPerLiterPerMinute, .milligramsPerDeciliterPerSecond):
return UnitMolarMassBloodGlucoseDivisible * 60
case (.millimolesPerLiterPerMinute, .milligramsPerDeciliterPerSecond):
return UnitMolarMassBloodGlucoseDivisible / 60
case (.gram, _),
(.gramsPerUnit, _),
(.internationalUnit, _),
Expand Down
35 changes: 31 additions & 4 deletions Tests/LoopAlgorithmTests/LoopUnitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,45 @@ class LoopUnitTests: XCTestCase {

unit1 = LoopUnit.milligramsPerDeciliterPerMinute
unit2 = LoopUnit.millimolesPerLiterPerSecond
XCTAssertEqual(unit1.conversionFactor(toUnit: unit2), 1/UnitMolarMassBloodGlucoseDivisible * 60)
XCTAssertEqual(unit2.conversionFactor(toUnit: unit1), UnitMolarMassBloodGlucoseDivisible / 60)
XCTAssertEqual(unit1.conversionFactor(toUnit: unit2), 1/UnitMolarMassBloodGlucoseDivisible / 60)
XCTAssertEqual(unit2.conversionFactor(toUnit: unit1), UnitMolarMassBloodGlucoseDivisible * 60)

unit1 = LoopUnit.milligramsPerDeciliterPerSecond
unit2 = LoopUnit.millimolesPerLiterPerMinute
XCTAssertEqual(unit1.conversionFactor(toUnit: unit2), 1/UnitMolarMassBloodGlucoseDivisible / 60)
XCTAssertEqual(unit2.conversionFactor(toUnit: unit1), UnitMolarMassBloodGlucoseDivisible * 60)
XCTAssertEqual(unit1.conversionFactor(toUnit: unit2), 1/UnitMolarMassBloodGlucoseDivisible * 60)
XCTAssertEqual(unit2.conversionFactor(toUnit: unit1), UnitMolarMassBloodGlucoseDivisible / 60)

let quantity1 = LoopQuantity(unit: .millimolesPerLiter, doubleValue: 4)
let quantity2 = LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 72.06235200002165)
XCTAssertEqual(quantity1, quantity2)
}

/// The conversion table is maintained by hand, so check every pair against HealthKit,
/// which does the dimensional analysis for us. This guards against transcription errors
/// like the inverted time factor that made mg/dL·s -> mmol/L·min 3600x too small.
func testConversionFactorsMatchHealthKit() {
for source in LoopUnit.allCases {
for target in LoopUnit.allCases {
let context = "\(source.unitString) -> \(target.unitString)"
let one = HKQuantity(unit: HKUnit(from: source.unitString), doubleValue: 1)
let hkTarget = HKUnit(from: target.unitString)
let convertible = one.is(compatibleWith: hkTarget)

guard let factor = source.conversionFactor(toUnit: target) else {
XCTAssertFalse(convertible, "\(context) is convertible in HealthKit but returned nil")
continue
}

guard convertible else {
XCTFail("\(context) returned a factor but is not convertible in HealthKit")
continue
}

let expected = one.doubleValue(for: hkTarget)
XCTAssertEqual(factor, expected, accuracy: max(abs(expected) * 1e-12, .ulpOfOne), context)
}
}
}
}

extension LoopUnitTests {
Expand Down