Fitbit Air: Health Connect sync + vitals dashboard
All checks were successful
Build & Release APK / build (push) Successful in 2m5s
All checks were successful
Build & Release APK / build (push) Successful in 2m5s
- react-native-health-connect (config plugin, minSdk 26, 11 read perms) - sync-on-open + manual sync: incremental via server watermark, chunked POST to /api/health/v1/fitbit/sync (HR, resting HR, HRV, SpO2, resp rate, steps/distance/kcal/floors dailies, staged sleep sessions) - Fitbit screen: steps/RHR/HRV/SpO2 tiles + daily charts + sleep list + per-stream record counts (answers the which-types-does-Google-Health- share question empirically) - Sleep tab now merges Fitbit Air nights (wins over older sources) - Today screen Fitbit card Server side (deployed separately on VPS): hc_samples/hc_daily/hc_sleep tables + sync/latest/summary endpoints in health-api; Caddy routes POST /api/health/v1/fitbit/* behind the same basic_auth. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,17 @@ import type {
|
||||
Week, Workout,
|
||||
} from './types';
|
||||
|
||||
export function shapeSleep(api: SleepApiResponse, sc: SleepCycleExport): Night[] {
|
||||
export interface HcSleepRow {
|
||||
id: string; started_at: string; ended_at: string;
|
||||
total_seconds: number; sleep_seconds: number;
|
||||
deep_seconds: number; rem_seconds: number; light_seconds: number; awake_seconds: number;
|
||||
}
|
||||
|
||||
export function shapeSleep(
|
||||
api: SleepApiResponse,
|
||||
sc: SleepCycleExport,
|
||||
fitbit: HcSleepRow[] = [],
|
||||
): Night[] {
|
||||
const byDate = new Map<string, Night>();
|
||||
(sc.sessions || []).forEach((r) => {
|
||||
const o: Record<string, any> = Object.fromEntries(sc.fields.map((f, i) => [f, r[i]]));
|
||||
@@ -39,6 +49,23 @@ export function shapeSleep(api: SleepApiResponse, sc: SleepCycleExport): Night[]
|
||||
src: 'Eight Sleep',
|
||||
});
|
||||
});
|
||||
// Fitbit Air wins where sources overlap — it's the current device.
|
||||
fitbit.forEach((f) => {
|
||||
const date = f.ended_at.slice(0, 10);
|
||||
byDate.set(date, {
|
||||
date,
|
||||
total: f.total_seconds,
|
||||
asleep: f.sleep_seconds,
|
||||
deep: f.deep_seconds,
|
||||
rem: f.rem_seconds,
|
||||
light: f.light_seconds,
|
||||
awake: f.awake_seconds,
|
||||
quality: null,
|
||||
hrv: null,
|
||||
hr: null,
|
||||
src: 'Fitbit Air',
|
||||
});
|
||||
});
|
||||
return [...byDate.values()].sort((a, b) => (a.date < b.date ? -1 : 1));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user