# 分配 · Agent 行为决策树

> 主管发起一批召回分配时，**人 / 模型 / 程序**各自在哪一步做什么。
> 通用原则见 [agent-doctrine.md](./agent-doctrine.md)（引用其编号）；业务教条见 [plan-assignment-doctrine.md](./plan-assignment-doctrine.md)（引用 T 编号）。

| | |
|---|---|
| **状态** | 定稿（Locked）—— ⚠️ 但**会随实测改判**：2026-08-13/14/15 各改过一轮，§五那张表曾漂出四行。改代码时回来对一眼，⛔ 别把「Locked」读成「不会变」 |
| **性质** | Agent 行为规格 —— 实现按此对齐 |
| **⚠️ 立场** | **这是状态图，不是流程脚本。** 描述「有哪些状态、哪些是确定性的」，⛔ 不描述「模型必须按什么顺序调工具」。⛔ 不得据此做工具门禁（B1 / C4） |

---

## 一、状态机

**主管只在两个地方停下来**：看草稿、看已确认的批次。其余全是转移。

```
              ┌──────────────┐
              │   无草稿      │  起点
              └──────┬───────┘
                     │
                     │  [人] 圈定：矩阵点格 / 中间表 / 直接说
                     │  [程] 一次算完（见 §二）→ 生成新草稿
                     │       ⭐ 新草稿一生成，前一版立即作废
                     ▼
    ┌────────────▶ ┌──────────────┐
    │              │  草稿 active  │  ← 系统在等主管
    │              └──┬──┬──┬──┬──┘
    │                 │  │  │  │
    │   改派法 ───────┘  │  │  └──── 放弃 ──▶ 无草稿
    │   同一版重算节点    │  │
    │                    │  └─────── 确认 ──▶ ┌──────────────┐
    │   改人群 ──────────┘                    │   已确认      │
    └───  出新版，旧版 [superseded]           └──────┬───────┘
                                                     │
                                          补福利 / 撤销 / 跟踪
                                          （主管在等客服）
```

### 三个状态

| 状态 | 谁在等谁 | 能做什么 |
|---|---|---|
| **无草稿** | 系统等主管 | 圈定 |
| **草稿 `active`** | 系统等主管 | 处置引导节点 · 改派法 · 改人群 · 确认 · 放弃 |
| **已确认** | 主管等客服 | 补福利 · 撤销（限时）· 跟踪 |

### ⚠️ 「圈定 → 排人 → 算引导节点」是**一次转移**，不是三个状态

服务端一次算完、一次往返。把它拆成三步画会让人以为中间有停顿——**没有**。
（`total == 0` 也不是独立状态：草稿照样生成，只是它是空的，唯一能做的是改人群。）

### 入口的三种来源

| | | 模型要做什么 |
|---|---|---|
| A | 矩阵点一格 | **什么都不用做**，参数确定 |
| B | 中间表确定 | 同上 |
| C | 对话里直接说 | 理解成 治疗项 · 时间档 · 口径；拿不准就反问，⛔ 不猜 |

⚠️ A/B 是主路径。**大多数情况下模型在入口这一步是零参与的**。

---

## 二、圈定这一次转移里，服务端算了什么

按顺序，全部确定性，一次返回：

```
   1. 候选集     口径（治疗项 · 时间档）→ total · 可切维度
                 排序：没被分过的优先

   2. 基数       每人每日 15 条 × 在岗人数 × D  →  N（与候选总数取小）

   3. 落人       专属优先（封顶在目标水位）
                 → 无主补空（给手上最少的）
                 二者共用水位法（= 负载均衡）
                 落不下去的 → 待分配残留

   4. 引导节点   基于 1–3 的结果做确定性判定（§五）
                 待分配残留 = 第一个节点

   5. 两段事实   怎么选的（选谁）/ 怎么派的（给谁）（§四）
```

**产出一个草稿对象**：明细 + 两段事实 + 引导节点。前端直渲，模型只拿事实和节点的摘要。

---

## 三、草稿 active 状态下的四条出路

```
   草稿 active
        │
        ├── 不动 ─────────────────────────────▶ 确认
        │      每个节点默认都是 no-op，所以这条路**永远安全**
        │
        ├── 点节点上的选项 ── [程] 执行 intent ──▶ 同一版，重算节点
        │
        ├── 说节点外的话 ──── [模] 判断动的是哪一层
        │      │
        │      ├─ 动「怎么派」 → [程] 局部改 ────▶ 同一版，重算节点
        │      │    谁给谁 · 时效 · 移出本批 · 福利
        │      │
        │      └─ 动「这批人」 → [程] 重跑 ──────▶ **出新版，旧版当场作废**
        │           换口径 · 加画像条件 · 改人数 · 换治疗项
        │
        └── 放弃 ──────────────────────────────▶ 无草稿
```

⚠️ 前三条都回到 `active`，只是**回到哪一版**不同：改派法回同一版，改人群回新版。

### 「派法」还是「人群」—— 模型在本流程中最实质的一次判断

| | 动的是 | 走 | 例 |
|---|---|---|---|
| **派法** | 这批人**怎么分** | 局部改，不重跑 | 「王强移出这批」「待分配各自归专属」「给 5 天」「带个福利」 |
| **人群** | 这批人**是谁** | 重跑，出新版 | 「只要商保直付的」「改成 200 人」「换成 1–2 年那档」 |

**代价不对称**：把「人群」误判成「派法」→ 主管以为条件生效了、其实没有（**静默错**）；反过来只是多算一次。
⇒ **拿不准时一律走重跑。**

### 🔴 重跑时，人群条件必须**原样带回**（2026-08-15 补，当天栽了两次）

「重跑」不是"重新算一遍"，是"**按同一批人**重新算一遍"。少带一格条件，圈出来的就是另一批人 ——
而两边都不报错，界面只显示「重新排了一版」。

| 必须带回 | 漏了会怎样 |
|---|---|
| 治疗项 · 时间档 | 按全池重算。实测：主管说「改成 200 人」，模型手里只有确认单本身（120 人、8 位客服、时效 1 天），不知道原来是哪一格 —— 它自己的话：「刚才我按全部候选重算了一版，结果对不上……跟您眼前那批完全不是一回事」 |
| 主管自己加的那几刀（`narrowedBy`） | **退回整格**。实测「换无专属客服的患者补上」漏传 `narrowedBy`：候选 2,178 vs 296，差 1,882 人 —— 他刚圈掉的人全回来了 |

⛔ **凡是能触发重算的载体都要带全这一段**，目前有三处：
`propose_assignment` 的入参 · `RefillProposalRequestSchema`（「补上」那颗按钮）·
`SheetSnapshot.criteria`（模型查当前确认单时的唯一依据）。
⚠️ 契约测试 `assignment-contract.spec.ts` 逐处锁着 —— 加第四处载体时，那份也要加一条。

### ⛔ 「怎么改这张单」不由模型讲

改单的操作方式（拖拽、点选、可改哪几项）写在**卡片的操作说明**里常驻。
模型只在主管开口之后**翻译成 intent**，⛔ 不主动教学、⛔ 不每轮复述（G3）。

---

## 四、事实两段 + 引导节点

草稿的可读产出只有两类：**已经确定的事实**（两段）和**还没确定的引导节点**（§五）。

> ⚠️ **不要写成「三段事实」** —— 第三段「还没定的」本来就是引导节点，列成事实等于把同一份数据说两遍。
> 同理，**落人不是四段**：专属优先与无主补空是两段，水位法是二者共用的**机制**，待分配是落不下去的**残留**（= `pending` 节点）。

服务端产出**结构化事实**（⛔ 不是成品句子，G6），模型据此组织语言。

**呈现顺序：怎么选的 → 怎么派的**，而**引导节点不是第三段** ——
它们**嵌在各自所属的那一段里**（2026-08-14 起）：

| 节点 | 嵌在 | 键名 |
|---|---|---|
| `narrow` | 怎么选的 | `这一格还能怎么选` |
| `batch_size_basis` | 怎么选的 | `本批人数还能怎么调` |
| `pending` · `daily_overload` | 怎么派的 | `要你定的` |

> 🔴 **键嵌在哪一段里，就是它的位置说明** —— ⛔ 别再去提示词里加一条「这几条要讲在第 1 段」：
> 结构能表达的事就别写成规则（规则越多越不被遵守）。顶层从三段变两段之后，
> 每个可动手的东西都长在它要改的那一段上。
> ⚠️ 曾经这里写着「⚠️ `assistant-prompts.ts` 第 0.5 条与此冲突，以本文为准，实现时一并改掉」——
> **那个冲突早已不存在**（提示词 2026-08-14 重写成五层，没有编号条款了）。

### 事实① 怎么选的（**选谁**）

- 口径：治疗项 · 时间档（锚点只有末诊一版，见教条 T6″）
- 排序：没被分过的优先
- 候选总数 → 取本批 N

| | |
|---|---|
| 每人每日 | **15 条**（当前默认；⛔ 暂不沿用上一次的值） |
| 时效 | `D` 天 |
| **本批目标 N** | **在岗人数 × 15 × D**，并与候选总数取小 |

### 事实② 怎么派的（**给谁**）

**两段落人**，按顺序：

| | 段 | 规则 |
|---|---|---|
| ① | **专属优先** | 有专属且在名册内 → 分给他，**封顶在目标水位** |
| ② | **无主补空** | 无专属 / 专属已离岗 → 给当前手上最少的人 |

**共用机制 · 水位法**：每一条都给当前手上最少的那个，⛔ 不是「每人加一样多」——起点不齐时那样终点还是不齐。
**派生**：每人本批多少条 · **折合每日多少条**（供 `daily_overload` 判定）。

> ⚠️ 「负载均衡」不是独立一段，就是这个水位法。它决定的是**给谁**，⛔ 不影响**选谁**。
> （目标水位封顶会把一部分专属患者挤进待分配、从而减少实际发出量——那是分配结果回头改变了发出量，候选集本身没变。）

### 落不下去的残留 → 引导节点

专属客服本轮已排满的那些人 **不分**，单列成 `pending` 引导节点交主管定
（T：关系层面的决定只有主管能做）。
⚠️ 有待分配就**必须说出来**，⛔ 不许省、不许弱化成「另有若干」。

⚠️ **但「还没定的」不只是待分配** —— 它是所有未处置的引导节点（另有每日超阈值、候选不足、还能再筛一刀）。待分配只是 severity 最高的那一个。

---

## 五、引导节点表

引导节点的通用设计（数据形状、五条规则、三方各取所需）见 [agent-architecture.md §12](./agent-architecture.md)。

### ⛔ 不截断 —— 命中的全部呈现

**凡是有实际后果的节点，一条都不能省。** 藏起任何一条，主管就不知道有东西卡着 —— 那正是待分配当初要解决的问题。

防噪音靠**分层**，不靠丢弃：

| 层 | 内容 | 呈现 |
|---|---|---|
| **需处置** | 不管就会有后果 | 全部展开，按 severity 排序 |
| **提示** | 纯信息，不管也没事 | 默认折叠成一行，可展开 |

### 确认前

| sev | 层 | key | 给模型的标识 | 判定（程序） | 默认（no-op） | 选项（点击执行） |
|---|---|---|---|---|---|---|
| **1** | 需处置 | `pending` | 待分配 | `pending > 0` | 留着 = 这批不发给他们 | ① **换无专属客服的患者补上**（见下，带取人范围）<br>② 铺平给在岗<br>③ 各自归专属客服<br>④ 移出本批 |
| **2** | 需处置 | `daily_overload` | 打不完 | 分完后最重的那位 `手上总条数 > 15 × D` | 就按 D 天发，到期落回池子 | **只有一个：`时效改成 N 天`**（N 由程序算） |
| **3** | 需处置 | `narrow` | 换个条件选 | 候选 ≥ 50 且切完 ≥ 10 人；主管已收窄过 / 已重挑过则不出 | 就按这一格全部来 | 几刀 + 兜底（见下） |
| **4** | 需处置 | `batch_size_basis` | 本批人数 | `basis === 'default'` 且候选 > 基数 | 就按这个数发 | 改时效 / 改每天几通（**都带输入框、预填当前值**） |

**终止分支（不是引导节点）**：`empty` —— `total == 0`，无法继续，只能放宽或换格。

> 🔴 **这张表 2026-08-15 重写过，因为它漂了四行。** 之前列着 `short_supply`（已删）、
> `highlight`（2026-08-13 被 `narrow` 取代，下文自己写了，表却没改）、
> `expiry_default` / `anchor_nondefault`（从未实现），却**没有** `narrow` 和 `batch_size_basis`。
> ⚠️ 真源是 `assignment-signals.ts` 的 `GUIDANCE_KIND` —— 那四个键就是全部。改代码时回来对一眼。

> 🔴 **`daily_overload` 的选项 2026-08-15 从三个砍到一个**（产品定）。删掉的
> 「改每人每天打几通」「减少本批人数」**一个数都不带**：既没算出目标值，也没有预填当前值的
> 输入框，点下去替主管说的那句话同样是空的（「减少一些」「按更少的通数算」）——
> 那个数只能由模型自己拍。⇒ **按钮不带数 ⇒ 严格弱于他自己说一句**（判据同 `cohort.widen` 删除时）。
> 这两件事仍然做得到：`batch_size_basis` 那两个输入框，以及常驻的「我自己说」。

#### `daily_overload` 是**工作量预估**，不是异常告警

N 按「在岗 × 每天 15 通 × 时效」算，**只算新增、⛔ 不扣在手** —— 所以只要谁手上还有东西，
需要的天数就会超过时效，这个节点**每批基本都会亮**。**这是对的**（产品定 2026-08-12）：
每一批本来就该把「这活多大、要打几天」讲清楚，主管据此决定延时效、调每日通数、还是减量。

由此推出两条硬要求：

1. **措辞不许像报错。** ⛔ 不出现「打不完 / 超了 / 过载」——它常亮，说成故障主管就会
   开始怀疑系统而不是做决定。
2. **数字必须拆开**：「手上共 60 条」要写成「本批 45 + 原本在手 15」。
   不拆开主管会读成"这批一下压了 60 条给他"（增量与总量分不开是同类问题里最常犯的）。

> ⚠️ 曾考虑把 N 改成 `Σ max(0, 15D − 在手)`（扣掉在手）让这个节点不再常亮。
> ⛔ **不要这么做** —— 那会踩回 2026-08-03 删掉的坑：第一批把所有人填满后，第二批恒为 0。

#### `pending` 的第 ③ 个选项：换无主患者补上

待分配的人不发出去，这批**实发量就少了**。从池子里取**等量的无主患者**补进来：
既不动任何专属关系（T：不许替主管把患者从专属客服手里挪走），又把产能填满。

#### `narrow` 展开：还能再筛一刀

🔴 **2026-08-13 换掉了原来的 `highlight`（画像亮点）。** 旧版比两份分布（这一格 vs 同治疗项池子），
用「倍数 ≥2 或高出 ≥12pp」判断哪一项"值得注意"，命中的每一项各出一个节点。

删的理由**不是它算错了** —— 40 格扫下来 29 格命中，结果也有临床意义。是它**在替主管判断**
「这值不值得注意」，而他要的只是「还能怎么切」。⇒ 不比基线、不设显著性闸，把几刀直接摆出来。

顺带去掉的两样：那次**全治疗项池子扫描**（每出一次确认单都要跑），以及一个格子最多三条亮点
各带一个标题的排版（四个标题说的其实是同一件事）。

**一个节点、几个按钮**，挂在「这批人怎么来的」下面（`stage: 'cohort'`）：

> 🔴 **措辞的要害：这是换个条件重新圈初选人群，⛔ 不是在已排好的那 N 个里再挑。**
> 原来标题写「这批 2663 人还能再筛一刀」，产品走查当场读成「在本批 495 人之后的进一步筛选」——
> 「再筛一刀」自带先后顺序的暗示，而「这批」在这条链路里同时指过候选集和本批。
> ⇒ 一律说**「这一格」**（他就是从矩阵格子点进来的），
> `why` 里明写「换的是这一格选进来多少人，本批人数跟着重新估」。
>
> ⭐ **动词是「选」**（2026-08-15 产品定；此前是「圈」，更早是被否掉的「筛」）。
> 理由不是文风：这条线的四步已经叫 `选人 → 分人 → 他确认 → 确认之后`
> （系统提示词、`propose_assignment` 描述、`modelFacts` 键名都用这套），
> 按钮上写「圈」等于给同一个动作起第二个名字 —— 主管读到的和模型讲的对不上。
> ⛔ 「筛」仍然不许用（自带"在已排好的人里再挑一遍"的先后暗示）。

| 刀 | 判据 | 实测（拔牙 · 3 年以上，候选 2,509） |
|---|---|---|
| 消费 | 高于**这一批自己的**平均 | ¥720 → 312 人（12%） |
| 转介绍达人 | 家庭型 ∪ 社交型 | 2 人 → ⛔ 不给（<10） |
| 权益身份 | 这批里**最多的那一项** | 儿牙会员 76 人（3%） |
| 获客渠道 | 同上 | 走入 804 人（32%） |
| 兜底 | —— | 「按别的条件选」→ 模型去调 `get_cohort_attributes` |

闸只剩两条：**候选集 ≥ 50**、**一刀切完 ≥ 10 人**。

> 🔴 **⛔ 不给「最多的那一项」加占比闸。** 权益那种最高项只占 3% 的维度，一刀下去批次会从
> 495 人缩到 76 人 —— 但**人数就写在按钮上**，他点之前看得见（②可见可撤），点完嫌少还能重来。
>
> ⚠️ 这条的理由 2026-08-15 修过一次：原文还写着「重排后 `short_supply` 还会再兜一次」——
> **那句话早就不成立了**（`short_supply` 加了 `narrowedBy` 闸之后恰恰在收窄出来的那一版不出，
> 而它现在整条已删）。⇒ **拿"另一条判据会兜住"当理由时，去看一眼那条判据当下的闸。**
> 结论没变，靠的是"点之前看得见 + 点完能重来"这一条，⛔ 不再靠任何兜底。

> 🔴 **消费的门槛是批内相对的**，文案必须写「这批的平均」。实测全库平均 ¥4,442，
> 而「拔牙 · 3 年以上」这一格只有 ¥720，差六倍；写成「高消费客户」他就会拿去跨格子比。
> ⚠️ 消费分布右偏（这一格中位数是 **¥0**），所以"高于平均"选出的是一成多而不是一半 —— 正合适。
> ⚠️ 这一刀走 `minSpendYuan`（元），**⛔ 不走 `personaTags`**：那是个等值字典，表达不了「大于」。

> ⛔ **`rfm.segment` 不参与。** 档位锚点改成末诊之后（T6″），时间档与 RFM 的 R 分完全同尺
> ⇒ 冷档里「低活跃 / 重要挽留」占比高是**恒等式不是发现**。R 被格子钉死，这个维度上
> 还能动的只有 M（消费）与 F（频次），所以只取消费。

> ⚠️ **「转介绍达人」⛔ 不能写成「有过转介绍的」**：这个维度的门槛是**推荐 ≥3 人且带来成交**。
> 要"推荐过 1 人也算"，那是另一个判据，现有画像里没有 —— 得新做特征。

⚠️ 折扣锚点仍未纳入：它的值是「最低折扣率 + 日期/项目」，是连续量、没有闭集取值，
要用它得先定分桶（≥9折 / 8–9折 / <8折），那是产品判断。
> 💡 它更适合做成**统计事实**（「这批人历史最深折扣中位数 X 折」，供福利定价参考）。

#### 引导节点分三组：改人群 / 改人数 / 改怎么派

每条节点带 `stage`，**由程序定**，⛔ 不靠提示词让模型自己判断该讲在哪一段：

| stage | 改的是 | 节点 | 嵌在哪一段 |
|---|---|---|---|
| `cohort` | 选进来哪些人 | `narrow` | 「怎么选的」 |
| `size` | 这一批发多少 | `batch_size_basis` | 「怎么选的」，`cohort` **之后** |
| `dispatch` | 怎么派 | `pending` · `daily_overload` | 「怎么派的」 |

> 🔴 **`cohort` 与 `size` 必须是两个键**，虽然都嵌在同一段。合成一个键时模型会拿
> 一个标题罩住两组按钮 —— 走查抓到「还能怎么选：」下面挂着「改时效 / 改每天几通」，
> 而改时效根本不是选人。⇒ **一个键只说一件事**。
>
> 🔴 **开放的粒度是「一件」不是「一段」**（2026-08-15 实测）。工具描述里一度写着
> 「在**它所属的那一段**讲完之后开放」，而「怎么选的」这一段里**有两件**
> （换个条件选 / 本批人数）—— 给了段这个粒度，模型就按段攒，两排按钮叠在段末。
> 它的思考原文：「Both 换个条件选 and 本批人数 belong to 选人 section,
> so calling them together after that section seems fine」。⇒ 一件一件、就地开放。

> ⚠️ 顺序 `cohort → size`，⛔ 不许反：换人群会让 N 重新估一遍
> （实测候选 2509→312 后，本批 495 被压成 312），先调 N 那下就白调了。

⚠️ 句柄是**中文标识**（`show_guidance` 的 `id`，取值域见 `GUIDANCE_KIND`），
⛔ **不是序号**（2026-08-14 改）。原来给的是编号，模型得自己维护「我讲的这段是第几条」
这套记账；标识让它按**意思**对上。
> 🔴 裸台实测：去掉编号、工具改成传标识之后，三条引导它只讲了一条也只调了一次 ——
> **标识不只是句柄，它承载「这是可以逐个处理的几件事」**。
> ⚠️ 工具名也从 `place_guidance` 改成了 `show_guidance`。

### 交互形态：点击为主，自由输入兜底

**每个选项都是一个可直接点击执行的动作**，⛔ 不是让主管照着打字。

⚠️ **卡片只渲染标题 + 按钮**；`为什么` / `不处理会怎样` 由模型在卡片上方讲
（2026-08-12 实测：两边都写全，主管要把同一件事读两遍）。

```
   ┌──────────────────────────────────────────────────┐
   │  34 人的专属客服这轮排满了                        │
   │  不处理 = 这批不发给他们                          │
   │  [各自归专属] [铺平] [换无主患者补上] [移出本批]   │
   └──────────────────────────────────────────────────┘
             ⋮
   也可以直接告诉我要怎么做。          ← 兜底：一句文字说明
```

**为什么点击优先**：主管打字表达 → 模型理解 → 翻译成 intent，这条链每一环都可能错（实测栽过 `owner`/`balance` 选反，18 人被散给 17 位别人）。点击是**确定性**的，intent 直接给出，没有理解环节。

**为什么仍要留自由输入**：引导节点覆盖不了的需求必须有出口（B1 / F8），否则助手只能回「做不到」。

⭐ **两条路收敛到同一个 `intent` 契约** —— 这样「按钮能做的、说话也能做」是结构保证，⛔ 不是提示词里的一句叮嘱。

### 确认后：**没有引导节点了**

🔴 **2026-08-15 产品定：确认单里不做福利引导。** 此前这里有一条 `benefit_missing`
（`stage: 'post_confirm'`），由界面在确认成功那一刻挂进这张单 —— 主管点完确认，
卡片里长出一块黄色引导。现在整条链路（节点、`postConfirm` 参数、`settleSheet` 合并、
只读态可点）都已删除。

**福利这件事整个前移到确认之前**：模型在他按下确认前问一句
（提示词 ⑤ 现场的「他确认」那一节：「要带现在说，不带就算了」）。
理由没变 —— 福利只影响**此后生成**的话术，而客服从确认那一刻起陆续打开这批单，
**每过一会儿能用上的人就少一个**；⇒ 与其确认后补救，不如在最后一个自然时机问掉。

⚠️ 代价是知情的：**点确认不触发模型发言**，所以确认之后现在一句提醒都没有。
这是取舍（③已经问过一次），⛔ 别用"前端拼一句成品话塞进对话"去填 ——
那是署助手的名而它根本没说过，被否掉过两次。
⛔ **绝不许说「带福利成功率更高 / 转化率提升」** —— 本系统不统计成功，那个结论编不出来（A2）。

---

## 六、确认之后

```
   已确认
        ├─ 福利引导   见上（唯一还能改的东西）
        ├─ 撤销说明   限时；⚠️ 客服**已经打开过**的单不会被收回
        │             ⛔「已撤销」四个字只能出现在撤销工具真的返回之后（A2）
        └─ 跟踪说明   → 另一条时间线，解读规范随工具返回值下发（F10）
```

**能改与不能改**：

| | 确认后 |
|---|---|
| 福利 | ✅ 可补挂，不设时限 |
| 人员 | ❌ 单已在客服手上 → 走撤销重分 |
| 时效 | ❌ 同上 |

---

## 七、草稿的生命周期

```
   出第 1 版 ──▶ [active]
        │
   出第 2 版 ──▶ 第 1 版立即 [superseded]   ⭐ 作废发生在**新版生成时**
        │                                     ⛔ 不是在确认时
   出第 3 版 ──▶ 第 2 版立即 [superseded]
        │
   点确认   ──▶ 第 3 版 [confirmed] → 转成批次
```

**规则**

1. **任一时刻只有一版 `active`。** `edit` 一律作用于它，⛔ 不用倒着扫消息找「最后一张」。
2. **作废时机 = 新草稿生成时**，不是确认时。
3. **作废的卡片必须在页面上明确标注**：显示「已作废」，**确认按钮禁用**。
   ⛔ 不许静默变灰、不许直接消失 —— 主管要能看见自己出过几版、以及哪一版才是当前的。
4. 刷新页面丢草稿是可接受的（重新出一版即可），**暂不落库**。

### 🔴 草稿态**模型看不见** —— 要查 `get_current_sheet`（2026-08-15 加）

确认之前，主管在卡片上做的改动（改派 / 移出 / 改时效 / 设福利）**只存在于浏览器里**：
服务端手上只有最初生成的那一版提案。所以模型对"这张单现在什么样"只有两条来路：

| | 谁推给它 | 覆盖 |
|---|---|---|
| **推** | 每次改单往对话里追加一行回声，`modelText` 后缀带当前值 | 改动发生的那一刻 |
| **拉** | `get_current_sheet`（无参数，读随请求捎来的 `SheetSnapshot`） | 任何时候 |

⚠️ 在这之前它只能把对话里几行回声**自己累加**，而手动拖动 / 点 × / 改时效这几条路
**当时连回声都没有** —— 卡片上写着 3 天，它嘴上还说 1 天，两边都不报错。
⛔ 别去给服务端加"草稿回传"的写路径：多一份要维护的状态，只为省模型一次加法。

---

## 八、模型的自主权边界

| ✅ 模型决定 | ⛔ 模型不许决定 |
|---|---|
| 理解自由输入（入口 C、节点外的话） | 哪个引导节点命中 |
| 要不要先查分布再选 | 任何数字（A2） |
| 判断动的是「派法」还是「人群」 | 谁分给谁 |
| 组织语言（G6） | 引导节点的默认值 |
| 拿不准时反问（B2） | 是否落库（A3） |

---

## 九、不在本树内

| | 去哪 |
|---|---|
| **跟踪线** | 同会话，但解读规范随工具返回值加载（F10）；节奏与分配完全不同 |
| **客服线** | 另一个身份 → 另一套工具清单与数据范围（D1–D4） |
| **患者个案查询 / 闲聊** | 自由通道，不进本树 |

---

## 十、随时可答：「下一步做什么」

主管在任何一步问「接下来呢 / 我现在该干嘛」，**答案是确定性的**，⛔ 不由模型即兴发挥：

```
   下一步 = 当前状态 + 未处置的引导节点 + 默认路径
```

| 当前状态 | 下一步 |
|---|---|
| 还没圈定 | 在矩阵上点一格，或直接说要哪一类人 |
| 候选为 0 | 放宽一档 / 换治疗项 / 不限时间档 |
| 草稿 `active`，有需处置的节点 | 逐条列出未处置的节点 + 各自的默认；并说明「都不动也可以直接确认」 |
| 草稿 `active`，无需处置的节点 | 直接确认 |
| 草稿 `active`，**他刚在卡片上动过手** | 先 `get_current_sheet` 看现在什么样，⛔ 别拿出方案那一版的数去算 |
| 草稿 `superseded` | 提示这版已作废，当前有效的是最新那版 |
| 已确认，无福利 | 可补挂福利；否则等客服处理，随时可跟踪 |
| 已确认，有福利 | 等客服处理；可跟踪；限时内可撤销 |

**这是 G1 的直接应用**：候选由程序算（状态机 + 引导节点是确定性的），措辞由模型给，决定权在人。
⚠️ 由于它完全由状态推出，**「下一步」永远不会指向系统做不到的事**（G2）。
