Read Apple Health liveness off the data, not only the batch stamp

The status verdict looked only at the user's last-batch column, which
moves on a live batch alone. Rows that reached the account another way,
such as the export.zip import, left it null, and the card said 'waiting
for first data' beside a four-digit record count. The verdict now takes
the newest of that stamp, the per-type arrival ledger and the newest
stored sample.
This commit is contained in:
Marc-André Bombeck committed 2026-10-08 18:18:46 +02:00
1 parent ac482c508d
commit f7b6ea5dad
3 files changed
+78 -15

No files matched your search

@@ -131,6 +131,32 @@ export async function getHealthKitTypeArrivals(
return out;
}
/**
* The newest instant Apple Health data is known to have reached the account:
* the live-batch stamp, the per-type arrival ledger, or — for rows that came
* any other way, such as the export.zip import — the newest stored sample.
* Null only when none of the three holds anything, which is the one case the
* status may honestly call "waiting for first data".
*/
export function newestAppleHealthDataAt(
lastSyncedAt: string | null,
arrivals: Map<string, HealthKitTypeArrivalRecord> | null,
samples: readonly { lastSeenAt: string }[],
): string | null {
let newest: number | null = null;
const consider = (value: string | null | undefined) => {
if (!value) return;
const ms = Date.parse(value);
if (Number.isFinite(ms) && (newest === null || ms > newest)) newest = ms;
};
consider(lastSyncedAt);
for (const arrival of arrivals?.values() ?? []) {
consider(arrival.lastReceivedAt);
}
for (const sample of samples) consider(sample.lastSeenAt);
return newest === null ? null : new Date(newest).toISOString();
}
/** A freshness entry with the arrival facts beside the newest sample. */
export interface HealthKitFreshnessEntry extends MetricFreshnessEntry {
/** When a live sync last carried this type; null when none has. */