diabetic-utils
v1.5.0
Published
Zero-bloat TypeScript utilities for diabetes data: glucose, A1C, conversions, time-in-range, and more.
Maintainers
Readme
🩸 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
🚀 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
npm install diabetic-utils
# or
pnpm add diabetic-utils
# or
yarn add diabetic-utilsRequirements: TypeScript 4.5+ or JavaScript (ES2020+)
⚡ Quick Start
Basic Conversions & Calculations
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.0Enhanced Time-in-Range
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
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)
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
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 functionFHIR & Open mHealth Export
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
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') // → falseVariability Analytics
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
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:
NormalizedCGMReadingwith 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
anytypes - ✅ 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/LmmolLToMgDl(value)- Convert mmol/L to mg/dLconvertGlucoseUnit({ value, unit })- Generic unit conversion
A1C & GMI
estimateA1CFromAverage(glucose, unit)- A1C from average glucoseestimateGMI(input, unit?)- GMI from average glucosea1cToGMI(a1c)- Convert A1C to GMIestimateAvgGlucoseFromA1C(a1c)- A1C to estimated average glucose (mg/dL)
Time-in-Range
calculateTimeInRange(readings, low, high)- Basic TIRcalculateEnhancedTIR(readings, options?)- 5-range TIRcalculatePregnancyTIR(readings, options?)- Pregnancy TIR
Glucose Analysis
getGlucoseLabel(value, unit, thresholds?)- Label as low/normal/highisHypo(value, unit, threshold?)- Check hypoglycemiaisHyper(value, unit, threshold?)- Check hyperglycemiaisValidGlucoseValue(value, unit)- Validate glucose value
A1C Analysis
getA1CCategory(a1c, cutoffs?)- Categorize A1CisA1CInTarget(a1c, target?)- Check if A1C meets target
Variability Metrics
glucoseStandardDeviation(readings)- SD (unbiased)glucoseCoefficientOfVariation(readings)- CV%glucosePercentiles(readings, percentiles)- Percentile ranksglucoseMAGE(readings, options?)- Mean Amplitude of Glycemic Excursions
Insulin Metrics
calculateHOMAIR(glucose, insulin, unit)- HOMA-IRisValidInsulin(value)- Validate insulin value
Advanced CGM Metrics
calculateAGPMetrics(readings, options?)- All Tier 1 metrics in a single callglucoseLBGI(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 observationbuildFHIRSensorReading(reading)- FHIR sensor reading observationbuildFHIRSensorReadings(readings)- FHIR sensor reading observations from a list of readingsbuildOMHBloodGlucose(reading)- Open mHealth blood-glucose bodybuildOMHBloodGlucoseList(readings)- Open mHealth blood-glucose bodies from a list of readingsbuildOMHDataPoint(reading, id)- Full OMH datapoint with header
Utilities
parseGlucoseString(str)- Parse "120 mg/dL" → { value, unit }formatGlucose(value, unit)- Format glucose with unitisValidGlucoseString(str)- Validate glucose string
🧪 Test Helpers
The repository includes test utilities in tests/test-helpers.ts for contributors and downstream developers:
// 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)
- Pregnancy TIR: ADA Standards of Care (2024)
- ADA 2026 Standards: ADA Standards of Care (2026)
- A1C/eAG: Nathan et al. (2008)
- HOMA-IR: Matthews et al. (1985)
- MAGE: Service et al. (1970)
- LBGI/HBGI/ADRR: Kovatchev et al. (2006)
- GRI: Klonoff et al. (2023)
- MODD: Service & Nelson (1980)
- GRADE: Hill et al. (2007)
- J-Index: Wojcicki (1995)
- CONGA: McDonnell et al. (2005)
- Active Percent: Danne et al. (2017)
- Variability: ISPAD Guidelines (2018)
- FHIR CGM IG: HL7 CGM IG v1.0.0
🏗️ 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:
- Fork the repository
- Create your feature branch:
git checkout -b feat/my-feature - Add tests for any new functionality
- Ensure 100% coverage:
pnpm test:coverage - Commit with conventional commits:
git commit -m "feat: add new feature" - Push to your branch:
git push origin feat/my-feature - Open a pull request
Development Commands
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Build library
pnpm build📝 Changelog
See CHANGELOG.md for detailed release notes and version history.
📄 License
This project is licensed under the MIT License. See the LICENSE file for details.
© 2024–2026 Mark Learst
Use it, fork it, build something that matters.
🔗 Links
- 📦 NPM Package
- 📚 API Documentation
- 🐙 GitHub Repository
- 🌐 Website (coming soon)
🙋♂️ Author
Mark Learst Full-stack developer, diabetes advocate, and open source contributor.
- 🐦 X (Twitter): @marklearst
- 💼 LinkedIn: Mark Learst
- 🌐 Portfolio: marklearst.com
💬 Using diabetic-utils in your project? Let me know—I'd love to feature it! ⭐ Star the repo and help us build the best diabetes toolkit together!
💬 Support
- 🐛 Bug Reports: Open an issue
- 💡 Feature Requests: Start a discussion
- 📧 Email: [email protected]
📝 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.
