feat: consolidate datastore into sleep, nutrition, mila databases
This commit is contained in:
@@ -173,12 +173,14 @@ class GarminMirrorWriter:
|
||||
"""Manages month-partitioned JSONL files with idempotent upserts."""
|
||||
|
||||
def __init__(self, data_dir: Path):
|
||||
self.data_dir = data_dir
|
||||
self.garmin_dir = data_dir / "garmin"
|
||||
self.daily_dir = self.garmin_dir / "daily"
|
||||
self.sleep_dir = self.garmin_dir / "sleep"
|
||||
self.unified_sleep_dir = data_dir / "sleep"
|
||||
self.activities_dir = self.garmin_dir / "activities"
|
||||
|
||||
for d in [self.daily_dir, self.sleep_dir, self.activities_dir]:
|
||||
for d in [self.daily_dir, self.sleep_dir, self.unified_sleep_dir, self.activities_dir]:
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _read_existing(self, file_path: Path) -> List[Dict[str, Any]]:
|
||||
@@ -230,8 +232,9 @@ class GarminMirrorWriter:
|
||||
|
||||
def upsert_sleep_data(self, sleep_data: Dict[str, Any]):
|
||||
cal_date = None
|
||||
if "dailySleepDTO" in sleep_data and isinstance(sleep_data["dailySleepDTO"], dict):
|
||||
cal_date = sleep_data["dailySleepDTO"].get("calendarDate")
|
||||
dto = sleep_data.get("dailySleepDTO") if isinstance(sleep_data.get("dailySleepDTO"), dict) else {}
|
||||
if dto:
|
||||
cal_date = dto.get("calendarDate")
|
||||
if not cal_date:
|
||||
cal_date = sleep_data.get("calendarDate")
|
||||
|
||||
@@ -267,7 +270,82 @@ class GarminMirrorWriter:
|
||||
|
||||
new_items.sort(key=_sort_key)
|
||||
self._write_atomic(target_file, new_items)
|
||||
logger.info(f"Upserted sleep data for {cal_date} into {target_file.name}")
|
||||
|
||||
# Also upsert into unified data/sleep/<YYYY-MM>.jsonl
|
||||
start_gmt = dto.get("sleepStartTimestampGMT")
|
||||
end_gmt = dto.get("sleepEndTimestampGMT")
|
||||
if start_gmt:
|
||||
start_dt = datetime.datetime.fromtimestamp(start_gmt / 1000, datetime.timezone.utc)
|
||||
start_iso = start_dt.isoformat()
|
||||
else:
|
||||
start_iso = f"{cal_date}T22:30:00+02:00"
|
||||
|
||||
if end_gmt:
|
||||
end_dt = datetime.datetime.fromtimestamp(end_gmt / 1000, datetime.timezone.utc)
|
||||
end_iso = end_dt.isoformat()
|
||||
else:
|
||||
end_iso = f"{cal_date}T06:30:00+02:00"
|
||||
|
||||
nap_sec = dto.get("napTimeSeconds") or 0
|
||||
sleep_sec = dto.get("sleepTimeSeconds") or 0
|
||||
sleep_type = "nap" if (nap_sec > 0 and sleep_sec == 0) else "night"
|
||||
|
||||
unified_rec = {
|
||||
"id": str(dto.get("id") or (int(start_gmt) if start_gmt else cal_date)),
|
||||
"subject": "moritz",
|
||||
"calendarDate": cal_date,
|
||||
"sleepType": sleep_type,
|
||||
"location": "bett",
|
||||
"isOpen": False,
|
||||
"startIso": start_iso,
|
||||
"endIso": end_iso,
|
||||
"sleepStartTimestampGMT": dto.get("sleepStartTimestampGMT"),
|
||||
"sleepEndTimestampGMT": dto.get("sleepEndTimestampGMT"),
|
||||
"sleepStartTimestampLocal": dto.get("sleepStartTimestampLocal"),
|
||||
"sleepEndTimestampLocal": dto.get("sleepEndTimestampLocal"),
|
||||
"sleepTimeSeconds": dto.get("sleepTimeSeconds"),
|
||||
"napTimeSeconds": nap_sec,
|
||||
"deepSleepSeconds": dto.get("deepSleepSeconds"),
|
||||
"lightSleepSeconds": dto.get("lightSleepSeconds"),
|
||||
"remSleepSeconds": dto.get("remSleepSeconds"),
|
||||
"awakeSleepSeconds": dto.get("awakeSleepSeconds"),
|
||||
"awakeCount": dto.get("awakeCount"),
|
||||
"avgSleepStress": dto.get("avgSleepStress"),
|
||||
"avgHeartRate": dto.get("avgHeartRate"),
|
||||
"averageSpO2Value": dto.get("averageSpO2Value"),
|
||||
"lowestSpO2Value": dto.get("lowestSpO2Value"),
|
||||
"highestSpO2Value": dto.get("highestSpO2Value"),
|
||||
"averageRespirationValue": dto.get("averageRespirationValue"),
|
||||
"sleepScores": dto.get("sleepScores"),
|
||||
"avgOvernightHrv": sleep_data.get("avgOvernightHrv"),
|
||||
"hrvStatus": sleep_data.get("hrvStatus"),
|
||||
"bodyBatteryChange": sleep_data.get("bodyBatteryChange"),
|
||||
"restingHeartRate": sleep_data.get("restingHeartRate"),
|
||||
"restlessMomentsCount": sleep_data.get("restlessMomentsCount"),
|
||||
"note": None,
|
||||
"actor": "garmin_sync",
|
||||
"created_at": start_iso,
|
||||
"updated_at": None,
|
||||
"deleted_at": None,
|
||||
}
|
||||
|
||||
unified_file = self.unified_sleep_dir / f"{month}.jsonl"
|
||||
existing_unified = self._read_existing(unified_file)
|
||||
updated_u = False
|
||||
new_unified = []
|
||||
for item in existing_unified:
|
||||
if item.get("subject") == "moritz" and item.get("calendarDate") == cal_date:
|
||||
new_unified.append(unified_rec)
|
||||
updated_u = True
|
||||
else:
|
||||
new_unified.append(item)
|
||||
|
||||
if not updated_u:
|
||||
new_unified.append(unified_rec)
|
||||
|
||||
new_unified.sort(key=lambda x: (str(x.get("calendarDate", "")), str(x.get("startIso", "")), str(x.get("id", ""))))
|
||||
self._write_atomic(unified_file, new_unified)
|
||||
logger.info(f"Upserted sleep data for {cal_date} into {target_file.name} and {unified_file.name}")
|
||||
|
||||
def upsert_activities(self, activities: List[Dict[str, Any]]):
|
||||
if not activities:
|
||||
|
||||
Reference in New Issue
Block a user