Skip to content

Importing a Fahrenheit WHOOP export stores the °F number as if it were °C #1849

Open

Description

@ryanbr

Both importers treat skin_temp_f as a plain alias of skin_temp_celsius and store the value unconverted:

// android WhoopCsvImporter.kt:349
val skinTemp = row.double("skin_temp_celsius", "skin_temp_f")
// Packages/StrandImport/.../WhoopExportImporter.swift:271
r.skinTempCelsius  = row.double("skin_temp_celsius", "skin_temp_f")

Byte-identical on both platforms, so parity holds — both are wrong the same way.

An export from a Fahrenheit WHOOP account therefore lands ~92.3 in a column the whole app reads as °C.

Why it doesn't just look slightly off

VitalBands.isAbsoluteSkinTemp(v) = v >= 20.0 classifies 92.3 as an absolute reading, so it flows through the normal absolute path rather than being rejected:

  • displayed as 92.3 °C — or, for a user on Fahrenheit, converted again to about 198.1 °F;
  • inside the 33–36 °C absolute band's history, dragging the baseline it folds;
  • indistinguishable after the fact from a genuine reading, because nothing records which unit the row arrived in.

The deviation case is not affected: skin_temp_f only ever appears on the absolute column.

Shape of the fix

Convert on the way in rather than aliasing — read skin_temp_f as its own key and apply (f - 32) * 5/9 before storing. The pair of importers must move together to keep the stored value byte-identical across platforms.

Two things to decide, which is why this is an issue and not a drive-by patch:

  1. Already-imported rows. A stored 92.3 is not distinguishable from a real reading by inspection alone, though anything above the worn gate's 42 °C ceiling is physically impossible and could be healed with an explicit one-off migration. Worth deciding deliberately rather than silently rewriting stored data.
  2. Which other columns share the problem. skin_temp_f was found while checking the display path for health: say when skin temp could not honour the Settings choice (#1847) #1847; the same alias pattern should be swept for every imperial-suffixed column in both importers before fixing just this one.

Found while verifying that the #1844/#1846 skin-temp work handles Fahrenheit — the display path does, correctly (absolute uses the full C→F, a deviation uses the factor alone, both pinned by tests). This is an ingest bug that predates that work.

Activity

  1. evoveotech commented on Sep 10, 2026

    @evoveotech

    Verified fixed: WhoopExportImporter now reads skin_temp_f and converts to Celsius on import. Explicit #1849 comment in Packages/StrandImport/Sources/StrandImport/WhoopExportImporter.swift lines 271-279. Android WhoopCsvImporter.kt also handles Fahrenheit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions