-
-
Notifications
You must be signed in to change notification settings - Fork 11
round glucose values via "round-to-even" before classifying #871
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?
Conversation
The aim is to maintain consistency with the front end, and be easy to explain when values don't align with other interpretations due to rounding differences. BACK-3800
func (rs *GlucoseRanges) Update(record *glucoseDatum.Glucose) { | ||
normalizedValue := *glucose.NormalizeValueForUnits(record.Value, record.Units) | ||
func (rs *GlucoseRanges) Update(record *glucoseDatum.Glucose) error { | ||
classification, err := glucoseDatum.TidepoolADAClassificationThresholdsMmolL.Classify(record) |
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 you need to replace the "config" values in the summary with the new thresholds
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.
My fix for this is somewhat ... awkward maybe? I welcome other ideas, but I think this is going to all be changing soonish with the introduction of alternate ranges, so I'm not sure it's worth spending a ton of time on.
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.
Some thoughts
} | ||
} | ||
// Ensure your highest threshold has a value like math.MaxFloat64 to avoid this. | ||
return ClassificationInvalid, errors.Newf("unable to classify value: %v", *g) |
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.
To remove this edge case (and remove the math.MaxFloat64
) can we just remove the last classificationThreshold
below and assume that if it isn't yet classified then in must be ExtremelyHigh
?
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 considered that, but then there's no way to intentionally return an error if a value is too high (out of range). Trade-offs.
I don't know how/when either is desirable, but it seemed easy enough to leave the capability, and there are sharp edges either way (hence the comment, warning of sharp edges). They can be removed later if that seems more appropriate.
|
||
b.GlucoseRanges.Update(record) | ||
if err := b.GlucoseRanges.Update(record); err != nil { | ||
return false, err |
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.
Does this mean that if a single value has an error then it will cause the entire summary to fail?
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.
Does this mean that if a single value has an error then it will cause the entire summary to fail?
A good question. does quick checking At the moment, yes. However, the only possible errors are unhandled classifications or failures to normalize a value, i.e. coding errors.
Coding errors typically fall into the "fail fast and early" category for me, so I feel like this is an appropriate response. Let me know if there's a different convention. I fear that a simple log and forget would lead to bad reports and we might not know why... Though I'd be surprised if there were ever an error raised from this code that wasn't caught via a unit test or QA.
// | ||
// It is the author's responsibility to ensure the thresholds remain sorted from smallest to | ||
// largest. | ||
var TidepoolADAClassificationThresholdsMmolL = Classifier([]classificationThreshold{ |
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.
Hmm, including the Name
in the classificationThreshold
could lead to some future errors. For example, not realizing that the order was important, someone could reorder these and cause havoc. Consider a map and explicitly classifying in the correct order (i.e. [VeryLow, Low, InRange, High, VeryHigh] and anything else is ExtremelyHigh)?
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.
Oh, and if a map doesn't include a classification (e.g. 'Low') skip it and go to the next one (e.g. 'In Range').
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'll take a look at what I can do. Using a map doesn't prevent errors (the user could miss a key, or leave a key out of the explicit ordering), and I don't wanna overcomplicate it too much.
If someone doesn't read the comment and misses the fact that it's using an ordered data structure and ignores all the unit tests and gets the change past code review... Forgive me, but that's on them, don't you think? There are limits to what I can prevent. I feel like I've taken reasonable steps.
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've applied a sort to the thresholds. I dunno that its worth it, but... it's... something. Let me know what you think.
data/types/blood/glucose/glucose.go
Outdated
var TidepoolADAClassificationThresholdsMmolL = Classifier([]classificationThreshold{ | ||
{Name: VeryLow, Value: 3, Inclusive: false}, // Source: https://doi.org/10.2337/dc24-S006 | ||
{Name: Low, Value: 3.9, Inclusive: false}, // Source: https://doi.org/10.2337/dc24-S006 | ||
{Name: OnTarget, Value: 10, Inclusive: true}, // Source: https://doi.org/10.2337/dc24-S006 |
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.
Will we ever have different values for Inclusive
for a specific classifier or will the classification itself determine the inclusiveness. That is, is High
always inclusive, or can I have a classifier where it is not?
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.
Will we ever have different values for
Inclusive
for a specific classifier or will the classification itself determine the inclusiveness. That is, isHigh
always inclusive, or can I have a classifier where it is not?
I don't know. I know there are "custom" ranges coming down the pipe, which is why I've kept the possibility.
type Classification string | ||
|
||
const ( | ||
ClassificationInvalid Classification = "invalid" |
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.
Can we get rid of this edge case?
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.
Nope. Go's type system isn't expressive enough.
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.
...unless you have a secret you're not sharing? 🤔
- modify roundToEventWithDecimalPlaces work for values < 0 It was never intended to be used that way, but it costs nothing to allow it. - use math.Pow10 > math.Pow(10,x) I don't think it makes a difference, but it's certainly possible that Pow10 has some optimizations, so why not. - rename "on target" => "in range" While some Tidepool docs used "target", Darin indicates that it's awkward and strange sounding, while "in range" is much more common. A quick review of some of the ADA materials correlates with Darin so ... change made. - Adjust the returned summary Config to use the threshold values actually used in the classification These used to be constants in another part of the code. Now they're pulled from the same place, so they'll hopefully not get out of sync. BACK-3800
BACK-3800
Sorts by Value ascending, then exclusive before inclusive. This should have a negligible performance impact, while providing extra safety, should someone mistakenly re-arrange the threshold classifications. One has to understand the sorting, but it doesn't even matter unless there are multiple classifications with the same Value, so that's a very small risk. BACK-3800
The aim is to maintain consistency with the front end, and be easy to
explain when values don't align with other interpretations due to
rounding differences.
BACK-3800