# Notion Bill

> Notion 记账系统 — 创建/查询账单、管理收支记录

- Skill: `publieople/notion-bill` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add publieople/notion-bill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/publieople/notion-bill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: publieople (https://skillmd.com/u/publieople)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/publieople/notion-bill

---


# notion-bill — Notion 记账系统

基于拉琪奥 Agent 迁移的 Notion 账本系统，管理「收支记录」数据库。

## 数据库配置

| 配置 | 值 |
|------|-----|
| 账本 database_id | `12366ad7-c9c4-8141-8f05-e335b3d0cb5b` |
| 账本 data_source_id | `12366ad7-c9c4-81d6-9cbc-000bcd11a4d0` |
| 人脉 database_id | `1f766ad7-c9c4-8007-a335-d4f650b7217f` |
| 人脉 data_source_id | `1f766ad7-c9c4-809b-b8aa-000b9c738b61` |
| API Key 路径 | `~/.config/notion/api_key` |
| Notion-Version | `2025-09-03` |

## ⚠️ 铁律（违反会出严重问题）

### 1. 选项验证铁律
**创建账单前必须查询数据库元数据，验证所有 select/multi_select 值已存在，绝对不允许直接创建新选项。**

验证方式：用 `data_source_id` 查询数据库元数据 `GET /v1/data_sources/{data_source_id}`

### 2. 审核流程铁律
**所有账单创建/删除操作，必须先展示计划，用户确认（Y/N）后再执行。**

审核清单：项目、金额、日期、大项、小项、渠道、平台、标签、相关人员

### 3. 平台字段处理
- 不确定填什么 → **留空**（不传该字段）
- 不要随意填「其他」
- 预设平台：拼多多、淘宝、京东、美团、steam、闲鱼

### 4. 状态字段
- ✅ 用「已结算」
- ❌ 不要用「已完成」

### 5. 金额正负号铁律
**所有金额都用正数，无论收入还是支出。** 方向由「大项」区分（收入 vs 饮食/日常购物等），不由正负号区分。

- ✅ 支出：正数（¥35.79）
- ✅ 收入：正数（¥10.00）
- ✅ 资金流转入/出：正数
- ❌ 收入不要用负数（¥-10.00 是错的）
- ⚠️ **唯一的例外：退款** — 退款记录用负数金额，标题含「退款」

### 6. 标题与图标分离
**标题字段不要包含 emoji。** Emoji 通过 `icon` 属性单独设置。

- ✅ 标题 = `"上海燕矽食品"`，icon = `"🥡"`
- ❌ 标题 = `"🥡 上海燕矽食品"` — emoji 重复，icon 已经设了
- 查询脚本展示时会自动根据 icon 加 emoji 前缀，那是展示层的行为，不影响存储

## 分类规则

### 大项（16 项）
饮食、日用杂货、休闲娱乐、日常购物、固定消费、生活服务、金融投资、收入、贷款、借款、退款、他人消费、信息遗失、仅记录、资金流转出、资金流转入

### 小项（53 项）
外卖、外出就餐、食材、日用品、房租、电费、电话费、电子产品、数字产品、内容消费、工资、兼职、打工、投资分红、转账、代单、交通、退款、利息、零食、饮料、服装、速食、生活费、资金流转、游戏、水费、学费、卡券包、医社保、户外活动、红包、酒店、租赁、流量、他人消费、借款、仅记录、打印、文创周边、信息遗失、服务费、班费、报销、报名费、投资理财、邮寄、贷款、理发、食堂、其他 等

### 交易渠道（16 项）
微信零钱、微信零钱通、**支付宝**（余额交易统一用这个，没有单独的「支付宝余额」渠道）、京东白条、美团月付、平安银行储蓄卡、中国银行储蓄卡、中国银行信用卡、中国银行社保卡、邮储银行储蓄卡、工商银行信用卡-银联/VISA、京东小金库、**余额宝**（余额宝内交易）、其他

### 特殊场景规则
- **转账/红包**：小项用「生活费」（家人转账）或「资金流转」（一般转账），不能用「工资」
  - ⚠️ 数据库中无「转账」小项，不要使用
- **代付/请客**：区分方向（看钱往哪流）：
  | 场景 | 大项 | 小项 | 示例 |
  |------|:----:|:----:|------|
  | **你帮舍友付**（你支出） | **饮食** | 代单 | 你下单付两份，舍友之后转账给你 |
  | **舍友还你钱**（你收入） | **收入** | 代单 | 舍友把AA份额转给你 |
  | **食堂带饭**（你支出，帮舍友带一份） | **饮食** | 代单 | 支付宝付给食堂摊主（如马海涛），20自己的+17舍友的；自己的记为食堂，舍友的记为代单。**银行账单显示为"支付宝-马海涛"**，不要被收款人姓名误导 |
  - 两种方向都需设父项指向对应的**实际消费记录**
  - 食堂带饭场景的父项指向**用户自己那份食堂记录**
  - 室友间AA吃饭通过微信转账付款，不要因为截图写着"转账"就分类为资金流转
- **大额餐饮（>200元）**：需确认
- **负数金额**：标题需含「退款」
- **外卖红包/卡券**：大项=饮食，标签=卡券包

## 退款场景

### 场景 A：单一退款（正常退货退款）
有退款的消费**必须创建两条关联记录**：

#### 记录 1：原始消费
- 项目：实际名称
- 金额：原值
- 大项：实际类别（如饮食）
- 小项：实际小项（如外卖）
- 标签：实际标签

#### 记录 2：退款记录（关联父项）
- 项目：「退款」+ 商户名（如"退款(肯德基)"）
- 金额：原值（正数）
- 大项：退款
- 小项：退款
- 标签：退款
- 父项：relation → 原始消费的 page ID

### 场景 B：先付 → 退款 → 再付（同一商品重新下单）
银行账单上会出现三条记录：一笔消费、一笔退款、再一笔消费。
**必须创建三条关联记录**：

#### 记录 1：第一单（被退款的）
- 大项/小项/金额/渠道：正常分类
- 标签：按首次下单时间

#### 记录 2：退款（关联第一单）
- 大项=退款，小项=退款
- 父项 → 第一单 page ID

#### 记录 3：第二单（实际的消费）
- 大项/小项/金额/渠道：正常分类
- 标签：按第二次下单时间（可能不同餐时）

净效果：两笔支出 + 一笔退款 = 一笔净支出 ✅

## 时间标签规则
- 06:00-10:00 → 早餐
- 11:00-14:00 → 午餐
- 17:00-21:00 → 晚餐
- 22:00-06:00 → 夜宵

## 默认值
用户无特殊说明时：
- 项目名 = 「外卖」（零食/饮料类用「零食」/「饮料」）
- 大项 = 饮食
- 小项 = 外卖（零食/饮料类用零食/饮料）
- 交易渠道 = 微信零钱
- 平台 = 留空
- 标签 = 外卖 + 午餐/晚餐（按时间）—— 零食/饮料类用零食/饮料代替"外卖"
- 图标 = 🥡（零食=🍿，饮料=🥤，外出就餐=🍜）
- 状态 = 已结算

## 易错点与经验教训（实战沉淀）

### 1. 白条/美团月付等「未结算分期」去重铁律

**触发条件**：银行账单显示白条/美团月付扣款时。

**错误模式**：直接在库中新建一条「白条还款 ¥金额」，导致库里出现两条同月同金额的重复记录。

**正确做法**：
1. 先用 `POST /v1/data_sources/{id}/query` 查：**`平台=京东 + 小项=还款 + 状态=未结算 + 交易渠道=空`** 的同月记录
2. 找到原版后 PATCH：`金额 → 银行实际值`、`交易日期 → 银行扣款日`、`交易渠道 → 扣款卡`、`状态 → 已结算`、`父项 → 补全涉及商品`
3. ❌ **绝对不要新建**

**搜索模板**：
```json
{"filter":{"and":[
  {"property":"平台","select":{"equals":"京东"}},
  {"property":"小项","select":{"equals":"还款"}},
  {"property":"状态","status":{"equals":"未结算"}}
]}}
```

### 2. 人脉关联不要只查我记得的

**错误模式**：用户告知几个名字后，我只查了"阮承远"+"冯忠耀"（因为我"记得"这俩在库），其他人漏了关联。

**正确做法**：用户每给一个名字 → **立即**用 `POST /v1/search` 在人脉库（`database_id=1f766ad7-c9c4-8007-a335-d4f650b7217f`）查。**人脉库是源头不是记忆**。

昵称≠真实姓名（"拿琛橡"="阮承远"、"马老师"="马涵君"、"裘隅闿"="谢雨婷"、"郑钩杰"="郑钧杰"）。同时尝试昵称+真实姓名。

### 3. 项目名用真实姓名，不用微信昵称

**触发场景**：群收款/转账/红包等含人名的记录。

- ❌ "群收款-来自马老师" → 保留昵称
- ✅ "群收款-来自马涵君" → 改为真实姓名

项目名是查询/检索的字符串，用真实姓名保证按"姓名"搜索能命中。

### 4. 金额"全款 vs 定金"必问

**触发场景**：银行卡账单显示"扣 ¥X、退款 ¥X、再付 ¥X"三连。

**必问用户**：
- ¥X 是全款还是定金？
- 实际成交价？
- 退款后再付走闲鱼还是支付宝？

**经典案例**：买移动空调 ¥420 → 原本想走闲鱼，但线下交易后支付宝扣 ¥420 → 卖家孙明良支付宝退款 ¥420 → 再付 ¥420 走中行储蓄卡。
- 建 3 条：原始消费（移动空调 ¥420 平台=闲鱼）+ 退款（¥420）+ 再付
- **不关联人脉**（一次性交易，无后续交集）

### 5. /tmp 下的脚本会被定期清理

**症状**：写完 `/tmp/batch5.py` 跑完后，下一轮说"can't open file"。

**正确做法**：
- 脚本写到**持久目录**：`/home/po/.hermes/scripts/` 或 `~/workspace/`
- 或每轮**重新 inline 写脚本**
- 关键中间结果（page_id 映射表）保存到 `.hermes/cache/` 不是 `/tmp/`

## API 调用要点

### 关键端点
- `POST /v1/search` — 查询记录和人脉（推荐，比 `/data_sources/{id}/query` 稳定）
- `POST /v1/pages` — 创建账单页
- `PATCH /v1/pages/{id}` — 更新账单
- `GET /v1/data_sources/{data_source_id}` — 获取数据库元数据（验证选项用）

### ⚠️ Python 脚本编写注意

**不要在 `write_file` 或 `terminal -c` 中直接写 `Path.home().joinpath(...)` 长路径。** 工具可能在序列化时截断为 `Path.h...`。

正确做法：用 `terminal` 的 heredoc 写脚本文件再执行：
```bash
cat > /tmp/script.py << 'SCRIPTEOF'
import ...
apikey = Path.home().joinpath('.config/notion/api_key').read_text().strip()
...
SCRIPTEOF
python3 /tmp/script.py
```

或者用 `os.path.expanduser` 避免长链式调用：
```python
import os
apikey = Path(os.path.expanduser("~/.config/notion/api_key")).read_text().strip()
```

### ⚠️ 金额浮点精度陷阱
Python 的 float 序列化与 Notion API 返回的金额可能有精度差异：
```
Notion API 返回: 60.9    (float from JSON)
Python f-string: 60.90   (float → "60.9")
```
当通过 `POST /v1/search` 按标题+金额+日期查找页面时，**不要直接用精确字符串匹配金额**。正确做法：
```python
# ❌ 会漏匹配
key = f"{title}|{amount}|{date_str}"  # 60.9 vs 60.90

# ✅ 用容差比较
if title == t_title and abs(amount - t_amount) < 0.01 and date_str == t_date:
    # match!
```

更稳健的方案：用 `data_sources/{id}/query` 拉取整页列表，按金额+日期+标题三元组模糊匹配。

### Python 模板
```python
import urllib.request, json
from pathlib import Path

key = Path.home().joinpath(".config/notion/api_key").read_text().strip()
headers = {
    "Authorization": f"Bearer {key}",
    "Notion-Version": "2025-09-03",
    "Content-Type": "application/json"
}

# 创建账单
body = {
    "parent": {"database_id": "12366ad7-c9c4-8141-8f05-e335b3d0cb5b"},
    "properties": {
        "项目": {"title": [{"text": {"content": "外卖"}}]},
        "金额": {"number": 19.07},
        "交易日期": {"date": {"start": "2026-04-30"}},
        "交易渠道": {"select": {"name": "微信零钱"}},
        "大项": {"select": {"name": "饮食"}},
        "小项": {"select": {"name": "外卖"}},
        "标签": {"multi_select": [{"name": "外卖"}, {"name": "晚餐"}]},
        "状态": {"status": {"name": "已结算"}}
    },
    "icon": {"type": "emoji", "emoji": "🥡"}
}
```

## 查询人脉
```python
search_body = {"query": "姓名"}
# POST /v1/search → 筛选人脉数据库的页面
# 用页面 ID 关联
"相关人员": {"relation": [{"id": "联系人页面ID"}]}
```

### ⚠️ 人脉创建流程

当代单/关联记录用到的人不在人脉库时，**必须先创建联系人再创建账单**：

1. 先用 `POST /v1/search` + 人脉 database_id 搜索姓名
2. 如果未找到，`POST /v1/pages` 到人脉库创建（只需「姓名」字段）
3. 拿到联系人 page_id 后在账单中设置 `"相关人员": {"relation": [{"id": "xxx"}]}`
4. 常见情况：微信昵称≠真实姓名（如"让漪聃"=王怡然），需要用户确认对应关系

## 京东白条分期管理

白条分期涉及**三层数据结构**，不要混淆：

### 数据结构

| 层级 | 说明 | 大项 | 小项 | 金额 | 示例 |
|:----:|:----:|:----:|:----:|:----:|:----:|
| **购买记录** | 在京东白条上买的商品本身 | 日常购物 | 电子产品/服装等 | 商品全价 | OPPO Find X8 4399元 |
| **分期记录** | 每期还款的独立记录 | 日常购物 | 还款 | 每期金额 (183.27) | OPPO第19/24期 |
| **月度账单** | 当月实际扣款的汇总记录 | 日常购物 | 还款 | 各分期之和 (309.60) | 5月账单 |

### 关系（父项指向）

```
月度账单 (309.60)
  ├── 父项 → 购买记录 (OPPO, 正装)        ← 指向商品
  │
分期记录 (183.27, 第19期)
  └── 父项 → 购买记录 (OPPO)              ← 指向商品

分期记录 (126.33, 第2期)
  └── 父项 → 购买记录 (正装)              ← 指向商品
```

### 关键规则

#### 1. 先查已有分期记录，不要新建

数据库中已有按**到期日**自动创建的 未结算 分期记录（平台=京东，无渠道，节点为未结算）。当银行账单显示白条扣款时：

```
❌ 不要新建一条分期记录
✅ 找到已有的那条，PATCH 更新：
   - 交易日期 → 改为实际扣款日（银行截图上的日期）
   - 交易渠道 → 扣款卡（如中国银行储蓄卡）
   - 状态 → 已结算
```

#### 2. 银行端扣款名称对照

京东白条还款在银行账单中可能显示为：
- `网银在线-肯特瑞小额贷...` → 白条还款

不要被商户名误导，直接记作：
- 项目名：「白条还款」
- 大项=日常购物，小项=还款，标签=还款
- 平台=京东

#### 3. 月度账单记录

每月会用扣款卡一次性扣款（金额=所有到期分期之和），需要建一条**月度账单记录**：

```
项目：白条还款
金额：各分期之和（如 126.33 + 183.27 = 309.60）
渠道：扣款卡（如中国银行储蓄卡）
大项：日常购物
小项：还款
标签：还款
平台：京东
状态：已结算
父项 → 所有涉及的商品购买记录（如 OPPO、正装）
```

#### 4. 补全缺失的分期记录

如果京东白条账单显示有分期（如正装第2期 126.33），但数据库中查不到对应记录，需要手动创建：

```
项目：白条还款
金额：分期金额（如 126.33）
渠道：扣款卡
大项：日常购物
小项：还款
标签：还款
平台：京东
状态：（如果已还=已结算，如果未还=未结算）
父项 → 对应商品购买记录
```

#### 5. 提前建好未来分期

当前月还完后，剩余的未还分期需要提前建好（设为 未结算，无渠道），有规律可循：
- 分期期数 = 24总期 - 已还期数
- 每期金额固定（如 OPPO 183.27/期）
- 到期日按原记录的间隔推算（如5/31 → 7/1 → 7/31 → 8/31 → 10/1 → 10/31）
- 父项 → 对应商品购买记录
- 参考已有的分期记录格式（平台=京东，渠道留空，标签=还款）

#### 6. 分期记录快速查询

```python
# 搜索所有 白条分期（小项=还款 且 平台=京东）
search 项目 含 "白条还款" OR 平台 = "京东"

# 区分：
# - 渠道为空 + 未结算 = 未来的分期（不要手动设为已结算）
# - 渠道有值 + 已结算 = 已还的分期
# - 平台=京东 + 小项≠还款 = 商品购买记录（用作父项）
```

## 截图记账工作流（通用版）

用户发送任意渠道的账单截图时（微信零钱、银行储蓄卡/信用卡、支付宝等），按以下流程处理：

### 第一步：视觉识别

使用视觉工具提取所有交易记录，注意识别：
- 交易日期、商户名称、金额（区分支出/收入）
- 交易类型（网上快捷支付、跨行转账、代收扣款、工资等）
- 卡号信息（确认是哪个银行/渠道）

**⚠️ 视觉工具选择（铁律）：**
- **直接用 `mmx vision describe <image_path>`** — 不要调用 `vision_analyze`，它走 MiniMax M2.7（纯文本模型），永远返回 "I don't see any image"
- mmx 路径：`~/.npm-global/bin/mmx`。`mmx quota` 可查 VLM 额度（`coding-plan-vlm` 配额）
- Feishu 收到的截图是 WebP 格式（文件名虽为 .jpg），不需要转换——`mmx vision describe` 直接支持 WebP
- 如需通过 Python 发送给 MiniMax API，需先 `PIL.Image.open()` 转 JPEG 再 base64
- ⚠️ mmx 超时：prompt 太长会超时（30s 不够，建议 60s）。保持 prompt 简洁——「逐条列出所有交易：日期、金额、商户名」即可
- **新方式：Hermes 原生 vision 工具可用** —— 用户发送图片后 Hermes 自动提取图片文本描述。如果已有描述可直接使用，不用重复调用 mmx

### 第二步：查询已有记录，交叉比对去重
**关键步骤**：先查询数据库中近期的所有记录（`POST /v1/search` 拉取最近 100 条），与截图内容做交叉比对：
- **已存在的记录**：金额 + 商户 + 日期能对应上的，标记为已有，不再创建
- **需要更新的记录**：用户可能要求修改已有记录的日期/渠道/状态（如白条还款改日期+加渠道），识别后进入更新流程
- **新增记录**：未在数据库中出现的交易，进入分类环节
- **特殊：还款类账单**（白条还款等定期还款）—— 数据库中可能已有记录（按还款日创建），截图中的扣款可能是同一条的银行端体现。**改已有记录不加新记录**

### 第三步：查询同渠道历史记录，参考分类风格
在分类前，先搜索数据库中使用**同一交易渠道**的历史记录：
- 如中国银行储蓄卡的电话费 → 固定消费/电话费/移动话费
- 同渠道同类型的分类规则具有一致性，优先沿用
- 这比单纯按商户名判断更准确

### 第四步：分类
按本 skill 的分类规则对每笔新交易分类：
- 餐饮店/餐厅 → **默认小项=外卖**（外卖订单），仅当明确堂食才用外出就餐
- 按"外卖 vs 外出就餐"判断表确定
- 数字产品/API → 日常购物/数字产品
- **物理桌面配件**（鼠标垫、数据线等）→ 日用杂货/日用品（不是数字产品！）
- 家人转账 → 收入/生活费
- 退款 → 退款/退款 + 关联父项
- 自贩机零食 → 饮食/零食
- 自贩机饮料 → 饮食/饮料
- 食堂 → 饮食/食堂
- **代单方向判断**：
  - 你转给舍友（你付钱）→ 饮食/代单
  - 舍友转给你（你还钱）→ 收入/代单
- 个人转账 → 资金流转出/资金流转

### 第五步：更新已有记录
当用户要求修改已有记录时（如改日期、改渠道、改状态）：
1. 用 `POST /v1/search` 找到该记录的 page ID
2. 用 `PATCH /v1/pages/{id}` 更新对应字段
3. 更新后验证确认

**常见更新场景：白条还款修改**
- 数据库中已有按还款日创建的白条还款记录（无渠道、未结算）
- 收到银行账单截图后，需要改已有记录：
  1. 查询该还款记录（按日期+标题）
  2. 获取完整 page ID（不可用截断的前8位）
  3. PATCH 更新：金额（按实际账单值）、交易日期（银行实际扣款日）、交易渠道（扣款卡）、状态→已结算
  4. 父项补充：如果账单中有新的分期商品不在已有父项里，需要先创建该商品的购买记录，再把父项添加进去
- 父项去重：PATCH relation 前检查是否有重复 ID，去重后再提交

**银行端显示名对照：**
- 京东白条还款：银行显示"网银在线-肯特瑞小额贷..." → 记"白条还款"
- 基金定投：银行显示"支付宝-蚂蚁(杭州)..." → 记"支付宝·基金定投"
- 公交卡扣款：银行显示"支付宝-宁德市公共" / "支付宝-上海公共交通" → 记"公共交通卡"
- 注意：银行账单的中间商户名 != 实际消费内容，如实际是 SteamPY 退款，银行显示"支付宝-上海部恩科"，应记作"退款(SteamPY)"

### 第六步：审核（铁律）
展示完整计划给用户确认：
- 更新操作：说明原值→新值
- 新增操作：项目、金额、日期、大项、小项、渠道、标签

### 第七步：批量创建

用 Notion API 批量创建新页面。

#### 父项-子项关联（退款 / 代单）

当有退款或代单需要关联父项时，**必须分两阶段创建**：

**阶段 1：创建所有原始消费记录（父项）**
- 用 `POST /v1/pages` 逐一创建，每条间隔 ≥0.3 秒（API 限速）
- 捕获每个返回的 `page.id`

```python
parent_ids = {}  # index → {title, amount, date, id}
for i, bill in enumerate(parent_bills):
    result = notion_post("pages", body)
    parent_ids[i] = {"title": bill["title"], "id": result["id"]}
    time.sleep(0.3)
```

**阶段 2：创建关联记录（子项），设置 relation**
- 退款记录：大项=退款、小项=退款，设置 `"父项": {"relation": [{"id": parent_id}]}`
- 代单记录（方向决定大项）：
  - **你帮舍友付**（你支出）→ 大项=**饮食**、小项=代单
  - **舍友还你钱**（你收入）→ 大项=**收入**、小项=代单
- 无论方向，代单的 `父项` 都指向对应的**实际消费记录**（不是反过来）

```python
"父项": {"relation": [{"id": "父项的pageID"}]}
```

#### 页面 ID 查找模式
创建完成后如果需要回头查找页面 ID（例如脚本中断后补跑），首选模式：
1. 调用 `POST /v1/data_sources/{DATA_SOURCE_ID}/query` 拉取最近记录
2. 按 `(title, round(amount, 2), date)` 三元组匹配
3. 注意 float 精度：API 返回 60.9 而非 60.90，用 `abs(amount - target) < 0.01` 比较
4. ⚠️ 页面创建后需要获取完整 ID（数据库中查到的才是完整 UUID），仅用创建返回值的前8位 PATCH 会 404

### 第八步：验证
查询确认写入成功

### 核心分类原则

⚠️ **关键区分**：同一家店（如雪芳餐饮、象郡餐饮），叫外卖 vs 堂食，小项和标签完全不同：

| 消费方式 | 项目名 | 大项 | 小项 | 标签 | 图标 | 说明 |
|---------|:-----:|:----:|:----:|:----:|:----:|:----:|
| **外卖（点餐平台送到）** | 「外卖」或商户简称 | 饮食 | **外卖** | 外卖+餐食 | 🥡 | **默认值**——不确定走这个 |
| **堂食/到店吃** | 商户全名 | 饮食 | **外出就餐** | 外食+餐食 | 🍜 | 仅当用户明确说去店里吃 |
| **食堂** | **「外食」**（用户偏好，不用商户名） | 饮食 | **食堂** | 餐时（午餐/晚餐） | 🍚 | |
| **零食（自贩机/小卖部）** | 「零食」 | 饮食 | **零食** | 零食+餐食 | 🍿 | |
| 饮料（自贩机/超市） | 「饮料」 | 饮食 | **饮料** | 饮料+餐食（或饮料+夜宵） | 🥤 | 注意：自贩机非代单时用饮料/零食 |
| **奶茶/饮品店**（悸动烧仙草等） | 商户简称 | 饮食 | **饮料** | 饮料+餐时 | 🥤 | 即时饮品消费，非外卖 |
| **淘宝/平台买饮料**（整箱/多瓶） | 「淘宝·盐汽水」 | 饮食 | **饮料** | 饮料 | 🥤 | 平台=淘宝，不标餐时标签 |

### 常见分类对照

| 微信商户类型 | 默认小项 | 标签 | 示例 |
|-------------|:--------:|:----:|------|
| 餐饮店/餐厅（外卖订单） | **外卖** | 外卖+餐食 | 雪芳餐饮、象郡餐饮、牛哥酸菜鱼、正新鸡排 |
| 餐饮店/餐厅（堂食） | 外出就餐 | 外食+餐食 | 上海市食堂三楼**（食堂）** |
| 快递/电商平台（外卖） | 外卖 | 外卖+餐食 | 京东（外卖） |
| 全国连锁快餐（微信支付） | 外卖 | 外卖+餐食 | 塔斯汀、正新鸡排 |
| 自动售货机（零食） | 零食 | 零食+餐食 | 农夫山泉自贩机 |
| 自动售货机（饮料） | 饮料 | 饮料+餐食 | 农夫山泉自贩机（饮料） |
| 自动售货机（泡面/非饮料即食） | 零食 | 零食+餐食 | 农夫山泉(安吉)智能生活（泡面） |
| 食堂/学校餐厅 | 食堂 | 餐食 | 上海市食堂三楼 |
| 食堂（支付宝付给个人） | 饮食/食堂 | 餐食 | 支付宝-马海涛（食堂摊位收款人） |
| 电费代扣（学校代收） | 固定消费/电费 | 电费 | 上海中侨职业技术大学-代收扣款 |
| 电费代单还款（舍友分摊） | 收入/代单 | 代单 | zzh/王怡然/sjh各转75，父项指向电费记录 |
| AI/API 服务 | 日常购物/数字产品 | 数字产品 | 杭州深度求索 |
| 阿里云/云服务 | 日用杂货或日常购物/数字产品 | API, 数字产品 | 阿里云百炼、阿里云计算 |
| 移动话费/手机充值 | 固定消费/电话费 | 移动话费 | 中移电子商务（电话费）**中国银行储蓄卡** |
| 蜜雪冰城/悸动烧仙草等奶茶店 | 饮食/饮料 | 饮料 | 蜜雪冰城、悸动烧仙草。标签=饮料（+餐时如果知道时间） |
| **基金定投**（银行端"支付宝-蚂蚁(杭州)"） | 金融投资/基金 | 基金 | 支付宝-蚂蚁(杭州)扣款。项目名=「支付宝·基金定投」，渠道=扣款卡。**智能定投金额随涨跌幅浮动**（如 ¥274→¥299→¥599），不要用之前金额当固定值比对 |
| **学校补贴工资**（"上海中侨职业技术大学"收入） | 收入/工资 | 工资 | 学校打款，项目名=「学校补贴工资」，渠道=入账卡 |
| 食堂（支付宝/校园卡） | 饮食/食堂 | 午餐或晚餐 | 支付宝-孙利纳 **中国银行储蓄卡** |
| **公交卡扣款**（银行端"支付宝-宁德市公共"/"支付宝-上海公共交通"） | 固定消费/交通 | 交通 | 项目名=「公共交通卡」，渠道=扣款卡。金额常见 ¥1/¥2/¥3/¥4/¥5 |
- **家人转账** → 收入/生活费 | 生活费 | 「生活费」，关联人脉库对应的人
- **家人暂存** → 资金流转入/资金流转 | 暂存 | 爸转的临时存放资金，不是生活费！标签=暂存，以后转回时做资金流转出对冲
- **刷课收入（微信收款/转账）** → 收入/工资 | 刷网课 | 项目名可简写如"来自hy老师"/"来自花椰菜"，小项=工资，标签=刷网课，关联人脉
- **代做作业收入** → 收入/工资 | 作业 | 项目名如"来自翩"，小项=工资，标签=作业。与刷网课同理，属于劳务收入
- **二手回收收入** → 收入/其他 | 二手回收 | OPPO Find X8 回收，回收宝打款到支付宝余额。父项关联原始购买记录。
| 余额宝收益 | 收入/利息 | 利息 | 渠道=余额宝，标签=利息 |
| 自贩机泡面/非饮料即食 | 饮食/零食 | 零食+餐食 | 农夫山泉(安吉)智能生活（泡面，非自产饮料） |
| 拼多多/淘宝买饮料 | 饮食/饮料 | 饮料 | 不标餐时标签，平台=拼多多/淘宝 | 付费通(拼多多支付)，记实际商品名 |
| 退款（平台退款） | 退款/退款 | 退款 | 项目名=「退款(平台名)」，如退款(SteamPY)、退款(肯德基)。**不要用银行账单显示的中间商户名**（如银行显示"支付宝-上海部恩科"，实际是 SteamPY 退款，记作退款(SteamPY)） |
| 个人转账 | 资金流转出/资金流转 | 资金流转 | 转给 zzh |
| **物理配件/周边**（鼠标垫、手机壳、数据线等） | **日用杂货/日用品** | 日用品 | 淘宝鼠标垫。❌ 不要错分类为"数字产品" |
| **多件杂类日用品**（京东白条一单多品如散热器+空气清新剂+扫把） | **日用杂货/日用品** | 日用品 | 按总价记一条，如酷睿冰尊等3种 ¥186.50。平台=京东 |

### 外卖 vs 外出就餐 快速判断

| 线索 | 判定 |
|------|:----:|
| 商户名是正新鸡排/塔斯汀/京东等外卖平台 | ✅ 外卖 |
| 用户没说"去吃了"、"堂食" | ✅ 默认=外卖 |
| 商户名带具体楼层（中侨一楼） | ✅ 默认=外卖（中侨一楼商户除非用户明确说是堂食，否则默认外卖） |
| 用户说"去吃的"、"到店"、"堂食" | ✅ 外出就餐 |

## 支付宝 ↔ 余额宝 多账户资金流转

参见 `references/alipay-yuebao-flow.md`。

核心原则：**双账户视角**，每一跳都记，不跳过任何环节。

跨账户资金流转（如银行充值微信）的对端关联模式参见 `references/cross-account-flow-pairing.md`。

### 渠道速查
| 渠道 | 用途 |
|------|------|
| **支付宝** | 支付宝余额变动（回收款到账、转入余额宝、余额宝转回、提现） |
| **余额宝** | 余额宝内交易（转入、转出、收益） |
| ⚠️ 不存在「支付宝余额」渠道，余额交易统一用「支付宝」 |

### 完整链路模式（以 OPPO 回收为例）

**支付宝侧：**
```
回收宝 +1382 → ①OPPO回收(收入/其他, 支付宝)
  → ②转入余额宝(资金流转出, 支付宝)  ← 子→①
  → 钱在余额宝里...
  → ⑦余额宝转回余额(资金流转入, 支付宝) ← 子→①
  → ⑥提现到社保卡(资金流转入, 中国银行社保卡) ← 子→⑦
```

**余额宝侧（独立记录）：**
```
③自动转入余额宝(资金流转入, 余额宝)
④余额宝收益(收入/利息, 余额宝)
⑤余额宝转出(资金流转出, 余额宝)
```

### 父项子项关系
- ①②⑦都挂在 ①OPPO回收 下作为子项
- ⑥提现到社保卡挂在 ⑦余额宝转回余额 下
- 余额宝侧的 ③④⑤ 独立，不需挂到 OPPO 回收
- 所有资金流转记录必须加标签「资金流转」

### 正负号：金额全是正数，方向靠大项
- 收入/资金流转入 → 流入
- 饮食/资金流转出/日常购物 → 流出
- 不存在负数金额（退款除外）

## 用户偏好
- ❌ 不需要查还款类账单（白条还款等定期还款）
- ✅ 只处理最近几笔日常交易
- ✅ 重点关注最新消费记录
- ⚠️ 平台字段不确定时留空
- ⚠️ 所有账单操作必须先展示计划，用户确认

