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

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