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
| Stage | Application responsibility | GlucoseIQ API |
|---|---|---|
| Ingest | Read a file or receive a vendor payload | parseGlucoseCSV or a connector adapter |
| Normalize | Produce unit-bearing readings with timestamps | GlucoseReading and NormalizedCGMReading |
| Check | Review rejected rows, gaps, coverage, and freshness | Safe normalizers, detectGaps, and dataSufficiency |
| Analyze | Calculate a report or one metric | analyzeGlucose and individual functions |
| Present | Render the returned data in the host application | Data 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.