Files
JARVIS/backend/app/services/memory/reminder_scheduler.py
WIN-JHFT4D3SIVT\caoxiaozhu 11160ec4d2 feat(memory): complete M.2-M.5 memory upgrade phases with tests
- M.2: ForgettingCurve, MemoryDecay, MemoryReinforcement (selective forgetting)
- M.3: DailyDigestGenerator, ReminderScheduler, ProactiveInformer (proactive reminders)
- M.4: MemoryExtractor with LLM-based memory extraction from conversations
- M.5: MemoryRecallInjector with token budget control for prompt injection
- All phases include comprehensive unit tests (109 tests passing)
- Updated checklist.md to mark all tasks complete
2026-04-05 14:09:51 +08:00

114 lines
3.4 KiB
Python

"""
ReminderScheduler
Schedules and manages user reminders.
"""
from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING
from sqlalchemy import select, and_
from app.models.reminder import Reminder
if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession
class ReminderScheduler:
"""Schedule and manage user reminders."""
async def create_reminder(
self,
db: "AsyncSession",
user_id: str,
content: str,
trigger_at: datetime,
trigger_type: str = "time",
context_memory_id: str | None = None,
) -> Reminder:
"""Create a new reminder."""
reminder = Reminder(
user_id=user_id,
content=content,
trigger_type=trigger_type,
trigger_at=trigger_at,
context_memory_id=context_memory_id,
status="pending",
)
db.add(reminder)
await db.commit()
await db.refresh(reminder)
return reminder
async def get_due_reminders(self, db: "AsyncSession", user_id: str) -> list[Reminder]:
"""Get reminders that are due (status=pending, trigger_at <= now, not snoozed)."""
now = datetime.now(UTC)
result = await db.execute(
select(Reminder)
.where(
Reminder.user_id == user_id,
Reminder.status == "pending",
Reminder.trigger_at <= now,
((Reminder.snoozed_until.is_(None)) | (Reminder.snoozed_until <= now)),
)
.order_by(Reminder.trigger_at.asc())
)
return list(result.scalars().all())
async def snooze(
self,
db: "AsyncSession",
reminder_id: int,
minutes: int,
) -> Reminder | None:
"""Snooze reminder for N minutes."""
result = await db.execute(select(Reminder).where(Reminder.id == reminder_id))
reminder = result.scalar_one_or_none()
if not reminder:
return None
reminder.status = "snoozed"
reminder.snoozed_until = datetime.now(UTC) + timedelta(minutes=minutes)
await db.commit()
await db.refresh(reminder)
return reminder
async def dismiss(self, db: "AsyncSession", reminder_id: int) -> bool:
"""Mark reminder as dismissed."""
result = await db.execute(select(Reminder).where(Reminder.id == reminder_id))
reminder = result.scalar_one_or_none()
if not reminder:
return False
reminder.status = "dismissed"
await db.commit()
return True
async def mark_sent(self, db: "AsyncSession", reminder_id: int) -> bool:
"""Mark reminder as sent."""
result = await db.execute(select(Reminder).where(Reminder.id == reminder_id))
reminder = result.scalar_one_or_none()
if not reminder:
return False
reminder.status = "sent"
await db.commit()
return True
async def get_pending_reminders(
self,
db: "AsyncSession",
user_id: str,
) -> list[Reminder]:
"""Get all pending reminders for a user."""
result = await db.execute(
select(Reminder)
.where(
Reminder.user_id == user_id,
Reminder.status.in_(["pending", "snoozed"]),
)
.order_by(Reminder.trigger_at.asc())
)
return list(result.scalars().all())