diabetic-utils
Version:
Zero-bloat TypeScript utilities for diabetes data: glucose, A1C, conversions, time-in-range, and more.
638 lines (489 loc) β’ 22.8 kB
Markdown
# π©Έ Diabetic Utils
Built and maintained by Mark Learst.
**A TypeScript toolkit for diabetes analytics and health data.**

A modern, strictly-typed utility library for glucose, A1C, insulin, and diabetes metrics. Designed for reliability and transparencyβno bloat, no guesswork, just robust utilities with referenced formulas from published guidelines.
> **Disclaimer**: This library is for **informational and educational purposes only**.
> It does not constitute medical advice, diagnosis, or treatment. Always consult
> a qualified healthcare provider for medical decisions.
> **v1.5.0** adds a full advanced CGM metrics suite, CGM vendor adapters, and health data interoperability (FHIR, Open mHealth).
---
## π Status & Quality

[](https://codecov.io/gh/marklearst/diabetic-utils)






---
## π What's New in v1.5.0
### π Advanced CGM Metrics Suite
A complete set of published CGM analytics, each with peer-reviewed references:
- **ADRR**: Average Daily Risk Range (Kovatchev 2006)
- **GRADE**: Glycemic Risk Assessment Diabetes Equation with hypo/eu/hyper partitioning (Hill 2007)
- **J-Index**: Composite mean + variability score (Wojcicki 1995)
- **CONGA**: Continuous Overall Net Glycemic Action for intra-day variability (McDonnell 2005)
- **Active Percent**: CGM wear-time tracking against clinical thresholds (Danne 2017)
- **AGP Aggregate**: Single-call `calculateAGPMetrics()` computes all Tier 1 metrics at once
- **LBGI / HBGI**: Low/High Blood Glucose Index (Kovatchev 2006)
- **GRI**: Glycemia Risk Index with zone A-E classification (Klonoff 2023)
- **MODD**: Mean of Daily Differences for day-to-day variability (Service 1980)
### π CGM Connector Adapters
Pure transformation helpers that normalize vendor payloads into a canonical `NormalizedCGMReading` type:
- **Dexcom Share** β normalize Dexcom Share API responses
- **Libre LinkUp** β normalize Libre LinkUp API responses
- **Nightscout** β normalize Nightscout SGV entries
### π₯ Health Data Interoperability
Build standards-compliant payloads for health data exchange:
- **FHIR CGM IG** β HL7 FHIR-aligned CGM summary and sensor reading observations
- **Open mHealth** β OMH blood-glucose datapoints with full header support
### β
337 Passing Tests
- 100% coverage across lines, branches, functions, and statements
- New edge-case coverage for out-of-order timestamps, mixed units, and cross-module interactions
---
## π¦ Installation
```bash
npm install diabetic-utils
# or
pnpm add diabetic-utils
# or
yarn add diabetic-utils
```
**Requirements:** TypeScript 4.5+ or JavaScript (ES2020+)
---
## β‘ Quick Start
### Basic Conversions & Calculations
```typescript
import {
mgDlToMmolL,
mmolLToMgDl,
estimateGMI,
estimateA1CFromAverage
} from 'diabetic-utils'
// Glucose unit conversions
mgDlToMmolL(180) // β 10.0
mmolLToMgDl(5.5) // β 99
// GMI calculation (multiple input formats)
estimateGMI(100, 'mg/dL') // β 5.4
estimateGMI('5.5 mmol/L') // β 5.4
estimateGMI({ value: 100, unit: 'mg/dL' }) // β 5.4
// A1C estimation
estimateA1CFromAverage(154, 'mg/dL') // β 7.0
```
### Enhanced Time-in-Range
```typescript
import { calculateEnhancedTIR } from 'diabetic-utils'
import type { GlucoseReading } from 'diabetic-utils'
const readings: GlucoseReading[] = [
{ value: 120, unit: 'mg/dL', timestamp: '2024-01-01T08:00:00Z' },
{ value: 95, unit: 'mg/dL', timestamp: '2024-01-01T08:05:00Z' },
{ value: 180, unit: 'mg/dL', timestamp: '2024-01-01T08:10:00Z' },
// ... more readings
]
const result = calculateEnhancedTIR(readings)
console.log(`TIR: ${result.inRange.percentage}%`)
// TIR: 72.5%
console.log(`Very Low: ${result.veryLow.percentage}%`)
// Very Low: 0.5%
console.log(`Assessment: ${result.meetsTargets.overallAssessment}`)
// Assessment: good
console.log(result.meetsTargets.recommendations)
// ['All metrics meet consensus targets.']
```
### Pregnancy TIR
```typescript
import { calculatePregnancyTIR } from 'diabetic-utils'
const result = calculatePregnancyTIR(readings)
console.log(`TIR (63-140 mg/dL): ${result.inRange.percentage}%`)
// TIR (63-140 mg/dL): 85.2%
console.log(`Meets pregnancy targets: ${result.meetsPregnancyTargets}`)
// Meets pregnancy targets: true
console.log(result.recommendations)
// ['All metrics meet pregnancy consensus targets.', ...]
```
### AGP Metrics (All-in-One)
```typescript
import { calculateAGPMetrics } from 'diabetic-utils'
import type { GlucoseReading } from 'diabetic-utils'
const readings: GlucoseReading[] = [
{ value: 120, unit: 'mg/dL', timestamp: '2024-01-01T08:00:00Z' },
{ value: 95, unit: 'mg/dL', timestamp: '2024-01-01T08:05:00Z' },
{ value: 180, unit: 'mg/dL', timestamp: '2024-01-01T08:10:00Z' },
// ... more readings across multiple days
]
const agp = calculateAGPMetrics(readings)
console.log(`Mean: ${agp.meanGlucose} mg/dL`)
console.log(`SD: ${agp.sd}, CV: ${agp.cv}%`)
console.log(`LBGI: ${agp.lbgi}, HBGI: ${agp.hbgi}`)
console.log(`ADRR: ${agp.adrr}`)
console.log(`GRADE: ${agp.grade.gradeScore}`)
console.log(`GRI: ${agp.gri.gri} (Zone ${agp.gri.zone})`)
console.log(`J-Index: ${agp.jIndex}`)
console.log(`MODD: ${agp.modd} mg/dL`)
console.log(`CONGA: ${agp.conga} mg/dL`)
console.log(`Active: ${agp.activePercent.activePercent}%`)
```
### CGM Connector Adapters
```typescript
import {
normalizeDexcomEntries,
normalizeLibreEntries,
normalizeNightscoutEntries
} from 'diabetic-utils'
// Normalize vendor data into a canonical format
const dexcomReadings = normalizeDexcomEntries(dexcomShareResponse)
const libreReadings = normalizeLibreEntries(libreLinkUpResponse)
const nightscoutReadings = normalizeNightscoutEntries(nightscoutSGVEntries)
// All return NormalizedCGMReading[] with:
// { value, unit, timestamp, trend, source }
// Ready to pass into any diabetic-utils analytics function
```
### FHIR & Open mHealth Export
```typescript
import {
buildFHIRCGMSummary,
buildFHIRSensorReading,
buildOMHDataPoint
} from 'diabetic-utils'
// Build a FHIR CGM summary observation
const fhirSummary = buildFHIRCGMSummary(tirResult, {
start: '2024-01-01',
end: '2024-01-14'
})
// Build a FHIR sensor reading observation
const fhirReading = buildFHIRSensorReading({
value: 120, unit: 'mg/dL', timestamp: '2024-01-01T08:00:00Z'
})
// Build an Open mHealth blood-glucose datapoint
const omhPoint = buildOMHDataPoint(
{ value: 120, unit: 'mg/dL', timestamp: '2024-01-01T08:00:00Z' },
'reading-001'
)
```
### Glucose Labeling & Validation
```typescript
import {
getGlucoseLabel,
isHypo,
isHyper,
isValidGlucoseValue
} from 'diabetic-utils'
// Label glucose values
getGlucoseLabel(60, 'mg/dL') // β 'low'
getGlucoseLabel(5.5, 'mmol/L') // β 'normal'
getGlucoseLabel(200, 'mg/dL') // β 'high'
// Threshold checks
isHypo(65, 'mg/dL') // β true
isHyper(180, 'mg/dL') // β false
// Validation
isValidGlucoseValue(120, 'mg/dL') // β true
isValidGlucoseValue(-10, 'mg/dL') // β false
```
### Variability Analytics
```typescript
import {
glucoseStandardDeviation,
glucoseCoefficientOfVariation,
glucosePercentiles,
glucoseMAGE
} from 'diabetic-utils'
const data = [90, 100, 110, 120, 130, 140, 150, 160, 170, 180]
// Standard deviation (unbiased sample SD, n-1)
glucoseStandardDeviation(data) // β 30.28
// Coefficient of variation (CV%)
glucoseCoefficientOfVariation(data) // β 22.43
// Percentiles (nearest-rank method)
glucosePercentiles(data, [10, 50, 90])
// β { 10: 90, 50: 130, 90: 170 }
// MAGE (Mean Amplitude of Glycemic Excursions)
const mage = glucoseMAGE([100, 120, 80, 160, 90, 140, 70, 180])
console.log(`MAGE: ${mage} mg/dL`)
```
### Custom Thresholds
```typescript
import { getGlucoseLabel, isHypo, getA1CCategory } from 'diabetic-utils'
// Custom hypoglycemia threshold
isHypo(75, 'mg/dL', { mgdl: 80 }) // β true
// Custom hyperglycemia threshold
isHyper(9.0, 'mmol/L', { mmoll: 8.5 }) // β true
// Custom glucose label thresholds
getGlucoseLabel(75, 'mg/dL', {
hypo: { mgdl: 80 },
hyper: { mgdl: 160 }
}) // β 'low'
// Custom A1C category cutoffs
getA1CCategory(6.5, {
normalMax: 6.0,
prediabetesMax: 7.0
}) // β 'prediabetes'
```
---
## π Features
### Core Utilities
- β
**Glucose Conversions**: mg/dL β mmol/L
- β
**A1C Calculations**: GMI, eAG, A1C estimation
- β
**Time-in-Range**: Enhanced TIR (5 ranges), Pregnancy TIR
- β
**HOMA-IR**: Insulin resistance calculation
- β
**Variability Metrics**: SD, CV, MAGE, percentiles
- β
**Validation**: Input guards, string parsing
- β
**Labeling**: Glucose status (low/normal/high)
### Advanced CGM Metrics
- β
**LBGI / HBGI**: Low/High Blood Glucose Index (Kovatchev 2006)
- β
**GRI**: Glycemia Risk Index with zone A-E classification (Klonoff 2023)
- β
**MODD**: Mean of Daily Differences for day-to-day variability (Service 1980)
- β
**ADRR**: Average Daily Risk Range (Kovatchev 2006)
- β
**GRADE**: Glycemic Risk Assessment Diabetes Equation with partitioning (Hill 2007)
- β
**J-Index**: Composite mean + variability score (Wojcicki 1995)
- β
**CONGA**: Continuous Overall Net Glycemic Action (McDonnell 2005)
- β
**Active Percent**: CGM wear-time tracking (Danne 2017)
- β
**AGP Aggregate**: All Tier 1 metrics in a single call
### CGM Connector Adapters
- β
**Dexcom Share**: Normalize Dexcom Share API responses
- β
**Libre LinkUp**: Normalize Libre LinkUp API responses
- β
**Nightscout**: Normalize Nightscout SGV entries
- β
**Canonical Type**: `NormalizedCGMReading` with trend + source metadata
### Interoperability
- β
**FHIR CGM IG**: Build HL7 FHIR-aligned CGM summary and sensor reading payloads
- β
**Open mHealth**: Build OMH blood-glucose datapoints
### Quality & DX
- β
**TypeScript-First**: 100% strict mode, zero `any` types
- β
**100% Test Coverage**: 337 tests, all edge cases covered
- β
**Zero Dependencies**: No bloat, tree-shakable
- β
**Published References**: ADA, CDC, ISPAD, PubMed citations
- β
**TSDoc**: Complete API documentation
- β
**ESM + CJS**: Works everywhere
- β
**Type Predicates**: Better type narrowing
- β
**Named Constants**: Self-documenting formulas
---
## π Why Choose Diabetic Utils?
### Referenced Formulas
Every formula, threshold, and calculation references published guidelines:
- **International Consensus on Time in Range (2019)** - TIR calculations
- **ADA Standards of Care (2024)** - Pregnancy targets, A1C guidelines
- **ISPAD Guidelines (2018)** - Glucose variability metrics
- **NIH/NIDDK** - HOMA-IR, eAG formulas
- **Kovatchev et al. (2006)** - LBGI, HBGI, ADRR
- **Hill et al. (2007)** - GRADE
- **Klonoff et al. (2023)** - GRI
- **Wojcicki (1995)** - J-Index
- **McDonnell et al. (2005)** - CONGA
- **Danne et al. (2017)** - Active Percent
### Production-Ready
- **100% Test Coverage** - Every line tested
- **Type-Safe** - Catch errors at compile time
- **Zero Dependencies** - Small bundle, no supply chain risk
- **Modern ESM** - Tree-shakable, works with Vite, Next.js, etc.
### Developer-Friendly
- **Clear API** - Predictable function signatures
- **Great DX** - Autocomplete with literal types
- **Working Examples** - Copy-paste ready code
- **Test Helpers** - Utilities for your own tests
### Unique Features
**Only TypeScript/JavaScript library with:**
- Full AGP metrics suite in a single call
- Enhanced TIR (5-range breakdown) and Pregnancy TIR
- MAGE calculation (Service 1970)
- ADRR, GRADE, J-Index, CONGA, and Active Percent
- CGM vendor adapters (Dexcom, Libre, Nightscout)
- FHIR CGM IG-aligned export utilities
- LBGI/HBGI, GRI, and MODD metrics
- Type predicates for validation
---
## π Full API Reference
### Glucose Conversions
- `mgDlToMmolL(value)` - Convert mg/dL to mmol/L
- `mmolLToMgDl(value)` - Convert mmol/L to mg/dL
- `convertGlucoseUnit({ value, unit })` - Generic unit conversion
### A1C & GMI
- `estimateA1CFromAverage(glucose, unit)` - A1C from average glucose
- `estimateGMI(input, unit?)` - GMI from average glucose
- `a1cToGMI(a1c)` - Convert A1C to GMI
- `estimateAvgGlucoseFromA1C(a1c)` - A1C to estimated average glucose (mg/dL)
### Time-in-Range
- `calculateTimeInRange(readings, low, high)` - Basic TIR
- `calculateEnhancedTIR(readings, options?)` - 5-range TIR
- `calculatePregnancyTIR(readings, options?)` - Pregnancy TIR
### Glucose Analysis
- `getGlucoseLabel(value, unit, thresholds?)` - Label as low/normal/high
- `isHypo(value, unit, threshold?)` - Check hypoglycemia
- `isHyper(value, unit, threshold?)` - Check hyperglycemia
- `isValidGlucoseValue(value, unit)` - Validate glucose value
### A1C Analysis
- `getA1CCategory(a1c, cutoffs?)` - Categorize A1C
- `isA1CInTarget(a1c, target?)` - Check if A1C meets target
### Variability Metrics
- `glucoseStandardDeviation(readings)` - SD (unbiased)
- `glucoseCoefficientOfVariation(readings)` - CV%
- `glucosePercentiles(readings, percentiles)` - Percentile ranks
- `glucoseMAGE(readings, options?)` - Mean Amplitude of Glycemic Excursions
### Insulin Metrics
- `calculateHOMAIR(glucose, insulin, unit)` - HOMA-IR
- `isValidInsulin(value)` - Validate insulin value
### Advanced CGM Metrics
- `calculateAGPMetrics(readings, options?)` - All Tier 1 metrics in a single call
- `glucoseLBGI(readings)` - Low Blood Glucose Index (Kovatchev 2006)
- `glucoseHBGI(readings)` - High Blood Glucose Index (Kovatchev 2006)
- `calculateADRR(readings)` - Average Daily Risk Range (Kovatchev 2006)
- `calculateGRADE(readings)` - Glycemic Risk Assessment Diabetes Equation (Hill 2007)
- `calculateGRI(input)` - Glycemia Risk Index with zone A-E (Klonoff 2023)
- `calculateJIndex(readings)` - J-Index composite score (Wojcicki 1995)
- `calculateMODD(readings, options?)` - Mean of Daily Differences (Service 1980)
- `calculateCONGA(readings, options?)` - Continuous Overall Net Glycemic Action (McDonnell 2005)
- `calculateActivePercent(readings, options?)` - CGM wear-time percentage (Danne 2017)
### CGM Connector Adapters
- `normalizeDexcomEntries(entries)` - Dexcom Share β NormalizedCGMReading[]
- `normalizeLibreEntries(entries)` - Libre LinkUp β NormalizedCGMReading[]
- `normalizeNightscoutEntries(entries)` - Nightscout SGV β NormalizedCGMReading[]
### Interoperability
- `buildFHIRCGMSummary(tir, period, options?)` - FHIR CGM summary observation
- `buildFHIRSensorReading(reading)` - FHIR sensor reading observation
- `buildFHIRSensorReadings(readings)` - FHIR sensor reading observations from a list of readings
- `buildOMHBloodGlucose(reading)` - Open mHealth blood-glucose body
- `buildOMHBloodGlucoseList(readings)` - Open mHealth blood-glucose bodies from a list of readings
- `buildOMHDataPoint(reading, id)` - Full OMH datapoint with header
### Utilities
- `parseGlucoseString(str)` - Parse "120 mg/dL" β { value, unit }
- `formatGlucose(value, unit)` - Format glucose with unit
- `isValidGlucoseString(str)` - Validate glucose string
**[View Complete API Docs β](https://marklearst.github.io/diabetic-utils/)**
---
## π§ͺ Test Helpers
The repository includes test utilities in `tests/test-helpers.ts` for contributors and downstream developers:
```typescript
// In your test files (not published to npm β copy as needed)
import {
createGlucoseReadings,
COMMON_TEST_VALUES,
TEST_TIMESTAMP_BASE
} from './tests/test-helpers'
// Create test data easily
const readings = createGlucoseReadings([100, 110, 120], 'mg/dL', 5)
// β 3 readings at 5-minute intervals
// Use common test values
const { NORMAL_GLUCOSE_MGDL, HYPO_GLUCOSE_MGDL } = COMMON_TEST_VALUES
```
---
## π¬ References
All calculations reference peer-reviewed published sources:
- **Time-in-Range**: [International Consensus (2019)](https://diabetesjournals.org/care/article/42/8/1593)
- **Pregnancy TIR**: [ADA Standards of Care (2024)](https://diabetesjournals.org/care/article/47/Supplement_1/S282)
- **ADA 2026 Standards**: [ADA Standards of Care (2026)](https://diabetesjournals.org/care/issue/49/Supplement_1)
- **A1C/eAG**: [Nathan et al. (2008)](https://diabetesjournals.org/care/article/31/8/1473)
- **HOMA-IR**: [Matthews et al. (1985)](https://diabetesjournals.org/diabetes/article/34/12/1212)
- **MAGE**: [Service et al. (1970)](https://diabetesjournals.org/diabetes/article/19/9/644)
- **LBGI/HBGI/ADRR**: [Kovatchev et al. (2006)](https://doi.org/10.2337/dc06-1085)
- **GRI**: [Klonoff et al. (2023)](https://doi.org/10.1177/19322968221085273)
- **MODD**: [Service & Nelson (1980)](https://doi.org/10.2337/diacare.3.1.58)
- **GRADE**: [Hill et al. (2007)](https://doi.org/10.1111/j.1464-5491.2007.02119.x)
- **J-Index**: [Wojcicki (1995)](https://doi.org/10.1055/s-2007-979906)
- **CONGA**: [McDonnell et al. (2005)](https://doi.org/10.1089/dia.2005.7.253)
- **Active Percent**: [Danne et al. (2017)](https://doi.org/10.2337/dc17-1600)
- **Variability**: [ISPAD Guidelines (2018)](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7445493/)
- **FHIR CGM IG**: [HL7 CGM IG v1.0.0](https://build.fhir.org/ig/HL7/cgm/index.html)
---
## ποΈ Architecture
```
diabetic-utils/
βββ src/
β βββ index.ts # Main exports
β βββ constants.ts # Clinical thresholds & formulas
β βββ types.ts # TypeScript types
β βββ conversions.ts # Glucose unit conversions
β βββ a1c.ts # A1C & GMI calculations
β βββ tir.ts # Basic time-in-range
β βββ tir-enhanced.ts # Enhanced & pregnancy TIR
β βββ glucose.ts # Glucose utilities
β βββ alignment.ts # HOMA-IR
β βββ variability.ts # SD, CV, percentiles
β βββ mage.ts # MAGE calculation
β βββ formatters.ts # String formatting
β βββ guards.ts # Type guards
β βββ validators.ts # Input validation
β βββ connectors/ # CGM vendor adapters
β β βββ dexcom.ts # Dexcom Share normalization
β β βββ libre.ts # Libre LinkUp normalization
β β βββ nightscout.ts # Nightscout SGV normalization
β β βββ types.ts # Vendor & canonical types
β βββ interop/ # Health data interoperability
β β βββ fhir.ts # FHIR CGM IG payload builders
β β βββ openmhealth.ts # Open mHealth payload builders
β β βββ types.ts # Interop payload types
β βββ metrics/ # Advanced CGM metrics
β βββ agp.ts # Aggregate AGP metrics
β βββ bgi.ts # LBGI / HBGI
β βββ adrr.ts # Average Daily Risk Range
β βββ grade.ts # GRADE score
β βββ gri.ts # Glycemia Risk Index
β βββ jindex.ts # J-Index
β βββ modd.ts # Mean of Daily Differences
β βββ conga.ts # CONGA
β βββ active-percent.ts # CGM wear time
βββ tests/
β βββ test-helpers.ts # Shared test utilities
β βββ *.test.ts # 100% coverage tests (337 tests)
βββ dist/ # Built output (ESM + CJS)
```
**Key Principles:**
- β
Zero dependencies
- β
Tree-shakable modules
- β
Strict TypeScript
- β
100% test coverage
- β
Published references in TSDoc
---
## π€ Contributing
Contributions are welcome! Please follow these steps:
1. **Fork** the repository
2. **Create** your feature branch: `git checkout -b feat/my-feature`
3. **Add tests** for any new functionality
4. **Ensure** 100% coverage: `pnpm test:coverage`
5. **Commit** with [conventional commits](https://www.conventionalcommits.org/): `git commit -m "feat: add new feature"`
6. **Push** to your branch: `git push origin feat/my-feature`
7. **Open** a pull request
### Development Commands
```bash
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Build library
pnpm build
```
---
## π Changelog
See [CHANGELOG.md](CHANGELOG.md) for detailed release notes and version history.
---
## π License
This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.
Β© 2024β2026 [Mark Learst](https://marklearst.com)
Use it, fork it, build something that matters.
---
## π Links
- π¦ [NPM Package](https://www.npmjs.com/package/diabetic-utils)
- π [API Documentation](https://marklearst.github.io/diabetic-utils/)
- π [GitHub Repository](https://github.com/marklearst/diabetic-utils)
- π [Website](https://diabeticutils.com) _(coming soon)_
---
## πββοΈ Author
**Mark Learst**
Full-stack developer, diabetes advocate, and open source contributor.
- π¦ X (Twitter): [@marklearst](https://x.com/marklearst)
- πΌ LinkedIn: [Mark Learst](https://linkedin.com/in/marklearst)
- π Portfolio: [marklearst.com](https://marklearst.com)
> π¬ Using diabetic-utils in your project? [Let me know](https://x.com/marklearst)βI'd love to feature it!
> β Star the repo and help us build the best diabetes toolkit together!
---
## π¬ Support
- π **Bug Reports**: [Open an issue](https://github.com/marklearst/diabetic-utils/issues)
- π‘ **Feature Requests**: [Start a discussion](https://github.com/marklearst/diabetic-utils/discussions)
- π§ **Email**: mark@marklearst.com
---
## π A Personal Note
I built diabetic-utils because I believe in the power of data-driven diabetes management. As someone who's lived with diabetes, I know how hard it can be to make sense of the numbers.
That's why I've poured my heart into creating a library that's both **accurate** and **easy to use**. Whether you're building an app, working on research, or just trying to understand your own data, I hope diabetic-utils can help.
Let's work together to make diabetes management better, one data point at a time. π©Έ
---
**Built with β€οΈ by the diabetes community, for the diabetes community.**