{"version":3,"sources":["../src/a1c.ts","../src/constants.ts","../src/guards.ts","../src/glucose.ts","../src/conversions.ts","../src/formatters.ts","../src/tir.ts","../src/types.ts"],"names":["formatA1C","val","isValidA1C","value","getA1CCategory","a1c","isA1CInTarget","target","a1cDelta","current","previous","a1cTrend","readings","delta","HYPO_THRESHOLD_MGDL","HYPER_THRESHOLD_MGDL","HYPO_THRESHOLD_MMOLL","HYPER_THRESHOLD_MMOLL","A1C_TO_EAG_MULTIPLIER","A1C_TO_EAG_CONSTANT","MGDL_MMOLL_CONVERSION","MG_DL","MMOL_L","GLUCOSE_COLOR_LOW","GLUCOSE_COLOR_NORMAL","GLUCOSE_COLOR_NORMAL_UP","GLUCOSE_COLOR_NORMAL_DOWN","GLUCOSE_COLOR_ELEVATED","GLUCOSE_COLOR_HIGH","GLUCOSE_ZONE_COLORS","TREND_ARROWS","isEstimateGMIOptions","input","isValidGlucoseString","isHypo","unit","isHyper","getGlucoseLabel","parseGlucoseString","match","rawValue","rawUnit","isValidGlucoseValue","estimateA1CFromAvgGlucose","avgMgDl","estimateAvgGlucoseFromA1C","estimateEAG","eAG","estimateA1CFromAverage","avgGlucose","a1cToGMI","estimateGMI","valueOrOptions","resolvedUnit","parsed","gmi","mgDlToMmolL","mmolLToMgDl","convertGlucoseUnit","formatGlucose","options","digits","suffix","formatPercentage","formatDate","iso","timeZone","calculateTIR","inRange","belowRange","aboveRange","r","total","getTIRSummary","result","groupByDay","acc","reading","day","calculateTimeInRange","lower","upper","AllowedGlucoseUnits"],"mappings":"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","file":"index.mjs","sourcesContent":["// @file src/a1c.ts\n\n/**\n * Formats an A1C value with % sign.\n * @param val - A1C number\n * @returns A1C as string with percent\n */\nexport function formatA1C(val: number): string {\n  return `${val.toFixed(1)}%`\n}\n\n/**\n * Validates if a value is a plausible A1C percentage.\n * @param value - Value to check\n * @returns True if valid A1C\n */\nexport function isValidA1C(value: unknown): boolean {\n  return (\n    typeof value === 'number' &&\n    Number.isFinite(value) &&\n    value > 0 &&\n    value < 20\n  )\n}\n\n/**\n * Returns a clinical category for an A1C value.\n * @param a1c - A1C value\n * @returns 'normal' | 'prediabetes' | 'diabetes' | 'invalid'\n */\nexport function getA1CCategory(\n  a1c: number\n): 'normal' | 'prediabetes' | 'diabetes' | 'invalid' {\n  if (!isValidA1C(a1c)) return 'invalid'\n  if (a1c < 5.7) return 'normal'\n  if (a1c < 6.5) return 'prediabetes'\n  return 'diabetes'\n}\n\n/**\n * Checks if an A1C value is within a target range.\n * @param a1c - A1C value\n * @param target - [min, max] range (default: [6.5, 7.0])\n * @returns True if in target range\n */\nexport function isA1CInTarget(\n  a1c: number,\n  target: [number, number] = [6.5, 7.0]\n): boolean {\n  return isValidA1C(a1c) && a1c >= target[0] && a1c <= target[1]\n}\n\n/**\n * Calculates the change (delta) between two A1C values.\n * @param current - Current A1C\n * @param previous - Previous A1C\n * @returns Delta (current - previous)\n * @throws If either value is invalid\n */\nexport function a1cDelta(current: number, previous: number): number {\n  if (!isValidA1C(current) || !isValidA1C(previous))\n    throw new Error('Invalid A1C value')\n  return +(current - previous).toFixed(2)\n}\n\n/**\n * Determines the trend of A1C values over time.\n * @param readings - Array of A1C values (chronological order)\n * @returns 'increasing' | 'decreasing' | 'stable' | 'insufficient data'\n */\nexport function a1cTrend(\n  readings: number[]\n): 'increasing' | 'decreasing' | 'stable' | 'insufficient data' {\n  if (!Array.isArray(readings) || readings.length < 2)\n    return 'insufficient data'\n  const delta = readings[readings.length - 1] - readings[0]\n  if (Math.abs(delta) < 0.1) return 'stable'\n  return delta > 0 ? 'increasing' : 'decreasing'\n}\n","// @file src/constants.ts\n\n/**\n * Hypoglycemia threshold in mg/dL.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport const HYPO_THRESHOLD_MGDL = 70\n\n/**\n * Hyperglycemia threshold in mg/dL.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport const HYPER_THRESHOLD_MGDL = 180\n\n/**\n * Hypoglycemia threshold in mmol/L.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport const HYPO_THRESHOLD_MMOLL = 3.9\n\n/**\n * Hyperglycemia threshold in mmol/L.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport const HYPER_THRESHOLD_MMOLL = 10\n\n/**\n * Multiplier for converting A1C to estimated average glucose (eAG).\n * @see https://www.cdc.gov/diabetes/managing/managing-blood-sugar/a1c.html\n */\nexport const A1C_TO_EAG_MULTIPLIER = 28.7\n\n/**\n * Constant for converting A1C to estimated average glucose (eAG).\n * @see https://www.cdc.gov/diabetes/managing/managing-blood-sugar/a1c.html\n */\nexport const A1C_TO_EAG_CONSTANT = 46.7\n\n/**\n * Conversion factor between mg/dL and mmol/L.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport const MGDL_MMOLL_CONVERSION = 18.0182\n\n/**\n * String literal for mg/dL unit.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport const MG_DL = 'mg/dL' as const\n\n/**\n * String literal for mmol/L unit.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport const MMOL_L = 'mmol/L' as const\n\n/**\n * Color codes for glucose zones.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport const GLUCOSE_COLOR_LOW = '#D32F2F' // Red (low)\nexport const GLUCOSE_COLOR_NORMAL = '#388E3C' // Green (steady normal)\nexport const GLUCOSE_COLOR_NORMAL_UP = '#4CAF50' // Lighter Green (normal, trending up)\nexport const GLUCOSE_COLOR_NORMAL_DOWN = '#2E7D32' // Darker Green (normal, trending down)\nexport const GLUCOSE_COLOR_ELEVATED = '#FBC02D' // Yellow (over 140)\nexport const GLUCOSE_COLOR_HIGH = '#F57C00' // Orange (over 180)\n\n/**\n * Glucose zone color mapping for different statuses and trends.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport const GLUCOSE_ZONE_COLORS = {\n  LOW: GLUCOSE_COLOR_LOW, // Red\n  NORMAL: GLUCOSE_COLOR_NORMAL, // Green\n  ELEVATED: GLUCOSE_COLOR_ELEVATED, // Yellow\n  HIGH: GLUCOSE_COLOR_HIGH, // Orange\n  // Colors for trending normal up and down\n  NORMAL_UP: GLUCOSE_COLOR_NORMAL_UP, // Lighter Green\n  NORMAL_DOWN: GLUCOSE_COLOR_NORMAL_DOWN, // Darker Green\n}\n\n/**\n * Unicode arrows for glucose trend indication.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport const TREND_ARROWS = {\n  STEADY: '→',\n  RISING: '↗',\n  FALLING: '↘',\n  RAPIDRISE: '↑',\n  RAPIDFALL: '↓',\n}\n","import type { EstimateGMIOptions } from './types'\n\n/**\n * Type guard to check if a value is a valid EstimateGMIOptions object.\n * @param input - The value to check for EstimateGMIOptions shape.\n * @returns True if the input is an object with numeric 'value' and string 'unit' properties, otherwise false.\n */\nexport function isEstimateGMIOptions(\n  input: unknown\n): input is EstimateGMIOptions {\n  return (\n    typeof input === 'object' &&\n    input !== null &&\n    'value' in input &&\n    'unit' in input &&\n    typeof (input as any).value === 'number' &&\n    typeof (input as any).unit === 'string'\n  )\n}\n\n/**\n * Checks if a string is in the format \"100 mg/dL\" or \"5.5 mmol/L\".\n * @param input - The value to validate as a glucose string.\n * @returns True if the input is a string matching the glucose value and unit format, otherwise false.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport function isValidGlucoseString(input: unknown): input is string {\n  if (typeof input !== 'string') return false\n\n  return /^\\d+(\\.\\d+)?\\s+(mg\\/dL|mmol\\/L)$/i.test(input.trim())\n}\n","// @file src/glucose.ts\n\nimport { GlucoseUnit } from './types'\nimport {\n  HYPO_THRESHOLD_MGDL,\n  HYPO_THRESHOLD_MMOLL,\n  HYPER_THRESHOLD_MGDL,\n  HYPER_THRESHOLD_MMOLL,\n  MG_DL,\n  MMOL_L,\n} from './constants'\n\nimport { isValidGlucoseString } from './guards'\n\n/**\n * Determines if a glucose value is below the hypoglycemia threshold for the given unit.\n * @param val - Glucose value to check.\n * @param unit - Unit of measurement (mg/dL or mmol/L). Defaults to mg/dL.\n * @returns True if the value is below the hypoglycemia threshold, otherwise false.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport function isHypo(val: number, unit: GlucoseUnit = MG_DL): boolean {\n  return unit === MG_DL ? val < HYPO_THRESHOLD_MGDL : val < HYPO_THRESHOLD_MMOLL\n}\n\n/**\n * Determines if a glucose value is above the hyperglycemia threshold for the given unit.\n * @param val - Glucose value to check.\n * @param unit - Unit of measurement (mg/dL or mmol/L). Defaults to mg/dL.\n * @returns True if the value is above the hyperglycemia threshold, otherwise false.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport function isHyper(val: number, unit: GlucoseUnit = MG_DL): boolean {\n  return unit === MG_DL\n    ? val > HYPER_THRESHOLD_MGDL\n    : val > HYPER_THRESHOLD_MMOLL\n}\n\n/**\n * Returns a glucose status label ('low', 'normal', or 'high') based on thresholds for the given unit.\n * @param val - Glucose value to label.\n * @param unit - Unit of measurement (mg/dL or mmol/L). Defaults to mg/dL.\n * @returns 'low' if below hypo threshold, 'high' if above hyper threshold, otherwise 'normal'.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-level-ranges.html\n */\nexport function getGlucoseLabel(\n  val: number,\n  unit: GlucoseUnit = MG_DL\n): 'low' | 'normal' | 'high' {\n  if (isHypo(val, unit)) return 'low'\n  if (isHyper(val, unit)) return 'high'\n  return 'normal'\n}\n\n/**\n * Parses a glucose string like \"100 mg/dL\" or \"5.5 mmol/L\" into a value and unit.\n * @param input - A string in the format \"value unit\" (e.g., \"100 mg/dL\").\n * @returns An object with numeric `value` and validated `unit`.\n * @throws {Error} If the input string is invalid or not in the expected format.\n * @example\n * parseGlucoseString(\"100 mg/dL\") // { value: 100, unit: \"mg/dL\" }\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport function parseGlucoseString(input: string): {\n  value: number\n  unit: GlucoseUnit\n} {\n  if (!isValidGlucoseString(input)) {\n    throw new Error(\n      'Invalid glucose string format. Use \"100 mg/dL\" or \"5.5 mmol/L\".'\n    )\n  }\n\n  const cleaned = input.trim().replace(/\\s+/g, ' ')\n  const match = cleaned.match(/^([\\d.]+) (mg\\/dL|mmol\\/L)$/i)\n  const [, rawValue, rawUnit] = match!\n\n  return {\n    value: parseFloat(rawValue),\n    unit: rawUnit.toLowerCase() === 'mg/dl' ? MG_DL : MMOL_L,\n  }\n}\n\n/**\n * Checks if a glucose value and unit are valid.\n * @param value - Glucose value to validate.\n * @param unit - Glucose unit to validate.\n * @returns True if the value is a positive finite number and the unit is supported, otherwise false.\n */\nexport function isValidGlucoseValue(value: unknown, unit: unknown): boolean {\n  return (\n    typeof value === 'number' &&\n    Number.isFinite(value) &&\n    value > 0 &&\n    (unit === MG_DL || unit === MMOL_L)\n  )\n}\n","// @file src/conversions.ts\n\nimport {\n  A1C_TO_EAG_MULTIPLIER,\n  A1C_TO_EAG_CONSTANT,\n  MGDL_MMOLL_CONVERSION,\n  MG_DL,\n  MMOL_L,\n} from './constants'\nimport { GlucoseUnit } from './types'\nimport type { EstimateGMIOptions } from './types'\nimport { isEstimateGMIOptions } from './guards'\nimport { parseGlucoseString } from './glucose'\n\n/**\n * Converts average glucose (mg/dL) to estimated A1C.\n * @param avgMgDl - Average glucose in mg/dL\n * @returns Estimated A1C value\n * @see https://www.cdc.gov/diabetes/managing/managing-blood-sugar/a1c.html\n */\nexport function estimateA1CFromAvgGlucose(avgMgDl: number): number {\n  return +((avgMgDl + A1C_TO_EAG_CONSTANT) / A1C_TO_EAG_MULTIPLIER).toFixed(2)\n}\n\n/**\n * Converts A1C value to estimated average glucose (mg/dL).\n * @param a1c - A1C value\n * @returns Estimated average glucose in mg/dL\n * @see https://www.cdc.gov/diabetes/managing/managing-blood-sugar/a1c.html\n */\nexport function estimateAvgGlucoseFromA1C(a1c: number): number {\n  return Math.round(a1c * A1C_TO_EAG_MULTIPLIER - A1C_TO_EAG_CONSTANT)\n}\n\n/**\n * Estimates eAG (estimated average glucose) from A1C.\n * @param a1c - A1C value\n * @returns Estimated average glucose (eAG) in mg/dL\n * @throws {Error} If a1c is negative.\n * @see https://www.cdc.gov/diabetes/managing/managing-blood-sugar/a1c.html\n */\nexport function estimateEAG(a1c: number): number {\n  if (a1c < 0) throw new Error('A1C must be positive')\n  const eAG = Number(\n    (a1c * A1C_TO_EAG_MULTIPLIER - A1C_TO_EAG_CONSTANT).toFixed(10)\n  )\n  return Math.round(eAG)\n}\n\n/**\n * Estimates A1C from average glucose.\n * @param avgGlucose - Average glucose value\n * @param unit - Glucose unit (mg/dL or mmol/L)\n * @returns Estimated A1C\n * @see https://www.cdc.gov/diabetes/managing/managing-blood-sugar/a1c.html\n */\nexport function estimateA1CFromAverage(\n  avgGlucose: number,\n  unit: GlucoseUnit = MG_DL\n): number {\n  const glucoseMgdl =\n    unit === MMOL_L ? avgGlucose * MGDL_MMOLL_CONVERSION : avgGlucose\n  return +((glucoseMgdl + 46.7) / 28.7).toFixed(2)\n}\n\n/**\n * Converts A1C to Glucose Management Indicator (GMI).\n * @param a1c - A1C value\n * @returns GMI value\n * @see https://diatribe.org/glucose-management-indicator-gmi\n */\nexport function a1cToGMI(a1c: number): number {\n  return +(3.31 + 0.02392 * a1c).toFixed(2)\n}\n\n/**\n * Estimate Glucose Management Indicator (GMI) from average glucose.\n * @param valueOrOptions - Glucose value, string, or options object\n * @param unit - Glucose unit (if value is a number)\n * @returns GMI value\n * @throws {Error} If unit is required but not provided when input is a number.\n * @throws {Error} If the glucose unit is unsupported.\n * @throws {Error} If the glucose value is not a positive number.\n * @see https://diatribe.org/glucose-management-indicator-gmi\n */\nexport function estimateGMI(\n  valueOrOptions: number | string | EstimateGMIOptions,\n  unit?: GlucoseUnit\n): number {\n  let value: number\n  let resolvedUnit: GlucoseUnit\n\n  if (isEstimateGMIOptions(valueOrOptions)) {\n    value = valueOrOptions.value\n    resolvedUnit = valueOrOptions.unit\n  } else if (typeof valueOrOptions === 'string') {\n    const parsed = parseGlucoseString(valueOrOptions)\n    value = parsed.value\n    resolvedUnit = parsed.unit\n  } else {\n    if (!unit) throw new Error('Unit is required when input is a number.')\n    value = valueOrOptions\n    resolvedUnit = unit\n  }\n\n  if (![MG_DL, MMOL_L].includes(resolvedUnit)) {\n    throw new Error(`Unsupported glucose unit: ${resolvedUnit}`)\n  }\n\n  if (value <= 0 || !Number.isFinite(value)) {\n    throw new Error('Glucose value must be a positive number.')\n  }\n\n  const gmi = resolvedUnit === MMOL_L ? 1.57 * value + 3.5 : 0.03 * value + 2.4\n\n  return parseFloat(gmi.toFixed(1))\n}\n\n/**\n * Converts mg/dL to mmol/L.\n * @param val - Glucose value in mg/dL\n * @returns Value in mmol/L\n * @throws {Error} If val is not a finite number or is negative/zero.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport function mgDlToMmolL(val: number): number {\n  if (!Number.isFinite(val) || val <= 0)\n    throw new Error('Invalid glucose value')\n  return +(val / MGDL_MMOLL_CONVERSION).toFixed(1)\n}\n\n/**\n * Converts mmol/L to mg/dL.\n * @param val - Glucose value in mmol/L\n * @returns Value in mg/dL\n * @throws {Error} If val is not a finite number or is negative/zero.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport function mmolLToMgDl(val: number): number {\n  if (!Number.isFinite(val) || val <= 0)\n    throw new Error('Invalid glucose value')\n  return Math.round(val * MGDL_MMOLL_CONVERSION)\n}\n\n/**\n * Converts glucose value between mg/dL and mmol/L.\n * @param value - Glucose value\n * @param unit - Current glucose unit\n * @returns Object with converted value and new unit\n * @throws {Error} If value is not a finite number or is negative/zero.\n * @throws {Error} If unit is not a supported glucose unit.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport function convertGlucoseUnit({\n  value,\n  unit,\n}: {\n  value: number\n  unit: GlucoseUnit\n}): { value: number; unit: GlucoseUnit } {\n  if (!Number.isFinite(value) || value <= 0)\n    throw new Error('Invalid glucose value')\n  if (![MG_DL, MMOL_L].includes(unit)) throw new Error('Invalid unit')\n  if (unit === MG_DL)\n    return {\n      value: Math.round((value / MGDL_MMOLL_CONVERSION) * 10) / 10,\n      unit: MMOL_L,\n    }\n  return {\n    value: Math.round(value * MGDL_MMOLL_CONVERSION),\n    unit: MG_DL,\n  }\n}\n","// @file src/formatters.ts\n\nimport { GlucoseUnit } from './types'\n\n/**\n * Formats a glucose value with its unit and optional rounding.\n * @param val - Glucose value to format.\n * @param unit - Unit of measurement (mg/dL or mmol/L).\n * @param options - Formatting options: number of digits and whether to include the unit suffix (default: { digits: 0, suffix: true }).\n * @returns Formatted glucose string, e.g., '5.5 mmol/L' or '120 mg/dL'.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport function formatGlucose(\n  val: number,\n  unit: GlucoseUnit,\n  options: { digits?: number; suffix?: boolean } = {}\n): string {\n  const digits = options.digits ?? 0\n  const suffix = options.suffix ?? true\n  const value = val.toFixed(digits)\n  return suffix ? `${value} ${unit}` : value\n}\n\n/**\n * Formats a number as a percentage string.\n * @param val - Value to format as a percentage (e.g., 0.85 or 85).\n * @param digits - Number of decimal places (default: 1).\n * @returns Formatted percentage string, e.g., '85.0%'.\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed\n */\nexport function formatPercentage(val: number, digits = 1): string {\n  return `${val.toFixed(digits)}%`\n}\n\n/**\n * Formats a UTC ISO timestamp to a local-readable string.\n * @param iso - ISO 8601 timestamp string (e.g., '2024-03-20T10:00:00Z').\n * @param timeZone - Optional IANA time zone name (e.g., 'America/New_York').\n * @returns Localized date and time string, e.g., 'Mar 20, 2024, 06:00 AM'.\n * @throws {RangeError} If the ISO string is invalid or cannot be parsed by Date.\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString\n * @see https://en.wikipedia.org/wiki/ISO_8601\n */\nexport function formatDate(iso: string, timeZone?: string): string {\n  if (isNaN(Date.parse(iso))) {\n    throw new RangeError('Invalid ISO timestamp')\n  }\n  return new Date(iso).toLocaleString('en-US', {\n    timeZone,\n    year: 'numeric',\n    month: 'short',\n    day: '2-digit',\n    hour: '2-digit',\n    minute: '2-digit',\n  })\n}\n","// @file src/tir.ts\n\nimport { GlucoseReading, TIRResult } from './types'\n\n/**\n * Calculates the percentage of time glucose readings are in, below, and above a target range.\n * @param readings - Array of glucose readings to analyze.\n * @param target - Object specifying the target range with { min, max } values.\n * @returns An object with the percentage of readings in range, below range, and above range.\n * @see https://care.diabetesjournals.org/content/42/8/1593\n */\nexport function calculateTIR(\n  readings: GlucoseReading[],\n  target: { min: number; max: number }\n): TIRResult {\n  if (readings.length === 0) {\n    return {\n      inRange: 0.0,\n      belowRange: 0.0,\n      aboveRange: 0.0,\n    }\n  }\n\n  let inRange = 0\n  let belowRange = 0\n  let aboveRange = 0\n\n  for (const r of readings) {\n    if (r.value < target.min) belowRange++\n    else if (r.value > target.max) aboveRange++\n    else inRange++\n  }\n\n  const total = readings.length\n  return {\n    inRange: +((inRange / total) * 100).toFixed(1),\n    belowRange: +((belowRange / total) * 100).toFixed(1),\n    aboveRange: +((aboveRange / total) * 100).toFixed(1),\n  }\n}\n\n/**\n * Generates a human-readable summary string from a TIRResult object.\n * @param result - The TIR result breakdown to summarize.\n * @returns A string summarizing the in-range, below-range, and above-range percentages (e.g., 'In Range: 70%, Below: 10%, Above: 20%').\n */\nexport function getTIRSummary(result: TIRResult): string {\n  return `In Range: ${result.inRange}%, Below: ${result.belowRange}%, Above: ${result.aboveRange}%`\n}\n\n/**\n * Groups glucose readings by date (YYYY-MM-DD).\n * @param readings - Array of glucose readings to group.\n * @returns An object mapping each date string to an array of readings for that day.\n */\nexport function groupByDay(\n  readings: GlucoseReading[]\n): Record<string, GlucoseReading[]> {\n  return readings.reduce((acc, reading) => {\n    const day = reading.timestamp.split('T')[0]\n    acc[day] = acc[day] || []\n    acc[day].push(reading)\n    return acc\n  }, {} as Record<string, GlucoseReading[]>)\n}\n\n/**\n * Calculates the percentage of readings within a specified numeric range.\n * @param readings - Array of glucose values (numbers) to analyze.\n * @param lower - Lower bound of the target range (inclusive).\n * @param upper - Upper bound of the target range (inclusive).\n * @returns The percentage of readings within the specified range (0-100).\n */\nexport function calculateTimeInRange(\n  readings: number[],\n  lower: number,\n  upper: number\n): number {\n  if (readings.length === 0) {\n    return 0\n  }\n\n  const inRange = readings.filter((r) => r >= lower && r <= upper).length\n  return (inRange / readings.length) * 100\n}\n","// @file src/types.ts\n\nimport { MG_DL, MMOL_L } from './constants'\n\n/**\n * Supported glucose units.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport type GlucoseUnit = typeof MG_DL | typeof MMOL_L\n\n/**\n * All allowed glucose units.\n * @see https://www.diabetes.co.uk/diabetes_care/blood-sugar-conversion.html\n */\nexport const AllowedGlucoseUnits: GlucoseUnit[] = [MG_DL, MMOL_L]\n\n/**\n * Represents a single glucose reading with value, unit, and timestamp.\n * @see https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7445493/\n */\nexport interface GlucoseReading {\n  readonly value: number\n  readonly unit: GlucoseUnit\n  readonly timestamp: string // ISO 8601\n}\n\n/**\n * Result of a Time-in-Range (TIR) calculation.\n * @see https://care.diabetesjournals.org/content/42/8/1593\n */\nexport interface TIRResult {\n  inRange: number\n  belowRange: number\n  aboveRange: number\n}\n\n/**\n * Options for estimating GMI (Glucose Management Indicator).\n * @see https://diatribe.org/glucose-management-indicator-gmi\n */\nexport interface EstimateGMIOptions {\n  value: number\n  unit: GlucoseUnit\n}\n\n/**\n * Options for Time-in-Range (TIR) calculations.\n */\nexport interface TIROptions {\n  readings: GlucoseReading[]\n  unit: GlucoseUnit\n  range: [number, number]\n}\n\n/**\n * Represents a single A1C reading with value and date.\n */\nexport interface A1CReading {\n  value: number\n  date: string // ISO 8601 date string\n}\n\n/**\n * Options for calculating glucose statistics.\n */\nexport interface GlucoseStatsOptions {\n  readings: GlucoseReading[]\n  unit: GlucoseUnit\n  range: [number, number]\n  gmi?: boolean\n  a1c?: boolean\n  tir?: boolean\n  tirRange?: [number, number]\n  tirPercent?: boolean\n  tirPercentBelow?: boolean\n  tirPercentAbove?: boolean\n  tirPercentInRange?: boolean\n  tirPercentBelowRounded?: boolean\n  tirPercentAboveRounded?: boolean\n  tirPercentInRangeRounded?: boolean\n}\n"]}