fix(datastore): include morning-ending night sleep in dashboard, pass data-dir, and auto git-sync on writes
This commit is contained in:
@@ -295,9 +295,9 @@ class DatastoreEngine:
|
|||||||
import subprocess, shutil
|
import subprocess, shutil
|
||||||
uv_bin = shutil.which("uv") or "/home/node/.openclaw/bin/uv"
|
uv_bin = shutil.which("uv") or "/home/node/.openclaw/bin/uv"
|
||||||
if Path(uv_bin).is_file():
|
if Path(uv_bin).is_file():
|
||||||
cmd = [str(uv_bin), "run", str(update_script)]
|
cmd = [str(uv_bin), "run", str(update_script), "--data-dir", str(self.data_dir)]
|
||||||
else:
|
else:
|
||||||
cmd = [sys.executable, str(update_script)]
|
cmd = [sys.executable, str(update_script), "--data-dir", str(self.data_dir)]
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
cmd,
|
cmd,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
@@ -306,6 +306,19 @@ class DatastoreEngine:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Could not trigger {script_name}: {e}")
|
logger.warning(f"Could not trigger {script_name}: {e}")
|
||||||
|
|
||||||
|
def _git_sync(self, commit_msg: str):
|
||||||
|
"""Safely commits and pushes data changes if data_dir is inside a git repository."""
|
||||||
|
try:
|
||||||
|
repo_dir = self.data_dir.parent if (self.data_dir.parent / ".git").is_dir() else self.data_dir
|
||||||
|
if not (repo_dir / ".git").is_dir():
|
||||||
|
return
|
||||||
|
subprocess.run(["git", "-C", str(repo_dir), "add", str(self.data_dir)], check=False, capture_output=True)
|
||||||
|
res = subprocess.run(["git", "-C", str(repo_dir), "commit", "-m", f"{commit_msg} [skip ci]"], check=False, capture_output=True)
|
||||||
|
if res.returncode == 0:
|
||||||
|
subprocess.run(["git", "-C", str(repo_dir), "push", "origin", "main"], check=False, capture_output=True, timeout=10)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Git auto-sync skipped/failed: {e}")
|
||||||
|
|
||||||
def _load_jsonl_file(self, file_path: Path, table_name: str):
|
def _load_jsonl_file(self, file_path: Path, table_name: str):
|
||||||
"""Reads lines from JSONL and inserts into target table."""
|
"""Reads lines from JSONL and inserts into target table."""
|
||||||
if not file_path.is_file() or file_path.stat().st_size == 0:
|
if not file_path.is_file() or file_path.stat().st_size == 0:
|
||||||
@@ -607,6 +620,7 @@ class DatastoreEngine:
|
|||||||
feedback["day_total_formatted"] = open_warning
|
feedback["day_total_formatted"] = open_warning
|
||||||
|
|
||||||
self._trigger_website_update(subject)
|
self._trigger_website_update(subject)
|
||||||
|
self._git_sync(f"chore(data): log {kind} for {subject}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
@@ -689,6 +703,7 @@ class DatastoreEngine:
|
|||||||
duration_minutes = int((end_dt.astimezone(BERLIN_TZ) - start_dt.astimezone(BERLIN_TZ)).total_seconds() / 60)
|
duration_minutes = int((end_dt.astimezone(BERLIN_TZ) - start_dt.astimezone(BERLIN_TZ)).total_seconds() / 60)
|
||||||
|
|
||||||
self._trigger_website_update(subject)
|
self._trigger_website_update(subject)
|
||||||
|
self._git_sync(f"chore(data): stop {kind} for {subject}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "success",
|
"status": "success",
|
||||||
@@ -718,6 +733,7 @@ class DatastoreEngine:
|
|||||||
self._update_jsonl_record(domain, event_id, {"deleted_at": now_iso})
|
self._update_jsonl_record(domain, event_id, {"deleted_at": now_iso})
|
||||||
|
|
||||||
self._trigger_website_update(subject)
|
self._trigger_website_update(subject)
|
||||||
|
self._git_sync(f"chore(data): delete event {event_id} ({kind}) for {subject}")
|
||||||
|
|
||||||
return {"status": "success", "deleted_event_id": event_id}
|
return {"status": "success", "deleted_event_id": event_id}
|
||||||
|
|
||||||
@@ -779,6 +795,8 @@ class DatastoreEngine:
|
|||||||
values,
|
values,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._git_sync(f"chore(data): update entity {collection}/{entity_id}")
|
||||||
|
|
||||||
return {"status": "success", "collection": collection, "id": entity_id}
|
return {"status": "success", "collection": collection, "id": entity_id}
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -151,7 +151,10 @@ def generate_dashboard_html(engine: DatastoreEngine) -> str:
|
|||||||
CASE WHEN is_open THEN NULL ELSE strftime(local_end, '%H:%M') END AS end_fmt,
|
CASE WHEN is_open THEN NULL ELSE strftime(local_end, '%H:%M') END AS end_fmt,
|
||||||
sleep_type, location, note, duration_minutes, is_open
|
sleep_type, location, note, duration_minutes, is_open
|
||||||
FROM v_sleep
|
FROM v_sleep
|
||||||
WHERE subject = 'mila' AND log_date = '{today_str}'
|
WHERE subject = 'mila' AND (
|
||||||
|
log_date = '{today_str}'
|
||||||
|
OR (sleep_type = 'night' AND CAST(local_end AS DATE) = '{today_str}')
|
||||||
|
)
|
||||||
ORDER BY start ASC
|
ORDER BY start ASC
|
||||||
"""
|
"""
|
||||||
).fetchall()
|
).fetchall()
|
||||||
@@ -173,12 +176,21 @@ def generate_dashboard_html(engine: DatastoreEngine) -> str:
|
|||||||
"""
|
"""
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
today_tot_hours = f"{today_agg[0]:.2f} h" if today_agg and today_agg[0] else "0 h"
|
# Prefer the completed night sleep that ended this morning/today for the top card
|
||||||
today_night_min = today_agg[2] if today_agg else 0
|
morning_night = next((s for s in today_nights if not s[7] and s[6] is not None), None)
|
||||||
|
if morning_night and morning_night[6]:
|
||||||
|
today_night_min = morning_night[6]
|
||||||
|
else:
|
||||||
|
today_night_min = today_agg[2] if today_agg else 0
|
||||||
|
|
||||||
|
today_nap_count = today_agg[1] if today_agg else len(today_naps)
|
||||||
|
today_nap_min = sum(n[6] or 0 for n in today_naps)
|
||||||
|
|
||||||
|
today_tot_min = today_night_min + today_nap_min
|
||||||
|
today_tot_hours = f"{today_tot_min / 60.0:.2f} h" if today_tot_min > 0 else "0 h"
|
||||||
|
|
||||||
today_night_h, today_night_m = divmod(today_night_min, 60)
|
today_night_h, today_night_m = divmod(today_night_min, 60)
|
||||||
today_night_dur_str = f"{today_night_h} h {today_night_m:02d} min" if today_night_h > 0 else f"{today_night_m} min"
|
today_night_dur_str = f"{today_night_h} h {today_night_m:02d} min" if today_night_h > 0 else f"{today_night_m} min"
|
||||||
today_nap_count = today_agg[1] if today_agg else 0
|
|
||||||
today_nap_min = today_agg[3] if today_agg else 0
|
|
||||||
|
|
||||||
# 3. Today's Feedings (conditional)
|
# 3. Today's Feedings (conditional)
|
||||||
today_feedings = con.execute(
|
today_feedings = con.execute(
|
||||||
|
|||||||
Reference in New Issue
Block a user