Skip to content

Commit

Permalink
Merge pull request #4 from tidepool-org/guard-rails
Browse files Browse the repository at this point in the history
[BACK-1423] Add device specific guard rails and increments
  • Loading branch information
toddkazakov authored Jul 6, 2020
2 parents 0aeafc9 + 5ff5393 commit 528418b
Show file tree
Hide file tree
Showing 12 changed files with 2,106 additions and 247 deletions.
1,552 changes: 1,370 additions & 182 deletions api/api.pb.go

Large diffs are not rendered by default.

145 changes: 144 additions & 1 deletion api/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ service Devices {
// Get pump by id
rpc GetPumpById(GetPumpByIdRequest) returns (GetPumpByIdResponse) {
option (google.api.http) = {
get: "/v1/devices/pumps/{id}"
get: "/v1/devices/pumps/{id}"
};
}

Expand Down Expand Up @@ -63,6 +63,149 @@ message Pump {

// device model
string model = 4;

// guard rails
GuardRails guardRails = 5;
}

message GuardRails {
// Suspend threshold guard rail
SuspendThresholdGuardRail suspendThreshold = 1;

// Insulin sensitivity guard rail
InsulinSensitivityGuardRail insulinSensitivity = 2;

// Basal rates guard rail
BasalRatesGuardRail basalRates = 3;

// Carbohydrate ratio guard rail
CarbohydrateRatioGuardRail carbohydrateRatio = 4;

// Basal rate maximum guard rail
BasalRateMaximumGuardRail basalRateMaximum = 5;

// Bolus amount maximum guard rail
BolusAmountMaximumGuardRail bolusAmountMaximum = 6;

// Correction range guard rail
CorrectionRangeGuardRail correctionRange = 7;
}

message SuspendThresholdGuardRail {
// Blood glucose units
BloodGlucoseUnits units = 1;

// Tidepool recommended bounds
RecommendedBounds recommendedBounds = 2;

// Device specific absolute bounds
AbsoluteBounds absoluteBounds = 3;
}

message InsulinSensitivityGuardRail {
// Blood glucose units
BloodGlucoseUnits units = 1;

// Tidepool recommended bounds
RecommendedBounds recommendedBounds = 2;

// Device specific absolute bounds
AbsoluteBounds absoluteBounds = 3;
}

message BasalRatesGuardRail {
// Basal rate units
BasalRateUnits units = 1;

// Default value
double defaultValue = 2;

// Device specific absolute bounds. Some pumps might have different increments for different ranges.
repeated AbsoluteBounds absoluteBounds = 3;
}

message CarbohydrateRatioGuardRail {
// Carbohydrate ratio units
CarbohydrateRatioUnits units = 1;

// Tidepool recommended bounds
RecommendedBounds recommendedBounds = 2;

// Device specific absolute bounds
AbsoluteBounds absoluteBounds = 3;
}

message BasalRateMaximumGuardRail {
// Basal rate units
BasalRateUnits units = 1;

// Default value
double defaultValue = 2;

// Device specific absolute bounds
AbsoluteBounds absoluteBounds = 3;
}

message BolusAmountMaximumGuardRail {
// Bolus units
BolusUnits units = 1;

// Default value
double defaultValue = 2;

// Tidepool recommended bounds
RecommendedBounds recommendedBounds = 3;

// Device specific absolute bounds
AbsoluteBounds absoluteBounds = 4;
}

message CorrectionRangeGuardRail {
// Blood glucose units
BloodGlucoseUnits units = 1;

// Tidepool recommended bounds
RecommendedBounds recommendedBounds = 2;

// Device specific absolute bounds
AbsoluteBounds absoluteBounds = 3;
}

// Closed range double interval
message AbsoluteBounds {
// Lower bound (inclusive)
double minimum = 1;

// Upper bound (inclusive)
double maximum = 2;

// Increment
double increment = 3;
}

// Closed range double interval
message RecommendedBounds {
// Lower bound (inclusive)
double minimum = 1;

// Upper bound (inclusive)
double maximum = 2;
}

enum BloodGlucoseUnits {
MilligramsPerDeciliter = 0;
}

enum BasalRateUnits {
UnitsPerHour = 0;
}

enum CarbohydrateRatioUnits {
GramsPerUnit = 0;
}

enum BolusUnits {
Units = 0;
}

message GetCgmByIdRequest {
Expand Down
35 changes: 34 additions & 1 deletion config/devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,40 @@ type CGM struct {
}

type Pump struct {
Device `yaml:",inline"`
Device `yaml:",inline"`
GuardRails GuardRails `yaml:"guard_rails" validate:"required"`
}

type GuardRails struct {
SuspendThreshold GuardRail `yaml:"suspend_threshold" validate:"required"`
InsulinSensitivity GuardRail `yaml:"insulin_sensitivity" validate:"required"`
BasalRates GuardRail `yaml:"basal_rates" validate:"required"`
CarbohydrateRatio GuardRail `yaml:"carbohydrate_ratio" validate:"required"`
BasalRateMaximum GuardRail `yaml:"basal_rate_maximum" validate:"required"`
BolusAmountMaximum GuardRail `yaml:"bolus_amount_maximum" validate:"required"`
CorrectionRange GuardRail `yaml:"correction_range" validate:"required"`
}

type GuardRail struct {
Units string `yaml:"units" validate:"required"`
DefaultValue *float64 `yaml:"default"`
AbsoluteBounds []*AbsoluteBounds `yaml:"absolute_bounds" validate:"required"`
RecommendedBounds *RecommendedBounds `yaml:"recommended_bounds"`
}

type AbsoluteBounds struct {
Bounds `yaml:",inline"`

Increment float64 `yaml:"increment" validate:"gt=0"`
}

type RecommendedBounds struct {
Bounds `yaml:",inline"`
}

type Bounds struct {
Minimum *float64 `yaml:"min"`
Maximum *float64 `yaml:"max"`
}

func NewDevicesConfig() *DevicesConfig {
Expand Down
184 changes: 184 additions & 0 deletions config/devices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,190 @@ func TestDevicesConfig_LoadFromFile(t *testing.T) {
}
})

t.Run("Guard Rails", func(t *testing.T) {
pointerFromFloat := func(i float64) *float64 { return &i }
isExpected := func(t *testing.T, result GuardRail, expected GuardRail) {
if result.Units != expected.Units {
t.Errorf("expected %v units got %v", expected.Units, result.Units)
}
if expected.DefaultValue != nil {
if result.DefaultValue == nil || *result.DefaultValue != *expected.DefaultValue {
t.Errorf("expected %v got %v", *expected.DefaultValue, *result.DefaultValue)
}
}
if expected.AbsoluteBounds != nil {
for i, b := range expected.AbsoluteBounds {
expectedValue := expected.AbsoluteBounds[i].Increment
resultValue := b.Increment
if expectedValue != resultValue {
t.Errorf("expected %v got %v", expectedValue, resultValue)
}

expectedValue = *expected.AbsoluteBounds[i].Minimum
resultValue = *b.Minimum
if expectedValue != resultValue {
t.Errorf("expected %v got %v", expectedValue, resultValue)
}

expectedValue = *expected.AbsoluteBounds[i].Maximum
resultValue = *b.Maximum
if expectedValue != resultValue {
t.Errorf("expected %v got %v", expectedValue, resultValue)
}
}
}
if expected.RecommendedBounds != nil {
expectedValue := *expected.RecommendedBounds.Minimum
resultValue := *result.RecommendedBounds.Minimum
if expectedValue != resultValue {
t.Errorf("expected %v got %v", expectedValue, resultValue)
}

expectedValue = *expected.RecommendedBounds.Maximum
resultValue = *result.RecommendedBounds.Maximum
if expectedValue != resultValue {
t.Errorf("expected %v got %v", expectedValue, resultValue)
}
}
}
t.Run("Suspend threshold is correct", func(t *testing.T) {
isExpected(t, omnipod.GuardRails.SuspendThreshold, GuardRail{
Units: "mg/dL",
DefaultValue: nil,
AbsoluteBounds: []*AbsoluteBounds{
&AbsoluteBounds{
Bounds: Bounds{
Minimum: pointerFromFloat(54),
Maximum: pointerFromFloat(180),
},
Increment: 1,
},
},
RecommendedBounds: &RecommendedBounds{
Bounds{
Minimum: pointerFromFloat(71),
Maximum: pointerFromFloat(119),
},
},
})
})
t.Run("Insulin sensitivity is correct", func(t *testing.T) {
isExpected(t, omnipod.GuardRails.InsulinSensitivity, GuardRail{
Units: "mg/dL",
DefaultValue: nil,
AbsoluteBounds: []*AbsoluteBounds{
&AbsoluteBounds{
Bounds: Bounds{
Minimum: pointerFromFloat(10),
Maximum: pointerFromFloat(500),
},
Increment: 1,
},
},
RecommendedBounds: &RecommendedBounds{
Bounds{
Minimum: pointerFromFloat(16),
Maximum: pointerFromFloat(399),
},
},
})
})
t.Run("Basal rates is correct", func(t *testing.T) {
isExpected(t, omnipod.GuardRails.BasalRates, GuardRail{
Units: "U/h",
DefaultValue: pointerFromFloat(0.05),
AbsoluteBounds: []*AbsoluteBounds{
&AbsoluteBounds{
Bounds: Bounds{
Minimum: pointerFromFloat(0.05),
Maximum: pointerFromFloat(30.0),
},
Increment: 0.05,
},
},
RecommendedBounds: nil,
})
})
t.Run("Carbohydrate ratio is correct", func(t *testing.T) {
isExpected(t, omnipod.GuardRails.CarbohydrateRatio, GuardRail{
Units: "g/U",
DefaultValue: nil,
AbsoluteBounds: []*AbsoluteBounds{
&AbsoluteBounds{
Bounds: Bounds{
Minimum: pointerFromFloat(1.0),
Maximum: pointerFromFloat(150.0),
},
Increment: 0.01,
},
},
RecommendedBounds: &RecommendedBounds{
Bounds{
Minimum: pointerFromFloat(3.01),
Maximum: pointerFromFloat(26.99),
},
},
})
})
t.Run("Basal rate maximum is correct", func(t *testing.T) {
isExpected(t, omnipod.GuardRails.BasalRateMaximum, GuardRail{
Units: "U/h",
DefaultValue: pointerFromFloat(0.0),
AbsoluteBounds: []*AbsoluteBounds{
&AbsoluteBounds{
Bounds: Bounds{
Minimum: pointerFromFloat(0.0),
Maximum: pointerFromFloat(30.0),
},
Increment: 0.05,
},
},
RecommendedBounds: nil,
})
})
t.Run("Bolus amount maximum is correct", func(t *testing.T) {
isExpected(t, omnipod.GuardRails.BolusAmountMaximum, GuardRail{
Units: "U",
DefaultValue: pointerFromFloat(0.0),
AbsoluteBounds: []*AbsoluteBounds{
&AbsoluteBounds{
Bounds: Bounds{
Minimum: pointerFromFloat(0.0),
Maximum: pointerFromFloat(30.0),
},
Increment: 0.05,
},
},
RecommendedBounds: &RecommendedBounds{
Bounds{
Minimum: pointerFromFloat(0.05),
Maximum: pointerFromFloat(19.95),
},
},
})
})
t.Run("Correction range is correct", func(t *testing.T) {
isExpected(t, omnipod.GuardRails.CorrectionRange, GuardRail{
Units: "mg/dL",
AbsoluteBounds: []*AbsoluteBounds{
&AbsoluteBounds{
Bounds: Bounds{
Minimum: pointerFromFloat(60.0),
Maximum: pointerFromFloat(180.0),
},
Increment: 1.0,
},
},
RecommendedBounds: &RecommendedBounds{
Bounds{
Minimum: pointerFromFloat(70.0),
Maximum: pointerFromFloat(120.0),
},
},
})
})
})

})
})

Expand Down
Loading

0 comments on commit 528418b

Please sign in to comment.