# Skill: Trigger Design Patterns(证伪触发器设计模式)

> ADVICE 集成 §4.2 · Skill 沉淀第 5 章 · 来源 FE-SKILLS-INTEGRATION.md §2.5("强制证伪触发器"机制),落地在 `triggers.py` 3 个内置 check + 24h cooldown + CLI `trigger set/list/delete/check`。

## 0. Trigger 是什么

Trigger = **硬阈值证伪触发器** —— 给每条 thesis 主线绑定数据驱动的"如果跌破 X 就 fire"机制。

直接对应 FE 的"事前主动 vs 事后被动" 哲学:
- **事后被动**(我们以前):事件命中风险维度才 alert
- **事前主动**(本 skill):先设硬阈值,数据驱动跑判定,命中即 alert

3 内置 check + 24h cooldown 防止同 window 反复 fire。

代码位置:[`agent/satellite_agent/triggers.py`](../satellite_agent/triggers.py)。

## 1. CLI 入口(atomic add/delete 模式)

每个 trigger 单独管理(不像 WYHTB 是 wholesale 整 list 替换),适合 operator 增量调整:

```bash
# 看
satagent trigger list                        # 列所有 thread + 已注册 check type
satagent trigger list --thread 核心网

# 设
satagent trigger set --thread 核心网 \
  --type thread_sentiment_below \
  --params '{"thread":"核心网","threshold":-1.5}' \
  --severity high

# 删
satagent trigger delete --thread 核心网 --type thread_sentiment_below

# 跑一次(dry-run 不写 cooldown,用于调试)
satagent trigger check --dry-run
satagent trigger check                        # 真跑,写 last_fired_at
```

## 2. 3 内置 check + 典型 params + 经验阈值

### 2.1 `thread_sentiment_below`(主线情绪跌破)

**触发**:本 window sentiment < threshold(对 4 主线 sentiment 取负值告警)。

**params**:
```json
{
  "thread": "核心网",          # 必填: 4 主线之一
  "threshold": -1.5            # 必填: sentiment 阈值 (浮点)
}
```

**经验阈值参考**(基于 25 条真实集 sentiment 分布):

| severity | threshold | 适用场景 |
|---|---|---|
| `low` | -0.5 | 早期预警(轻微负向信号) |
| `med` | -1.0 | 中等关注(2-3 周持续负向) |
| `high` | -1.5 | 强烈告警(主线 thesis 明显走弱) |
| `critical` | -2.0 | 红色级别(连续多周持续严重负向) |

**调优注意**:
- 不同主线 sentiment 波动幅度不同 — 芯片波动 > 终端波动 > 核心网波动
- threshold 应基于历史 6-8 周 sentiment 分布的 P10 / P5 / P1 分位数标定
- 起步可全设 -1.5(med),跑 4 周观察后调整

### 2.2 `event_count_drop`(事件数跌幅)

**触发**:本 window 事件数 < baseline N 周平均 × (1 - drop_pct)。

**params**:
```json
{
  "thread": "核心网",
  "drop_pct": 0.5,             # 必填: 跌幅百分比 (0.5 = 50%)
  "baseline_weeks": 4           # 可选: baseline 平均的窗口数, 默认 4
}
```

**典型用法**:抓"主线突然冷却" —— 4 周平均 5 条/周,本周 0 条 → 跌幅 100%,命中。

**baseline 0 的防护**:`baseline_avg <= 0` 时跳过(防 0/0 错误)。

**经验阈值**:
- `drop_pct = 0.5`(50%):中等跌幅,1-2 周冷却即告警
- `drop_pct = 0.7`(70%):大幅冷却,主线明显失温
- `drop_pct = 0.9`(90%):几乎完全停滞

### 2.3 `company_order_concentration`(单家公司订单集中度)

**触发**:本 window 某主线 top-1 公司订单 / 主线总订单 > ratio。

**params**:
```json
{
  "thread": "终端",
  "ratio": 0.70                # 必填: 集中度阈值 (0-1 之间)
}
```

**典型用法**:抓"致命大客户依赖" — 同主线一家公司订单 8 亿,其他 0 → 100%,命中。

**只数同主线公司**:跨主线公司订单(如海格通信只是终端主线,芯片事件命中海格不计入芯片主线 concentration)。

**经验阈值**:
- `ratio = 0.70`(70%):FE 文档建议阈值,命中"大客户依赖"
- `ratio = 0.50`(50%):中等依赖,2 家公司双寡头
- `ratio = 0.90`(90%):严重依赖,事实垄断

## 3. cooldown 设计(24h 防反复)

```python
DEFAULT_COOLDOWN_HOURS = 24
```

**目的**:同 trigger 触发后 24h 内不再 fire,防止同 window 反复推送相同警示。

**调优**:
- 高频跑(每小时跑一次 `check_triggers`)→ 默认 24h 合适
- 低频跑(每天跑一次)→ 可降到 12h 让连续 2 天触发都 fire
- 关键策略级别 → 升到 7 days(weekly cycle)

**跳过 cooldown**:
- `check_triggers(write_back=False)` —— 不写 last_fired_at,纯只读视图(`compute_alerts(include_triggers=True)` 用这个)
- CLI `trigger check --dry-run` —— operator 调试用

## 4. severity 等级 + EH-3 飞书消费

3 等级 `high / med / low`,影响:
- `check_triggers` 返回的 alert 列表按 severity 排序(`high → med → low`)
- 排序后 `compute_alerts(include_triggers=True)` 把 trigger alert 排在 risk alert 之前
- EH-3 飞书 follow-up 可基于 severity 决定推送优先级

代码位置:`triggers.check_triggers` 末尾的 `_sev_order` sort。

## 5. 与 WYHTB 边界(再次)

**配对设计**:每条 trigger 理想情况下应**有对应的 WYHTB 命题**:

| Trigger | WYHTB bear 命题 |
|---|---|
| `thread_sentiment_below threshold=-1.5` | "核心网 sentiment 跌破 -1.5(主线 thesis 明显走弱)" |
| `event_count_drop drop_pct=0.5` | "核心网本周事件数 < 4 周均值的 50%(主线突然冷却)" |
| `company_order_concentration ratio=0.7` | "终端 top-1 公司订单占主线 > 70%(致命大客户依赖)" |

WYHTB notes 字段建议写"对应 trigger thread_sentiment_below 阈值 -1.5"做交叉引用。

## 6. 自定义 check(register_check 扩展)

`triggers.py` 用装饰器注册扩展。**新加 check type 不必改主干**:

```python
from satellite_agent.triggers import register_check

@register_check("custom_my_check")
def _my_check(conn, params, *, end_date, window_days):
    """自定义检查 — 例: 某主线营收同比下滑超 X%."""
    thread = params.get("thread")
    threshold = params.get("threshold")
    if not thread or threshold is None:
        return None

    # 业务逻辑...
    revenue_yoy = _compute_revenue_yoy(conn, thread, end_date)
    if revenue_yoy >= threshold:
        return None  # 未触发

    return {
        "reason": f"{thread} 营收同比 {revenue_yoy:.1%} < 阈值 {threshold:.1%}",
        "metrics": {"thread": thread, "current": revenue_yoy, "threshold": threshold},
    }
```

自定义后 operator 用 `satagent trigger set --type custom_my_check ...` 即可。

⚠️ **自定义注意**:同型 `_distance` 函数也要注册(给 5 步语法"如何崩塌"段计算余量用,见 [`triggers.py::_DISTANCES`](../satellite_agent/triggers.py))。

## 7. trigger 在 decision 输出中的位置

```markdown
## ⚠️ 如何崩塌 (falsification · layer=falsification)

| 主线 | trigger | severity | 当前 | 阈值 | 距离 | 状态 |
|------|---------|---------|-----|------|-----|------|
| 核心网 | thread_sentiment_below | high | +2.25 | -1.50 | +3.75 | ✅ 安全 |
| 芯片  | event_count_drop      | med  | 0     | 0.75  | -0.75 | 🔥 已触发 |
```

代码位置:
- 距离计算:`triggers.evaluate_distance` + `list_trigger_distances`
- 渲染:`decision.render_falsification_section`

## 8. 与代码 / 测试的对应关系

| 功能 | 代码 | 测试 |
|---|---|---|
| 注册 + 3 内置 check | `triggers._CHECKS` + `@register_check` | `test_triggers.py::test_three_builtin_checks_registered` |
| `thread_sentiment_below` | `triggers._check_sentiment_below` | `test_triggers.py::test_sentiment_below_*`(正/反/未知 thread) |
| `event_count_drop` | `triggers._check_event_count_drop` | `test_triggers.py::test_event_count_drop_*`(baseline 0 / drop_pct 越界) |
| `company_order_concentration` | `triggers._check_concentration` | `test_triggers.py::test_concentration_*`(跨主线 / 阈值锁定) |
| cooldown | `triggers._is_cooled_down` | `test_triggers.py::test_cooldown_blocks_repeat_fire` |
| 距离余量(给 B 5 步语法用) | `triggers._DISTANCES` + `evaluate_distance` + `list_trigger_distances` | `test_triggers.py::test_distance_*` |
| set / list / delete | `thesis.set/list/delete_trigger` | `test_triggers.py::test_set_trigger_*` |
| CLI 子命令 | `cli.cmd_trigger_*` | (CLI 端到端) |

## 9. 不构成投资建议

3 内置 check 类型 + 经验阈值参考均为**示范**:

- `threshold` / `drop_pct` / `ratio` 数值应根据 operator 自己的产业历史数据 backtest 校准
- 不同主线 / 不同市场环境(牛市 / 熊市)阈值不同
- 24h cooldown 是工程默认值,operator 应基于自己的告警接收节奏调整

**触发器命中只是"达到了 operator 预设的告警阈值",不构成任何投资建议**。命中后 operator 必须独立核实事件背景 + 判断是否真的需要行动。
