sugarsense-units 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +47 -0
- data/lib/sugarsense/units/version.rb +7 -0
- data/lib/sugarsense/units.rb +98 -0
- data/lib/sugarsense-units.rb +3 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: dcc1213237aae5f99e43cf1f88dc91a40e62e8e285227223961022d23863c13b
|
|
4
|
+
data.tar.gz: 5ef263aaa45acd74d33487b2a7657e702c566e4b6f208df7464953bf8426aa27
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6d7833ba0a82dc6e8a2761807dae113954a856edf8855240176e0b9ae436c7577602a81d1bbdb6fe8652ebf9796718797f8f2b51cea3a84f0c9c9c6ba2d0fbf0
|
|
7
|
+
data.tar.gz: 7bbe2fb05bbf457f2a62d902235e5422ff0188c6dd9b9d64991b71564b0105bf8a2d066753e3db0ff8834cd5b05106d0596bcfeb7c9b65924bddc88dea830016
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sugar Sense (https://sugarsense.io)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# sugarsense-units
|
|
2
|
+
|
|
3
|
+
Blood glucose unit conversion (mg/dL to mmol/L and back), CGM trend arrows, glucose zone classification, time in range and GMI helpers for Ruby. No dependencies.
|
|
4
|
+
|
|
5
|
+
Extracted from [Sugar Sense](https://sugarsense.io), a CGM monitoring app for iOS, Apple Watch and Android that connects to FreeStyle Libre, Dexcom and [Nightscout](https://nightscout.github.io/) and shares glucose with family in real time. These are the exact conventions the app uses in production, published so other diabetes tools can reuse them instead of re-deriving the math.
|
|
6
|
+
|
|
7
|
+
> This library is informational only. It contains no insulin dosing logic and gives no medical advice.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
gem install sugarsense-units
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require "sugarsense-units"
|
|
19
|
+
|
|
20
|
+
SugarSense::Units.mgdl_to_mmol(100) # => 5.55
|
|
21
|
+
SugarSense::Units.format_glucose(120, :mmol) # => "6.7" (display form, 1 decimal)
|
|
22
|
+
SugarSense::Units.trend_arrow(2) # => "↘"
|
|
23
|
+
SugarSense::Units.classify_zone(65) # => :low
|
|
24
|
+
SugarSense::Units.gmi(154) # => 6.99 (approximates lab A1C, %)
|
|
25
|
+
|
|
26
|
+
SugarSense::Units.time_in_range([50, 60, 100, 150, 200, 300])
|
|
27
|
+
# => { total: 6,
|
|
28
|
+
# counts: { very_low: 1, low: 1, in_range: 2, high: 1, very_high: 1 },
|
|
29
|
+
# percent: { very_low: 16.67, low: 16.67, in_range: 33.33, high: 16.67, very_high: 16.67 } }
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Conventions
|
|
33
|
+
|
|
34
|
+
These match the Sugar Sense production apps and backend:
|
|
35
|
+
|
|
36
|
+
- All math is done in **mg/dL**; mmol/L is a display conversion (factor `0.0555`, shown with 1 decimal).
|
|
37
|
+
- **Trend codes** are integers 1 to 5: `1 ↓ falling fast, 2 ↘ falling, 3 → steady, 4 ↗ rising, 5 ↑ rising fast` (the same scheme Dexcom, Libre and Nightscout data can be normalized to).
|
|
38
|
+
- **Zones**: `:very_low, :low, :in_range, :high, :very_high`, with boundaries inclusive on the out-of-range side (a value exactly at the low limit is `:low`, exactly at the high limit is `:high`).
|
|
39
|
+
- **Default thresholds** are `55 / 70 / 180 / 250` mg/dL (`DEFAULT_THRESHOLDS`), aligned with ADA guidance. Every method accepts custom thresholds.
|
|
40
|
+
|
|
41
|
+
## About Sugar Sense
|
|
42
|
+
|
|
43
|
+
[Sugar Sense](https://sugarsense.io) turns CGM data into real-time monitoring for the whole family: glucose on the lock screen and Apple Watch, low and high alerts, an automated emergency call on urgent lows, and a Care Circle so loved ones can follow along. There is also a [web dashboard](https://app.sugarsense.io) and a free [download page](https://sugarsense.io/download).
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "units/version"
|
|
4
|
+
|
|
5
|
+
# Glucose unit and zone helpers, extracted from Sugar Sense (https://sugarsense.io).
|
|
6
|
+
# All internal math is mg/dL; mmol/L exists only as a display conversion.
|
|
7
|
+
# This library is informational only and contains no dosing logic.
|
|
8
|
+
module SugarSense
|
|
9
|
+
module Units
|
|
10
|
+
MMOL_PER_MGDL = 0.0555
|
|
11
|
+
|
|
12
|
+
# ADA-aligned defaults used by Sugar Sense for new accounts (mg/dL).
|
|
13
|
+
DEFAULT_THRESHOLDS = { very_low: 55, low: 70, high: 180, very_high: 250 }.freeze
|
|
14
|
+
|
|
15
|
+
# CGM trend codes: 1 falling fast, 2 falling, 3 steady, 4 rising, 5 rising fast.
|
|
16
|
+
TREND_ARROWS = { 1 => "↓", 2 => "↘", 3 => "→", 4 => "↗", 5 => "↑" }.freeze
|
|
17
|
+
TREND_NAMES = {
|
|
18
|
+
1 => "falling_fast", 2 => "falling", 3 => "steady", 4 => "rising", 5 => "rising_fast"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# mg/dL -> mmol/L (unrounded float). Use format_glucose for the display form.
|
|
24
|
+
def mgdl_to_mmol(mgdl)
|
|
25
|
+
Float(mgdl) * MMOL_PER_MGDL
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# mmol/L -> mg/dL, rounded to the nearest whole number.
|
|
29
|
+
def mmol_to_mgdl(mmol)
|
|
30
|
+
(Float(mmol) / MMOL_PER_MGDL).round
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Display formatting identical to the Sugar Sense apps:
|
|
34
|
+
# mmol/L is shown with exactly 1 decimal, mg/dL as a whole number.
|
|
35
|
+
# Rounding goes through Rational so the exact value of the IEEE double is
|
|
36
|
+
# rounded, matching JavaScript's toFixed(1) (100 mg/dL displays as "5.5");
|
|
37
|
+
# Ruby's own %.1f and Float#round(1) both give "5.6" for the same double.
|
|
38
|
+
def format_glucose(mgdl, unit = :mgdl)
|
|
39
|
+
case unit
|
|
40
|
+
when :mmol
|
|
41
|
+
mmol = Float(mgdl) * MMOL_PER_MGDL
|
|
42
|
+
format("%.1f", (Rational(mmol) * 10).round / 10.0)
|
|
43
|
+
when :mgdl then Float(mgdl).round.to_s
|
|
44
|
+
else raise ArgumentError, "unit must be :mgdl or :mmol"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def trend_arrow(code)
|
|
49
|
+
TREND_ARROWS.fetch(code, "")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def trend_name(code)
|
|
53
|
+
TREND_NAMES.fetch(code, "")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Zone boundaries are inclusive on the out-of-range side:
|
|
57
|
+
# :very_low value <= very_low
|
|
58
|
+
# :low very_low < value <= low
|
|
59
|
+
# :in_range low < value < high
|
|
60
|
+
# :high high <= value < very_high
|
|
61
|
+
# :very_high value >= very_high
|
|
62
|
+
def classify_zone(mgdl, thresholds = DEFAULT_THRESHOLDS)
|
|
63
|
+
v = Float(mgdl)
|
|
64
|
+
t = validate_thresholds(thresholds)
|
|
65
|
+
return :very_high if v >= t[:very_high]
|
|
66
|
+
return :very_low if v <= t[:very_low]
|
|
67
|
+
return :high if v >= t[:high]
|
|
68
|
+
return :low if v <= t[:low]
|
|
69
|
+
|
|
70
|
+
:in_range
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Five-band distribution over an array of mg/dL values.
|
|
74
|
+
# Returns { total:, counts: {zone => n}, percent: {zone => Float} }.
|
|
75
|
+
def time_in_range(readings, thresholds = DEFAULT_THRESHOLDS)
|
|
76
|
+
t = validate_thresholds(thresholds)
|
|
77
|
+
counts = { very_low: 0, low: 0, in_range: 0, high: 0, very_high: 0 }
|
|
78
|
+
readings.each { |v| counts[classify_zone(v, t)] += 1 }
|
|
79
|
+
total = readings.length
|
|
80
|
+
percent = counts.transform_values { |c| total.zero? ? 0.0 : c * 100.0 / total }
|
|
81
|
+
{ total: total, counts: counts, percent: percent }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Glucose Management Indicator (approximates lab A1C, %) from mean mg/dL.
|
|
85
|
+
# International consensus formula (Bergenstal et al., 2018).
|
|
86
|
+
def gmi(mean_mgdl)
|
|
87
|
+
3.31 + 0.02392 * Float(mean_mgdl)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def validate_thresholds(t)
|
|
91
|
+
unless t[:very_low] < t[:low] && t[:low] < t[:high] && t[:high] < t[:very_high]
|
|
92
|
+
raise ArgumentError, "thresholds must satisfy very_low < low < high < very_high"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
t
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sugarsense-units
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sugar Sense
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: 'Glucose helpers extracted from the Sugar Sense CGM monitoring app: mg/dL
|
|
14
|
+
to mmol/L conversion, trend arrow codes, glucose zone classification, time in range
|
|
15
|
+
distribution and the GMI formula. Informational only, contains no dosing logic.'
|
|
16
|
+
email:
|
|
17
|
+
- info@sugarsense.io
|
|
18
|
+
executables: []
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/sugarsense-units.rb
|
|
25
|
+
- lib/sugarsense/units.rb
|
|
26
|
+
- lib/sugarsense/units/version.rb
|
|
27
|
+
homepage: https://sugarsense.io
|
|
28
|
+
licenses:
|
|
29
|
+
- MIT
|
|
30
|
+
metadata:
|
|
31
|
+
homepage_uri: https://sugarsense.io
|
|
32
|
+
documentation_uri: https://www.rubydoc.info/gems/sugarsense-units
|
|
33
|
+
rubygems_mfa_required: 'true'
|
|
34
|
+
post_install_message:
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '2.5'
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
requirements: []
|
|
49
|
+
rubygems_version: 3.0.3.1
|
|
50
|
+
signing_key:
|
|
51
|
+
specification_version: 4
|
|
52
|
+
summary: Blood glucose unit conversion, CGM trend arrows, zone classification and
|
|
53
|
+
time in range helpers.
|
|
54
|
+
test_files: []
|