diff --git a/alerts/config.go b/alerts/config.go index f26387c1b..5ea1c9513 100644 --- a/alerts/config.go +++ b/alerts/config.go @@ -20,6 +20,12 @@ import ( "github.com/tidepool-org/platform/user" ) +// DosingDecision removes a stutter to improve readability. +type DosingDecision = dosingdecision.DosingDecision + +// Glucose removes a stutter to improve readability. +type Glucose = glucose.Glucose + // Config wraps Alerts to include user relationships. // // As a wrapper type, Config provides a clear demarcation of what a user @@ -60,7 +66,7 @@ func (c Config) Validate(validator structure.Validator) { // While this method, or the methods it calls, can fail, there's no point in returning an // error. Instead errors are logged before continuing. This is to ensure that any possible alert // that should be triggered, will be triggered. -func (c Config) Evaluate(ctx context.Context, gd []*glucose.Glucose, dd []*dosingdecision.DosingDecision) *Note { +func (c Config) Evaluate(ctx context.Context, gd []*Glucose, dd []*DosingDecision) *Note { n := c.Alerts.Evaluate(ctx, gd, dd) if n != nil { n.FollowedUserID = c.FollowedUserID @@ -117,7 +123,7 @@ func (a Alerts) Validate(validator structure.Validator) { // Evaluations are performed according to priority. The process is // "short-circuited" at the first indicated notification. func (a Alerts) Evaluate(ctx context.Context, - gd []*glucose.Glucose, dd []*dosingdecision.DosingDecision) *Note { + gd []*Glucose, dd []*DosingDecision) *Note { if a.NoCommunication != nil && a.NoCommunication.Enabled { if n := a.NoCommunication.Evaluate(ctx, gd); n != nil { @@ -160,7 +166,7 @@ func (b Base) Validate(validator structure.Validator) { validator.Bool("enabled", &b.Enabled) } -func (b Base) Evaluate(ctx context.Context, data []*glucose.Glucose) *Note { +func (b Base) Evaluate(ctx context.Context, data []*Glucose) *Note { if lgr := log.LoggerFromContext(ctx); lgr != nil { lgr.Warn("alerts.Base.Evaluate called, this shouldn't happen!") } @@ -221,7 +227,7 @@ func (a UrgentLowAlert) Validate(validator structure.Validator) { // Evaluate urgent low condition. // // Assumes data is pre-sorted in descending order by Time. -func (a *UrgentLowAlert) Evaluate(ctx context.Context, data []*glucose.Glucose) (note *Note) { +func (a *UrgentLowAlert) Evaluate(ctx context.Context, data []*Glucose) (note *Note) { lgr := log.LoggerFromContext(ctx) if len(data) == 0 { lgr.Debug("no data to evaluate for urgent low") @@ -247,7 +253,7 @@ func (a *UrgentLowAlert) Evaluate(ctx context.Context, data []*glucose.Glucose) return &Note{Message: genGlucoseThresholdMessage("below urgent low")} } -func validateGlucoseAlertDatum(datum *glucose.Glucose, t Threshold) (float64, float64, error) { +func validateGlucoseAlertDatum(datum *Glucose, t Threshold) (float64, float64, error) { if datum.Blood.Units == nil || datum.Blood.Value == nil || datum.Blood.Time == nil { return 0, 0, errors.Newf("Unable to evaluate datum: Units, Value, or Time is nil") } @@ -270,12 +276,55 @@ func (a NotLoopingAlert) Validate(validator structure.Validator) { validator.Duration("delay", &dur).InRange(0, 2*time.Hour) } -// Evaluate if the device is looping. -func (a NotLoopingAlert) Evaluate(ctx context.Context, decisions []*dosingdecision.DosingDecision) (note *Note) { - // TODO will be implemented in the near future. +// Evaluate if the user's device is looping. +func (a *NotLoopingAlert) Evaluate(ctx context.Context, decisions []*DosingDecision) (note *Note) { + if len(decisions) == 0 { + return nil + } + lgr := log.LoggerFromContext(ctx) + defer func() { lgr.WithField("isAlerting?", note != nil).Info("not looping") }() + var lastLooped time.Time + for _, decision := range decisions { + if !a.isInteresting(decision) { + continue + } + if decision.Time.After(lastLooped) { + lastLooped = *decision.Time + } + } + alerting := time.Since(lastLooped) > NotLoopingTriggeredAfter+a.Delay.Duration() + if alerting { + if !a.IsActive() { + a.Triggered = time.Now() + } + return &Note{Message: NotLoopingMessage} + } + if a.IsActive() { + a.Resolved = time.Now() + } + return nil } +func (a NotLoopingAlert) isInteresting(decision *DosingDecision) bool { + // Only dosing decisions for loop are of interest. + if decision.Reason == nil || *decision.Reason != DosingDecisionReasonLoop { + return false + } + // Dosing decisions with errors can't indicate a loop. + if decision.Errors != nil && len(*decision.Errors) != 0 { + return false + } + if decision.Time == nil || (decision.Time).IsZero() { + return false + } + return true +} + +const NotLoopingTriggeredAfter = 20 * time.Minute + +const NotLoopingMessage = "A user's Loop isn't looping" + // DosingDecisionReasonLoop is specified in a [dosingdecision.DosingDecision] to indicate that // the decision is part of a loop adjustment (as opposed to bolus or something else). const DosingDecisionReasonLoop string = "loop" @@ -295,7 +344,7 @@ func (a NoCommunicationAlert) Validate(validator structure.Validator) { // Evaluate if CGM data is being received by Tidepool. // // Assumes data is pre-sorted by Time in descending order. -func (a NoCommunicationAlert) Evaluate(ctx context.Context, data []*glucose.Glucose) *Note { +func (a NoCommunicationAlert) Evaluate(ctx context.Context, data []*Glucose) *Note { var newest time.Time for _, d := range data { if d != nil && d.Time != nil && !(*d.Time).IsZero() { @@ -337,7 +386,7 @@ func (a LowAlert) Validate(validator structure.Validator) { // Evaluate the given data to determine if an alert should be sent. // // Assumes data is pre-sorted in descending order by Time. -func (a *LowAlert) Evaluate(ctx context.Context, data []*glucose.Glucose) (note *Note) { +func (a *LowAlert) Evaluate(ctx context.Context, data []*Glucose) (note *Note) { lgr := log.LoggerFromContext(ctx) if len(data) == 0 { lgr.Debug("no data to evaluate for low") @@ -404,7 +453,7 @@ func (a HighAlert) Validate(validator structure.Validator) { // Evaluate the given data to determine if an alert should be sent. // // Assumes data is pre-sorted in descending order by Time. -func (a *HighAlert) Evaluate(ctx context.Context, data []*glucose.Glucose) (note *Note) { +func (a *HighAlert) Evaluate(ctx context.Context, data []*Glucose) (note *Note) { lgr := log.LoggerFromContext(ctx) if len(data) == 0 { lgr.Debug("no data to evaluate for high") diff --git a/alerts/config_test.go b/alerts/config_test.go index 74d7ca611..ecd85b586 100644 --- a/alerts/config_test.go +++ b/alerts/config_test.go @@ -14,7 +14,6 @@ import ( nontypesglucose "github.com/tidepool-org/platform/data/blood/glucose" "github.com/tidepool-org/platform/data/types" "github.com/tidepool-org/platform/data/types/blood" - "github.com/tidepool-org/platform/data/types/blood/glucose" "github.com/tidepool-org/platform/log" logtest "github.com/tidepool-org/platform/log/test" "github.com/tidepool-org/platform/pointer" @@ -136,7 +135,7 @@ var _ = Describe("Config", func() { Context("when a note is returned", func() { It("injects the userIDs", func() { ctx := contextWithTestLogger() - mockGlucoseData := []*glucose.Glucose{ + mockGlucoseData := []*Glucose{ { Blood: blood.Blood{ Base: types.Base{ @@ -218,18 +217,6 @@ var _ = Describe("Config", func() { }) }) - var testGlucoseDatum = func(v float64) *glucose.Glucose { - return &glucose.Glucose{ - Blood: blood.Blood{ - Base: types.Base{ - Time: pointer.FromAny(time.Now()), - }, - Units: pointer.FromAny(nontypesglucose.MmolL), - Value: pointer.FromAny(v), - }, - } - } - Context("UrgentLowAlert", func() { Context("Threshold", func() { It("accepts values between 0 and 1000 mg/dL", func() { @@ -272,7 +259,7 @@ var _ = Describe("Config", func() { alert := testUrgentLow() Expect(func() { - note = alert.Evaluate(ctx, []*glucose.Glucose{}) + note = alert.Evaluate(ctx, []*Glucose{}) }).ToNot(Panic()) Expect(note).To(BeNil()) Expect(func() { @@ -283,7 +270,7 @@ var _ = Describe("Config", func() { It("logs evaluation results", func() { ctx := contextWithTestLogger() - data := []*glucose.Glucose{testGlucoseDatum(1.1)} + data := []*Glucose{testGlucoseDatum(1.1)} alert := testUrgentLow() @@ -307,11 +294,11 @@ var _ = Describe("Config", func() { alert := testUrgentLow() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(1.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(1.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).ToNot(BeZero()) }) @@ -324,16 +311,16 @@ var _ = Describe("Config", func() { alert := testUrgentLow() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(1.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(1.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).ToNot(BeZero()) was := alert.Resolved Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(Equal(was)) }) @@ -345,11 +332,11 @@ var _ = Describe("Config", func() { alert := testUrgentLow() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Triggered).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(1.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(1.0)}) }).ToNot(Panic()) Expect(alert.Triggered).ToNot(BeZero()) }) @@ -359,28 +346,28 @@ var _ = Describe("Config", func() { var note *Note Expect(func() { - note = testUrgentLow().Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(1)}) + note = testUrgentLow().Evaluate(ctx, []*Glucose{testGlucoseDatum(1)}) }).ToNot(Panic()) Expect(note).ToNot(BeNil()) badUnits := testGlucoseDatum(1) badUnits.Units = nil Expect(func() { - note = testUrgentLow().Evaluate(ctx, []*glucose.Glucose{badUnits}) + note = testUrgentLow().Evaluate(ctx, []*Glucose{badUnits}) }).ToNot(Panic()) Expect(note).To(BeNil()) badValue := testGlucoseDatum(1) badValue.Value = nil Expect(func() { - note = testUrgentLow().Evaluate(ctx, []*glucose.Glucose{badValue}) + note = testUrgentLow().Evaluate(ctx, []*Glucose{badValue}) }).ToNot(Panic()) Expect(note).To(BeNil()) badTime := testGlucoseDatum(1) badTime.Time = nil Expect(func() { - note = testUrgentLow().Evaluate(ctx, []*glucose.Glucose{badTime}) + note = testUrgentLow().Evaluate(ctx, []*Glucose{badTime}) }).ToNot(Panic()) Expect(note).To(BeNil()) @@ -463,7 +450,7 @@ var _ = Describe("Config", func() { alert := testLow() Expect(func() { - note = alert.Evaluate(ctx, []*glucose.Glucose{}) + note = alert.Evaluate(ctx, []*Glucose{}) }).ToNot(Panic()) Expect(note).To(BeNil()) Expect(func() { @@ -474,7 +461,7 @@ var _ = Describe("Config", func() { It("logs evaluation results", func() { ctx := contextWithTestLogger() - data := []*glucose.Glucose{testGlucoseDatum(1.1)} + data := []*Glucose{testGlucoseDatum(1.1)} alert := testLow() @@ -498,11 +485,11 @@ var _ = Describe("Config", func() { alert := testLow() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(1.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(1.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).ToNot(BeZero()) }) @@ -515,16 +502,16 @@ var _ = Describe("Config", func() { alert := testLow() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(1.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(1.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).ToNot(BeZero()) was := alert.Resolved Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(Equal(was)) }) @@ -536,11 +523,11 @@ var _ = Describe("Config", func() { alert := testLow() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Triggered).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(1.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(1.0)}) }).ToNot(Panic()) Expect(alert.Triggered).ToNot(BeZero()) }) @@ -550,28 +537,28 @@ var _ = Describe("Config", func() { var note *Note Expect(func() { - note = testLow().Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(1)}) + note = testLow().Evaluate(ctx, []*Glucose{testGlucoseDatum(1)}) }).ToNot(Panic()) Expect(note).ToNot(BeNil()) badUnits := testGlucoseDatum(1) badUnits.Units = nil Expect(func() { - note = testLow().Evaluate(ctx, []*glucose.Glucose{badUnits}) + note = testLow().Evaluate(ctx, []*Glucose{badUnits}) }).ToNot(Panic()) Expect(note).To(BeNil()) badValue := testGlucoseDatum(1) badValue.Value = nil Expect(func() { - note = testLow().Evaluate(ctx, []*glucose.Glucose{badValue}) + note = testLow().Evaluate(ctx, []*Glucose{badValue}) }).ToNot(Panic()) Expect(note).To(BeNil()) badTime := testGlucoseDatum(1) badTime.Time = nil Expect(func() { - note = testLow().Evaluate(ctx, []*glucose.Glucose{badTime}) + note = testLow().Evaluate(ctx, []*Glucose{badTime}) }).ToNot(Panic()) Expect(note).To(BeNil()) }) @@ -646,7 +633,7 @@ var _ = Describe("Config", func() { alert := testHigh() Expect(func() { - note = alert.Evaluate(ctx, []*glucose.Glucose{}) + note = alert.Evaluate(ctx, []*Glucose{}) }).ToNot(Panic()) Expect(note).To(BeNil()) Expect(func() { @@ -657,7 +644,7 @@ var _ = Describe("Config", func() { It("logs evaluation results", func() { ctx := contextWithTestLogger() - data := []*glucose.Glucose{testGlucoseDatum(21.1)} + data := []*Glucose{testGlucoseDatum(21.1)} alert := testHigh() @@ -681,11 +668,11 @@ var _ = Describe("Config", func() { alert := testHigh() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(21.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(21.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).ToNot(BeZero()) }) @@ -698,16 +685,16 @@ var _ = Describe("Config", func() { alert := testHigh() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(21.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(21.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).ToNot(BeZero()) was := alert.Resolved Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Resolved).To(Equal(was)) }) @@ -719,11 +706,11 @@ var _ = Describe("Config", func() { alert := testHigh() Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(5.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(5.0)}) }).ToNot(Panic()) Expect(alert.Triggered).To(BeZero()) Expect(func() { - alert.Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(21.0)}) + alert.Evaluate(ctx, []*Glucose{testGlucoseDatum(21.0)}) }).ToNot(Panic()) Expect(alert.Triggered).ToNot(BeZero()) }) @@ -733,28 +720,28 @@ var _ = Describe("Config", func() { var note *Note Expect(func() { - note = testHigh().Evaluate(ctx, []*glucose.Glucose{testGlucoseDatum(21)}) + note = testHigh().Evaluate(ctx, []*Glucose{testGlucoseDatum(21)}) }).ToNot(Panic()) Expect(note).ToNot(BeNil()) badUnits := testGlucoseDatum(1) badUnits.Units = nil Expect(func() { - note = testHigh().Evaluate(ctx, []*glucose.Glucose{badUnits}) + note = testHigh().Evaluate(ctx, []*Glucose{badUnits}) }).ToNot(Panic()) Expect(note).To(BeNil()) badValue := testGlucoseDatum(1) badValue.Value = nil Expect(func() { - note = testHigh().Evaluate(ctx, []*glucose.Glucose{badValue}) + note = testHigh().Evaluate(ctx, []*Glucose{badValue}) }).ToNot(Panic()) Expect(note).To(BeNil()) badTime := testGlucoseDatum(1) badTime.Time = nil Expect(func() { - note = testHigh().Evaluate(ctx, []*glucose.Glucose{badTime}) + note = testHigh().Evaluate(ctx, []*Glucose{badTime}) }).ToNot(Panic()) Expect(note).To(BeNil()) }) @@ -810,7 +797,115 @@ var _ = Describe("Config", func() { b.Validate(val) Expect(val.Error()).To(MatchError("value 2h0m1s is not between 0s and 2h0m0s")) }) + }) + + Context("Evaluate", func() { + testNotLooping := func() *NotLoopingAlert { + return &NotLoopingAlert{} + } + + It("handles being passed empty data", func() { + ctx := contextWithTestLogger() + var note *Note + + alert := testNotLooping() + + Expect(func() { + note = alert.Evaluate(ctx, []*DosingDecision{}) + }).ToNot(Panic()) + Expect(note).To(BeNil()) + Expect(func() { + note = alert.Evaluate(ctx, nil) + }).ToNot(Panic()) + Expect(note).To(BeNil()) + }) + + It("logs evaluation results", func() { + ctx := contextWithTestLogger() + data := []*DosingDecision{testDecisionDatum("loop", time.Now())} + + alert := testNotLooping() + + Expect(func() { + alert.Evaluate(ctx, data) + }).ToNot(Panic()) + Expect(func() { + lgr := log.LoggerFromContext(ctx).(*logtest.Logger) + lgr.AssertLog(log.InfoLevel, "not looping", log.Fields{ + "isAlerting?": false, + }) + }).ToNot(Panic()) + }) + + Context("when currently active", func() { + It("marks itself resolved", func() { + ctx := contextWithTestLogger() + longAgo := time.Now().Add(-time.Hour) + data := []*DosingDecision{ + testDecisionDatum("loop", longAgo), + } + + alert := testNotLooping() + + Expect(func() { + alert.Evaluate(ctx, data) + }).ToNot(Panic()) + Expect(alert.Resolved).To(BeZero()) + Expect(func() { + alert.Evaluate(ctx, append(data, testDecisionDatum("loop", time.Now()))) + }).ToNot(Panic()) + Expect(alert.Resolved).ToNot(BeZero()) + }) + }) + + Context("when currently INactive", func() { + It("doesn't re-mark itself resolved", func() { + ctx := contextWithTestLogger() + longAgo := time.Now().Add(-time.Hour) + data := []*DosingDecision{ + testDecisionDatum("loop", longAgo), + } + + alert := testNotLooping() + + Expect(func() { + alert.Evaluate(ctx, data) + }).ToNot(Panic()) + Expect(alert.Resolved).To(BeZero()) + Expect(func() { + alert.Evaluate(ctx, append(data, testDecisionDatum("loop", time.Now()))) + }).ToNot(Panic()) + Expect(alert.IsActive()).To(BeFalse()) + was := alert.Resolved + Expect(func() { + alert.Evaluate(ctx, append(data, testDecisionDatum("loop", time.Now()))) + }).ToNot(Panic()) + Expect(alert.Resolved).To(Equal(was)) + }) + }) + It("marks itself triggered", func() { + ctx := contextWithTestLogger() + justNow := time.Now().Add(-time.Minute) + dataBefore := []*DosingDecision{ + testDecisionDatum("loop", justNow), + } + longAgo := time.Now().Add(-time.Hour) + dataNow := []*DosingDecision{ + testDecisionDatum("loop", longAgo), + } + + alert := testNotLooping() + + Expect(func() { + alert.Evaluate(ctx, dataBefore) + }).ToNot(Panic()) + Expect(alert.Triggered).To(BeZero()) + Expect(func() { + alert.Evaluate(ctx, dataNow) + }).ToNot(Panic()) + Expect(alert.Triggered).ToNot(BeZero()) + }) }) }) @@ -960,7 +1055,7 @@ var ( Base: Base{Enabled: true}, } } - testNoCommunicationDatum = &glucose.Glucose{ + testNoCommunicationDatum = &Glucose{ Blood: blood.Blood{ Base: types.Base{ Time: pointer.FromAny(time.Now()), @@ -969,7 +1064,7 @@ var ( Value: pointer.FromAny(11.0), }, } - testHighDatum = &glucose.Glucose{ + testHighDatum = &Glucose{ Blood: blood.Blood{ Base: types.Base{ Time: pointer.FromAny(time.Now()), @@ -978,7 +1073,7 @@ var ( Value: pointer.FromAny(11.0), }, } - testLowDatum = &glucose.Glucose{ + testLowDatum = &Glucose{ Blood: blood.Blood{ Base: types.Base{ Time: pointer.FromAny(time.Now()), @@ -987,7 +1082,7 @@ var ( Value: pointer.FromAny(3.9), }, } - testUrgentLowDatum = &glucose.Glucose{ + testUrgentLowDatum = &Glucose{ Blood: blood.Blood{ Base: types.Base{ Time: pointer.FromAny(time.Now()), @@ -1063,7 +1158,7 @@ var _ = Describe("Alerts", func() { Context("when not communicating", func() { It("returns only NoCommunication alerts", func() { ctx := contextWithTestLogger() - data := []*glucose.Glucose{testNoCommunicationDatum} + data := []*Glucose{testNoCommunicationDatum} data[0].Value = pointer.FromAny(0.0) a := Alerts{ NoCommunication: testNoCommunicationAlert(), @@ -1084,7 +1179,7 @@ var _ = Describe("Alerts", func() { It("detects low data", func() { ctx := contextWithTestLogger() - data := []*glucose.Glucose{testLowDatum} + data := []*Glucose{testLowDatum} a := Alerts{ Low: testLowAlert(), } @@ -1097,7 +1192,7 @@ var _ = Describe("Alerts", func() { It("detects high data", func() { ctx := contextWithTestLogger() - data := []*glucose.Glucose{testHighDatum} + data := []*Glucose{testHighDatum} a := Alerts{ High: testHighAlert(), } @@ -1111,7 +1206,7 @@ var _ = Describe("Alerts", func() { Context("with both low and urgent low alerts detected", func() { It("prefers urgent low", func() { ctx := contextWithTestLogger() - data := []*glucose.Glucose{testUrgentLowDatum} + data := []*Glucose{testUrgentLowDatum} a := Alerts{ Low: testLowAlert(), UrgentLow: testUrgentLowAlert(), @@ -1204,3 +1299,24 @@ func contextWithTestLogger() context.Context { lgr := logtest.NewLogger() return log.NewContextWithLogger(context.Background(), lgr) } + +func testGlucoseDatum(v float64) *Glucose { + return &Glucose{ + Blood: blood.Blood{ + Base: types.Base{ + Time: pointer.FromAny(time.Now()), + }, + Units: pointer.FromAny(nontypesglucose.MmolL), + Value: pointer.FromAny(v), + }, + } +} + +func testDecisionDatum(reason string, t time.Time) *DosingDecision { + return &DosingDecision{ + Base: types.Base{ + Time: &t, + }, + Reason: &reason, + } +}