# 架构设计

> Satellite Agent Infra Analyst · Phase 1 Research + **Phase 2 抓取脚手架 + Phase 3a 决策层规则版**(2026-06-02 状态)
>
> 本文档 §1-§5 描述 Phase 1 设计哲学与架构(仍然有效);§7-§9 增补 Phase 2 / 3a 的新增模块、数据流和扩展点。新模块独立成节,便于按层阅读。

## 1. 设计哲学

四条互相强化的取舍,共同决定了 MVP 的样子。

| 取舍 | 选择 | 原因 |
|---|---|---|
| **分类逻辑** | 关键词规则 | LLM 在 MVP 阶段引入会带来非确定性与延迟成本,且无现成的产业本体监督信号。规则可解释、可回归、可被产业专家直接审阅 |
| **存储** | SQLite 本地落盘 | 单机即可跑,无运维负担。等到事件量 ≥10⁵ 再迁 Postgres 不迟 |
| **接口** | CLI + FastAPI 双轨 | CLI 用于本地灌数据与脚本化回测;API 用于前端 / 别的 Agent 调用;同一套 repository 层支撑,行为一致 |
| **本体** | 显式 4 主线 + 6 场景 + 6 维度 + Thesis 三态 | PRD 已经把产业切面定下来。显式 enum 比自由 tag 更利于回归与跨事件聚合 |

**显式不做**:LLM 语义、共指消解、向量检索、网页抓取、动态市场模型、自动 PPT — 全部留给 Phase 2 / 3。

## 2. 模块图

```
┌─────────────────────────────────────────────────────────────────┐
│                       Interface layer                            │
│  ┌──────────────────┐                ┌──────────────────────┐   │
│  │   cli.py         │                │     api.py           │   │
│  │   (argparse)     │                │   (FastAPI)          │   │
│  └────────┬─────────┘                └──────────┬───────────┘   │
└───────────┼──────────────────────────────────────┼──────────────┘
            │                                      │
            └──────────────────┬───────────────────┘
                               ▼
            ┌──────────────────────────────────────┐
            │           Domain layer                │
            │   ┌────────────┐   ┌──────────────┐  │
            │   │classifier  │──▶│  ontology    │  │
            │   │            │   │ (4 主线/场景/│  │
            │   │            │   │  维度/词典)  │  │
            │   └────────────┘   └──────────────┘  │
            │   ┌────────────┐   ┌──────────────┐  │
            │   │  report    │   │  regress     │  │
            │   │ (周报聚合) │   │(回归评测)    │  │
            │   └────────────┘   └──────────────┘  │
            └──────────────────┬───────────────────┘
                               ▼
            ┌──────────────────────────────────────┐
            │       Persistence layer               │
            │   ┌────────────┐   ┌──────────────┐  │
            │   │repository  │──▶│   db.py      │  │
            │   │  (CRUD)    │   │  (SQLite)    │  │
            │   └────────────┘   └──────────────┘  │
            │   ┌──────────────────────────────┐    │
            │   │  seed.py (市场模型 + 公司)   │    │
            │   └──────────────────────────────┘    │
            └──────────────────────────────────────┘
```

每个模块的职责严格分离:

- **`ontology.py`** — **唯一**真实来源。4 主线/场景/维度的 Enum 定义和关键词词典都在这里。改分类规则只动这一个文件
- **`classifier.py`** — 纯函数,无状态。输入文本,输出分类 dict。可被任意层(CLI / API / pytest / regress)调用
- **`db.py`** — schema + 连接管理。schema 改动集中于此
- **`repository.py`** — CRUD + 公司主线反哺 (`enrich_with_company_threads`)
- **`report.py`** — 时间窗口聚合 + markdown 渲染
- **`regress.py`** — 回归评测,与 classifier 同级,不入 ingest 流程
- **`seed.py`** — 市场模型(4×7×3 = 84 条)+ 6 家代表公司种子数据
- **`cli.py` / `api.py`** — 薄薄一层,把上面这些组合起来

## 3. 数据流

### 3.1 Ingest pipeline

```
原文(标题+正文)
    │
    ▼
classify(text)              ────▶ {threads, scenarios, dimensions,
                                    thesis_impact, confidence, evidence}
    │
    ▼
match_companies(conn, text) ────▶ ["中国卫通", "震有科技", ...]
    │
    ▼
enrich_with_company_threads ────▶ 把公司所属主线并入 threads
(把命中公司的所属 thread 并回 threads,evidence.company_threads 记录哪些是反哺来的)
    │
    ▼
insert_event(conn, ...)     ────▶ events 表
```

### 3.2 Weekly report pipeline

```
events 表 ──filter by occurred_at window──▶ 候选事件
                                              │
                          ┌───────────────────┴────────────────┐
                          ▼                                    ▼
            按 thread 桶聚合 strengthen/weaken/        提取 Risk dim 或 weaken
            neutral 计数 + 置信度加权净分             事件 → 风险预警列表
                          │                                    │
                          └───────────────────┬────────────────┘
                                              ▼
                                  收集所有 next_indicators 去重保序
                                              │
                                              ▼
                                  render_markdown / JSON
```

## 4. 关键决策

### 4.1 为什么 ontology 用 Enum,不用自由 tag

自由 tag 看似灵活,实际带来三个问题:

1. **聚合困难** — 周报要按主线分桶,Enum 是天然的桶 key
2. **回归没标尺** — labeled regression 需要明确的 ground truth,Enum 让 expected 可序列化
3. **关键词词典自然挂载** — `THREAD_KEYWORDS[Thread.CHIP]` 比 `keywords_for("chip" or "chips" or "芯片"?)` 简单

代价:增加新主线 / 场景需要改代码而不仅是配置。MVP 阶段四主线已经在 PRD 定死,不是问题。

### 4.2 为什么分类器是纯函数

`classifier.classify(text) -> dict` 不依赖 DB、不依赖任何全局状态。这带来:

- 测试简单:`pytest` 直接传字符串
- 回归可批量:`regress.py` 跑 30 条 < 1ms
- 公司反哺是后处理:在 ingest pipeline 单独做,classifier 不需要懂"公司"概念

### 4.3 为什么有 `_NEGATION_PAIRS`

当负向短语含正向子串时(`未中标` 含 `中标`),`kw in text` 会同时命中 pos 和 neg,变成假中性。`_NEGATION_PAIRS` 是一组 `(负向, 正向)` 关系对,负向短语出现时扣减对应正向 hit。

目前覆盖 8 对:`未中标 / 中标失败 / 发射失败 / 未通过 / 未完成 / 未获批 / 启动调查 / 被调查`。

代价:扩展正向词典时,如果新词常以"未/被/暂停/停止 + X"出现,需要同步加反义对。

### 4.4 为什么 SQLite 不用 ORM

SQLAlchemy 在 MVP 阶段是过度设计:

- schema 只有 4 张表,且变动慢
- 查询都是简单 filter + sort
- 行级 row_factory 直接给 dict 就够了
- 测试用 tmp_path 创建临时 DB 一行搞定

代价:迁 Postgres 时要重写 SQL,但 SQL 都集中在 `db.py` + `repository.py`,工作量可控。

## 5. 数据模型

### `events`

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | INT PK | 自增 |
| `title` | TEXT | 事件标题 |
| `content` | TEXT | 事件正文 |
| `source` / `url` | TEXT | 来源 / 链接(可空) |
| `occurred_at` | TEXT | ISO8601 时间 |
| `created_at` | TEXT | 入库时间 |
| `threads` / `scenarios` / `dimensions` | TEXT (JSON array) | 分类结果 |
| `thesis_impact` | TEXT | 增强 / 削弱 / 中性 |
| `confidence` | REAL | 0.0–1.0 |
| `next_indicators` | TEXT (JSON array) | 下周跟踪项 |
| `companies` | TEXT (JSON array) | 命中公司名 |

索引:`(occurred_at)`、`(thesis_impact)`。

> ✅ **Phase 2 已扩字段**(2026-06-02 落地, 决策层启用):
> | 字段 | 类型 | 说明 |
> |---|---|---|
> | `order_amount_cny_yi` / `capex_cny_yi` / `opex_cny_yi` | REAL | 订单金额 / 资本开支 / 运营开支(亿元),NULL 表示无数字 |
> | `time_window` | TEXT | ISO8601 区间, e.g. `2026-Q3` / `2026-H2` / `2026-06` |
> | `customer_subject` | TEXT | 客户主体 |
> | `numeric_evidence` | TEXT (JSON) | extract 留痕 — pattern / matched / raw_value / raw_unit / context |
>
> 索引:新增 `idx_events_order_amount`。`quality_flag` 推迟到接入真实数据源时再加。

### `companies`

| 字段 | 说明 |
|---|---|
| `name` UNIQUE | 公司名 |
| `thread` | 所属主线(用于公司反哺) |
| `products` / `customers` / `aliases` | JSON array |
| `moat` / `risk` / `revenue_mapping` | TEXT |
| `score` | REAL |

> ✅ **Phase 2 已扩字段**(2026-06-02 落地, Phase 3a 已使用):
> | 字段 | 类型 | 说明 |
> |---|---|---|
> | `orders` | TEXT (JSON array) | 已披露订单清单 `[{"amount_cny_yi": ..., "customer": "...", "date": "...", "source": "..."}]`,seed 已填示例 |
> | `peer_rank` / `peer_rank_delta` | INTEGER | 同主线当前排名 + 本周变化;Phase 3a 决策层目前**在查询时计算**不写回,后续 Phase 3b 改为周期持久化 |
> | `last_quarter_revenue_cny_yi` | REAL | 最近一期营收(亿元) |
>
> 仓位信号 `position_signal` 留作 Phase 3b 写库时再加;目前由 `decision.py::_position_signals` 在查询时算出,不入表。

### `market_model`

| 字段 | 说明 |
|---|---|
| `thread` + `year` + `scenario` | 联合唯一(2025-2031 × 4 主线 × 3 情景) |
| `value_cny_yi` | REAL,亿元 |
| `note` | seed 版本标记 |

### `thesis_state`

为 Phase 2 thesis impact scoring 预留,Phase 1 未写入。

## 6. 扩展指引

### 加新关键词

只动 `ontology.py`。如果新词易引发反义误判,同步在 `classifier.py` 的 `_NEGATION_PAIRS` 加对。

加完后跑回归:
```
satagent regress samples/labeled_regression.jsonl
pytest -q
```

### 加新主线 / 场景 / 维度

1. 在 `ontology.py` 加 Enum 成员
2. 在 `THREAD_KEYWORDS` / `SCENARIO_KEYWORDS` / `DIMENSION_KEYWORDS` 加词典
3. `report.py` 的 `_empty_thread_bucket` 自动覆盖(用 enum iteration)
4. 加 ≥3 条 labeled regression 样本验证

### 接 LLM

`classifier.classify()` 的签名保持不变即可。最小入侵方式:

```python
def classify(text):
    rules_result = _rules_classify(text)
    if rules_result["confidence"] < 0.4:
        llm_result = _llm_classify(text)  # 新增
        return _merge(rules_result, llm_result)
    return rules_result
```

LLM 只在规则置信度低时介入,既省 token 又保留可解释性。

## 7. Phase 2 抓取层(已交付脚手架)

### 7.1 模块新增

```
            ┌──────────────────────────────────────┐
            │      Ingestion layer (Phase 2)        │
            │   ┌────────────┐   ┌──────────────┐  │
            │   │ sources/   │──▶│ ingest_pipe- │  │
            │   │ JsonlSource│   │   line       │  │
            │   │ TextFile-  │   │              │  │
            │   │ Source     │   └──────┬───────┘  │
            │   └────────────┘          │           │
            │   ┌────────────┐          │           │
            │   │ extract.py │◀─────────┘           │
            │   │(中文金额) │                       │
            │   └────────────┘                       │
            └──────────────────────────────────────┘
```

- **`sources/base.py`** — `Source` ABC + `RawEvent`(frozen dataclass)。`Source.fetch()` 流式吐 RawEvent
- **`sources/jsonl.py`** / **`sources/text.py`** — 两个 zero-dependency 文件源,真实 HTTP 源待对接(在 `sources/` 下加一个模块 + 注册到 `load_source` 即可)
- **`extract.py`** — 规则版中文金额提取器。订单/合同/中标(亿/万自动换算)、capex、opex、时间窗(年-Q/年-H/年-月)、`numeric_evidence` 留痕
- **`ingest_pipeline.py`** — 编排:`Source.fetch() → classify → match_companies + enrich → extract_numeric → insert_event`。单条失败不阻塞整批

### 7.2 抓取数据流

```
RawEvent (title, text, source, occurred_at, numeric_overrides, companies)
    │
    ▼
classify(text)                ────▶ ontology 词典 hit 计数
    │
    ▼
match_companies + enrich      ────▶ aliases 反向匹配 → 主线反哺
    │
    ▼
extract_numeric(text)         ────▶ 订单/capex/opex/time_window + evidence
    │
    ▼ (numeric_overrides 覆盖自动 extract)
    ▼
insert_event(numeric=...)     ────▶ events 表(含 6 个数字字段)
```

## 8. Phase 3a 决策层(已交付规则版)

### 8.1 模块

- **`decision.py`** — `decide(conn, end_date, window_days, baseline_weeks)` 产出双视角 JSON;`render_ceo_view` / `render_investor_view` / `render_both` 渲染 markdown
- CEO 视角:`_top_drivers`(主线 × 维度强度排序 top-3)+ `_thread_sentiment` / `_thread_money` + `_strategy_recommendations`(进入/加速/等待/退出/观察)
- 投资视角:`_baseline_average`(N 周均值)+ `_company_matrix`(composite = log1p(订单)+log1p(营收)+0.5·点名+0.3·净 thesis)+ `_position_signals`(加/减/持 + 证据链)

### 8.2 决策数据流

```
events (current window) ──▶ thread_sentiment / thread_money / top_drivers
events (previous window) ─▶ delta calculation
events (baseline N weeks)─▶ avg_4w
companies (with orders, last_quarter_revenue) ─▶ company_matrix
                              │
                              ▼
                  CEO view (drivers + scores + strategies + risks)
                  Investor view (heat + matrix + signals + alerts)
```

## 9. 当前文件树(2026-06-02)

```
satellite_agent/
├── ontology.py             # Phase 1 · 词典 (365 词 + 12 反义对)
├── classifier.py           # Phase 1 · 规则分类
├── db.py                   # Phase 1 + migrate_schema (Phase 2)
├── repository.py           # Phase 1 + numeric 字段透传 (Phase 2)
├── seed.py                 # Phase 1 + 公司 orders (Phase 2)
├── report.py               # Phase 1 · 周报聚合
├── regress.py              # Phase 1 · 回归评测
├── extract.py              # Phase 2 · 中文金额提取
├── ingest_pipeline.py      # Phase 2 · 抓取管线编排
├── sources/                # Phase 2 · Source 抽象
│   ├── base.py
│   ├── jsonl.py
│   └── text.py
├── decision.py             # Phase 3a · 双视角周报
├── cli.py                  # 12 个子命令
└── api.py                  # 10 个 REST 端点

tests/                      # 49 tests
├── test_classifier.py      # Phase 1
├── test_report.py          # Phase 1
├── test_enrich.py          # Phase 1
├── test_regression.py      # Phase 1
├── test_schema.py          # Phase 2 · migration + numeric
├── test_extract.py         # Phase 2 · 12 条提取器用例
├── test_sources.py         # Phase 2 · source + pipeline
└── test_decision.py        # Phase 3a · 5 条决策断言
```
