Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions community/sleep-quality-tracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Sleep Quality Tracker

A personal sleep coach that learns your patterns over time. Log your sleep each morning, track evening habits, and get personalised insights drawn from your own data — not generic advice.

## Trigger Phrases

- `"how did I sleep"`
- `"sleep check-in"`
- `"log my sleep"`
- `"sleep report"`
- `"sleep patterns"`
- `"tips for tonight"`
- `"evening habits"`
- `"sleep history"`

## How It Works

**First time:** Setup takes 30 seconds — your name and sleep goal. From there, say "how did I sleep" every morning.

**Morning check-in (3 questions, under 60 seconds):**
1. How many hours did you sleep?
2. Quality from 1 to 10?
3. Anything to note? (optional)

The ability extracts hours from natural speech — "went to bed around midnight, up at seven" works just as well as "seven hours."

**Evening habits (optional, 4 yes/no questions):**
Caffeine after 3pm, exercise, stress level, screen time before bed. These get correlated with your next-morning quality rating to surface personal patterns.

**Reports (after 5+ nights):**
Not generic advice — actual correlations from your data. "On nights you exercised AND skipped caffeine after 3pm, your quality averaged 8.1 — that's 1.8 points above your baseline."

**Sleep debt tracker:**
Every check-in and report tells you how many hours ahead or behind you are against your personal goal this week.

## Background Daemon

| Time | Action |
|---|---|
| 7:00 – 8:00 AM | Morning nudge if sleep not yet logged |
| 9:00 – 10:00 PM | Evening habit nudge if habits not yet logged |
| Sunday 8:00 – 9:00 AM | Weekly pattern summary |

## What Makes It Different from Alexa

| | Alexa | Sleep Quality Tracker |
|---|---|---|
| Remembers last night | No | Yes — every night |
| Personalised correlations | No | Yes — from your own data |
| Sleep debt tracking | No | Yes — cumulative vs your goal |
| Gets smarter over time | No | Yes — week 3 is different from week 1 |
| Proactive morning prompt | Basic alarm | Conversational check-in |
| Evening habit coaching | None | Habit-aware, correlation-driven |

## Setup

No API keys required. All pattern analysis uses the built-in LLM.

## Example Sessions

**First morning:**
> User: "how did I sleep"
> AI: "Morning Hassan! How many hours did you sleep?"
> User: "went to bed at midnight, up at half six"
> AI: "And how do you feel — 1 to 10?"
> User: "about a 6, a bit groggy"
> AI: "Anything to note? Say 'nothing' to skip."
> User: "nothing"
> AI: "Logged — 6.5 hours, 6 out of 10. 1 night tracked so far."

**After 2 weeks:**
> User: "sleep report"
> AI: "Here's your sleep picture for the last 14 nights, Hassan. Average 7.1 hours, quality 6.8 out of 10. Best night: 2026-08-02 — 8.5h, rated 9. Worst night: 2026-07-28 — 5.5h, rated 4. You're 3.5 hours in sleep debt against your 8-hour goal this week. Based on your data: on nights you exercised and skipped caffeine after 3pm, you averaged 8.2 out of 10 — that's 1.4 points above your baseline. The pattern holds across 6 nights."

**Evening:**
> User: "evening habits"
> AI: "Quick habit check, Hassan. Caffeine after 3pm today?"
> User: "no"
> AI: "Any exercise today?"
> User: "yeah, went for a run"
> AI: "How stressed are you feeling tonight?"
> User: "pretty calm"
> AI: "Screen time in the last hour?"
> User: "yeah been on my phone"
> AI: "Logged. I'll compare this with how you sleep tonight. Sleep well!"
Empty file.
153 changes: 153 additions & 0 deletions community/sleep-quality-tracker/background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import json
from datetime import datetime, timedelta

from src.agent.capability import MatchingCapability
from src.agent.capability_worker import CapabilityWorker
from src.main import AgentWorker

STORAGE_KEY = "sleep_quality_tracker_data"

WEEKLY_SUMMARY_PROMPT = (
"You are giving {name} their weekly sleep summary in a warm, conversational tone. "
"Data from the last 7 nights: {data}. "
"Cover: average hours, average quality, best night, and one standout pattern if visible. "
"End with one encouraging sentence. Max 4 sentences. Plain text only."
)


class SleepQualityTrackerBackgroundCapability(MatchingCapability):
worker: AgentWorker = None
capability_worker: CapabilityWorker = None
background_daemon_mode: bool = False

# Do not change following tag of register capability
# {{register capability}}

def call(self, worker: AgentWorker, background_daemon_mode: bool):
self.background_daemon_mode = background_daemon_mode
self.worker = worker
self.capability_worker = CapabilityWorker(self.worker)
self.worker.session_tasks.create(self.watch_loop())

# ------------------------------------------------------------------
# Storage
# ------------------------------------------------------------------

def _load_data(self) -> dict:
try:
result = self.capability_worker.get_single_key(STORAGE_KEY)
if result and result.get("value"):
return result["value"]
except Exception as e:
self.worker.editor_logging_handler.error(f"[SleepTrackerBG] Load error: {e!r}")
return {}

def _save_data(self, data: dict):
try:
result = self.capability_worker.create_key(STORAGE_KEY, data)
if not result.get("success"):
self.capability_worker.update_key(STORAGE_KEY, data)
except Exception as e:
self.worker.editor_logging_handler.error(f"[SleepTrackerBG] Save error: {e!r}")

# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------

def _today_str(self) -> str:
return datetime.now().strftime("%Y-%m-%d")

def _current_hour(self) -> int:
return datetime.now().hour

def _already_logged_today(self, data: dict) -> bool:
today = self._today_str()
return any(e.get("date") == today for e in data.get("sleep_log", []))

def _evening_habits_logged_today(self, data: dict) -> bool:
today = self._today_str()
for e in data.get("sleep_log", []):
if e.get("date") == today and e.get("evening_habits"):
return True
return False

def _is_sunday(self) -> bool:
return datetime.now().weekday() == 6

def _weekly_summary_sent_this_week(self, data: dict) -> bool:
last_sent = data.get("weekly_summary_last_sent")
if not last_sent:
return False
last_dt = datetime.strptime(last_sent, "%Y-%m-%d")
return (datetime.now() - last_dt).days < 7

def _get_week_log(self, data: dict) -> list:
cutoff = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
return [e for e in data.get("sleep_log", []) if e.get("date", "") >= cutoff]

def _has_enough_data(self, data: dict) -> bool:
return len(data.get("sleep_log", [])) >= 3

# ------------------------------------------------------------------
# Daemon loop
# ------------------------------------------------------------------

async def watch_loop(self):
self.capability_worker.resume_normal_flow()
self.worker.editor_logging_handler.info("[SleepTrackerBG] Daemon started")

while True:
try:
await self.worker.session_tasks.sleep(900.0)

data = self._load_data()

if not data.get("setup_complete"):
continue

name = data.get("user_name", "")
hour = self._current_hour()

# Morning nudge: 7–8 AM, sleep not yet logged
if 7 <= hour < 8 and not self._already_logged_today(data):
self.worker.editor_logging_handler.info("[SleepTrackerBG] Morning nudge")
await self.capability_worker.send_interrupt_signal()
await self.capability_worker.speak(
f"Morning{', ' + name if name else ''}! "
f"Ready for your sleep check-in? Just say 'how did I sleep'."
)

# Evening habit nudge: 9–10 PM, habits not yet logged
elif 21 <= hour < 22 and not self._evening_habits_logged_today(data):
self.worker.editor_logging_handler.info("[SleepTrackerBG] Evening nudge")
await self.capability_worker.send_interrupt_signal()
await self.capability_worker.speak(
f"Evening{', ' + name if name else ''}! "
f"Want to log your habits before bed? "
f"Say 'evening habits' and it takes under a minute."
)

# Weekly summary: Sunday 8–9 AM, not yet sent this week
elif (
self._is_sunday()
and 8 <= hour < 9
and not self._weekly_summary_sent_this_week(data)
and self._has_enough_data(data)
):
self.worker.editor_logging_handler.info("[SleepTrackerBG] Weekly summary")
week_log = self._get_week_log(data)
if week_log:
summary = self.capability_worker.text_to_text_response(
WEEKLY_SUMMARY_PROMPT.format(
name=name or "friend",
data=json.dumps(week_log, indent=2),
)
)
await self.capability_worker.send_interrupt_signal()
await self.capability_worker.speak(summary)
data["weekly_summary_last_sent"] = self._today_str()
self._save_data(data)

except Exception as e:
self.worker.editor_logging_handler.error(f"[SleepTrackerBG] Loop error: {e!r}")
await self.worker.session_tasks.sleep(300.0)
Loading
Loading