---
name: intervention-data
description: 生成和校验单句精确干预、正则干预候选 TSV，复用标签大师的 target 语法校验，并从历史干预表推断 code 到下发 domain 的固定映射。
when_to_use: 当用户希望新增、检查或整理干预规则，例如给某个 query 或多轮正则生成干预条目、确认设备和标签是否合法、推断下发 domain 时使用。
aliases: intervention, rule-intervention, 干预数据
allowed_tools: read_file, write_file, edit_file, grep_search, glob_search, ask_user_question, python_exec
---

使用这个 skill 处理“query/多轮表达 -> 干预候选 -> 合并 + 上传 HDFS”的任务。

干预数据目前有两类，**文件无扩展名**（HDFS 上文件名是 `260610` 这种纯日期），内容是真实 TAB 字符分隔的纯文本，**绝不是字面 `\t` 两字符**。下文所有 `\t` 都表示一个真实的 TAB。

- **单句精确干预（codeTopQuery）**：一行 4 列，`设备\tquery\tcode\t下发domain`
  示例：
  ```
  unify	播放音乐	Agent(tag="音乐")	contentCopilot|music
  unify	小爱同学	Chat(type="wakeup")	dialogCopilot|michat
  ```
- **正则干预（codeIntervene）**：一行 3 列，`设备\t正则\t标签`
  示例：
  ```
  unify	^query#播放歌曲关机又关机$	Agent(tag="音乐")
  unify	^beforeQuery#播放歌曲#query#关机又关机$	Agent(tag="音乐")
  glass	^query#这是什么$	QA()
  ```

精确干预的 `code` 和正则干预的 `标签` 使用同一套 target 语法，和 `label-master` 的输出一致，但不包含复杂度判断。校验时必须复用 `skills/label-master/scripts/validate_label_output.py` 的逻辑。

## 关键约定

- `unify` 表示全部设备。
- 设备必须优先从下载下来的最新干预文件中归纳，不要随手发明设备名。
- **单句精确干预的 `下发domain` 必须由人拍板**：脚本基于最新干预文件按 `code → 下发domain` 频次给出 top-N 候选 + 计数，agent 列出来让用户从用户给的 tag 信息出发选一个，**不要默认用多数派直接落盘**。
- 正则干预支持多轮。多轮正则使用 `beforeQuery#上一轮#query#当前轮` 这种片段表达，最终通常包在 `^...$` 中。
- 默认只生成候选行 + 校验结果，不直接追加到历史干预文件。上传 HDFS 必须等用户明确确认。
- 所有产物优先写到当前会话的 `$AUTORESEARCH_CHAT_ROOT/output/` 或 `scratchpad/` 下，不要写到项目根目录的 `output/`。

## HDFS 干预文件路径

线上干预的权威源在 HDFS。两类文件 × 四个环境（p4t = 测试，ptr = 灰度，preview = 预发，production = 生产）：

| 类型 | HDFS 目录 |
|---|---|
| 单句精确干预（codeTopQuery） | `hdfs://zjyprc-hadoop/user/s_ai_service/nlp/PlanningPredict/all/<env>/strategy/codeTopQuery` |
| 正则干预（codeIntervene） | `hdfs://zjyprc-hadoop/user/s_ai_service/nlp/PlanningPredict/all/<env>/strategy/codeIntervene` |

`<env>` 取值：`p4t` / `ptr` / `preview` / `production`。每个目录下文件名"形式"按日期 `yymmdd` 命名（**纯数字、无扩展名**，例：`260609`），但实际值不要求严格等于当天日期——新增一份就是上一份**按一天 +1**（要正确处理月末/年末进位），最大值仍须落在合法 `yymmdd` 范围内（最大不超过今天）。**最新文件 = 文件名尾号最大的那一个**。

## 推荐流程

0. **先确认链接到远端**（强制第一步，不通过就停下）。
   - "远端"概念同 [model-iteration](../model-iteration/SKILL.md)：bash 实际跑在远端 jupyter pod 工作区，`$AUTORESEARCH_CHAT_ROOT` 是后端注入的远端 chat 工作区路径（位于共享 NFS：`/mnt/<email_prefix>/autoresearch-zk-users/<chat_session_id>/`）。系统提示里的"当前会话目录"是后端宿主机本地路径，**不是**远端，不能拿来跑脚本或读历史文件。
   - 起手第一条 bash 必须是预检（同时验远端 + HDFS 工具）：
     ```bash
     pwd && echo "AUTORESEARCH_CHAT_ROOT=$AUTORESEARCH_CHAT_ROOT" && uname -a && which hls hget hput
     ```
   - 通过条件：`pwd` 返回远端绝对路径（一般 `/root/zk_agent_workspaces/LOCALID_xxx` 或类似 Linux 路径）、`$AUTORESEARCH_CHAT_ROOT` 非空、`hls/hget/hput` 都能找到。
   - 不通过的情形（任一命中就停下问用户）：
     - `$AUTORESEARCH_CHAT_ROOT` 为空 → 后端没注入，会话没绑定 chat。
     - `pwd` 是 Windows 路径（如 `D:\...`、`/d/...`）或后端本地路径（如 `/home/mi/zk-data-agent-wsh/...`）→ 当前在本地，不是远端。
     - `hls/hget/hput` 找不到 → 远端环境没装 HDFS 客户端，下一步拉文件直接卡死。
     - bash 报命令找不到 / 没权限 → 远端 pod 没起来。
1. **获取最新的干预文件**（强制第二步，所有后续校验和上传都基于这一步拉下来的文件）。
   - 先确认 (a) 干预类型：精确干预 → `codeTopQuery`，正则干预 → `codeIntervene`；(b) 目标环境（`p4t/ptr/preview/production`，用户没说就问，建议先在 `p4t` 验证）。
   - 用 `hls` 列目录，按文件名尾号取最大的那个作为最新文件，然后用 `hget` 拉下来：
     ```bash
     ENV=p4t            # 或 ptr / preview / production
     TYPE=codeTopQuery  # 或 codeIntervene
     HDFS_DIR=hdfs://zjyprc-hadoop/user/s_ai_service/nlp/PlanningPredict/all/$ENV/strategy/$TYPE
     hls "$HDFS_DIR"
     LATEST=$(hls "$HDFS_DIR" | awk '{print $NF}' | grep -v '^$' | sort -V | tail -1)
     echo "LATEST=$LATEST"

     WORK="$AUTORESEARCH_CHAT_ROOT/intervention/${ENV}_${TYPE}"
     mkdir -p "$WORK"
     # base 不带扩展名（HDFS 上原文件名就是日期），保持原貌
     hget "$LATEST" "$WORK/base"
     wc -l "$WORK/base"
     # 抽 1-2 行肉眼校验是不是真 TAB 分隔（cat -A 把 TAB 显成 ^I）
     head -2 "$WORK/base" | cat -A
     ```
   - **设备枚举、`code → 下发domain` 频次、重复/冲突检查全部基于这份刚拉下来的 `base`**，不要再用本地历史快照（旧快照可能已过时）。文件是 TAB 分隔纯文本，无扩展名，不要当 csv/json 处理。
2. 先确认用户要哪种干预方式（如果第 1 步还没定就在这里定，确定后回到第 1 步拉对应文件）：
   - 单句精确干预：query 必须完全命中。
   - 正则干预：适合一类表达、多轮上下文或需要泛化的高频 case。
3. 确认设备。用户没说设备时，必须问设备；可以提示 `unify` 表示全部设备。设备枚举从 base.tsv 第一列归纳。
4. 确认目标标签/code。它必须是完整 target，例如 `Agent(tag="地图导航")`、`QA()`、`Chat()` 或合法 function 调用。
5. 如果是单句精确干预：
   - 确认 `query`。
   - **下发domain 必须人选**：基于 base 文件统计该 `code`（target）历史上配过的 `下发domain` 频次，给出 top-N 候选 + 计数，让用户结合自己提的 tag 信息选一个。例：
     ```bash
     # 用 awk 抽该 target 的下发domain 分布
     awk -F'\t' -v t='Agent(tag="音乐")' '$3==t {print $4}' "$WORK/base" \
       | sort | uniq -c | sort -rn | head -5
     ```
     把结果（如 `12345 contentCopilot|music / 87 contentCopilot|audio_book / ...`）作为候选给用户，**不要默认取第一个直接落盘**。
6. 如果是正则干预：
   - 确认当前轮 query。
   - 如果有多轮，确认前序 query 顺序。
   - 用户给了原始正则就保留；没给时由脚本生成 `^query#...$` 或 `^beforeQuery#...#query#...$`。
7. 调用 `scripts/generate_intervention_candidate.py` 生成候选 TSV 和校验报告（产物写到 `$AUTORESEARCH_CHAT_ROOT/output/` 或 `scratchpad/`）。
8. **合并 + diff 给人看**。把候选行追加进 `base` 副本，跑 unified diff：
   ```bash
   cp "$WORK/base" "$WORK/merged"
   cat candidate >> "$WORK/merged"   # candidate 是上一步生成的候选行文件，纯 TAB 分隔
   diff -u "$WORK/base" "$WORK/merged" | tee "$WORK/diff.patch"
   ```
9. 展示给用户：干预类型、设备、候选行（原文 TAB 分隔）、校验结果、diff 摘要、目标 HDFS 路径。**用户没明确说"上传 / 推到 X 环境 / hput"前不要执行 hput**——这是写远端 + 影响线上的不可逆动作，必须等确认。
10. **用户确认后 hput 上传**。新文件名规则：**取 `LATEST` 的尾号「按一天」+1**——必须正确处理月末 / 年末进位（260131 +1 = 260201，261231 +1 = 270101，260228 在闰年 +1 = 260229、平年 +1 = 260301），**不是**数值 `+1`（那样 260131+1=260132 直接失效）。用 `date` 做日期算术：
    ```bash
    OLD_NUM=$(basename "$LATEST")                # 例：260609 或 260131
    NEW_NUM=$(date -d "20${OLD_NUM} + 1 day" +%y%m%d)
    TODAY=$(date +%y%m%d)

    # 校验：date 解析失败（OLD_NUM 本身就不合法）会直接报错；新尾号也必须是合法 yymmdd
    if ! [[ "$NEW_NUM" =~ ^[0-9]{2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])$ ]]; then
      echo "ERR: $NEW_NUM 不符合 yymmdd 形式，停下问用户"; exit 1
    fi
    if [ "$NEW_NUM" -gt "$TODAY" ]; then
      echo "ERR: $NEW_NUM=$NEW_NUM 超过今天 $TODAY（说明上一份就是今天的，已经发过一版），停下问用户"; exit 1
    fi

    hput "$WORK/merged" "$HDFS_DIR/$NEW_NUM"
    hls "$HDFS_DIR" | tail -3   # 校验新文件已落、尾号最大
    ```
    说明：HDFS 上文件名"形式"按日期来，实际值是「上一份按一天 +1」而不是严格当天日期；只要 +1 后仍是合法 yymmdd 且不超过今天就行。如果上一份已经是今天（OLD_NUM == TODAY），`date +1 day` 会算出明天 → 命中"超过今天"分支，停下问用户怎么处理（通常是当天已经发过一版，要不要并入现有版本，或推迟到明天再发）。
11. 展示最终结果：上传后的 HDFS 路径 + 新文件尾号。

## 脚本能力

```text
skills/intervention-data/
  SKILL.md
  knowledge/
    type_match_agent.json
  scripts/
    intervention_common.py
    analyze_intervention_sources.py
    audit_intervention_sources.py
    generate_intervention_candidate.py
    validate_intervention_records.py
```

### analyze_intervention_sources.py

读取历史干预文件，输出设备枚举、`code -> 下发domain` 多数映射、常见标签，以及 `knowledge/type_match_agent.json` 中的下发表。

示例：

```bash
python skills/intervention-data/scripts/analyze_intervention_sources.py
```

### generate_intervention_candidate.py

从 JSON 输入生成候选 TSV，并自动校验。输入可以来自文件或 stdin。

### audit_intervention_sources.py

审计现有两个历史干预文件，只输出摘要，不修改文件。适合验证当前 skill 的校验规则和存量数据是否贴合。

示例：

```bash
python skills/intervention-data/scripts/audit_intervention_sources.py
```

单句精确干预示例：

```json
{
  "mode": "exact",
  "device": "unify",
  "query": "播放周杰伦的歌",
  "target": "Agent(tag=\"音乐\")"
}
```

正则多轮干预示例：

```json
{
  "mode": "regex",
  "device": "miCar",
  "before_queries": ["第一个"],
  "query": "红绿灯少的路线",
  "target": "Agent(tag=\"地图导航\")"
}
```

### validate_intervention_records.py

校验候选 TSV。支持 `--mode exact` 或 `--mode regex`。

规则包括：

- 列数正确。
- 设备合法。
- `code` / `标签` 优先通过 label-master 校验；如果 label-master 未收录但历史干预表中已经稳定出现，允许作为候选通过，并给出 warning 让用户确认知识库是否需要补齐。
- 单句精确干预的 `下发domain` 与历史多数映射一致，默认不一致为 warning，`--strict-domain` 时为 error。
- 单句精确干预还会用 `type_match_agent.json` 做配置校对；配置值可以是更宽泛前缀，例如 `音乐 -> contentCopilot` 可以兼容历史里的 `contentCopilot|music`。
- 正则可编译，且包含 `query#` 片段。
- 与历史文件重复或冲突时给出 warning/error。

## 展示格式

生成候选后，优先用这个格式回复：

```text
我生成了一条候选干预，先不写入历史文件。
- 类型：单句精确干预 / 正则干预
- 设备：xxx
- 标签：xxx
- 校验：通过 / 有问题

候选 TSV：`设备	query	code	下发domain`

需要确认：是否写入某个文件，或是否调整设备/标签/正则。
```

如果校验失败，先解释失败原因，不要要求用户直接落盘。
