# Sophnet 日程提醒 — 架构设计

## 一、整体分层架构

```
┌─────────────────────────────────────────────────────────────────┐
│                        用户 / 前端 App                           │
│   自然语言输入  │  文件/截图上传  │  日历视图（待建）            │
└──────────────────────────┬──────────────────────────────────────┘
                           │  对话消息 / 文件路径       │ HTTP REST
┌──────────────────────────▼──────────────────┐   ┌────▼──────────────────┐
│           Agent 层（SKILL.md）               │   │  server.py（FastAPI） │
│  解析用户意图 → 调用 app → 格式化结果回复    │   │  Sophnet 日程提醒 API │
└──────┬───────────────────┬──────────────────┘   └────┬──────────────────┘
       │ 定时触发           │ 用户触发                   │ 直接调用 services/
┌──────▼──────┐   ┌────────▼───────────────────────────▼──────────────────┐
│ OpenClaw    │   │                  apps/ 入口层                           │
│ Cron 调度器  │   │  add_event │ import_events │ check_roc │ check_reminders│
│(jobs.json)  │   │  generate_daily_plan │ monitor_calendar_changes        │
└──────┬──────┘   └──────────────────────────┬──────────────────────────┘
       │                                      │ 调用
┌──────▼───────────────────────────────────── ▼──────────────────────────┐
│                       services/ 业务逻辑层                               │
│  time_window │ event_classifier │ reminder_planner │ conflict_detector  │
│  event_diff  │ task_builder     │ table_parser                          │
└──────────────────────────────────────────┬─────────────────────────────┘
                                           │ 调用
┌──────────────────────────────────────────▼─────────────────────────────┐
│                          gcal/ 接入层                                    │
│              client.py（OAuth2 + REST）  │  models.py                   │
└──────────────────────────────────────────┬─────────────────────────────┘
                                           │ HTTPS
┌──────────────────────────────────────────▼─────────────────────────────┐
│                       Google Calendar API v3                             │
└────────────────────────────────────────────────────────────────────────┘

配置层（只读）                      状态层（读写）
config/settings.py                  data/last_events.json
config/reminder_rules.yaml          data/roc_state.json
config/task_registry.yaml           daily/cron-YYYYMMDD.md
config/roc_events.yaml              ~/.openclaw/cron/jobs.json
api/models.py（API schema）
```

---

## 二、各层职责

### 1. Agent 层（`SKILL.md`）

OpenClaw Agent 的行为规范文件。定义：
- 欢迎语和自动功能说明
- 17 个用户交互场景的触发词、处理步骤、响应格式
- 错误处理话术

**Agent 本身不包含业务逻辑**，只负责意图识别 → 调用 app → 格式化输出。

---

### 1.5 HTTP API 层（`server.py` + `api/models.py`）

为前端 App 提供 REST 接口，与 Agent 层**共用同一套** `services/` 和 `gcal/` 逻辑。

| 文件 | 职责 |
|------|------|
| `server.py` | FastAPI 应用，18 条路由，API Key 认证，CORS |
| `api/models.py` | 所有接口的 Pydantic 请求/响应 schema |

**启动方式**：
```bash
# 在 skills/sophnet-schedule/src 目录下
API_KEY=your-secret uv run uvicorn server:app --host 0.0.0.0 --port 8765
```

**接口速览**：

| 分组 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 系统 | GET | `/health` | 健康检查 |
| 日历 | GET | `/events` | 查询事件（支持 `date` 或 `start/end`）|
| 日历 | POST | `/events` | 创建事件 + 可选注册提醒 |
| 日历 | DELETE | `/events/{id}` | 删除事件 |
| 日历 | GET | `/events/conflicts` | 冲突检测 |
| ROC | GET | `/roc/events` | ROC 事件清单（含今年日期）|
| ROC | POST | `/roc/events` | 新增 ROC 事件 |
| ROC | PUT | `/roc/events/{id}` | 修改 ROC 事件 |
| ROC | DELETE | `/roc/events/{id}` | 删除 ROC 事件 |
| ROC | GET | `/roc/upcoming` | 近期 ROC 提醒预览 |
| 提醒 | GET | `/reminders` | 窗口内即将触发的提醒 |
| 任务 | GET | `/tasks` | Cron 任务列表 |
| 任务 | POST | `/tasks/trigger/daily-plan` | 手动触发今日计划生成 |
| 日报 | GET | `/daily-plan` | 日报数据（含 Markdown 原文）|

Swagger UI：`http://host:8765/docs`

---

### 2. Apps 层（`apps/`）

各功能的 CLI 入口，均输出 JSON，供 Agent 读取后格式化。

| 脚本 | 触发方式 | 职责 |
|------|---------|------|
| `generate_daily_plan.py` | Cron 07:50 / 用户手动 | 拉取今日 GCal 事件 → 冲突检测 → 注册提醒任务 → 生成日报 |
| `check_reminders.py` | Cron 每分钟 / 用户查询 | 检查指定时间窗口内即将触发的提醒 |
| `monitor_calendar_changes.py` | Cron 每小时 | 对比日程快照，有变化才输出，无变化静默 |
| `register_tasks.py` | 启动时 / 手动 | 读取 `task_registry.yaml`，写入 `jobs.json` |
| `add_event.py` | 用户自然语言输入 | 接收结构化参数，写入 Google Calendar，可选注册提醒 |
| `import_events.py` | 文件/OCR 导入 | 批量接收 JSON 事件列表，逐一写入 Google Calendar |
| `check_roc.py` | Cron 08:05 / 用户查询 | 读取 `roc_events.yaml`，计算今年日期，检查今日是否需要提醒 |

---

### 3. Services 层（`services/`）

纯业务逻辑，无 I/O 副作用，100% 单元可测试。

| 模块 | 职责 |
|------|------|
| `time_window.py` | 计算今日/明日的时间区间（UTC/本地时间转换）|
| `event_classifier.py` | 根据 `reminder_rules.yaml` 关键词将事件分类为 meeting/sport/meal |
| `reminder_planner.py` | 根据分类和规则生成 `ReminderPlan`（提醒时间 + 消息文本）|
| `conflict_detector.py` | 检测时间重叠（`⚠️ 冲突`）和紧接场景（`⚡ 紧接`，间隔 < 5 分钟）|
| `event_diff.py` | 对比两份事件列表，输出新增/取消/修改的差异 |
| `task_builder.py` | 构造 OpenClaw Cron 的 `job` payload（区分三种投递模式）|
| `table_parser.py` | 解析 Markdown 表格，自动识别 30+ 中英文列名别名，提取事件 |

---

### 4. GCal 层（`gcal/`）

Google Calendar API 的统一访问封装。

| 模块 | 职责 |
|------|------|
| `client.py` | OAuth2 token 管理（自动刷新）、list_events / create_event / delete_event |
| `models.py` | `CalendarEvent` dataclass（屏蔽 API 原始 JSON 结构）|

Token 存于 `~/.config/gcalcli/token.json`，过期自动刷新写回，无需手动管理。

---

### 5. 配置层（`config/`）

所有行为参数集中于此，**修改 YAML 无需改代码**。

| 文件 | 内容 |
|------|------|
| `settings.py` | 读取 `.env`，暴露 `SKILL_BASE / GCAL_TOKEN_PATH / PROXIES / TIMEZONE` 等路径和常量 |
| `reminder_rules.yaml` | 事件类型提前提醒分钟数 + 分类关键词 |
| `task_registry.yaml` | 全部固定重复任务注册表（7 条，含 ROC 检查）|
| `roc_events.yaml` | ROC 周期事件清单 + 春节日期（**前端 CRUD 的唯一数据源**）|

---

### 6. 状态层（`data/`）

运行时读写的状态文件，不纳入 Git。

| 文件 | 内容 |
|------|------|
| `data/last_events.json` | 日程快照，`monitor_calendar_changes.py` 用于差异对比 |
| `data/roc_state.json` | 记录每个 ROC 事件最近一次已提醒的日期，防重复推送 |
| `daily/cron-YYYYMMDD.md` | 每日生成的任务日报（含今日日程 + 注册的 cron 任务列表）|

---

## 三、核心数据流

### 3.1 每日计划生成（07:50 自动）

```
OpenClaw Cron
  → generate_daily_plan.py
      → time_window.get_today_window()
      → gcal.client.list_events(start, end)        # 拉取今日 GCal 事件
      → event_classifier.classify(events)           # 分类
      → reminder_planner.plan(events, rules)        # 计算提醒时间
      → conflict_detector.detect(events)            # 冲突检测
      → task_builder.build_reminder_jobs(plans)     # 构造 cron payload
      → register_tasks.write(jobs, jobs.json)       # 写入 OpenClaw 调度器
      → 生成 daily/cron-YYYYMMDD.md                 # 日报落盘
  → OpenClaw announce → Telegram 推送
```

### 3.2 自然语言写入日历（用户触发）

```
用户："后天下午3点牙医预约，提前15分钟提醒"
  → Agent 解析日期时间 → 展示确认卡片
  → 用户确认
  → add_event.py --summary "牙医预约" --date ... --remind 15
      → gcal.client.create_event()                  # 写入 Google Calendar
      → task_builder.build_one_off_reminder()       # 构造一次性提醒 payload
      → register_tasks.write(job, jobs.json)        # 注册到 OpenClaw
  → Agent 回复成功消息
```

### 3.3 文件 / OCR 导入（用户触发）

```
用户上传 Excel / 截图
  → Agent 定位文件路径
  → markitdown <file> → /tmp/schedule_converted.md  # 文档转 Markdown
  → table_parser.parse_events_from_markdown()        # 提取结构化事件列表
  → Agent 展示确认表格
  → 用户确认
  → import_events.py --json '[...]'
      → gcal.client.create_event() × N              # 批量写入 Google Calendar
  → Agent 回复写入汇总
```

### 3.4 ROC 周期事件检查（08:05 自动）

```
OpenClaw Cron
  → check_roc.py
      → 读取 config/roc_events.yaml                 # 事件清单
      → 按 rule 计算今年各事件日期                   # 固定日期/春节/第N个周X
      → 对比今日 vs reminder_date                   # 是否到提醒日
      → 检查 data/roc_state.json                    # 防重复提醒
      → 输出 JSON（reminders 数组）
      → 写回 roc_state.json
  → Agent 读取 JSON → 格式化推送
```

---

## 四、OpenClaw Cron 投递模式

`task_builder.py` 强制三条路径，不可混用：

| 场景 | sessionTarget | payload.kind | delivery |
|------|--------------|--------------|----------|
| 会前/事前一次性提醒 | `main` | `systemEvent` | 不设置 |
| 周期静默监控（日程变化）| `isolated` | `agentTurn` | `none` |
| 日报/ROC 结果推送 | `isolated` | `agentTurn` | `announce` |

> **为什么提醒用 `systemEvent + main`**：`isolated + announce` 不支持 webchat 渠道，会报 `Unsupported channel`；`systemEvent + main` 直接注入主会话，所有渠道均可用。

---

## 五、前端扩展接入点

当前 skill 是纯后端（Bot + GCal），前端 App 接入时各数据接口已预留：

| 前端功能 | 接入方式 |
|---------|---------|
| 日/周/月日历视图 | 封装 `gcal.client.list_events()` 为 REST API |
| ROC 事件管理页 | CRUD `config/roc_events.yaml`（已是结构化 YAML，天然适合 API 包装）|
| 提醒状态查询 | 读取 `data/roc_state.json` |
| 系统推送通知 | App 层注册 APNs/FCM；skill 层已生成提醒时间，无需修改 |
| 日程操作同步 | `add_event.py` / `delete_event()` 均已支持，包一层 HTTP endpoint |

---

## 六、目录速查

```
sophnet-schedule/src/
├── SKILL.md                    # Agent 行为规范（17 个交互场景）
├── server.py                   # HTTP API 服务（FastAPI，18 条路由）
│
├── api/                        # API Schema 层
│   └── models.py               # Pydantic 请求/响应模型
│
├── compat.py                   # Python 版本兼容补丁
│
├── gcal/                       # Google Calendar 接入层
│   ├── client.py               # OAuth2 + REST（list/create/delete）
│   └── models.py               # CalendarEvent dataclass
│
├── services/                   # 业务逻辑层（纯函数，无 I/O）
│   ├── time_window.py          # 今日/明日时间区间计算
│   ├── event_classifier.py     # 事件类型分类（meeting/sport/meal）
│   ├── reminder_planner.py     # 提醒计划生成 + 消息格式化
│   ├── conflict_detector.py    # 时间冲突 + 紧接检测
│   ├── event_diff.py           # 日程变化对比
│   ├── task_builder.py         # OpenClaw Cron payload 构造
│   └── table_parser.py         # Markdown 表格 → 事件列表（列名别名识别）
│
├── apps/                       # CLI 入口层（输出 JSON）
│   ├── generate_daily_plan.py  # 每日计划生成（核心入口）
│   ├── check_reminders.py      # 即时提醒窗口检查
│   ├── monitor_calendar_changes.py  # 日程变化监控
│   ├── register_tasks.py       # 任务注册到 jobs.json
│   ├── add_event.py            # 单条事件写入 GCal
│   ├── import_events.py        # 批量事件写入 GCal
│   └── check_roc.py            # ROC 周期事件提醒检查
│
├── config/                     # 配置层（改 YAML 不改代码）
│   ├── settings.py             # 路径 + 环境变量读取
│   ├── reminder_rules.yaml     # 提醒规则 + 分类关键词
│   ├── task_registry.yaml      # 固定重复任务注册表
│   └── roc_events.yaml         # ROC 周期事件清单
│
├── data/                       # 运行时状态（不纳入 Git）
│   ├── last_events.json        # 日程快照（监控用）
│   └── roc_state.json          # ROC 已提醒记录（防重复）
│
├── daily/                      # 每日自动生成的日报
│   └── cron-YYYYMMDD.md
│
├── scripts/
│   └── setup_gcal_token.py     # Google OAuth 初始化工具
│
├── tests/                      # 单元测试（8 个文件，121 个用例）
│   ├── test_time_window.py
│   ├── test_event_classifier.py
│   ├── test_reminder_planner.py
│   ├── test_conflict_detector.py
│   ├── test_event_diff.py
│   ├── test_task_builder.py
│   ├── test_add_event.py
│   ├── test_table_parser.py
│   └── test_check_roc.py
│
└── docs/
    ├── GOOGLE_CALENDAR_SETUP.md  # OAuth 配置指南
    └── TESTING_GUIDE.md          # 测试运行指南
```
