Skip to content

fix(backend): convert Health Connect blood glucose from mmol/L to mg/dL#1123

Open
knowald wants to merge 3 commits into
the-momentum:mainfrom
knowald:fix/hc-blood-glucose-unit
Open

fix(backend): convert Health Connect blood glucose from mmol/L to mg/dL#1123
knowald wants to merge 3 commits into
the-momentum:mainfrom
knowald:fix/hc-blood-glucose-unit

Conversation

@knowald

@knowald knowald commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Description

Blood glucose values synced from Android via Health Connect arrive in mmol/L (Health Connect's canonical unit for BloodGlucoseRecord), but _build_statistic_bundles stored them verbatim while the blood_glucose series unit is mg_dl. A 110 mg/dL reading was stored as 6.111.

The conversion is gated on the unit string the SDK already sends (unit starts with mmol, case-insensitive), so SDK paths that send mg/dL (the iOS SDK reads glucose with a fixed mg/dL HKUnit) and any other source already sending mg/dL are unaffected. The factor lives in a module constant MMOL_L_TO_MG_DL = Decimal("18.0182").

Tests live in TestSDKImportUnitConversion next to the existing body-fat/height conversion tests: parametrized over mmol/L / MMOL/L (converted, exact Decimal("110.1092202")) and None / "" (stored unchanged), plus a HealthKit mg/dL passthrough test.

Resolves #1119

Notes

  • Conversion factor 18.0182 mg/dL per mmol/L - the standard clinical convention (inverse of the rounded 0.0555 mmol/L-per-mg/dL factor). Health Connect's own internal mg/dL converter uses exactly 18.0, so values written to HC in mg/dL round-trip with a ~0.1% offset under this factor (max ~0.25 mg/dL at 250 mg/dL). Noted in a code comment.
  • Scope: this fixes the mobile SDK ingestion path only. The Apple Health XML export path (apple_xml/xml_service.py) has the same class of bug for users whose Health app displays glucose in mmol/L (Apple writes the export in the display unit, e.g. unit="mmol<180.15588...>/L"); that path never reads the record's unit attribute. Filed separately so it can be fixed for all affected series types at once rather than special-cased here.
  • This touches the same block as fix: convert walking units from healthkit #1114 and the same territory as feat(backend): centralized unit conversion service (concept for #424) #1120. If fix: convert walking units from healthkit #1114 lands first I will move this case into its _normalize_unit helper (it then needs the record's unit passed in). For feat(backend): centralized unit conversion service (concept for #424) #1120: its registry needs a (blood_glucose, mmol/L) entry plus unit-token normalization, since it currently skips samples with unrecognized units.
  • Existing data: mmol-stored rows are not migrated here, and value-range heuristics are not safe for glucose (genuine severe-hypoglycemia readings below 30 mg/dL exist, and mmol values can be missed above range cutoffs). The reliable repair path is re-deriving the affected samples from stored raw payloads (store_raw_payload keeps the original records incl. the unit field), matching on external_id. Happy to add that migration on request, here or as a follow-up.

Checklist

General

  • My code follows the project's code style
  • I have performed a self-review of my code
  • I have added tests that prove my fix/feature works (if applicable)
  • New and existing tests pass locally
  • I have updated relevant documentation in docs/ (or no docs update needed)

Backend Changes

You have to be in backend directory to make it work:

  • uv run pre-commit run --all-files passes (ruff + ty pass; prettier hook needs frontend node_modules, no frontend files touched)

Testing Instructions

Steps to test:

  1. cd backend && uv run pytest tests/integrations/test_apple_sdk_import.py
  2. Sync a Health Connect payload containing a BLOOD_GLUCOSE record with unit: "mmol/L" and inspect the stored sample.

Expected behavior:

Stored blood_glucose value is in mg/dL (6.111 mmol/L -> 110.1092202); HealthKit mg/dL values unchanged.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added automatic unit conversion for blood glucose measurements during health data imports. Values are now consistently converted to mg/dL regardless of the source unit format.
  • Tests

    • Expanded test coverage to verify blood glucose import and unit conversion functionality works correctly across different input formats.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fa2b403e-3200-4431-bc5f-0e0a5d1d8184

📥 Commits

Reviewing files that changed from the base of the PR and between 9b962ea and 923eb99.

📒 Files selected for processing (1)
  • backend/tests/integrations/test_apple_sdk_import.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/integrations/test_apple_sdk_import.py

📝 Walkthrough

Walkthrough

Detects incoming blood_glucose records whose record.unit starts with "mmol" (case-insensitive) and multiplies their numeric value by 18.0182 to store mg/dL; tests updated to cover Health Connect conversion and HealthKit pass-through.

Changes

Blood Glucose Unit Conversion

Layer / File(s) Summary
Conversion implementation
backend/app/services/apple/healthkit/import_service.py
Adds MMOL_L_TO_MG_DL = Decimal("18.0182") and scales SeriesType.blood_glucose values when rjson.unit starts with "mmol" (case-insensitive) in _build_statistic_bundles.
Tests: unit handling and helper update
backend/tests/integrations/test_apple_sdk_import.py
Extends _record helper to accept unit and adds tests that assert Health Connect (samsung) mmol/L inputs are converted to mg/dL and that HealthKit (apple) mg/dL inputs are stored unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • KaliszS
  • bartmichalak
  • czajkub

Poem

🐰 I hopped through records, sniffed the unit line,
mmol whispered softly, "convert me fine."
Multiply by eighteen and a tad more,
mg/dL settles at the data's core.
Hooray — sweet numbers hop back to shore.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions converting Health Connect blood glucose from mmol/L to mg/dL, which aligns with the primary change detected in the code: adding unit conversion logic for blood glucose records in the import service.
Linked Issues check ✅ Passed The PR implements option A from issue #1119: detects mmol/L units and converts blood glucose values to mg/dL using the factor 18.0182, with comprehensive test coverage for both Health Connect and HealthKit paths.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the blood glucose unit conversion for Health Connect and adding appropriate test coverage; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/app/services/apple/healthkit/import_service.py (1)

140-143: ⚡ Quick win

Move conversion factor to module-level constant.

The Decimal("18.0182") conversion factor is allocated on every call to _build_statistic_bundles, which processes all incoming records. Based on learnings, constant data structures should be defined at module or class level to avoid repetitive allocations in hot paths.

♻️ Suggested refactor

At module level (e.g., after imports, around line 40):

+# Unit conversion constants
+MMOL_L_TO_MG_DL = Decimal("18.0182")  # Blood glucose conversion factor
+
 class ImportService:

Then in the conversion logic:

         # Health Connect reports blood glucose in mmol/L; the series unit is mg/dL.
         if series_type == SeriesType.blood_glucose and (rjson.unit or "").lower().startswith("mmol"):
-            value = value * Decimal("18.0182")
+            value = value * MMOL_L_TO_MG_DL
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/services/apple/healthkit/import_service.py` around lines 140 -
143, Move the hard-coded conversion factor Decimal("18.0182") to a module-level
constant and use that constant inside the _build_statistic_bundles function:
define a descriptive constant (e.g., BLOOD_GLUCOSE_MGDL_PER_MMOLL) near the top
of the module and replace the inline Decimal("18.0182") in the
SeriesType.blood_glucose conversion branch (the block that checks (rjson.unit or
"").lower().startswith("mmol")) to reference the new constant so the Decimal is
allocated once at import time rather than on every call.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/tests/integrations/test_apple_sdk_import.py`:
- Around line 376-403: The test payload in
test_healthkit_blood_glucose_stored_unchanged is missing the "source" object
which causes import_service._build_statistic_bundles to call
extract_device_info(rjson.source) with None; update the test record to include a
minimal "source" object (matching other tests) such as {"name": "...",
"bundleIdentifier": "..."} so extract_device_info receives a populated source
and the test no longer fails due to a missing attribute.

---

Nitpick comments:
In `@backend/app/services/apple/healthkit/import_service.py`:
- Around line 140-143: Move the hard-coded conversion factor Decimal("18.0182")
to a module-level constant and use that constant inside the
_build_statistic_bundles function: define a descriptive constant (e.g.,
BLOOD_GLUCOSE_MGDL_PER_MMOLL) near the top of the module and replace the inline
Decimal("18.0182") in the SeriesType.blood_glucose conversion branch (the block
that checks (rjson.unit or "").lower().startswith("mmol")) to reference the new
constant so the Decimal is allocated once at import time rather than on every
call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 86bcf0d5-40d1-4ab9-ac46-2e83aa0c74dd

📥 Commits

Reviewing files that changed from the base of the PR and between 85bb567 and fcd33f5.

📒 Files selected for processing (2)
  • backend/app/services/apple/healthkit/import_service.py
  • backend/tests/integrations/test_apple_sdk_import.py

Comment thread backend/tests/integrations/test_apple_sdk_import.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/services/apple/healthkit/import_service.py`:
- Line 42: Add an explicit type annotation for the module-level constant
MMOL_L_TO_MG_DL: declare it as a Decimal (e.g., MMOL_L_TO_MG_DL: Decimal =
Decimal("18.0182")) so static type checkers and IDEs recognize its type; update
the import if Decimal is not already imported and ensure the constant remains
unchanged otherwise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0b0447ea-ddf4-46a7-80c0-69f88e5b90f0

📥 Commits

Reviewing files that changed from the base of the PR and between fcd33f5 and 9b962ea.

📒 Files selected for processing (2)
  • backend/app/services/apple/healthkit/import_service.py
  • backend/tests/integrations/test_apple_sdk_import.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/integrations/test_apple_sdk_import.py

Comment thread backend/app/services/apple/healthkit/import_service.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(backend): blood_glucose values from Health Connect stored in mmol/L but unit says mg_dl

1 participant