Skip to content

marklearst/diabetic-utils

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

257 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🩸 Diabetic Utils

Built and maintained by Mark Learst.

A TypeScript toolkit for diabetes analytics and health data.

Diabetic Utils Logo

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

Status codecov CI TypeScript Coverage npm npm downloads License


πŸš€ 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-utils

Requirements: 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.0

Enhanced 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 function

FHIR & 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')  // β†’ false

Variability 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: 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 β†’


πŸ§ͺ 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:


πŸ—οΈ 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: 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

# 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


πŸ™‹β€β™‚οΈ Author

Mark Learst Full-stack developer, diabetes advocate, and open source contributor.

πŸ’¬ 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


πŸ“ 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.

About

🩸 A modern TypeScript utility library for glucose, A1C, and diabetic health data.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

 
 
 

Contributors