# Phase M.3：主动提醒系统

日期：2026-04-04
状态：规划中
依赖：M.1 (重要性评分), M.2 (遗忘曲线)
工作量：5 天

---

## 1. 本阶段目的

让 Jarvis 从「等用户问」变成「主动关心」。

核心问题：
- Jarvis 知道用户关心什么（高重要性记忆）
- Jarvis 知道用户最近做了什么（每日摘要）
- Jarvis 主动提醒，而不是等用户问

---

## 2. 核心架构

```
┌─────────────────────────────────────────────────────────────┐
│                 ProactiveReminderSystem                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐  │
│  │ DailyDigest  │ +  │ Reminder     │ +  │ MemoryInformer│  │
│  │ Generator    │    │ Scheduler    │    │              │  │
│  │              │    │              │    │              │  │
│  │ 每日摘要生成  │    │ 提醒调度     │    │ 主动提醒推送  │  │
│  └──────────────┘    └──────────────┘    └──────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

---

## 3. 每日摘要生成器 (DailyDigestGenerator)

### 3.1 输入来源

```
今日输入:
- 用户今日对话摘要 (memory_summaries)
- 用户今日提到的重点 (high importance memories)
- 用户今日创建的任务 (tasks)
- 用户今日查阅的知识 (knowledge retrieval logs)
```

### 3.2 输出格式

```json
{
  "date": "2026-04-04",
  "summary": "今天你主要在处理工作问题，提到了换工作的想法",
  "key_points": [
    {"content": "想换工作", "importance": 0.9, "source": "conversation"},
    {"content": "项目 deadline 是周五", "importance": 0.8, "source": "task"}
  ],
  "pending_questions": [
    {"content": "量子计算的问题还没完全理解", "importance": 0.6}
  ],
  "suggestions": [
    {"text": "明天可以继续聊换工作的话题", "reason": "重要性高且今天没深入"},
    {"text": "量子计算的资料可以找找更通俗的解释", "reason": "还没理解"}
  ]
}
```

### 3.3 生成时机

```python
# 每天晚上 10 点生成
@scheduler.scheduled_task("cron", hour=22)
async def generate_daily_digest():
    """生成每日摘要"""
    digest = await generator.generate(user_id)
    await storage.save(digest)
```

---

## 4. 提醒调度器 (ReminderScheduler)

### 4.1 提醒类型

| 类型 | 触发条件 | 推送时机 |
|------|---------|---------|
| 后续提醒 | 用户说「回头提醒我」 | 指定时间 |
| 关联提醒 | 提到的话题有关联记忆 | 下次对话时 |
| 周期提醒 | 每周/每月固定事项 | 周期首日 |
| 遗忘提醒 | 归档记忆被重新提到 | 恢复后提醒 |

### 4.2 调度逻辑

```python
class ReminderScheduler:
    def schedule_reminder(self, user_id: int, reminder: Reminder):
        """安排提醒"""
        
    def get_due_reminders(self, user_id: int) -> list[Reminder]:
        """获取到期的提醒"""
        
    def snooze_reminder(self, reminder_id: int, minutes: int):
        """推迟提醒"""
```

### 4.3 提醒存储

```python
class Reminder:
    id: int
    user_id: int
    content: str
    trigger_type: str  # "time" / "context" / "periodic"
    trigger_at: DateTime
    context: dict  # 关联的 memory_id 等
    status: str  # "pending" / "sent" / "snoozed"
    created_at: DateTime
```

---

## 5. 主动记忆告知器 (ProactiveMemoryInformer)

### 5.1 触发条件

```python
TRIGGERS = {
    "high_importance_topic": {
        "condition": "用户提到高重要性话题",
        "action": "提及关联记忆",
        "example": "你说想换工作，要不要看看之前收藏的 JD？"
    },
    "repeat_question": {
        "condition": "用户重复问某个问题",
        "action": "主动说之前回答过",
        "example": "你之前问过量子计算，我再给你解释一下？"
    },
    "forgotten_context": {
        "condition": "用户提到已归档的记忆",
        "action": "提示可以恢复",
        "example": "这个话题你一个月前聊过，要我恢复一下吗？"
    },
    "pending_goal": {
        "condition": "用户设了目标但没进展",
        "action": "温和提醒",
        "example": "你之前说想学 Python，有进展吗？"
    }
}
```

### 5.2 告知时机

```python
# 不是每次对话都告知，有概率控制
INFORM_PROBABILITY = {
    "high_importance_topic": 0.8,  # 高重要性话题 80% 主动提
    "repeat_question": 1.0,        # 重复问题 100% 主动提
    "forgotten_context": 0.5,      # 已归档话题 50% 提示
    "pending_goal": 0.3,           # 待办目标 30% 温和提醒
}
```

### 5.3 告知风格

```python
INFORM_STYLE = {
    "casual": "对了，你之前提到...",
    "gentle": "不知道你有没有注意到...",
    "helpful": "我记起你关心这个，要不看看..."
}
```

---

## 6. 核心文件

### 6.1 新增文件

| 文件 | 职责 |
|------|------|
| `services/memory/daily_digest.py` | 每日摘要生成 |
| `services/memory/reminder_scheduler.py` | 提醒调度 |
| `services/memory/proactive_informer.py` | 主动告知 |
| `services/memory/reminder_model.py` | 提醒数据模型 |

### 6.2 修改文件

| 文件 | 修改内容 |
|------|---------|
| `services/scheduler_service.py` | 集成主动提醒调度 |
| `services/memory_service.py` | 集成 ProactiveInformer |
| `routers/conversation.py` | 主动告知触发点 |

---

## 7. API 设计

### 7.1 DailyDigestGenerator

```python
class DailyDigestGenerator:
    async def generate(self, user_id: int, date: date = None) -> DailyDigest:
        """生成每日摘要"""
        
    async def get_recent_digests(self, user_id: int, limit: int = 7) -> list[Digest]:
        """获取最近 N 天的摘要"""
```

### 7.2 ReminderScheduler

```python
class ReminderScheduler:
    async def create_reminder(self, user_id: int, content: str, trigger_at: datetime):
        """创建提醒"""
        
    async def get_due_reminders(self, user_id: int) -> list[Reminder]:
        """获取到期提醒"""
        
    async def snooze(self, reminder_id: int, minutes: int):
        """推迟提醒"""
```

### 7.3 ProactiveInformer

```python
class ProactiveInformer:
    def should_inform(self, user_id: int, trigger_type: str) -> bool:
        """是否应该告知"""
        
    def get_inform_message(self, user_id: int, trigger_type: str, context: dict) -> str:
        """生成告知消息"""
        
    async def check_and_inform(self, conversation_context: dict) -> str | None:
        """检查并返回告知消息，无则返回 None"""
```

---

## 8. 前端集成

### 8.1 每日摘要展示

```vue
<!-- 每日摘要卡片 -->
<div v-if="dailyDigest">
  <h3>今日摘要</h3>
  <p>{{ dailyDigest.summary }}</p>
  <div v-for="point in dailyDigest.keyPoints">
    - {{ point.content }}
  </div>
  <div v-if="dailyDigest.suggestions.length">
    <h4>建议</h4>
    <p>{{ dailyDigest.suggestions[0].text }}</p>
  </div>
</div>
```

### 8.2 主动提醒推送

```vue
<!-- 主动提醒弹窗 -->
<div v-if="activeReminder" class="reminder-toast">
  <p>{{ activeReminder.content }}</p>
  <button @click="snooze">稍后</button>
  <button @click="dismiss">知道了</button>
</div>
```

---

## 9. 测试设计

### 9.1 每日摘要测试

```python
async def test_digest_generation():
    digest = await generator.generate(user_id=1)
    assert digest.summary is not None
    assert len(digest.key_points) > 0

async def test_digest_includes_high_importance():
    # 高重要性记忆应该出现在摘要中
    pass
```

### 9.2 提醒调度测试

```python
async def test_schedule_reminder():
    reminder = await scheduler.create_reminder(
        user_id=1,
        content="检查邮件",
        trigger_at=datetime.now() + timedelta(hours=1)
    )
    assert reminder.status == "pending"

async def test_get_due_reminders():
    due = await scheduler.get_due_reminders(user_id=1)
    assert len(due) >= 0
```

### 9.3 主动告知测试

```python
def test_should_inform_probability():
    # 高重要性话题 80% 触发
    count = sum(informer.should_inform(1, "high_importance_topic") 
                for _ in range(100))
    assert 70 < count < 90  # 允许一点随机波动

def test_repeat_question_always_informs():
    # 重复问题 100% 触发
    assert informer.should_inform(1, "repeat_question") == True
```

---

## 10. 验收标准

| 标准 | 说明 |
|------|------|
| 每日摘要生成正常 | 22:00 自动生成 |
| 提醒创建正常 | 用户可创建提醒 |
| 提醒到期触发 | 定时推送 |
| 主动告知概率正确 | 按配置的概率触发 |
| 告知消息自然 | 像人说话，不生硬 |
| 用户可控制 | 可以关闭主动提醒 |
| 单元测试覆盖率 | > 80% |

---

## 11. 工作量估算

| 任务 | 工作量 |
|------|--------|
| DailyDigestGenerator | 1.5 天 |
| ReminderScheduler | 1.5 天 |
| ProactiveInformer | 1 天 |
| 前端摘要展示 | 0.5 天 |
| 前端提醒推送 | 0.5 天 |
| 测试 | 1 天 |
| **合计** | **6 天** |

---

## 12. 与现有系统的关系

```
现有 SchedulerService
        │
        ├── 凌晨任务重建 → 现有功能
        ├── 每日摘要生成 → M.3 新增
        └── 提醒检查     → M.3 新增
```

提醒系统独立于现有任务系统，但可以复用调度基础设施。
