diff --git a/src/lib/measurements/__tests__/import-apple-health-export.test.ts b/src/lib/measurements/__tests__/import-apple-health-export.test.ts
index c63637ec9..607a332b2 100644
--- a/src/lib/measurements/__tests__/import-apple-health-export.test.ts
+++ b/src/lib/measurements/__tests__/import-apple-health-export.test.ts
@@ -1297,6 +1297,38 @@ describe("streamParseExportXml — the record's own unit (issue #944)", () => {
expect(prisma._measurements[0].value).toBeCloseTo(2484, 6);
});
+ it("stores a kilojoule workout burn in kilocalories", async () => {
+ const tmp = mkdtempSync(join(tmpdir(), "healthlog-parser-energy-test-"));
+ const xmlPath = join(tmp, "export.xml");
+ writeFileSync(
+ xmlPath,
+ `
+
+
+`,
+ );
+ const prisma = makeFakePrisma();
+ await streamParseExportXml({
+ xmlPath,
+ userId: "user-energy",
+ userTimezone: "Europe/Berlin",
+ prisma: prisma as unknown as PrismaClient,
+ });
+
+ expect(prisma._workouts).toHaveLength(1);
+ expect(prisma._workouts[0].totalDistanceM).toBeCloseTo(6500, 6);
+ expect(prisma._workouts[0].totalEnergyKcal).toBeCloseTo(412.0458891, 6);
+ });
+
it("stores a mile-scale walking distance in metres", async () => {
const { prisma } = await importCumulativeFixture(
cumulativeExportXml(
diff --git a/src/lib/measurements/import-apple-health-export.ts b/src/lib/measurements/import-apple-health-export.ts
index 4a4bb9229..ea14ad8fe 100644
--- a/src/lib/measurements/import-apple-health-export.ts
+++ b/src/lib/measurements/import-apple-health-export.ts
@@ -42,7 +42,10 @@ import {
} from "@/lib/measurements/drain-per-sample-cumulative";
import { reconcileExternalMeasurement } from "@/lib/measurements/reconcile-external-measurement";
import { resolveHkWorkoutSportType } from "@/lib/measurements/hk-workout-activity-type-map";
-import { hkDistanceToMetres } from "@/lib/measurements/hk-units";
+import {
+ convertHkValue,
+ hkDistanceToMetres,
+} from "@/lib/measurements/hk-units";
import { emitInsertedMeasurementArrivals } from "@/lib/arrivals/measurement-emit";
import { maybeEnqueueMorningRefresh } from "@/lib/daily/morning-refresh-trigger";
import { emitDataArrival } from "@/lib/arrivals/emit-shared";
@@ -948,6 +951,7 @@ export async function streamParseExportXml(
const totalDistance = Number.parseFloat(attrs.totalDistance ?? "");
const totalEnergy = Number.parseFloat(attrs.totalEnergyBurned ?? "");
const distanceUnit = attrs.totalDistanceUnit ?? "";
+ const energyUnit = attrs.totalEnergyBurnedUnit ?? "";
// Apple ships the ACCOUNT's own length unit on HKWorkout.totalDistance
// (km for a metric account, mi for an imperial one); metres is the
// canonical DB unit. issue #944 — the same conversion the ``
@@ -969,7 +973,13 @@ export async function streamParseExportXml(
startedAt: startDate,
endedAt: endDate,
durationSec,
- totalEnergyKcal: Number.isFinite(totalEnergy) ? totalEnergy : null,
+ totalEnergyKcal: Number.isFinite(totalEnergy)
+ ? // issue #944 — the energy attribute carries the account's own
+ // unit too: a Health app set to kilojoules writes `kJ` here, and
+ // the column is kilocalories. Same shared conversion; an unknown
+ // unit leaves the number alone.
+ (convertHkValue(totalEnergy, energyUnit, "kcal") ?? totalEnergy)
+ : null,
totalDistanceM: distanceM,
externalId,
externalSourceVersion: attrs.sourceVersion ?? null,