Module: SugarSense::Units
- Defined in:
- lib/sugarsense/units.rb,
lib/sugarsense/units/version.rb
Constant Summary collapse
- MMOL_PER_MGDL =
0.0555- DEFAULT_THRESHOLDS =
ADA-aligned defaults used by Sugar Sense for new accounts (mg/dL).
{ very_low: 55, low: 70, high: 180, very_high: 250 }.freeze
- TREND_ARROWS =
CGM trend codes: 1 falling fast, 2 falling, 3 steady, 4 rising, 5 rising fast.
{ 1 => "↓", 2 => "↘", 3 => "→", 4 => "↗", 5 => "↑" }.freeze
- TREND_NAMES =
{ 1 => "falling_fast", 2 => "falling", 3 => "steady", 4 => "rising", 5 => "rising_fast" }.freeze
- VERSION =
"0.1.0"
Class Method Summary collapse
-
.classify_zone(mgdl, thresholds = DEFAULT_THRESHOLDS) ⇒ Object
Zone boundaries are inclusive on the out-of-range side: :very_low value <= very_low :low very_low < value <= low :in_range low < value < high :high high <= value < very_high :very_high value >= very_high.
-
.format_glucose(mgdl, unit = :mgdl) ⇒ Object
Display formatting identical to the Sugar Sense apps: mmol/L is shown with exactly 1 decimal, mg/dL as a whole number.
-
.gmi(mean_mgdl) ⇒ Object
Glucose Management Indicator (approximates lab A1C, %) from mean mg/dL.
-
.mgdl_to_mmol(mgdl) ⇒ Object
mg/dL -> mmol/L (unrounded float).
-
.mmol_to_mgdl(mmol) ⇒ Object
mmol/L -> mg/dL, rounded to the nearest whole number.
-
.time_in_range(readings, thresholds = DEFAULT_THRESHOLDS) ⇒ Object
Five-band distribution over an array of mg/dL values.
- .trend_arrow(code) ⇒ Object
- .trend_name(code) ⇒ Object
- .validate_thresholds(t) ⇒ Object
Class Method Details
.classify_zone(mgdl, thresholds = DEFAULT_THRESHOLDS) ⇒ Object
Zone boundaries are inclusive on the out-of-range side:
:very_low value <= very_low
:low very_low < value <= low
:in_range low < value < high
:high high <= value < very_high
:very_high value >= very_high
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/sugarsense/units.rb', line 62 def classify_zone(mgdl, thresholds = DEFAULT_THRESHOLDS) v = Float(mgdl) t = validate_thresholds(thresholds) return :very_high if v >= t[:very_high] return :very_low if v <= t[:very_low] return :high if v >= t[:high] return :low if v <= t[:low] :in_range end |
.format_glucose(mgdl, unit = :mgdl) ⇒ Object
Display formatting identical to the Sugar Sense apps: mmol/L is shown with exactly 1 decimal, mg/dL as a whole number. Rounding goes through Rational so the exact value of the IEEE double is rounded, matching JavaScript's toFixed(1) (100 mg/dL displays as "5.5"); Ruby's own %.1f and Float#round(1) both give "5.6" for the same double.
38 39 40 41 42 43 44 45 46 |
# File 'lib/sugarsense/units.rb', line 38 def format_glucose(mgdl, unit = :mgdl) case unit when :mmol mmol = Float(mgdl) * MMOL_PER_MGDL format("%.1f", (Rational(mmol) * 10).round / 10.0) when :mgdl then Float(mgdl).round.to_s else raise ArgumentError, "unit must be :mgdl or :mmol" end end |
.gmi(mean_mgdl) ⇒ Object
Glucose Management Indicator (approximates lab A1C, %) from mean mg/dL. International consensus formula (Bergenstal et al., 2018).
86 87 88 |
# File 'lib/sugarsense/units.rb', line 86 def gmi(mean_mgdl) 3.31 + 0.02392 * Float(mean_mgdl) end |
.mgdl_to_mmol(mgdl) ⇒ Object
mg/dL -> mmol/L (unrounded float). Use format_glucose for the display form.
24 25 26 |
# File 'lib/sugarsense/units.rb', line 24 def mgdl_to_mmol(mgdl) Float(mgdl) * MMOL_PER_MGDL end |
.mmol_to_mgdl(mmol) ⇒ Object
mmol/L -> mg/dL, rounded to the nearest whole number.
29 30 31 |
# File 'lib/sugarsense/units.rb', line 29 def mmol_to_mgdl(mmol) (Float(mmol) / MMOL_PER_MGDL).round end |
.time_in_range(readings, thresholds = DEFAULT_THRESHOLDS) ⇒ Object
Five-band distribution over an array of mg/dL values. Returns { total:, counts: => n, percent: => Float }.
75 76 77 78 79 80 81 82 |
# File 'lib/sugarsense/units.rb', line 75 def time_in_range(readings, thresholds = DEFAULT_THRESHOLDS) t = validate_thresholds(thresholds) counts = { very_low: 0, low: 0, in_range: 0, high: 0, very_high: 0 } readings.each { |v| counts[classify_zone(v, t)] += 1 } total = readings.length percent = counts.transform_values { |c| total.zero? ? 0.0 : c * 100.0 / total } { total: total, counts: counts, percent: percent } end |
.trend_arrow(code) ⇒ Object
48 49 50 |
# File 'lib/sugarsense/units.rb', line 48 def trend_arrow(code) TREND_ARROWS.fetch(code, "") end |
.trend_name(code) ⇒ Object
52 53 54 |
# File 'lib/sugarsense/units.rb', line 52 def trend_name(code) TREND_NAMES.fetch(code, "") end |
.validate_thresholds(t) ⇒ Object
90 91 92 93 94 95 96 |
# File 'lib/sugarsense/units.rb', line 90 def validate_thresholds(t) unless t[:very_low] < t[:low] && t[:low] < t[:high] && t[:high] < t[:very_high] raise ArgumentError, "thresholds must satisfy very_low < low < high < very_high" end t end |