GlucoseIQ

Core Concepts

The mental model for composing GlucoseIQ from ingestion to analysis and presentation.

@glucoseiq/core accepts glucose readings and returns typed analytics, series, or SVG strings. Your application supplies storage, vendor transport, and presentation.

Processing flow

StageApplication responsibilityGlucoseIQ API
IngestRead a file or receive a vendor payloadparseGlucoseCSV or a connector adapter
NormalizeProduce unit-bearing readings with timestampsGlucoseReading and NormalizedCGMReading
CheckReview rejected rows, gaps, coverage, and freshnessSafe normalizers, detectGaps, and dataSufficiency
AnalyzeCalculate a report or one metricanalyzeGlucose and individual functions
PresentRender the returned data in the host applicationData results, SVG renderers, or React adapters

Each stage is a function call with a defined input and result:

import {
  analyzeGlucose,
  buildAGPProfile,
  detectGaps,
  parseGlucoseCSV,
} from '@glucoseiq/core'

const csvText = `Timestamp,Glucose Value (mg/dL)
2026-07-01T08:00:00Z,112
2026-07-01T08:05:00Z,118`

const readings = parseGlucoseCSV(csvText, {
  timestampColumn: 'Timestamp',
  valueColumn: 'Glucose Value (mg/dL)',
  unit: 'mg/dL',
})

const gaps = detectGaps(readings, { maxGapMinutes: 15 })
const report = analyzeGlucose(readings, {
  timeZone: 'America/New_York',
})
const profile = buildAGPProfile(readings, {
  timeZone: 'America/New_York',
})

You can store, serialize, or test each result. The functions do not require global configuration.

Start with the smallest stable contract

The common input boundary is GlucoseReading:

interface GlucoseReading {
  readonly value: number
  readonly unit: 'mg/dL' | 'mmol/L'
  readonly timestamp: string
}

Vendor adapters return NormalizedCGMReading, which extends that shape with a normalized trend, source, and optional vendor metadata. Because it is a structural superset, normalized vendor readings can be passed directly to functions that accept GlucoseReading[].

See Data Model for units, timestamps, and the normalized connector contract.

Choose a report or individual metrics

Use analyzeGlucose when a surface needs summary metrics from one cleaned reading set:

import { analyzeGlucose, type GlucoseReading } from '@glucoseiq/core'

const readings: GlucoseReading[] = [
  { value: 112, unit: 'mg/dL', timestamp: '2026-07-01T08:00:00Z' },
  { value: 118, unit: 'mg/dL', timestamp: '2026-07-01T08:05:00Z' },
]
const report = analyzeGlucose(readings, {
  timeZone: 'America/New_York',
  includeProfile: true,
})

if (!report.valid) {
  // Render an empty state. Scalar fields are NaN and computed blocks are null.
} else {
  console.log(report.gmi)
  console.log(report.timeInRange?.inRange.percentage)
  console.log(report.episodes?.summary.hypoCount)
}

A valid result includes summary metrics and data sufficiency. Set includeProfile: false to omit the AGP percentile series.

Use individual functions when you need a smaller computation or different options. Examples include calculateEnhancedTIR, detectEpisodes, analyzeMealResponse, computeGlucoseTrend, splitDayNight, and alignToGrid.

import {
  computeGlucoseTrend,
  detectEpisodes,
  type GlucoseReading,
} from '@glucoseiq/core'

const readings: GlucoseReading[] = [
  { value: 112, unit: 'mg/dL', timestamp: '2026-07-01T08:00:00Z' },
  { value: 118, unit: 'mg/dL', timestamp: '2026-07-01T08:05:00Z' },
]

const trend = computeGlucoseTrend(readings.slice(-12))
const episodes = detectEpisodes(readings)

Individual functions also let two surfaces use different policies. A live tile can derive a short-window trend while a retrospective report analyzes fourteen days of observed data.

Choose data or SVG output

Most core APIs return typed data. buildAGPProfile returns a full-day grid of AGP-style percentile bins, while calculateEnhancedTIR returns five labeled range blocks. Use those results with your own charting or component system.

The render subpath returns SVG strings:

import { type GlucoseReading } from '@glucoseiq/core'
import { agpChartToSVG, tirBarToSVG } from '@glucoseiq/core/render'

const readings: GlucoseReading[] = [
  { value: 112, unit: 'mg/dL', timestamp: '2026-07-01T08:00:00Z' },
  { value: 118, unit: 'mg/dL', timestamp: '2026-07-01T08:05:00Z' },
]

const agpSvg = agpChartToSVG(readings, { theme: 'dark' })
const tirSvg = tirBarToSVG(readings, { theme: 'dark' })

Use the SVG functions for a fixed renderer. Use the data APIs when the host needs custom drawing or interaction.

Pass policy in options

Pass the unit, time zone, and other policy values to the function that needs them:

import { buildAGPProfile, type GlucoseReading } from '@glucoseiq/core'

const readings: GlucoseReading[] = [
  { value: 112, unit: 'mg/dL', timestamp: '2026-07-01T08:00:00Z' },
  { value: 6.6, unit: 'mmol/L', timestamp: '2026-07-01T08:05:00Z' },
]
const user = {
  profileTimeZone: 'America/Detroit',
  displayUnit: 'mg/dL',
} as const

const profile = buildAGPProfile(readings, {
  timeZone: user.profileTimeZone,
  unit: user.displayUnit,
  binMinutes: 5,
})

The same input and options produce the same analytical result. minutesSinceLastReading uses the current clock when you omit now, so pass now in tests or reproducible jobs.

import { minutesSinceLastReading, type GlucoseReading } from '@glucoseiq/core'

const readings: GlucoseReading[] = [
  { value: 112, unit: 'mg/dL', timestamp: '2024-01-15T11:55:00Z' },
]

const age = minutesSinceLastReading(
  readings,
  '2024-01-15T12:00:00Z',
)

Fetch vendor data outside the analytics layer

The Dexcom, Libre, and Nightscout APIs in @glucoseiq/core/connectors normalize payloads that your application already obtained. They do not authenticate, poll, retry, or persist vendor data.

Keep credentials and network policy in your application, then pass the vendor payload to a normalizer:

import {
  normalizeDexcomEntries,
  type DexcomShareEntry,
} from '@glucoseiq/core/connectors'
import { analyzeGlucose } from '@glucoseiq/core'

const payload: DexcomShareEntry[] = [
  { Value: 112, Trend: 'Flat', WT: '/Date(1705320000000)/' },
  { Value: 118, Trend: 'SingleUp', WT: '/Date(1705320300000)/' },
]
const readings = normalizeDexcomEntries(payload)
const report = analyzeGlucose(readings)

For feeds where one malformed record should not discard the whole batch, use a safe normalizer and surface its per-entry errors. See Data Quality.

Informational use

GlucoseIQ produces descriptive analytics for informational and educational use. It does not provide medical advice, diagnosis, or treatment.

On this page