GlucoseIQ

Export FHIR and Open mHealth

Build FHIR and Open mHealth payloads from normalized glucose data.

GlucoseIQ can turn normalized readings and Time-in-Range results into plain, serializable health-data objects. The builders are stateless, dependency-free, and do not require a FHIR SDK.

Use this guide when you need to hand glucose data to a clinical system, research pipeline, personal data store, or another standards-aware service.

Integration responsibilities

GlucoseIQ builds payload objects. Your application supplies authentication, resource references, transport, and validation against the receiving system.

Choose an output

GoalBuilderResult
Export one CGM reading as FHIRbuildFHIRSensorReadingA lightweight Observation
Export many CGM readings as FHIRbuildFHIRSensorReadingsAn array of Observation objects
Export aggregate CGM metrics as FHIRbuildFHIRCGMSummaryA CGM summary Observation
Export an Open mHealth bodybuildOMHBloodGlucoseA blood-glucose body
Export several Open mHealth bodiesbuildOMHBloodGlucoseListAn array of bodies
Export a wrapped Open mHealth datapointbuildOMHDataPointA header plus blood-glucose body

All builders are available from @glucoseiq/core and the @glucoseiq/core/interop subpath.

Start with normalized readings

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

const readings: GlucoseReading[] = [
  { value: 104, unit: 'mg/dL', timestamp: '2026-07-12T12:00:00Z' },
  { value: 5.9, unit: 'mmol/L', timestamp: '2026-07-12T12:05:00Z' },
]

FHIR and Open mHealth builders preserve the value and unit supplied for each reading. Normalize vendor payloads and validate timestamps before export. The builders pass invalid input through unchanged.

FHIR

Export sensor readings

import type { GlucoseReading } from '@glucoseiq/core'
import { buildFHIRSensorReadings } from '@glucoseiq/core/interop'

const readings: GlucoseReading[] = [
  { value: 104, unit: 'mg/dL', timestamp: '2026-07-12T12:00:00Z' },
  { value: 5.9, unit: 'mmol/L', timestamp: '2026-07-12T12:05:00Z' },
]
const observations = buildFHIRSensorReadings(readings)

observations[0]
// {
//   resourceType: 'Observation',
//   status: 'final',
//   code: { coding: [{ system: 'http://loinc.org', code: '99504-3', ... }] },
//   effectiveDateTime: '2026-07-12T12:00:00Z',
//   valueQuantity: {
//     value: 104,
//     unit: 'mg/dL',
//     system: 'http://unitsofmeasure.org',
//     code: 'mg/dL'
//   }
// }

The builder selects the interstitial-fluid LOINC code for the reading's unit: mass-per-volume for mg/dL, or molar concentration for mmol/L. It emits one Observation per reading and does not wrap the array in a FHIR Bundle.

Export a CGM summary

Build the analytics once, then pass its five-range Time-in-Range result into the summary builder:

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

export function buildSummaryObservation(
  readings: GlucoseReading[],
  period: { start: string; end: string },
) {
  const report = analyzeGlucose(readings, { includeProfile: false })

  if (!report.valid || !report.timeInRange) {
    throw new Error('A CGM summary requires valid glucose readings')
  }

  return buildFHIRCGMSummary(report.timeInRange, period, {
    meanGlucose: report.meanGlucose,
    gmi: report.gmi,
    cv: report.cv,
  })
}

const readings: GlucoseReading[] = [
  { value: 104, unit: 'mg/dL', timestamp: '2026-07-12T12:00:00Z' },
  { value: 112, unit: 'mg/dL', timestamp: '2026-07-12T12:05:00Z' },
]
const summary = buildSummaryObservation(readings, {
  start: '2026-06-29T00:00:00Z',
  end: '2026-07-12T23:59:59Z',
})

The result is a CGM summary Observation with LOINC-coded components for very low, low, target, high, and very high percentages. The optional mean glucose, GMI, and coefficient of variation become additional components.

Add server-required fields

The lightweight output is aligned with the HL7 CGM Implementation Guide, but a receiving server may require fields such as subject, device, resource identifiers, provenance, or a particular profile URL. Add those in your integration layer and validate the final resource against that server.

Send an Observation

After adding the references and metadata required by your FHIR server, transmit the resource using that server's authentication and error-handling policy:

async function createObservation(
  baseUrl: string,
  accessToken: string,
  observation: object,
) {
  const response = await fetch(`${baseUrl}/Observation`, {
    method: 'POST',
    headers: {
      authorization: `Bearer ${accessToken}`,
      'content-type': 'application/fhir+json',
      accept: 'application/fhir+json',
    },
    body: JSON.stringify(observation),
  })

  if (!response.ok) {
    throw new Error(`FHIR server rejected the resource: ${response.status}`)
  }

  return response.json()
}

For bulk ingestion, construct the Bundle type required by your server around the output of buildFHIRSensorReadings.

Open mHealth

Export bodies

Use body-only builders when another system supplies its own headers or storage envelope:

import type { GlucoseReading } from '@glucoseiq/core'
import { buildOMHBloodGlucoseList } from '@glucoseiq/core/interop'

const readings: GlucoseReading[] = [
  { value: 104, unit: 'mg/dL', timestamp: '2026-07-12T12:00:00Z' },
  { value: 5.9, unit: 'mmol/L', timestamp: '2026-07-12T12:05:00Z' },
]
const bodies = buildOMHBloodGlucoseList(readings)

bodies[0]
// {
//   blood_glucose: { value: 104, unit: 'mg/dL' },
//   effective_time_frame: { date_time: '2026-07-12T12:00:00Z' },
//   specimen_source: 'interstitial fluid'
// }

Use buildOMHDataPoint when you want the Open mHealth header and schema identifier as well:

import type { GlucoseReading } from '@glucoseiq/core'
import { buildOMHDataPoint } from '@glucoseiq/core/interop'

const readings: GlucoseReading[] = [
  { value: 104, unit: 'mg/dL', timestamp: '2026-07-12T12:00:00Z' },
]
const dataPoints = readings.map((reading) =>
  buildOMHDataPoint(reading, crypto.randomUUID()),
)

const firstDataPoint = dataPoints.at(0)
if (!firstDataPoint) throw new Error('Expected one Open mHealth datapoint')
firstDataPoint.header.schema_id
// { namespace: 'omh', name: 'blood-glucose', version: '3.0' }

The builder generates creation_date_time when called. Supply and persist your own stable IDs when repeated exports must refer to the same datapoints.

Test the exported contracts

The payloads are plain objects, so contract tests do not need a FHIR runtime:

import { buildFHIRSensorReading } from '@glucoseiq/core/interop'

const observation = buildFHIRSensorReading({
  value: 104,
  unit: 'mg/dL',
  timestamp: '2026-07-12T12:00:00Z',
})

const expectedQuantity = {
  value: 104,
  unit: 'mg/dL',
  system: 'http://unitsofmeasure.org',
  code: 'mg/dL',
}

if (JSON.stringify(observation.valueQuantity) !== JSON.stringify(expectedQuantity)) {
  throw new Error('FHIR quantity did not match the expected UCUM contract')
}

Run representative payloads through the validator or sandbox supplied by the receiving system. Production integrations need both a stable local shape and acceptance by the target server.

On this page