-
-
Notifications
You must be signed in to change notification settings - Fork 13
WIP first stab at new bucketing using rounded mg/dL #910
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,8 +2,8 @@ package types | |
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "log/slog" | ||
| "math" | ||
| "strconv" | ||
| "strings" | ||
|
|
@@ -15,6 +15,7 @@ import ( | |
| "github.com/tidepool-org/platform/data/blood/glucose" | ||
| "github.com/tidepool-org/platform/data/types/blood/glucose/continuous" | ||
| "github.com/tidepool-org/platform/data/types/blood/glucose/selfmonitored" | ||
| "github.com/tidepool-org/platform/errors" | ||
| ) | ||
|
|
||
| const ( | ||
|
|
@@ -24,6 +25,7 @@ const ( | |
|
|
||
| type Glucose interface { | ||
| NormalizedValue() float64 | ||
| RawValueAndUnits() (float64, string, error) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't you expose the underlying value and the underlying units functions in the adapters directly instead of combining them? |
||
| Type() string | ||
| Time() *time.Time | ||
| CreatedTime() *time.Time | ||
|
|
@@ -49,6 +51,17 @@ type SelfMonitoredGlucoseAdapter struct { | |
| datum *selfmonitored.SelfMonitored | ||
| } | ||
|
|
||
| func (s SelfMonitoredGlucoseAdapter) RawValueAndUnits() (float64, string, error) { | ||
| if s.datum == nil { | ||
| return 0, "", errors.New("datum is nil") | ||
| } else if s.datum.Value == nil { | ||
| return 0, "", errors.New("datum's value is nil") | ||
| } else if s.datum.Units == nil { | ||
| return 0, "", errors.New("datum's units are nil") | ||
| } | ||
| return *s.datum.Value, *s.datum.Units, nil | ||
| } | ||
|
|
||
| func (s SelfMonitoredGlucoseAdapter) NormalizedValue() float64 { | ||
| return *glucose.NormalizeValueForUnits(s.datum.Value, s.datum.Units) | ||
| } | ||
|
|
@@ -75,6 +88,17 @@ type ContinuousGlucoseAdapter struct { | |
| datum *continuous.Continuous | ||
| } | ||
|
|
||
| func (c ContinuousGlucoseAdapter) RawValueAndUnits() (float64, string, error) { | ||
| if c.datum == nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this a little too defensive? The
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
My experience tells me assumptions about things like this will come back to bite us, with difficult to identify bugs or errors. However, as you noted, it is far more defensive than the existing related code, making it awkward as a result. So I'll adjust it to align better with what's there presently.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This particular code path hasn't blown up in the past 2-3 years, so it's pretty safe |
||
| return 0, "", errors.New("datum is nil") | ||
| } else if c.datum.Value == nil { | ||
| return 0, "", errors.New("datum value is nil") | ||
| } else if c.datum.Units == nil { | ||
| return 0, "", errors.New("datum units are nil") | ||
| } | ||
| return *c.datum.Value, *c.datum.Units, nil | ||
| } | ||
|
|
||
| func (c ContinuousGlucoseAdapter) NormalizedValue() float64 { | ||
| return *glucose.NormalizeValueForUnits(c.datum.Value, c.datum.Units) | ||
| } | ||
|
|
@@ -262,24 +286,44 @@ func (rs *GlucoseRanges) Finalize(days int) { | |
| } | ||
| } | ||
|
|
||
| const ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As you noted, we should update the summary config to include those new thresholds. I'd recommend keeping the threshold units consistent with the normalized glucose units already used in the summary, specifically, converting those integer values to mmol/L. As far as I know, we don't store glucose values or thresholds in mg/dL in the database, so it's better not to introduce a precedent. |
||
| veryLowBloodGlucoseMgdL int = 54 | ||
| lowBloodGlucoseMgdL int = 70 | ||
| highBloodGlucoseMgdL int = 180 | ||
| veryHighBloodGlucoseMgdL int = 250 | ||
| extremeHighBloodGlucoseMgdL int = 350 | ||
| ) | ||
|
|
||
| func (rs *GlucoseRanges) Update(record Glucose) { | ||
| normalizedValue := record.NormalizedValue() | ||
| rawValue, rawUnits, err := record.RawValueAndUnits() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can probably be less defensive here - if we are here we should have a value and units and those should not be nil. If you really need to log something, I suggest you change the signature of the function and return an error, instead of passing a logger. Just let the caller log it. |
||
| if err != nil { | ||
| // TODO pass in a proper platform logger | ||
| slog.Error("unable to update datum", "error", err) | ||
| return | ||
| } | ||
| mgdlValue, err := glucose.ConvertValue(rawValue, rawUnits, glucose.MgdL) | ||
| if err != nil { | ||
| // TODO pass in a proper platform logger | ||
| slog.Error("unable to update datum: conversion error", "error", err) | ||
| return | ||
| } | ||
| mgdlRounded := int(math.Round(mgdlValue)) | ||
|
|
||
| if normalizedValue < veryLowBloodGlucose { | ||
| if mgdlRounded < veryLowBloodGlucoseMgdL { | ||
| rs.VeryLow.Update(record) | ||
| rs.AnyLow.Update(record) | ||
| } else if normalizedValue > veryHighBloodGlucose { | ||
| } else if mgdlRounded > veryHighBloodGlucoseMgdL { | ||
| rs.VeryHigh.Update(record) | ||
| rs.AnyHigh.Update(record) | ||
|
|
||
| // VeryHigh is inclusive of extreme high, this is intentional | ||
| if normalizedValue >= extremeHighBloodGlucose { | ||
| if mgdlRounded >= extremeHighBloodGlucoseMgdL { | ||
| rs.ExtremeHigh.Update(record) | ||
| } | ||
| } else if normalizedValue < lowBloodGlucose { | ||
| } else if mgdlRounded < lowBloodGlucoseMgdL { | ||
| rs.Low.Update(record) | ||
| rs.AnyLow.Update(record) | ||
| } else if normalizedValue > highBloodGlucose { | ||
| } else if mgdlRounded > highBloodGlucoseMgdL { | ||
| rs.AnyHigh.Update(record) | ||
| rs.High.Update(record) | ||
| } else { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a rather complicated way to implement a generic function that will always be restrained to one of two operations
mg/dL -> mmol/lormmol\l -> mg/dL. I suggest defining two functions instead and using those directly:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I took a slightly different approach, but hopefully it captures your intent.